erosolar-cli 1.7.410 → 1.7.411

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 (205) hide show
  1. package/README.md +6 -6
  2. package/dist/StringUtils.d.ts +1 -4
  3. package/dist/StringUtils.d.ts.map +1 -1
  4. package/dist/StringUtils.js +2 -8
  5. package/dist/StringUtils.js.map +1 -1
  6. package/dist/browser/BrowserSessionManager.d.ts +1 -3
  7. package/dist/browser/BrowserSessionManager.d.ts.map +1 -1
  8. package/dist/browser/BrowserSessionManager.js +4 -24
  9. package/dist/browser/BrowserSessionManager.js.map +1 -1
  10. package/dist/capabilities/askUserCapability.d.ts.map +1 -1
  11. package/dist/capabilities/askUserCapability.js +64 -10
  12. package/dist/capabilities/askUserCapability.js.map +1 -1
  13. package/dist/capabilities/toolRegistry.d.ts +2 -0
  14. package/dist/capabilities/toolRegistry.d.ts.map +1 -1
  15. package/dist/capabilities/toolRegistry.js +40 -5
  16. package/dist/capabilities/toolRegistry.js.map +1 -1
  17. package/dist/contracts/agent-profiles.schema.json +5 -5
  18. package/dist/contracts/agent-schemas.json +6 -16
  19. package/dist/contracts/schemas/agent.schema.json +1 -5
  20. package/dist/contracts/schemas/tool-selection.schema.json +1 -7
  21. package/dist/contracts/tools.schema.json +80 -207
  22. package/dist/contracts/unified-schema.json +4 -5
  23. package/dist/contracts/v1/agent.d.ts +0 -3
  24. package/dist/contracts/v1/agent.d.ts.map +1 -1
  25. package/dist/contracts/v1/provider.d.ts +1 -2
  26. package/dist/contracts/v1/provider.d.ts.map +1 -1
  27. package/dist/contracts/v1/toolAccess.d.ts +1 -1
  28. package/dist/contracts/v1/toolAccess.d.ts.map +1 -1
  29. package/dist/core/agent.d.ts +1 -7
  30. package/dist/core/agent.d.ts.map +1 -1
  31. package/dist/core/agent.js +2 -131
  32. package/dist/core/agent.js.map +1 -1
  33. package/dist/core/alphaZeroEngine.d.ts +0 -8
  34. package/dist/core/alphaZeroEngine.d.ts.map +1 -1
  35. package/dist/core/alphaZeroEngine.js +35 -149
  36. package/dist/core/alphaZeroEngine.js.map +1 -1
  37. package/dist/core/alphaZeroOrchestrator.d.ts +0 -17
  38. package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
  39. package/dist/core/alphaZeroOrchestrator.js +8 -95
  40. package/dist/core/alphaZeroOrchestrator.js.map +1 -1
  41. package/dist/core/claudeCodeFeatures.d.ts +2 -1
  42. package/dist/core/claudeCodeFeatures.d.ts.map +1 -1
  43. package/dist/core/claudeCodeFeatures.js +2 -1
  44. package/dist/core/claudeCodeFeatures.js.map +1 -1
  45. package/dist/core/cliTestHarness.d.ts +0 -5
  46. package/dist/core/cliTestHarness.d.ts.map +1 -1
  47. package/dist/core/cliTestHarness.js +3 -14
  48. package/dist/core/cliTestHarness.js.map +1 -1
  49. package/dist/core/contextManager.d.ts +0 -30
  50. package/dist/core/contextManager.d.ts.map +1 -1
  51. package/dist/core/contextManager.js +5 -87
  52. package/dist/core/contextManager.js.map +1 -1
  53. package/dist/core/contextWindow.d.ts +4 -4
  54. package/dist/core/contextWindow.js +9 -9
  55. package/dist/core/contextWindow.js.map +1 -1
  56. package/dist/core/modelDiscovery.js +3 -3
  57. package/dist/core/modelDiscovery.js.map +1 -1
  58. package/dist/core/preferences.d.ts +2 -3
  59. package/dist/core/preferences.d.ts.map +1 -1
  60. package/dist/core/preferences.js +11 -18
  61. package/dist/core/preferences.js.map +1 -1
  62. package/dist/core/secretStore.d.ts.map +1 -1
  63. package/dist/core/secretStore.js +31 -0
  64. package/dist/core/secretStore.js.map +1 -1
  65. package/dist/core/toolPreconditions.d.ts.map +1 -1
  66. package/dist/core/toolPreconditions.js +0 -60
  67. package/dist/core/toolPreconditions.js.map +1 -1
  68. package/dist/core/toolRuntime.d.ts.map +1 -1
  69. package/dist/core/toolRuntime.js +0 -17
  70. package/dist/core/toolRuntime.js.map +1 -1
  71. package/dist/core/types.d.ts +1 -1
  72. package/dist/core/types.d.ts.map +1 -1
  73. package/dist/headless/headlessApp.d.ts.map +1 -1
  74. package/dist/headless/headlessApp.js +6 -22
  75. package/dist/headless/headlessApp.js.map +1 -1
  76. package/dist/plugins/providers/google/index.js +3 -2
  77. package/dist/plugins/providers/google/index.js.map +1 -1
  78. package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
  79. package/dist/providers/openaiChatCompletionsProvider.js +6 -60
  80. package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
  81. package/dist/runtime/agentController.d.ts.map +1 -1
  82. package/dist/runtime/agentController.js +6 -27
  83. package/dist/runtime/agentController.js.map +1 -1
  84. package/dist/shell/interactiveShell.d.ts +30 -79
  85. package/dist/shell/interactiveShell.d.ts.map +1 -1
  86. package/dist/shell/interactiveShell.js +726 -1511
  87. package/dist/shell/interactiveShell.js.map +1 -1
  88. package/dist/shell/shellApp.d.ts.map +1 -1
  89. package/dist/shell/shellApp.js +41 -15
  90. package/dist/shell/shellApp.js.map +1 -1
  91. package/dist/shell/systemPrompt.d.ts.map +1 -1
  92. package/dist/shell/systemPrompt.js +0 -1
  93. package/dist/shell/systemPrompt.js.map +1 -1
  94. package/dist/shell/terminalInput.d.ts +21 -85
  95. package/dist/shell/terminalInput.d.ts.map +1 -1
  96. package/dist/shell/terminalInput.js +60 -517
  97. package/dist/shell/terminalInput.js.map +1 -1
  98. package/dist/shell/terminalInputAdapter.d.ts +16 -37
  99. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  100. package/dist/shell/terminalInputAdapter.js +22 -44
  101. package/dist/shell/terminalInputAdapter.js.map +1 -1
  102. package/dist/shell/updateManager.d.ts.map +1 -1
  103. package/dist/shell/updateManager.js +17 -1
  104. package/dist/shell/updateManager.js.map +1 -1
  105. package/dist/subagents/parallelAgentManager.d.ts.map +1 -1
  106. package/dist/subagents/parallelAgentManager.js +2 -1
  107. package/dist/subagents/parallelAgentManager.js.map +1 -1
  108. package/dist/tools/buildTools.d.ts.map +1 -1
  109. package/dist/tools/buildTools.js +76 -19
  110. package/dist/tools/buildTools.js.map +1 -1
  111. package/dist/tools/editTools.js +1 -1
  112. package/dist/tools/editTools.js.map +1 -1
  113. package/dist/tools/enhancedCodeIntelligenceTools.js +2 -1
  114. package/dist/tools/enhancedCodeIntelligenceTools.js.map +1 -1
  115. package/dist/tools/fileTools.js +0 -3
  116. package/dist/tools/fileTools.js.map +1 -1
  117. package/dist/tools/frontendTestingTools.js +1 -1
  118. package/dist/tools/frontendTestingTools.js.map +1 -1
  119. package/dist/tools/interactionTools.d.ts.map +1 -1
  120. package/dist/tools/interactionTools.js +82 -15
  121. package/dist/tools/interactionTools.js.map +1 -1
  122. package/dist/tools/learnTools.d.ts +0 -2
  123. package/dist/tools/learnTools.d.ts.map +1 -1
  124. package/dist/tools/learnTools.js +81 -29
  125. package/dist/tools/learnTools.js.map +1 -1
  126. package/dist/tools/localExplore.d.ts.map +1 -1
  127. package/dist/tools/localExplore.js +1 -0
  128. package/dist/tools/localExplore.js.map +1 -1
  129. package/dist/tools/notebookEditTools.js.map +1 -1
  130. package/dist/tools/repoChecksTools.js +3 -4
  131. package/dist/tools/repoChecksTools.js.map +1 -1
  132. package/dist/tools/searchTools.js +0 -4
  133. package/dist/tools/searchTools.js.map +1 -1
  134. package/dist/tools/softwareEngineeringTools.d.ts.map +1 -1
  135. package/dist/tools/softwareEngineeringTools.js +0 -1
  136. package/dist/tools/softwareEngineeringTools.js.map +1 -1
  137. package/dist/tools/webTools.d.ts.map +1 -1
  138. package/dist/tools/webTools.js.map +1 -1
  139. package/dist/ui/ShellUIAdapter.d.ts +17 -54
  140. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  141. package/dist/ui/ShellUIAdapter.js +90 -378
  142. package/dist/ui/ShellUIAdapter.js.map +1 -1
  143. package/dist/ui/display.d.ts +38 -19
  144. package/dist/ui/display.d.ts.map +1 -1
  145. package/dist/ui/display.js +311 -96
  146. package/dist/ui/display.js.map +1 -1
  147. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  148. package/dist/ui/orchestration/UIUpdateCoordinator.js +3 -5
  149. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  150. package/dist/ui/shortcutsHelp.d.ts +11 -1
  151. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  152. package/dist/ui/shortcutsHelp.js +54 -8
  153. package/dist/ui/shortcutsHelp.js.map +1 -1
  154. package/dist/ui/streamingFormatter.d.ts +16 -0
  155. package/dist/ui/streamingFormatter.d.ts.map +1 -0
  156. package/dist/ui/streamingFormatter.js +62 -0
  157. package/dist/ui/streamingFormatter.js.map +1 -0
  158. package/dist/ui/theme.d.ts +100 -100
  159. package/dist/ui/theme.d.ts.map +1 -1
  160. package/dist/ui/theme.js.map +1 -1
  161. package/dist/ui/toolDisplay.d.ts +3 -3
  162. package/dist/ui/toolDisplay.d.ts.map +1 -1
  163. package/dist/ui/toolDisplay.js +8 -7
  164. package/dist/ui/toolDisplay.js.map +1 -1
  165. package/dist/ui/unified/index.d.ts +3 -2
  166. package/dist/ui/unified/index.d.ts.map +1 -1
  167. package/dist/ui/unified/index.js +1 -0
  168. package/dist/ui/unified/index.js.map +1 -1
  169. package/dist/ui/unified/layout.d.ts +23 -0
  170. package/dist/ui/unified/layout.d.ts.map +1 -1
  171. package/dist/ui/unified/layout.js +113 -11
  172. package/dist/ui/unified/layout.js.map +1 -1
  173. package/package.json +24 -37
  174. package/dist/core/alphaZeroConfig.d.ts +0 -11
  175. package/dist/core/alphaZeroConfig.d.ts.map +0 -1
  176. package/dist/core/alphaZeroConfig.js +0 -59
  177. package/dist/core/alphaZeroConfig.js.map +0 -1
  178. package/dist/core/alphaZeroEnhanced.d.ts +0 -125
  179. package/dist/core/alphaZeroEnhanced.d.ts.map +0 -1
  180. package/dist/core/alphaZeroEnhanced.js +0 -386
  181. package/dist/core/alphaZeroEnhanced.js.map +0 -1
  182. package/dist/core/autonomousVerification.d.ts +0 -103
  183. package/dist/core/autonomousVerification.d.ts.map +0 -1
  184. package/dist/core/autonomousVerification.js +0 -583
  185. package/dist/core/autonomousVerification.js.map +0 -1
  186. package/dist/core/offsecAlphaZeroEnhanced.d.ts +0 -98
  187. package/dist/core/offsecAlphaZeroEnhanced.d.ts.map +0 -1
  188. package/dist/core/offsecAlphaZeroEnhanced.js +0 -441
  189. package/dist/core/offsecAlphaZeroEnhanced.js.map +0 -1
  190. package/dist/core/parallelAgentOrchestrator.d.ts +0 -171
  191. package/dist/core/parallelAgentOrchestrator.d.ts.map +0 -1
  192. package/dist/core/parallelAgentOrchestrator.js +0 -459
  193. package/dist/core/parallelAgentOrchestrator.js.map +0 -1
  194. package/dist/index.d.ts +0 -5
  195. package/dist/index.d.ts.map +0 -1
  196. package/dist/index.js +0 -3
  197. package/dist/index.js.map +0 -1
  198. package/dist/tools/detectCommands.d.ts +0 -8
  199. package/dist/tools/detectCommands.d.ts.map +0 -1
  200. package/dist/tools/detectCommands.js +0 -183
  201. package/dist/tools/detectCommands.js.map +0 -1
  202. package/dist/ui/assistantBlockRenderer.d.ts +0 -30
  203. package/dist/ui/assistantBlockRenderer.d.ts.map +0 -1
  204. package/dist/ui/assistantBlockRenderer.js +0 -121
  205. package/dist/ui/assistantBlockRenderer.js.map +0 -1
@@ -1,9 +1,11 @@
1
1
  import { createSpinner } from 'nanospinner';
2
2
  import { clearScreenDown, cursorTo } from 'node:readline';
3
3
  import { theme, icons } from './theme.js';
4
+ import { formatRichContent, renderMessagePanel, renderMessageBody } from './richText.js';
4
5
  import { getTerminalColumns } from './layout.js';
5
6
  import { highlightError } from './textHighlighter.js';
6
7
  import { renderSectionHeading } from './designSystem.js';
8
+ import { isPlainOutputMode } from './outputMode.js';
7
9
  import { writeLock } from './writeLock.js';
8
10
  import { renderStatusLine } from './unified/layout.js';
9
11
  import { isStreamingMode } from './globalWriteLock.js';
@@ -147,8 +149,11 @@ const DISPLAY_CONSTANTS = {
147
149
  MIN_MESSAGE_WIDTH: 42,
148
150
  MAX_MESSAGE_WIDTH: 110,
149
151
  MESSAGE_PADDING: 4,
152
+ MIN_ACTION_WIDTH: 40,
153
+ MAX_ACTION_WIDTH: 90,
150
154
  MIN_THOUGHT_WIDTH: 48,
151
155
  MAX_THOUGHT_WIDTH: 96,
156
+ MIN_CONTENT_WIDTH: 10,
152
157
  MIN_WRAP_WIDTH: 12,
153
158
  SPINNER_INTERVAL: 80,
154
159
  };
@@ -161,7 +166,7 @@ const SPINNER_FRAMES = ['✻', '◐', '✻', '◓', '✻', '◑', '✻', '◒'];
161
166
  * Architecture:
162
167
  * - Per-stream line tracking via StdoutLineTracker for consistent banner updates
163
168
  * - Output interceptor pattern for live update integration
164
- * - Banner state management for streamed updates
169
+ * - Banner state management for in-place updates
165
170
  * - Configurable width constraints via DISPLAY_CONSTANTS
166
171
  *
167
172
  * Claude Code Style Formatting:
@@ -195,8 +200,6 @@ export class Display {
195
200
  thinkingElapsedTimer = null;
196
201
  thinkingBaseMessage = 'Thinking...';
197
202
  pendingCarriageReturn = false;
198
- captureStack = [];
199
- mutedOutput = false;
200
203
  // Streaming status line (Claude Code style - fixed at bottom using scroll region)
201
204
  streamingStatusVisible = false;
202
205
  scrollRegionActive = false;
@@ -259,68 +262,10 @@ export class Display {
259
262
  interceptors[index]?.afterWrite?.(content);
260
263
  }
261
264
  }
262
- /**
263
- * Capture all display output emitted during the provided function while optionally
264
- * silencing writes to the terminal. Supports nesting by appending captured output
265
- * to the parent buffer when present.
266
- */
267
- async captureOutput(fn, options = {}) {
268
- const scope = {
269
- buffer: [],
270
- includeStreaming: options.includeStreaming ?? true,
271
- suppressTypes: new Set(options.suppressTypes ?? []),
272
- };
273
- this.captureStack.push(scope);
274
- let output = '';
275
- const previousMute = this.mutedOutput;
276
- if (options.silenceOutput) {
277
- this.mutedOutput = true;
278
- }
279
- try {
280
- const result = await fn();
281
- output = scope.buffer.join('');
282
- return { output, result };
283
- }
284
- catch (error) {
285
- output = scope.buffer.join('');
286
- if (error && typeof error === 'object') {
287
- error.capturedOutput = output;
288
- }
289
- throw error;
290
- }
291
- finally {
292
- this.captureStack.pop();
293
- this.mutedOutput = previousMute;
294
- if (output && this.captureStack.length > 0) {
295
- this.captureStack[this.captureStack.length - 1].buffer.push(output);
296
- }
297
- }
298
- }
299
- appendToCapture(content, type) {
300
- if (!content || this.captureStack.length === 0) {
301
- return;
302
- }
303
- for (const scope of this.captureStack) {
304
- if (type === 'stream' && !scope.includeStreaming) {
305
- continue;
306
- }
307
- scope.buffer.push(content);
308
- }
309
- }
310
- shouldSuppressOutput(type) {
311
- if (this.mutedOutput) {
312
- return true;
313
- }
314
- return this.captureStack.some((scope) => scope.suppressTypes.has(type));
315
- }
316
265
  write(value, target = this.outputStream) {
317
266
  // Write directly - this method is called from within locked contexts
318
267
  // like withOutput(), so we don't need additional locking here.
319
268
  // The globalWriteLock wrapper will handle coordination if needed.
320
- this.appendToCapture(value, 'normal');
321
- if (this.shouldSuppressOutput('normal')) {
322
- return;
323
- }
324
269
  try {
325
270
  target.write(value);
326
271
  }
@@ -347,24 +292,33 @@ export class Display {
347
292
  if (!normalized) {
348
293
  return;
349
294
  }
350
- this.appendToCapture(normalized, 'stream');
351
- if (this.shouldSuppressOutput('stream')) {
295
+ // During streaming, use withLock to prevent interleaving with escape codes.
296
+ // This ensures the before/write/after sequence is atomic.
297
+ if (isStreamingMode()) {
298
+ writeLock.withLock(() => {
299
+ this.notifyBeforeOutput();
300
+ try {
301
+ this.outputStream.write(normalized);
302
+ }
303
+ catch {
304
+ // Ignore write failures to keep UI resilient
305
+ }
306
+ finally {
307
+ this.notifyAfterOutput(normalized);
308
+ }
309
+ }, 'display.writeRaw.streaming');
352
310
  return;
353
311
  }
354
- const writeStreamChunk = () => {
312
+ // Outside streaming mode, use locks to coordinate with other UI
313
+ writeLock.withLock(() => {
355
314
  this.notifyBeforeOutput();
356
315
  try {
357
- this.outputStream.write(normalized);
358
- }
359
- catch {
360
- // Ignore write failures to keep UI resilient
316
+ this.write(normalized);
361
317
  }
362
318
  finally {
363
319
  this.notifyAfterOutput(normalized);
364
320
  }
365
- };
366
- // Use locks in both streaming and non-streaming modes to keep output atomic
367
- writeLock.withLock(writeStreamChunk, isStreamingMode() ? 'display.writeRaw.streaming' : 'display.writeRaw');
321
+ }, 'display.writeRaw');
368
322
  }
369
323
  /**
370
324
  * Normalize streaming chunks so progress-style carriage returns render cleanly.
@@ -455,9 +409,6 @@ export class Display {
455
409
  }
456
410
  // Banner is now streamed by the shell - no storage needed
457
411
  showThinking(message = 'Thinking…') {
458
- if (this.shouldSuppressOutput('normal')) {
459
- return;
460
- }
461
412
  // If we already have a spinner, just update its text instead of creating a new one
462
413
  if (this.activeSpinner) {
463
414
  this.thinkingBaseMessage = message;
@@ -558,9 +509,6 @@ export class Display {
558
509
  * This allows content to scroll while status stays fixed.
559
510
  */
560
511
  setupScrollRegion() {
561
- if (this.shouldSuppressOutput('normal')) {
562
- return;
563
- }
564
512
  if (this.scrollRegionActive || !this.isTTY()) {
565
513
  return;
566
514
  }
@@ -577,9 +525,6 @@ export class Display {
577
525
  * Tear down scroll region and restore normal terminal.
578
526
  */
579
527
  teardownScrollRegion() {
580
- if (this.shouldSuppressOutput('normal')) {
581
- return;
582
- }
583
528
  if (!this.scrollRegionActive) {
584
529
  return;
585
530
  }
@@ -683,43 +628,134 @@ export class Display {
683
628
  });
684
629
  }
685
630
  }
686
- showSystemMessage(content) {
631
+ showAssistantMessage(content, metadata) {
632
+ if (!content.trim()) {
633
+ return;
634
+ }
687
635
  this.clearSpinnerIfActive();
636
+ const isThought = metadata?.isFinal === false;
637
+ const body = isThought ? this.buildClaudeStyleThought(content) : this.buildChatBox(content, metadata);
638
+ if (!body.trim()) {
639
+ return;
640
+ }
688
641
  this.withOutput(() => {
689
- this.writeLine(content.trim());
642
+ this.writeLine(); // Ensure clean start for the box
643
+ this.writeLine(body);
690
644
  this.writeLine();
691
645
  });
692
646
  }
693
- showError(message, error) {
647
+ showNarrative(content) {
648
+ if (!content.trim()) {
649
+ return;
650
+ }
651
+ this.showAssistantMessage(content, { isFinal: false });
652
+ }
653
+ showAction(text, status = 'info') {
654
+ if (!text.trim()) {
655
+ return;
656
+ }
694
657
  this.clearSpinnerIfActive();
695
- const details = this.formatErrorDetails(error);
658
+ // Claude Code style: always use ⏺ prefix for actions
659
+ const icon = this.formatActionIcon(status);
696
660
  this.withOutput(() => {
697
- this.writeLine(`${theme.error('✗')} ${message}`, this.errorStream);
698
- if (details) {
699
- this.writeLine(` ${details}`, this.errorStream);
700
- }
661
+ this.writeLine(this.wrapWithPrefix(text, `${icon} `));
701
662
  });
702
663
  }
703
- showWarning(message) {
664
+ showSubAction(text, status = 'info') {
665
+ if (!text.trim()) {
666
+ return;
667
+ }
704
668
  this.clearSpinnerIfActive();
669
+ const prefersRich = text.includes('```');
670
+ let rendered = prefersRich ? this.buildRichSubActionLines(text, status) : this.buildWrappedSubActionLines(text, status);
671
+ if (!rendered.length && prefersRich) {
672
+ rendered = this.buildWrappedSubActionLines(text, status);
673
+ }
674
+ if (!rendered.length) {
675
+ return;
676
+ }
705
677
  this.withOutput(() => {
706
- this.writeLine(`${theme.warning('!')} ${message}`, this.errorStream);
678
+ this.writeLine(rendered.join('\n'));
679
+ this.writeLine();
707
680
  });
708
681
  }
682
+ buildWrappedSubActionLines(text, status) {
683
+ const lines = text.split('\n').map((line) => line.trimEnd());
684
+ while (lines.length && !lines[lines.length - 1]?.trim()) {
685
+ lines.pop();
686
+ }
687
+ if (!lines.length) {
688
+ return [];
689
+ }
690
+ const rendered = [];
691
+ for (let index = 0; index < lines.length; index += 1) {
692
+ const segment = lines[index] ?? '';
693
+ const isLast = index === lines.length - 1;
694
+ const { prefix, continuation } = this.buildSubActionPrefixes(status, isLast);
695
+ rendered.push(this.wrapWithPrefix(segment, prefix, { continuationPrefix: continuation }));
696
+ }
697
+ return rendered;
698
+ }
699
+ buildRichSubActionLines(text, status) {
700
+ const normalized = text.trim();
701
+ if (!normalized) {
702
+ return [];
703
+ }
704
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_ACTION_WIDTH, Math.min(this.getColumnWidth(), DISPLAY_CONSTANTS.MAX_ACTION_WIDTH));
705
+ const samplePrefix = this.buildSubActionPrefixes(status, true).prefix;
706
+ const contentWidth = Math.max(DISPLAY_CONSTANTS.MIN_CONTENT_WIDTH, width - this.visibleLength(samplePrefix));
707
+ const blocks = formatRichContent(normalized, contentWidth);
708
+ if (!blocks.length) {
709
+ return [];
710
+ }
711
+ return blocks.map((line, index) => {
712
+ const isLast = index === blocks.length - 1;
713
+ const { prefix } = this.buildSubActionPrefixes(status, isLast);
714
+ if (!line.trim()) {
715
+ return prefix.trimEnd();
716
+ }
717
+ return `${prefix}${line}`;
718
+ });
719
+ }
720
+ showMessage(content, role = 'assistant') {
721
+ if (role === 'system') {
722
+ this.showSystemMessage(content);
723
+ }
724
+ else {
725
+ this.showAssistantMessage(content);
726
+ }
727
+ }
728
+ showSystemMessage(content) {
729
+ this.clearSpinnerIfActive();
730
+ const normalized = content.trim();
731
+ if (!normalized) {
732
+ return;
733
+ }
734
+ this.stream(`${normalized}\n\n`);
735
+ }
736
+ showError(message, error) {
737
+ this.clearSpinnerIfActive();
738
+ const details = this.formatErrorDetails(error);
739
+ const parts = [`${theme.error('✗')} ${message}`];
740
+ if (details) {
741
+ parts.push(` ${details}`);
742
+ }
743
+ this.stream(`${parts.join('\n')}\n`);
744
+ }
745
+ showWarning(message) {
746
+ this.clearSpinnerIfActive();
747
+ this.stream(`${theme.warning('!')} ${message}\n`);
748
+ }
709
749
  showInfo(message) {
710
750
  this.clearSpinnerIfActive();
711
- this.withOutput(() => {
712
- this.writeLine(`${theme.info('ℹ')} ${message}`);
713
- });
751
+ this.stream(`${theme.info('ℹ')} ${message}\n`);
714
752
  }
715
753
  /**
716
754
  * Show a success message with simple styling
717
755
  */
718
756
  showSuccess(message) {
719
757
  this.clearSpinnerIfActive();
720
- this.withOutput(() => {
721
- this.writeLine(`${theme.success('✓')} ${message}`);
722
- });
758
+ this.stream(`${theme.success('✓')} ${message}\n`);
723
759
  }
724
760
  /**
725
761
  * Show a stylish progress indicator for long operations
@@ -793,6 +829,13 @@ export class Display {
793
829
  }
794
830
  // Claude Code style: don't show providers on startup, keep it minimal
795
831
  }
832
+ /**
833
+ * Show unified status bar - Claude Code style (minimal)
834
+ * Note: No-op - status is shown in mode controls line
835
+ */
836
+ showUnifiedStatusBar(_providers) {
837
+ // No-op - banner is streamed as content, status shown in control bar
838
+ }
796
839
  /**
797
840
  * Show streaming header - no-op, banner is streamed as content
798
841
  */
@@ -864,6 +907,11 @@ export class Display {
864
907
  this.stdoutTracker.reset();
865
908
  // Banner is streamed content - no re-render on clear
866
909
  }
910
+ newLine() {
911
+ this.withOutput(() => {
912
+ this.writeLine();
913
+ });
914
+ }
867
915
  // Legacy banner methods removed - banner is streamed as content by the shell
868
916
  formatModelLabel(model) {
869
917
  if (/gpt-5\.1-?codex/i.test(model)) {
@@ -905,6 +953,51 @@ export class Display {
905
953
  }
906
954
  return `${parts[0]}/.../${parts[parts.length - 1]}`;
907
955
  }
956
+ buildChatBox(content, metadata) {
957
+ const normalized = content.trim();
958
+ if (!normalized) {
959
+ return '';
960
+ }
961
+ if (isPlainOutputMode()) {
962
+ const body = renderMessageBody(normalized, this.resolveMessageWidth());
963
+ const telemetry = this.formatTelemetryLine(metadata);
964
+ return telemetry ? `${body}\n${telemetry}` : body;
965
+ }
966
+ const width = this.resolveMessageWidth();
967
+ const panel = renderMessagePanel(normalized, {
968
+ width,
969
+ title: 'Assistant',
970
+ icon: icons.assistant,
971
+ accentColor: theme.assistant ?? theme.primary,
972
+ borderColor: theme.ui.border,
973
+ });
974
+ const telemetry = this.formatTelemetryLine(metadata);
975
+ if (!telemetry) {
976
+ return panel;
977
+ }
978
+ return `${panel}\n${telemetry}`;
979
+ }
980
+ resolveMessageWidth() {
981
+ const columns = this.getColumnWidth();
982
+ return Math.max(DISPLAY_CONSTANTS.MIN_MESSAGE_WIDTH, Math.min(columns - DISPLAY_CONSTANTS.MESSAGE_PADDING, DISPLAY_CONSTANTS.MAX_MESSAGE_WIDTH));
983
+ }
984
+ formatTelemetryLine(metadata) {
985
+ if (!metadata) {
986
+ return '';
987
+ }
988
+ const parts = [];
989
+ const elapsed = this.formatElapsed(metadata.elapsedMs);
990
+ if (elapsed) {
991
+ const elapsedLabel = theme.metrics?.elapsedLabel ?? theme.accent;
992
+ const elapsedValue = theme.metrics?.elapsedValue ?? theme.secondary;
993
+ parts.push(`${elapsedLabel('elapsed')} ${elapsedValue(elapsed)}`);
994
+ }
995
+ if (!parts.length) {
996
+ return '';
997
+ }
998
+ const separator = theme.ui.muted(' • ');
999
+ return ` ${parts.join(separator)}`;
1000
+ }
908
1001
  formatElapsed(elapsedMs) {
909
1002
  if (typeof elapsedMs !== 'number' || !Number.isFinite(elapsedMs) || elapsedMs < 0) {
910
1003
  return null;
@@ -1008,6 +1101,128 @@ export class Display {
1008
1101
  return theme.ui.muted(text);
1009
1102
  }
1010
1103
  }
1104
+ /**
1105
+ * Wraps text with a prefix on the first line and optional continuation prefix.
1106
+ * Handles multi-line text and word wrapping intelligently.
1107
+ */
1108
+ wrapWithPrefix(text, prefix, options) {
1109
+ if (!text) {
1110
+ return prefix.trimEnd();
1111
+ }
1112
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_ACTION_WIDTH, Math.min(this.getColumnWidth(), DISPLAY_CONSTANTS.MAX_ACTION_WIDTH));
1113
+ const prefixWidth = this.visibleLength(prefix);
1114
+ const available = Math.max(DISPLAY_CONSTANTS.MIN_CONTENT_WIDTH, width - prefixWidth);
1115
+ const indent = typeof options?.continuationPrefix === 'string'
1116
+ ? options.continuationPrefix
1117
+ : ' '.repeat(Math.max(0, prefixWidth));
1118
+ const segments = text.split('\n');
1119
+ const lines = [];
1120
+ let usedPrefix = false;
1121
+ for (const segment of segments) {
1122
+ if (!segment.trim()) {
1123
+ if (usedPrefix) {
1124
+ lines.push(indent);
1125
+ }
1126
+ else {
1127
+ lines.push(prefix.trimEnd());
1128
+ usedPrefix = true;
1129
+ }
1130
+ continue;
1131
+ }
1132
+ const wrapped = this.wrapLine(segment.trim(), available);
1133
+ for (const line of wrapped) {
1134
+ lines.push(!usedPrefix ? `${prefix}${line}` : `${indent}${line}`);
1135
+ usedPrefix = true;
1136
+ }
1137
+ }
1138
+ return lines.join('\n');
1139
+ }
1140
+ resolveStatusColor(status) {
1141
+ switch (status) {
1142
+ case 'success':
1143
+ return theme.success;
1144
+ case 'error':
1145
+ return theme.error;
1146
+ case 'warning':
1147
+ return theme.warning;
1148
+ case 'pending':
1149
+ return theme.info;
1150
+ default:
1151
+ return theme.secondary;
1152
+ }
1153
+ }
1154
+ formatActionIcon(status) {
1155
+ const colorize = this.resolveStatusColor(status);
1156
+ return colorize(`${icons.action}`);
1157
+ }
1158
+ buildClaudeStyleThought(content, durationMs) {
1159
+ // Claude Code style: ∴ Thought for Xs or ✻ Thinking…
1160
+ const thinkingStyle = theme.thinking || {
1161
+ icon: theme.info,
1162
+ text: theme.ui.muted,
1163
+ border: theme.ui.border,
1164
+ label: theme.info,
1165
+ };
1166
+ const width = Math.min(this.getColumnWidth() - 4, 70);
1167
+ const lines = [];
1168
+ // Header: "∴ Thought for Xs (ctrl+o to show thinking)" for completed, "✻ Thinking…" for active
1169
+ if (durationMs !== undefined) {
1170
+ const elapsed = this.formatElapsedTime(Math.floor(durationMs / 1000));
1171
+ const expandHint = theme.ui.muted('(ctrl+o to show thinking)');
1172
+ lines.push(`${theme.info('∴')} Thought for ${elapsed} ${expandHint}`);
1173
+ }
1174
+ else {
1175
+ lines.push(`${theme.info('✻')} ${thinkingStyle.label('Thinking…')}`);
1176
+ }
1177
+ // Parse and format the thinking content with simple indentation
1178
+ const contentLines = content.split('\n');
1179
+ const hasContent = contentLines.some(line => line.trim().length > 0);
1180
+ if (hasContent) {
1181
+ lines.push(''); // Visual gap between header and content
1182
+ }
1183
+ for (const line of contentLines) {
1184
+ const trimmed = line.replace(/\s+$/, '');
1185
+ if (!trimmed.trim()) {
1186
+ lines.push(''); // Preserve intentional blank lines inside the block
1187
+ continue;
1188
+ }
1189
+ const wrapped = this.wrapLine(trimmed, width - 4);
1190
+ for (const wrappedLine of wrapped) {
1191
+ lines.push(` ${thinkingStyle.text(wrappedLine)}`);
1192
+ }
1193
+ }
1194
+ return lines.join('\n');
1195
+ }
1196
+ /**
1197
+ * Show a thinking block with rich formatting (public method for external use)
1198
+ * @param content The thinking content to display
1199
+ * @param durationMs Optional duration in milliseconds to show "Thought for Xs"
1200
+ */
1201
+ showThinkingBlock(content, durationMs) {
1202
+ this.clearSpinnerIfActive();
1203
+ const block = this.buildClaudeStyleThought(content, durationMs);
1204
+ this.withOutput(() => {
1205
+ this.writeLine();
1206
+ this.writeLine(block);
1207
+ this.writeLine();
1208
+ this.writeLine(); // Extra newline for better visual separation
1209
+ });
1210
+ }
1211
+ buildSubActionPrefixes(status, isLast) {
1212
+ if (isLast) {
1213
+ const colorize = this.resolveStatusColor(status);
1214
+ // Claude Code style: use ⎿ for sub-action result/detail prefix
1215
+ return {
1216
+ prefix: ` ${colorize(icons.subaction)} `,
1217
+ continuation: ' ',
1218
+ };
1219
+ }
1220
+ const branch = theme.ui.muted('│');
1221
+ return {
1222
+ prefix: ` ${branch} `,
1223
+ continuation: ` ${branch} `,
1224
+ };
1225
+ }
1011
1226
  /**
1012
1227
  * Wraps a single line of text to fit within the specified width.
1013
1228
  * Intelligently handles word breaking and preserves spaces.