erosolar-cli 2.1.167 → 2.1.168

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 (128) hide show
  1. package/agents/erosolar-code.rules.json +2 -2
  2. package/agents/general.rules.json +3 -21
  3. package/dist/StringUtils.d.ts +8 -0
  4. package/dist/StringUtils.d.ts.map +1 -0
  5. package/dist/StringUtils.js +11 -0
  6. package/dist/StringUtils.js.map +1 -0
  7. package/dist/capabilities/statusCapability.js +2 -2
  8. package/dist/capabilities/statusCapability.js.map +1 -1
  9. package/dist/contracts/agent-schemas.json +0 -5
  10. package/dist/core/agent.d.ts +11 -72
  11. package/dist/core/agent.d.ts.map +1 -1
  12. package/dist/core/agent.js +182 -869
  13. package/dist/core/agent.js.map +1 -1
  14. package/dist/core/aiFlowSupervisor.d.ts +44 -0
  15. package/dist/core/aiFlowSupervisor.d.ts.map +1 -0
  16. package/dist/core/aiFlowSupervisor.js +299 -0
  17. package/dist/core/aiFlowSupervisor.js.map +1 -0
  18. package/dist/core/cliTestHarness.d.ts +200 -0
  19. package/dist/core/cliTestHarness.d.ts.map +1 -0
  20. package/dist/core/cliTestHarness.js +549 -0
  21. package/dist/core/cliTestHarness.js.map +1 -0
  22. package/dist/core/preferences.d.ts +0 -1
  23. package/dist/core/preferences.d.ts.map +1 -1
  24. package/dist/core/preferences.js +2 -9
  25. package/dist/core/preferences.js.map +1 -1
  26. package/dist/core/schemaValidator.js +3 -3
  27. package/dist/core/schemaValidator.js.map +1 -1
  28. package/dist/core/testUtils.d.ts +121 -0
  29. package/dist/core/testUtils.d.ts.map +1 -0
  30. package/dist/core/testUtils.js +235 -0
  31. package/dist/core/testUtils.js.map +1 -0
  32. package/dist/core/toolPreconditions.d.ts +11 -0
  33. package/dist/core/toolPreconditions.d.ts.map +1 -1
  34. package/dist/core/toolPreconditions.js +164 -33
  35. package/dist/core/toolPreconditions.js.map +1 -1
  36. package/dist/core/toolRuntime.d.ts.map +1 -1
  37. package/dist/core/toolRuntime.js +114 -9
  38. package/dist/core/toolRuntime.js.map +1 -1
  39. package/dist/core/toolValidation.d.ts +116 -0
  40. package/dist/core/toolValidation.d.ts.map +1 -0
  41. package/dist/core/toolValidation.js +282 -0
  42. package/dist/core/toolValidation.js.map +1 -0
  43. package/dist/core/updateChecker.d.ts +1 -61
  44. package/dist/core/updateChecker.d.ts.map +1 -1
  45. package/dist/core/updateChecker.js +3 -147
  46. package/dist/core/updateChecker.js.map +1 -1
  47. package/dist/headless/headlessApp.d.ts.map +1 -1
  48. package/dist/headless/headlessApp.js +39 -0
  49. package/dist/headless/headlessApp.js.map +1 -1
  50. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
  51. package/dist/plugins/tools/nodeDefaults.js +2 -0
  52. package/dist/plugins/tools/nodeDefaults.js.map +1 -1
  53. package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
  54. package/dist/providers/openaiResponsesProvider.js +74 -79
  55. package/dist/providers/openaiResponsesProvider.js.map +1 -1
  56. package/dist/runtime/agentController.d.ts.map +1 -1
  57. package/dist/runtime/agentController.js +0 -6
  58. package/dist/runtime/agentController.js.map +1 -1
  59. package/dist/runtime/agentSession.d.ts.map +1 -1
  60. package/dist/runtime/agentSession.js +2 -3
  61. package/dist/runtime/agentSession.js.map +1 -1
  62. package/dist/shell/interactiveShell.d.ts +8 -16
  63. package/dist/shell/interactiveShell.d.ts.map +1 -1
  64. package/dist/shell/interactiveShell.js +159 -388
  65. package/dist/shell/interactiveShell.js.map +1 -1
  66. package/dist/shell/systemPrompt.d.ts.map +1 -1
  67. package/dist/shell/systemPrompt.js +15 -4
  68. package/dist/shell/systemPrompt.js.map +1 -1
  69. package/dist/subagents/taskRunner.js +1 -2
  70. package/dist/subagents/taskRunner.js.map +1 -1
  71. package/dist/tools/bashTools.d.ts.map +1 -1
  72. package/dist/tools/bashTools.js +8 -101
  73. package/dist/tools/bashTools.js.map +1 -1
  74. package/dist/tools/diffUtils.d.ts +2 -8
  75. package/dist/tools/diffUtils.d.ts.map +1 -1
  76. package/dist/tools/diffUtils.js +13 -72
  77. package/dist/tools/diffUtils.js.map +1 -1
  78. package/dist/tools/grepTools.d.ts.map +1 -1
  79. package/dist/tools/grepTools.js +2 -10
  80. package/dist/tools/grepTools.js.map +1 -1
  81. package/dist/tools/searchTools.d.ts.map +1 -1
  82. package/dist/tools/searchTools.js +2 -4
  83. package/dist/tools/searchTools.js.map +1 -1
  84. package/dist/ui/PromptController.d.ts +0 -2
  85. package/dist/ui/PromptController.d.ts.map +1 -1
  86. package/dist/ui/PromptController.js +0 -2
  87. package/dist/ui/PromptController.js.map +1 -1
  88. package/dist/ui/ShellUIAdapter.d.ts +18 -71
  89. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  90. package/dist/ui/ShellUIAdapter.js +139 -237
  91. package/dist/ui/ShellUIAdapter.js.map +1 -1
  92. package/dist/ui/UnifiedUIController.d.ts +1 -0
  93. package/dist/ui/UnifiedUIController.d.ts.map +1 -1
  94. package/dist/ui/UnifiedUIController.js +1 -0
  95. package/dist/ui/UnifiedUIController.js.map +1 -1
  96. package/dist/ui/UnifiedUIRenderer.d.ts +5 -122
  97. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  98. package/dist/ui/UnifiedUIRenderer.js +125 -830
  99. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  100. package/dist/ui/compactRenderer.d.ts +139 -0
  101. package/dist/ui/compactRenderer.d.ts.map +1 -0
  102. package/dist/ui/compactRenderer.js +398 -0
  103. package/dist/ui/compactRenderer.js.map +1 -0
  104. package/dist/ui/display.d.ts +48 -13
  105. package/dist/ui/display.d.ts.map +1 -1
  106. package/dist/ui/display.js +105 -22
  107. package/dist/ui/display.js.map +1 -1
  108. package/dist/ui/streamingFormatter.d.ts +30 -0
  109. package/dist/ui/streamingFormatter.d.ts.map +1 -0
  110. package/dist/ui/streamingFormatter.js +91 -0
  111. package/dist/ui/streamingFormatter.js.map +1 -0
  112. package/dist/ui/unified/index.d.ts +1 -1
  113. package/dist/ui/unified/index.d.ts.map +1 -1
  114. package/dist/ui/unified/index.js +2 -0
  115. package/dist/ui/unified/index.js.map +1 -1
  116. package/dist/utils/errorUtils.d.ts +16 -0
  117. package/dist/utils/errorUtils.d.ts.map +1 -0
  118. package/dist/utils/errorUtils.js +66 -0
  119. package/dist/utils/errorUtils.js.map +1 -0
  120. package/package.json +2 -1
  121. package/dist/core/reliabilityPrompt.d.ts +0 -9
  122. package/dist/core/reliabilityPrompt.d.ts.map +0 -1
  123. package/dist/core/reliabilityPrompt.js +0 -31
  124. package/dist/core/reliabilityPrompt.js.map +0 -1
  125. package/dist/ui/animatedStatus.d.ts +0 -129
  126. package/dist/ui/animatedStatus.d.ts.map +0 -1
  127. package/dist/ui/animatedStatus.js +0 -384
  128. package/dist/ui/animatedStatus.js.map +0 -1
@@ -13,13 +13,15 @@ import { InterruptPriority } from './interrupts/InterruptManager.js';
13
13
  import { getTerminalColumns } from './layout.js';
14
14
  import { theme, icons } from './theme.js';
15
15
  import { UIUpdateCoordinator } from './orchestration/UIUpdateCoordinator.js';
16
+ import { formatRichContent } from './richText.js';
16
17
  export class ShellUIAdapter {
17
18
  uiController;
18
19
  display;
19
20
  config;
21
+ isProcessing = false;
22
+ contextUsage = 0;
20
23
  fileChangeCallback;
21
24
  _toolStatusCallback;
22
- _activityCallback;
23
25
  profileSwitcherTimeout;
24
26
  updateCoordinator;
25
27
  // Track tool operations for compact rendering
@@ -36,16 +38,6 @@ export class ShellUIAdapter {
36
38
  activeToolStatus = null;
37
39
  activeToolHeartbeatId = null;
38
40
  statusSpinnerIndex = 0;
39
- // Store last tool result for expansion with ctrl+o
40
- lastToolResult = null;
41
- // Scrolling output buffer - shows only the last N lines of tool output in-place
42
- maxScrollingLines = 3;
43
- scrollingOutputBuffer = new Map();
44
- scrollingPanelOwner = null;
45
- // Scrolling tool call display - shows only the last N tool calls, scrolling older ones up
46
- maxVisibleToolCalls = 3;
47
- recentToolCalls = [];
48
- toolCallDisplayedCount = 0;
49
41
  constructor(writeStream, display, config = {}, updateCoordinator) {
50
42
  this.display = display;
51
43
  this.config = {
@@ -146,12 +138,6 @@ export class ShellUIAdapter {
146
138
  else {
147
139
  this.logToolProgress(call, progress);
148
140
  }
149
- // Update activity line with streaming output from bash commands
150
- if (this._activityCallback && progress.message) {
151
- // Show the streaming line as the activity (e.g., npm install progress)
152
- const activityText = progress.message.slice(0, 60);
153
- this._activityCallback(activityText);
154
- }
155
141
  // Update status bar with progress - use in-place update for supported terminals
156
142
  if (this._toolStatusCallback) {
157
143
  const description = this.getToolActionDescription(call, progress);
@@ -181,10 +167,12 @@ export class ShellUIAdapter {
181
167
  const now = Date.now();
182
168
  // Complete the operation tracking
183
169
  const op = this.pendingOperations.get(call.id);
170
+ let durationMs;
184
171
  if (op) {
185
172
  op.status = 'success';
186
173
  op.completedAt = now;
187
174
  op.summary = this.extractResultSummary(call, output);
175
+ durationMs = op.startedAt ? Math.max(0, now - op.startedAt) : undefined;
188
176
  this.pendingOperations.delete(call.id);
189
177
  this.completedOperations.push(op);
190
178
  // Keep only recent completed operations
@@ -203,22 +191,19 @@ export class ShellUIAdapter {
203
191
  this.fileChangeCallback(fileChange.path, fileChange.type, fileChange.additions, fileChange.removals);
204
192
  }
205
193
  }
206
- // Store result for expansion with ctrl+o
207
- this.lastToolResult = {
208
- toolName: call.name,
209
- output,
210
- timestamp: Date.now(),
211
- };
212
- // Claude Code style: compact display for all tools
213
- if (call.name === 'Edit' || call.name === 'edit_file') {
214
- this.displayEditResult(call, output, true);
215
- }
216
- else if (this.compactDisplayMode && this.isCompactTool(call.name)) {
217
- this.displayCompactResult(call, output, true);
218
- }
219
- else {
220
- // Display tool result with ⎿ prefix
221
- this.displayToolResultSummary(call, output, true);
194
+ const renderedRich = this.maybeRenderRichToolResult(call, output, true, durationMs);
195
+ // Use compact display for quick single-result tools and structured edit output
196
+ if (!renderedRich) {
197
+ if (call.name === 'Edit' || call.name === 'edit_file') {
198
+ this.displayEditResult(call, output, true);
199
+ }
200
+ else if (this.compactDisplayMode && this.isCompactTool(call.name)) {
201
+ this.displayCompactResult(call, output, true);
202
+ }
203
+ else {
204
+ // Erosolar-CLI style: Display tool result with ⎿ prefix
205
+ this.displayToolResultSummary(call, output, true);
206
+ }
222
207
  }
223
208
  // Surface any captured preflight warnings in a structured block
224
209
  this.flushToolWarnings(call.id, call.name);
@@ -232,10 +217,12 @@ export class ShellUIAdapter {
232
217
  onToolError: (call, message) => {
233
218
  // Complete the operation tracking with error
234
219
  const op = this.pendingOperations.get(call.id);
220
+ let durationMs;
235
221
  if (op) {
236
222
  op.status = 'error';
237
223
  op.completedAt = Date.now();
238
224
  op.summary = message;
225
+ durationMs = op.startedAt ? Math.max(0, (op.completedAt ?? Date.now()) - op.startedAt) : undefined;
239
226
  this.pendingOperations.delete(call.id);
240
227
  this.completedOperations.push(op);
241
228
  }
@@ -243,16 +230,19 @@ export class ShellUIAdapter {
243
230
  this.logExploreCompletion(call.id, false);
244
231
  }
245
232
  this.clearToolState(call.id);
246
- // Claude Code style: compact display for errors too
247
- if (call.name === 'Edit' || call.name === 'edit_file') {
248
- this.displayEditResult(call, message, false);
249
- }
250
- else if (this.compactDisplayMode && this.isCompactTool(call.name)) {
251
- this.displayCompactResult(call, message, false);
252
- }
253
- else {
254
- // Display error with ⎿ prefix (error color)
255
- this.displayToolResultSummary(call, message, false);
233
+ const renderedRich = this.maybeRenderRichToolResult(call, message, false, durationMs);
234
+ // Use compact display for errors too
235
+ if (!renderedRich) {
236
+ if (call.name === 'Edit' || call.name === 'edit_file') {
237
+ this.displayEditResult(call, message, false);
238
+ }
239
+ else if (this.compactDisplayMode && this.isCompactTool(call.name)) {
240
+ this.displayCompactResult(call, message, false);
241
+ }
242
+ else {
243
+ // Erosolar-CLI style: Display error with ⎿ prefix (error color)
244
+ this.displayToolResultSummary(call, message, false);
245
+ }
256
246
  }
257
247
  this.flushToolWarnings(call.id, call.name);
258
248
  // Clear status bar after showing error
@@ -320,27 +310,17 @@ export class ShellUIAdapter {
320
310
  * Shows the top two tools and how many more were used.
321
311
  */
322
312
  showToolUsageSummary() {
323
- // Compute summary for metadata but don't display it
324
- // The tool calls are already visible in the conversation flow
325
313
  this.lastToolUsageSummary = this.formatToolUsageSummary();
326
314
  this.lastToolUsageSummaryPlain = this.formatToolUsageSummary({ plain: true });
327
- // Don't stream the summary - it's redundant after the assistant response
315
+ const summary = this.lastToolUsageSummary;
316
+ if (!summary) {
317
+ return;
318
+ }
319
+ this.display.stream(`\n${summary}\n`);
328
320
  }
329
321
  getToolUsageSummary(options = {}) {
330
322
  return options.plain ? this.lastToolUsageSummaryPlain : this.lastToolUsageSummary;
331
323
  }
332
- /**
333
- * Get the last tool result for expansion with ctrl+o
334
- */
335
- getLastToolResult() {
336
- return this.lastToolResult;
337
- }
338
- /**
339
- * Clear the stored tool result (e.g., after expansion)
340
- */
341
- clearLastToolResult() {
342
- this.lastToolResult = null;
343
- }
344
324
  formatToolUsageSummary(options = {}) {
345
325
  const plain = options.plain === true;
346
326
  if (this.toolUsageCounts.size === 0) {
@@ -459,6 +439,7 @@ export class ShellUIAdapter {
459
439
  case 'Write':
460
440
  case 'write_file': {
461
441
  const path = this.extractPath(args, ['file_path', 'path']);
442
+ const lines = this.countLines(output);
462
443
  return path ? `${this.truncatePath(path, 40)} written` : 'file written';
463
444
  }
464
445
  case 'Edit':
@@ -523,8 +504,8 @@ export class ShellUIAdapter {
523
504
  return;
524
505
  }
525
506
  const prefix = success ? theme.success(icons.subaction) : theme.error(icons.subaction);
526
- // Just show the result summary - tool name was already shown on the call line
527
- this.display.stream(` ${prefix} ${summary}\n`);
507
+ const toolLabel = theme.tool(call.name);
508
+ this.display.stream(` ${prefix} ${toolLabel} ${summary}\n\n`);
528
509
  }
529
510
  /**
530
511
  * Display edit results with full colored diff output.
@@ -542,10 +523,12 @@ export class ShellUIAdapter {
542
523
  if (success) {
543
524
  const diffSection = this.extractEditDiffSection(output);
544
525
  if (diffSection) {
545
- // Show diff with single trailing newline
546
- this.display.stream(`${diffSection}\n`);
526
+ this.display.stream(`${diffSection}\n\n`);
527
+ return;
547
528
  }
548
529
  }
530
+ // Add spacing when no diff section is available
531
+ this.display.stream('\n');
549
532
  }
550
533
  logToolProgress(call, progress) {
551
534
  const description = this.getToolActionDescription(call, progress);
@@ -568,97 +551,12 @@ export class ShellUIAdapter {
568
551
  }
569
552
  const separator = theme.ui.muted(' — ');
570
553
  const line = parts.filter(Boolean).join(separator);
571
- if (!line)
572
- return;
573
- // Use scrolling output for ALL tools on TTY - show only last N lines, updating in place
574
- // This prevents flooding the terminal with hundreds of progress lines
575
- if (process.stdout.isTTY) {
576
- this.appendScrollingOutput(call.id, line);
577
- }
578
- else {
579
- // Non-TTY: original streaming behavior (for log files, pipes, etc.)
580
- const last = this.toolProgressSnapshots.get(call.id);
581
- if (line !== last) {
582
- this.display.stream(`${line}\n`);
583
- this.toolProgressSnapshots.set(call.id, line);
584
- }
585
- }
586
- }
587
- /**
588
- * Append a line to scrolling output buffer and update display in-place
589
- * Shows only the last maxScrollingLines, replacing previous output
590
- */
591
- appendScrollingOutput(callId, line) {
592
- let buffer = this.scrollingOutputBuffer.get(callId);
593
- if (!buffer) {
594
- buffer = { lines: [], displayedLineCount: 0 };
595
- this.scrollingOutputBuffer.set(callId, buffer);
554
+ const last = this.toolProgressSnapshots.get(call.id);
555
+ if (line && line !== last) {
556
+ // Single trailing newline - no leading newline to avoid gaps
557
+ this.display.stream(`${line}\n`);
558
+ this.toolProgressSnapshots.set(call.id, line);
596
559
  }
597
- // Add line to buffer
598
- buffer.lines.push(line);
599
- // Keep only the last maxScrollingLines
600
- while (buffer.lines.length > this.maxScrollingLines) {
601
- buffer.lines.shift();
602
- }
603
- // Prefer inline panel (scroll box) when unified renderer is active
604
- if (this.shouldUseInlineScrollBox()) {
605
- this.scrollingPanelOwner = callId;
606
- const panelLines = this.buildScrollingPanelLines(buffer.lines);
607
- this.display.showInlinePanel(panelLines);
608
- buffer.displayedLineCount = 0;
609
- return;
610
- }
611
- // Move cursor up to overwrite previous lines (if any were displayed)
612
- if (buffer.displayedLineCount > 0) {
613
- process.stdout.write(`\x1b[${buffer.displayedLineCount}A`);
614
- }
615
- // Clear and rewrite the scrolling region
616
- const linesToShow = buffer.lines;
617
- for (let i = 0; i < linesToShow.length; i++) {
618
- process.stdout.write(`\r\x1b[K${linesToShow[i]}\n`);
619
- }
620
- // Track how many lines we displayed for next update
621
- buffer.displayedLineCount = linesToShow.length;
622
- }
623
- /**
624
- * Build the pinned scroll-box contents for live tool output.
625
- */
626
- buildScrollingPanelLines(lines) {
627
- if (!lines.length) {
628
- return [];
629
- }
630
- const header = theme.ui.muted(`${icons.action} live output (latest ${Math.min(lines.length, this.maxScrollingLines)})`);
631
- const indented = lines.map(text => ` ${text}`);
632
- return [header, ...indented];
633
- }
634
- /**
635
- * Use inline scroll box only when an interactive renderer is active.
636
- */
637
- shouldUseInlineScrollBox() {
638
- return this.display.hasRenderer() && process.stdout.isTTY && !process.env['CI'];
639
- }
640
- /**
641
- * Finalize scrolling output - called when tool completes
642
- * Clears the scrolling progress lines so only the final result is shown
643
- */
644
- finalizeScrollingOutput(callId) {
645
- if (this.scrollingPanelOwner === callId && this.shouldUseInlineScrollBox()) {
646
- this.display.clearInlinePanel();
647
- this.scrollingPanelOwner = null;
648
- this.scrollingOutputBuffer.delete(callId);
649
- return;
650
- }
651
- const buffer = this.scrollingOutputBuffer.get(callId);
652
- if (buffer && buffer.displayedLineCount > 0 && process.stdout.isTTY) {
653
- // Move cursor up and clear all the progress lines
654
- process.stdout.write(`\x1b[${buffer.displayedLineCount}A`);
655
- for (let i = 0; i < buffer.displayedLineCount; i++) {
656
- process.stdout.write(`\r\x1b[K\n`);
657
- }
658
- // Move cursor back up to where progress started
659
- process.stdout.write(`\x1b[${buffer.displayedLineCount}A`);
660
- }
661
- this.scrollingOutputBuffer.delete(callId);
662
560
  }
663
561
  logExploreProgress(call, progress) {
664
562
  const previous = this.exploreProgressState.get(call.id) ?? { lastCurrent: 0 };
@@ -672,14 +570,8 @@ export class ShellUIAdapter {
672
570
  }
673
571
  const line = parts.join(theme.ui.muted(' • '));
674
572
  if (line && line !== previous.lastLine) {
675
- // Use in-place update for TTY - single line that updates without scrolling
676
- if (process.stdout.isTTY && previous.lastLine) {
677
- // Move cursor up one line and clear it, then write new line
678
- process.stdout.write(`\x1b[1A\r\x1b[K${line}\n`);
679
- }
680
- else {
681
- this.display.stream(`${line}\n`);
682
- }
573
+ // Single trailing newline - no leading newline to avoid gaps
574
+ this.display.stream(`${line}\n`);
683
575
  }
684
576
  this.exploreProgressState.set(call.id, {
685
577
  total,
@@ -693,10 +585,6 @@ export class ShellUIAdapter {
693
585
  const summary = success
694
586
  ? `${theme.success('✓')} Indexed ${total} file${total === 1 ? '' : 's'}`
695
587
  : `${theme.error('✗')} Explore interrupted`;
696
- // Clear the progress line before showing summary (if TTY and had progress)
697
- if (process.stdout.isTTY && state?.lastLine) {
698
- process.stdout.write(`\x1b[1A\r\x1b[K`);
699
- }
700
588
  this.display.stream(`${summary}\n`);
701
589
  }
702
590
  /**
@@ -706,13 +594,12 @@ export class ShellUIAdapter {
706
594
  clearToolState(callId) {
707
595
  this.toolProgressSnapshots.delete(callId);
708
596
  this.exploreProgressState.delete(callId);
709
- this.finalizeScrollingOutput(callId);
710
597
  }
711
598
  /**
712
599
  * Display tool call start with Erosolar-CLI style prefix
713
600
  * - ✢ for Task/agent tools (active task indicator)
714
601
  * - ⏺ for other tools
715
- * Uses scrolling display to show only the last N tool calls
602
+ * Routes through display module to work with scroll regions
716
603
  */
717
604
  displayToolCallStart(call, isCacheHit = false) {
718
605
  const args = call.arguments || {};
@@ -735,41 +622,8 @@ export class ShellUIAdapter {
735
622
  }
736
623
  // Erosolar-CLI style: bullet + optional task marker + ToolName(args)
737
624
  const line = `${bullet}${taskMarker ? taskMarker : ''} ${displayName}${argsStr}${cacheHint}`;
738
- // Use scrolling display for tool calls - shows only recent calls
739
- this.appendToolCallDisplay(call.id, line);
740
- }
741
- /**
742
- * Append a tool call to the scrolling display, keeping only the last N visible.
743
- * Older tool calls scroll up and disappear, showing only recent activity.
744
- */
745
- appendToolCallDisplay(callId, line) {
746
- // Add to recent tool calls
747
- this.recentToolCalls.push({ line, id: callId });
748
- // Keep only the last maxVisibleToolCalls
749
- while (this.recentToolCalls.length > this.maxVisibleToolCalls) {
750
- this.recentToolCalls.shift();
751
- }
752
- // On TTY, use in-place updating to show only recent tool calls
753
- if (process.stdout.isTTY && this.toolCallDisplayedCount > 0) {
754
- // Move cursor up to overwrite previous tool call lines
755
- const moveUp = `\x1b[${this.toolCallDisplayedCount}A`;
756
- // Clear from cursor to end of screen
757
- const clearDown = '\x1b[J';
758
- process.stdout.write(moveUp + clearDown);
759
- }
760
- // Display all recent tool calls
761
- for (const toolCall of this.recentToolCalls) {
762
- this.display.stream(`${toolCall.line}\n`);
763
- }
764
- // Track how many lines we displayed
765
- this.toolCallDisplayedCount = this.recentToolCalls.length;
766
- }
767
- /**
768
- * Clear tool call display state when processing completes
769
- */
770
- clearToolCallDisplay() {
771
- this.recentToolCalls = [];
772
- this.toolCallDisplayedCount = 0;
625
+ // Stream with spacing to keep it as a distinct event
626
+ this.display.stream(`\n${line}\n`);
773
627
  }
774
628
  /**
775
629
  * Format tool arguments inline for display (Erosolar-CLI style)
@@ -918,8 +772,8 @@ export class ShellUIAdapter {
918
772
  * Start processing a request
919
773
  */
920
774
  startProcessing(message = 'Working on your request') {
775
+ this.isProcessing = true;
921
776
  this.resetToolUsageSummary();
922
- this.clearToolCallDisplay(); // Clear previous tool call display
923
777
  this.uiController.startProcessing();
924
778
  this.uiController.setBaseStatus(message, 'info');
925
779
  }
@@ -927,17 +781,20 @@ export class ShellUIAdapter {
927
781
  * End processing
928
782
  */
929
783
  endProcessing(message = 'Ready for prompts') {
784
+ this.isProcessing = false;
930
785
  this.uiController.endProcessing();
931
786
  this.showToolUsageSummary();
932
787
  this.uiController.setBaseStatus(message, 'success');
933
- // Clear tool call scrolling state for next request
934
- this.clearToolCallDisplay();
935
788
  }
936
789
  /**
937
- * Update context usage (no-op - status handled by display.showStatusLine)
790
+ * Update context usage
791
+ * NOTE: This only tracks the value internally. The actual context display
792
+ * is handled by display.showStatusLine() which shows "Session Xm • Context Y% used • Ready"
793
+ * We don't push a status override here to avoid duplicate context lines.
938
794
  */
939
- updateContextUsage(_percentage) {
940
- // No-op - context display is handled by display.showStatusLine()
795
+ updateContextUsage(percentage) {
796
+ this.contextUsage = percentage;
797
+ // Status updates are internal; avoid forcing overlay rerender
941
798
  }
942
799
  /**
943
800
  * Show a user interrupt
@@ -957,7 +814,7 @@ export class ShellUIAdapter {
957
814
  /**
958
815
  * Show profile switcher (Shift+Tab)
959
816
  */
960
- showProfileSwitcher(profiles, _currentProfile) {
817
+ showProfileSwitcher(profiles, currentProfile) {
961
818
  this.updateCoordinator.enqueue({
962
819
  lane: 'overlay',
963
820
  description: 'profile-switcher',
@@ -1169,12 +1026,6 @@ export class ShellUIAdapter {
1169
1026
  callback(this.buildRunningStatusLine(this.activeToolStatus.description, this.activeToolStatus.startedAt));
1170
1027
  }
1171
1028
  }
1172
- /**
1173
- * Set activity callback - called during tool progress to update activity line with streaming output
1174
- */
1175
- setActivityCallback(callback) {
1176
- this._activityCallback = callback;
1177
- }
1178
1029
  /**
1179
1030
  * Keep a live status line ticking while an SSE tool or long-running tool is active.
1180
1031
  * Uses a heartbeat to animate the spinner and update elapsed time without spamming writes.
@@ -1295,10 +1146,79 @@ export class ShellUIAdapter {
1295
1146
  const prefix = success
1296
1147
  ? theme.success(icons.subaction)
1297
1148
  : theme.error(icons.subaction);
1298
- // Single newline - next tool call adds its own spacing
1299
- this.display.stream(` ${prefix} ${summary}\n`);
1149
+ // Add newline after result for visual separation
1150
+ this.display.stream(` ${prefix} ${summary}\n\n`);
1300
1151
  }
1301
1152
  }
1153
+ /**
1154
+ * Render a rich block for non-streamed SSE-style tool results so they feel like a "completed event".
1155
+ * Returns true if a block was rendered and downstream compact formatting should be skipped.
1156
+ */
1157
+ maybeRenderRichToolResult(call, output, success, durationMs) {
1158
+ if (!this.shouldRenderRichToolBlock(call, output)) {
1159
+ return false;
1160
+ }
1161
+ const block = this.renderRichToolBlock(call, output, success, durationMs);
1162
+ if (!block.trim()) {
1163
+ return false;
1164
+ }
1165
+ this.display.stream(`\n${block}\n\n`);
1166
+ return true;
1167
+ }
1168
+ shouldRenderRichToolBlock(call, output) {
1169
+ const trimmed = output?.trim() ?? '';
1170
+ if (!trimmed)
1171
+ return false;
1172
+ // Skip tools that already have bespoke rendering
1173
+ if (call.name === 'Edit' || call.name === 'edit_file' || call.name === 'list_files') {
1174
+ return false;
1175
+ }
1176
+ // Read operations should stay compact (single-line summary), not boxed
1177
+ if (call.name === 'Read' || call.name === 'read_file') {
1178
+ return false;
1179
+ }
1180
+ const isMcpTool = call.name.startsWith('mcp__');
1181
+ const isMultiline = trimmed.includes('\n');
1182
+ const isLong = trimmed.length > 160;
1183
+ return isMcpTool || isMultiline || isLong;
1184
+ }
1185
+ renderRichToolBlock(call, output, success, durationMs) {
1186
+ const columns = Math.max(60, Math.min(getTerminalColumns(), 120));
1187
+ const innerWidth = Math.max(40, columns - 4);
1188
+ const statusIcon = success ? theme.success(icons.success) : theme.error(icons.error);
1189
+ const labels = [
1190
+ `${statusIcon} ${theme.tool(call.name)}`,
1191
+ call.name.startsWith('mcp__') ? theme.ui.muted('SSE result') : theme.ui.muted('Tool result'),
1192
+ ];
1193
+ const elapsed = this.formatElapsedMs(durationMs);
1194
+ if (elapsed) {
1195
+ labels.push(`${theme.metrics.elapsedLabel('elapsed')} ${theme.metrics.elapsedValue(elapsed)}`);
1196
+ }
1197
+ const title = labels.filter(Boolean).join(theme.ui.muted(' • '));
1198
+ const titlePadding = Math.max(0, innerWidth - this.visibleLength(title) - 2);
1199
+ const top = theme.ui.muted(`╭─ ${title} ${'─'.repeat(titlePadding)}╮`);
1200
+ const bottom = theme.ui.muted(`╰${'─'.repeat(innerWidth + 2)}╯`);
1201
+ const bodyLines = formatRichContent(output.trim(), innerWidth);
1202
+ const boxed = bodyLines.length ? bodyLines : [''];
1203
+ const content = boxed.map(line => {
1204
+ const truncated = this.visibleLength(line) > innerWidth
1205
+ ? `${line.slice(0, Math.max(0, innerWidth - 1))}…`
1206
+ : line;
1207
+ const pad = Math.max(0, innerWidth - this.visibleLength(truncated));
1208
+ return `${theme.ui.muted('│')} ${truncated}${' '.repeat(pad)} ${theme.ui.muted('│')}`;
1209
+ });
1210
+ return [top, ...content, bottom].join('\n');
1211
+ }
1212
+ visibleLength(value) {
1213
+ if (!value)
1214
+ return 0;
1215
+ return this.stripAnsi(value).length;
1216
+ }
1217
+ stripAnsi(value) {
1218
+ if (!value)
1219
+ return '';
1220
+ return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '');
1221
+ }
1302
1222
  /**
1303
1223
  * Format tool result summary in Erosolar-CLI style
1304
1224
  * Returns compact summary like "Found 4 files" or "Read 50 lines"
@@ -1411,32 +1331,14 @@ export class ShellUIAdapter {
1411
1331
  }
1412
1332
  return 'Todos updated';
1413
1333
  }
1414
- case 'grep_search':
1415
- case 'search_files':
1416
- case 'Grep': {
1417
- // Count matches in output - look for file paths or line numbers
1418
- const lines = output.trim().split('\n').filter(l => l.trim());
1419
- if (lines.length === 0 || output.includes('No matches found') || output.includes('no matches')) {
1420
- return theme.ui.muted('No matches');
1421
- }
1422
- // Count unique files or match lines
1423
- const fileMatches = output.match(/^[^\n:]+:\d+:/gm);
1424
- if (fileMatches) {
1425
- const uniqueFiles = new Set(fileMatches.map(m => m.split(':')[0])).size;
1426
- return `${theme.info(String(fileMatches.length))} matches in ${uniqueFiles} file${uniqueFiles === 1 ? '' : 's'}`;
1427
- }
1428
- return `${theme.info(String(lines.length))} matches`;
1429
- }
1430
1334
  default: {
1431
- // Generic result - show more info for better context
1335
+ // Generic result
1432
1336
  const lines = output.trim().split('\n').filter(l => l).length;
1433
1337
  if (lines === 0) {
1434
- return theme.ui.muted('No output');
1338
+ return 'Completed';
1435
1339
  }
1436
1340
  if (lines <= 3) {
1437
- // Show the actual output if short
1438
- const shortOutput = output.trim().substring(0, 60);
1439
- return shortOutput.length < output.trim().length ? `${shortOutput}…` : shortOutput;
1341
+ return 'Completed';
1440
1342
  }
1441
1343
  return `${theme.info(String(lines))} lines`;
1442
1344
  }