erosolar-cli 2.1.172 → 2.1.174

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 (172) hide show
  1. package/README.md +1 -1
  2. package/agents/erosolar-code.rules.json +2 -2
  3. package/agents/general.rules.json +21 -3
  4. package/dist/capabilities/statusCapability.js +2 -2
  5. package/dist/capabilities/statusCapability.js.map +1 -1
  6. package/dist/contracts/agent-schemas.json +5 -5
  7. package/dist/core/agent.d.ts +83 -24
  8. package/dist/core/agent.d.ts.map +1 -1
  9. package/dist/core/agent.js +499 -248
  10. package/dist/core/agent.js.map +1 -1
  11. package/dist/core/preferences.d.ts +1 -0
  12. package/dist/core/preferences.d.ts.map +1 -1
  13. package/dist/core/preferences.js +8 -1
  14. package/dist/core/preferences.js.map +1 -1
  15. package/dist/core/reliabilityPrompt.d.ts +9 -0
  16. package/dist/core/reliabilityPrompt.d.ts.map +1 -0
  17. package/dist/core/reliabilityPrompt.js +31 -0
  18. package/dist/core/reliabilityPrompt.js.map +1 -0
  19. package/dist/core/schemaValidator.js +3 -3
  20. package/dist/core/schemaValidator.js.map +1 -1
  21. package/dist/core/toolPreconditions.d.ts +0 -11
  22. package/dist/core/toolPreconditions.d.ts.map +1 -1
  23. package/dist/core/toolPreconditions.js +33 -164
  24. package/dist/core/toolPreconditions.js.map +1 -1
  25. package/dist/core/toolRuntime.d.ts.map +1 -1
  26. package/dist/core/toolRuntime.js +9 -114
  27. package/dist/core/toolRuntime.js.map +1 -1
  28. package/dist/core/updateChecker.d.ts +61 -1
  29. package/dist/core/updateChecker.d.ts.map +1 -1
  30. package/dist/core/updateChecker.js +147 -3
  31. package/dist/core/updateChecker.js.map +1 -1
  32. package/dist/headless/headlessApp.d.ts.map +1 -1
  33. package/dist/headless/headlessApp.js +0 -39
  34. package/dist/headless/headlessApp.js.map +1 -1
  35. package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
  36. package/dist/plugins/tools/nodeDefaults.js +0 -2
  37. package/dist/plugins/tools/nodeDefaults.js.map +1 -1
  38. package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
  39. package/dist/providers/openaiResponsesProvider.js +79 -74
  40. package/dist/providers/openaiResponsesProvider.js.map +1 -1
  41. package/dist/runtime/agentController.d.ts.map +1 -1
  42. package/dist/runtime/agentController.js +6 -3
  43. package/dist/runtime/agentController.js.map +1 -1
  44. package/dist/runtime/agentSession.d.ts +0 -2
  45. package/dist/runtime/agentSession.d.ts.map +1 -1
  46. package/dist/runtime/agentSession.js +2 -2
  47. package/dist/runtime/agentSession.js.map +1 -1
  48. package/dist/shell/interactiveShell.d.ts +11 -18
  49. package/dist/shell/interactiveShell.d.ts.map +1 -1
  50. package/dist/shell/interactiveShell.js +273 -291
  51. package/dist/shell/interactiveShell.js.map +1 -1
  52. package/dist/shell/shellApp.d.ts.map +1 -1
  53. package/dist/shell/shellApp.js +7 -1
  54. package/dist/shell/shellApp.js.map +1 -1
  55. package/dist/shell/systemPrompt.d.ts.map +1 -1
  56. package/dist/shell/systemPrompt.js +4 -15
  57. package/dist/shell/systemPrompt.js.map +1 -1
  58. package/dist/subagents/taskRunner.js +2 -1
  59. package/dist/subagents/taskRunner.js.map +1 -1
  60. package/dist/tools/bashTools.d.ts.map +1 -1
  61. package/dist/tools/bashTools.js +101 -8
  62. package/dist/tools/bashTools.js.map +1 -1
  63. package/dist/tools/diffUtils.d.ts +8 -2
  64. package/dist/tools/diffUtils.d.ts.map +1 -1
  65. package/dist/tools/diffUtils.js +72 -13
  66. package/dist/tools/diffUtils.js.map +1 -1
  67. package/dist/tools/grepTools.d.ts.map +1 -1
  68. package/dist/tools/grepTools.js +10 -2
  69. package/dist/tools/grepTools.js.map +1 -1
  70. package/dist/tools/planningTools.d.ts +0 -10
  71. package/dist/tools/planningTools.d.ts.map +1 -1
  72. package/dist/tools/planningTools.js +0 -16
  73. package/dist/tools/planningTools.js.map +1 -1
  74. package/dist/tools/searchTools.d.ts.map +1 -1
  75. package/dist/tools/searchTools.js +4 -2
  76. package/dist/tools/searchTools.js.map +1 -1
  77. package/dist/ui/PromptController.d.ts +1 -4
  78. package/dist/ui/PromptController.d.ts.map +1 -1
  79. package/dist/ui/PromptController.js +1 -7
  80. package/dist/ui/PromptController.js.map +1 -1
  81. package/dist/ui/ShellUIAdapter.d.ts +292 -28
  82. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  83. package/dist/ui/ShellUIAdapter.js +1513 -121
  84. package/dist/ui/ShellUIAdapter.js.map +1 -1
  85. package/dist/ui/UnifiedUIController.d.ts +81 -0
  86. package/dist/ui/UnifiedUIController.d.ts.map +1 -0
  87. package/dist/ui/UnifiedUIController.js +212 -0
  88. package/dist/ui/UnifiedUIController.js.map +1 -0
  89. package/dist/ui/UnifiedUIRenderer.d.ts +133 -30
  90. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  91. package/dist/ui/UnifiedUIRenderer.js +939 -370
  92. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  93. package/dist/ui/animatedStatus.d.ts +128 -6
  94. package/dist/ui/animatedStatus.d.ts.map +1 -1
  95. package/dist/ui/animatedStatus.js +383 -50
  96. package/dist/ui/animatedStatus.js.map +1 -1
  97. package/dist/ui/animation/AnimationScheduler.d.ts +192 -0
  98. package/dist/ui/animation/AnimationScheduler.d.ts.map +1 -0
  99. package/dist/ui/animation/AnimationScheduler.js +432 -0
  100. package/dist/ui/animation/AnimationScheduler.js.map +1 -0
  101. package/dist/ui/display.d.ts +182 -26
  102. package/dist/ui/display.d.ts.map +1 -1
  103. package/dist/ui/display.js +678 -97
  104. package/dist/ui/display.js.map +1 -1
  105. package/dist/ui/inPlaceUpdater.d.ts +181 -0
  106. package/dist/ui/inPlaceUpdater.d.ts.map +1 -0
  107. package/dist/ui/inPlaceUpdater.js +515 -0
  108. package/dist/ui/inPlaceUpdater.js.map +1 -0
  109. package/dist/ui/interrupts/InterruptManager.d.ts +142 -0
  110. package/dist/ui/interrupts/InterruptManager.d.ts.map +1 -0
  111. package/dist/ui/interrupts/InterruptManager.js +439 -0
  112. package/dist/ui/interrupts/InterruptManager.js.map +1 -0
  113. package/dist/ui/layout.d.ts +0 -1
  114. package/dist/ui/layout.d.ts.map +1 -1
  115. package/dist/ui/layout.js +0 -12
  116. package/dist/ui/layout.js.map +1 -1
  117. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts +61 -7
  118. package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
  119. package/dist/ui/orchestration/UIUpdateCoordinator.js +232 -20
  120. package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
  121. package/dist/ui/shortcutsHelp.d.ts.map +1 -1
  122. package/dist/ui/shortcutsHelp.js +0 -1
  123. package/dist/ui/shortcutsHelp.js.map +1 -1
  124. package/dist/ui/telemetry/ResponseTracker.d.ts +22 -0
  125. package/dist/ui/telemetry/ResponseTracker.d.ts.map +1 -0
  126. package/dist/ui/telemetry/ResponseTracker.js +60 -0
  127. package/dist/ui/telemetry/ResponseTracker.js.map +1 -0
  128. package/dist/ui/telemetry/UITelemetry.d.ts +181 -0
  129. package/dist/ui/telemetry/UITelemetry.d.ts.map +1 -0
  130. package/dist/ui/telemetry/UITelemetry.js +446 -0
  131. package/dist/ui/telemetry/UITelemetry.js.map +1 -0
  132. package/dist/ui/unified/index.d.ts +30 -1
  133. package/dist/ui/unified/index.d.ts.map +1 -1
  134. package/dist/ui/unified/index.js +45 -2
  135. package/dist/ui/unified/index.js.map +1 -1
  136. package/dist/ui/unified/layout.d.ts +12 -0
  137. package/dist/ui/unified/layout.d.ts.map +1 -0
  138. package/dist/ui/unified/layout.js +96 -0
  139. package/dist/ui/unified/layout.js.map +1 -0
  140. package/package.json +2 -3
  141. package/dist/StringUtils.d.ts +0 -8
  142. package/dist/StringUtils.d.ts.map +0 -1
  143. package/dist/StringUtils.js +0 -11
  144. package/dist/StringUtils.js.map +0 -1
  145. package/dist/core/aiFlowSupervisor.d.ts +0 -44
  146. package/dist/core/aiFlowSupervisor.d.ts.map +0 -1
  147. package/dist/core/aiFlowSupervisor.js +0 -299
  148. package/dist/core/aiFlowSupervisor.js.map +0 -1
  149. package/dist/core/cliTestHarness.d.ts +0 -200
  150. package/dist/core/cliTestHarness.d.ts.map +0 -1
  151. package/dist/core/cliTestHarness.js +0 -549
  152. package/dist/core/cliTestHarness.js.map +0 -1
  153. package/dist/core/testUtils.d.ts +0 -121
  154. package/dist/core/testUtils.d.ts.map +0 -1
  155. package/dist/core/testUtils.js +0 -235
  156. package/dist/core/testUtils.js.map +0 -1
  157. package/dist/core/toolValidation.d.ts +0 -116
  158. package/dist/core/toolValidation.d.ts.map +0 -1
  159. package/dist/core/toolValidation.js +0 -282
  160. package/dist/core/toolValidation.js.map +0 -1
  161. package/dist/ui/planOverlay.d.ts +0 -28
  162. package/dist/ui/planOverlay.d.ts.map +0 -1
  163. package/dist/ui/planOverlay.js +0 -156
  164. package/dist/ui/planOverlay.js.map +0 -1
  165. package/dist/ui/streamingFormatter.d.ts +0 -30
  166. package/dist/ui/streamingFormatter.d.ts.map +0 -1
  167. package/dist/ui/streamingFormatter.js +0 -91
  168. package/dist/ui/streamingFormatter.js.map +0 -1
  169. package/dist/utils/errorUtils.d.ts +0 -16
  170. package/dist/utils/errorUtils.d.ts.map +0 -1
  171. package/dist/utils/errorUtils.js +0 -66
  172. package/dist/utils/errorUtils.js.map +0 -1
@@ -1,53 +1,223 @@
1
+ /**
2
+ * Display - Simplified UI facade that routes all output through UnifiedUIRenderer
3
+ *
4
+ * This class now serves as a compatibility layer, providing the same API
5
+ * but delegating all actual rendering to UnifiedUIRenderer.
6
+ */
1
7
  import readline from 'node:readline';
2
- import { theme } from './theme.js';
8
+ import { theme, icons } from './theme.js';
9
+ import { renderMessagePanel, renderMessageBody } from './richText.js';
10
+ import { getTerminalColumns } from './layout.js';
11
+ import { highlightError } from './textHighlighter.js';
12
+ import { renderSectionHeading } from './designSystem.js';
13
+ import { isPlainOutputMode } from './outputMode.js';
14
+ // Display configuration constants
15
+ const DISPLAY_CONSTANTS = {
16
+ MIN_BANNER_WIDTH: 32,
17
+ MAX_BANNER_WIDTH: 120,
18
+ BANNER_PADDING: 4,
19
+ MIN_MESSAGE_WIDTH: 42,
20
+ MAX_MESSAGE_WIDTH: 110,
21
+ MESSAGE_PADDING: 4,
22
+ MIN_ACTION_WIDTH: 40,
23
+ MAX_ACTION_WIDTH: 90,
24
+ MIN_THOUGHT_WIDTH: 48,
25
+ MAX_THOUGHT_WIDTH: 96,
26
+ MIN_CONTENT_WIDTH: 10,
27
+ MIN_WRAP_WIDTH: 12,
28
+ };
29
+ /**
30
+ * Display class - now a thin wrapper around UnifiedUIRenderer
31
+ *
32
+ * Provides backward-compatible API while routing all output through the renderer.
33
+ */
3
34
  export class Display {
4
- renderer = null;
5
35
  outputStream;
36
+ errorStream;
37
+ renderer = null;
38
+ outputInterceptors = new Set();
6
39
  inlinePanelHandler = null;
7
- thinkingStart = null;
8
- lastToolResult = null;
9
- activeSpinner = null;
10
- constructor(stream = process.stdout, _errorStream) {
40
+ constructor(stream = process.stdout, errorStream) {
11
41
  this.outputStream = stream;
42
+ this.errorStream = errorStream ?? stream;
12
43
  }
13
44
  setRenderer(renderer) {
14
45
  this.renderer = renderer;
15
46
  }
47
+ hasRenderer() {
48
+ return Boolean(this.renderer);
49
+ }
16
50
  setInlinePanelHandler(handler) {
17
51
  this.inlinePanelHandler = handler;
18
52
  }
19
- emit(type, content, options) {
53
+ maybeHandleInlinePanel(content) {
54
+ if (!this.inlinePanelHandler) {
55
+ return false;
56
+ }
57
+ try {
58
+ return this.inlinePanelHandler(content) === true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ enqueueEvent(type, content) {
65
+ if (!this.renderer || !content) {
66
+ return false;
67
+ }
68
+ this.renderer.addEvent(type, content);
69
+ return true;
70
+ }
71
+ registerOutputInterceptor(interceptor) {
72
+ if (!interceptor) {
73
+ return () => { };
74
+ }
75
+ this.outputInterceptors.add(interceptor);
76
+ return () => {
77
+ this.outputInterceptors.delete(interceptor);
78
+ };
79
+ }
80
+ notifyBeforeOutput() {
81
+ for (const interceptor of this.outputInterceptors) {
82
+ interceptor.beforeWrite?.();
83
+ }
84
+ }
85
+ notifyAfterOutput(content) {
86
+ const interceptors = Array.from(this.outputInterceptors);
87
+ for (let index = interceptors.length - 1; index >= 0; index -= 1) {
88
+ interceptors[index]?.afterWrite?.(content);
89
+ }
90
+ }
91
+ /**
92
+ * Write raw content directly
93
+ */
94
+ writeRaw(content) {
20
95
  if (!content)
21
96
  return;
22
- if (this.renderer) {
23
- this.renderer.addEvent(type, content, options);
97
+ this.notifyBeforeOutput();
98
+ if (this.enqueueEvent('raw', content)) {
99
+ this.notifyAfterOutput(content);
24
100
  return;
25
101
  }
102
+ // Fallback if no renderer
26
103
  this.outputStream.write(content);
104
+ this.notifyAfterOutput(content);
27
105
  }
106
+ /**
107
+ * Stream chunk (for streaming responses)
108
+ */
28
109
  stream(chunk) {
29
110
  if (!chunk)
30
111
  return;
31
- this.emit('streaming', chunk);
32
- }
33
- parallelAgentStatus(content) {
34
- if (!content)
112
+ this.notifyBeforeOutput();
113
+ if (this.enqueueEvent('streaming', chunk)) {
114
+ this.notifyAfterOutput(chunk);
35
115
  return;
36
- const payload = content.endsWith('\n') ? content : `${content}\n`;
37
- this.emit('streaming', payload);
116
+ }
117
+ // Fallback
118
+ this.outputStream.write(chunk);
119
+ this.notifyAfterOutput(chunk);
120
+ }
121
+ /**
122
+ * Backward-compatible alias
123
+ */
124
+ writeStreamChunk(chunk) {
125
+ this.stream(chunk);
126
+ }
127
+ /**
128
+ * Get the output stream for direct access
129
+ */
130
+ getOutputStream() {
131
+ return this.outputStream;
38
132
  }
133
+ /**
134
+ * Show thinking indicator
135
+ * NO-OP: Don't emit events during SSE streaming.
136
+ * The animated spinner in the status line shows thinking state.
137
+ */
138
+ showThinking(_message = 'Thinking…') {
139
+ // NO-OP: Spinner animation handles thinking state visually.
140
+ // No events emitted - final message handler renders complete content.
141
+ }
142
+ /**
143
+ * Update thinking indicator (status line only, not scrollback)
144
+ * This is called frequently during streaming to show progress snippets.
145
+ * The actual content will be rendered as a complete block later.
146
+ */
147
+ updateThinking(_message) {
148
+ // NO-OP: Don't emit events for transient thinking updates.
149
+ // Thinking snippets are shown only in the status indicator (animated spinner).
150
+ // The complete thought/response will be rendered as a block when ready.
151
+ }
152
+ /**
153
+ * Stop thinking (no-op - renderer handles state)
154
+ */
155
+ stopThinking(_addNewLine = true) {
156
+ // No-op - renderer handles thinking state
157
+ }
158
+ /**
159
+ * Show assistant message
160
+ */
39
161
  showAssistantMessage(content, metadata) {
40
162
  const normalized = content.trim();
41
163
  if (!normalized)
42
164
  return;
43
- this.clearActiveSpinner();
44
165
  const isThought = metadata?.isFinal === false;
45
- const eventType = isThought ? 'thought' : 'response';
46
- this.emit(eventType, normalized);
166
+ const useRichBlock = !isThought && metadata?.wasStreamed !== true;
167
+ const wrapped = useRichBlock ? this.buildChatBox(normalized, metadata) : this.wrapForRenderer(normalized);
168
+ const telemetry = !useRichBlock && metadata?.isFinal !== false ? this.formatTelemetryLine(metadata) : '';
169
+ const payload = telemetry ? `${wrapped}\n${telemetry}` : wrapped;
170
+ const eventType = isThought ? 'thought' : useRichBlock ? 'banner' : 'response';
171
+ this.notifyBeforeOutput();
172
+ if (!this.enqueueEvent(eventType, payload)) {
173
+ this.outputStream.write(`${payload}\n`);
174
+ }
175
+ this.notifyAfterOutput(payload);
47
176
  }
48
- showNarrative(content) {
49
- this.showAssistantMessage(content, { isFinal: false });
177
+ /**
178
+ * Show narrative (thought)
179
+ * NO-OP: Don't emit events during SSE streaming.
180
+ * Narratives are buffered and rendered as part of the final response.
181
+ */
182
+ showNarrative(_content) {
183
+ // NO-OP: Don't emit intermediate narrative events.
184
+ // Final message handler renders complete thought + response.
50
185
  }
186
+ /**
187
+ * Show action
188
+ */
189
+ showAction(text, status = 'info') {
190
+ if (!text.trim())
191
+ return;
192
+ const icon = this.formatActionIcon(status);
193
+ const rendered = this.wrapWithPrefix(text, `${icon} `);
194
+ this.enqueueEvent('raw', `${rendered}\n`);
195
+ }
196
+ /**
197
+ * Show sub-action
198
+ */
199
+ showSubAction(text, status = 'info') {
200
+ if (!text.trim())
201
+ return;
202
+ const lines = this.buildWrappedSubActionLines(text, status);
203
+ if (!lines.length)
204
+ return;
205
+ this.enqueueEvent('raw', `${lines.join('\n')}\n\n`);
206
+ }
207
+ /**
208
+ * Show message
209
+ */
210
+ showMessage(content, role = 'assistant') {
211
+ if (role === 'system') {
212
+ this.showSystemMessage(content);
213
+ }
214
+ else {
215
+ this.showAssistantMessage(content);
216
+ }
217
+ }
218
+ /**
219
+ * Show system message
220
+ */
51
221
  showSystemMessage(content) {
52
222
  const normalized = content.trim();
53
223
  if (!normalized)
@@ -55,69 +225,29 @@ export class Display {
55
225
  if (this.maybeHandleInlinePanel(normalized)) {
56
226
  return;
57
227
  }
58
- this.emit('response', normalized);
59
- }
60
- showError(message, error) {
61
- const parts = [`${theme.error('✗')} ${message}`];
62
- if (error) {
63
- const detail = error instanceof Error ? error.message : String(error);
64
- parts.push(theme.ui.muted(detail));
228
+ const output = `${normalized}\n\n`;
229
+ this.notifyBeforeOutput();
230
+ if (!this.enqueueEvent('response', output)) {
231
+ this.outputStream.write(output);
65
232
  }
66
- this.emit('response', parts.join('\n'));
67
- }
68
- showWarning(message) {
69
- this.emit('response', `${theme.warning('!')} ${message}`);
70
- }
71
- showInfo(message) {
72
- this.emit('response', `${theme.info('•')} ${message}`);
73
- }
74
- showSuccess(message) {
75
- this.emit('response', `${theme.success('✓')} ${message}`);
233
+ this.notifyAfterOutput(output);
76
234
  }
235
+ /**
236
+ * Show test event (renders in unified UI "test" lane)
237
+ */
77
238
  showTestEvent(content) {
78
239
  const normalized = content.trim();
79
240
  if (!normalized)
80
241
  return;
81
- this.emit('test', normalized);
82
- }
83
- showThinking(message = 'Thinking…') {
84
- this.thinkingStart = Date.now();
85
- this.emit('thought', message);
86
- }
87
- updateThinking(message) {
88
- this.emit('thought', message);
89
- }
90
- stopThinking(addNewline = true) {
91
- this.clearActiveSpinner(!addNewline);
92
- this.thinkingStart = null;
93
- }
94
- clearActiveSpinner(useStop = false) {
95
- if (!this.activeSpinner) {
96
- return;
97
- }
98
- const spinner = this.activeSpinner;
99
- this.activeSpinner = null;
100
- try {
101
- if (!useStop && typeof spinner.clear === 'function') {
102
- spinner.clear();
103
- }
104
- else if (typeof spinner.stop === 'function') {
105
- spinner.stop();
106
- }
107
- }
108
- catch {
109
- // ignore spinner errors
242
+ this.notifyBeforeOutput();
243
+ if (!this.enqueueEvent('test', normalized)) {
244
+ this.outputStream.write(`${normalized}\n\n`);
110
245
  }
246
+ this.notifyAfterOutput(normalized);
111
247
  }
112
- isSpinnerActive() {
113
- return false;
114
- }
115
- getThinkingElapsedMs() {
116
- if (!this.thinkingStart) {
117
- return null;
118
- }
119
- return Date.now() - this.thinkingStart;
120
- }
248
+ /**
249
+ * Show an inline panel pinned below the prompt (overlay only, no scrollback).
250
+ */
121
251
  showInlinePanel(content) {
122
252
  const lines = Array.isArray(content) ? content : content.split('\n');
123
253
  const normalized = lines.map(line => line.trimEnd()).filter(line => line.trim().length > 0);
@@ -129,15 +259,22 @@ export class Display {
129
259
  this.renderer.setInlinePanel(normalized);
130
260
  return;
131
261
  }
262
+ // Fallback for plain mode: emit directly without routing through the inline handler
132
263
  const output = `${normalized.join('\n')}\n`;
264
+ this.notifyBeforeOutput();
133
265
  this.outputStream.write(output);
266
+ this.notifyAfterOutput(output);
134
267
  }
135
268
  clearInlinePanel() {
136
269
  if (this.renderer && typeof this.renderer.clearInlinePanel === 'function') {
137
270
  this.renderer.clearInlinePanel();
138
271
  }
139
272
  }
140
- captureUserInput(options = {}) {
273
+ /**
274
+ * Capture a single line of user input without logging it to scrollback.
275
+ * Uses the unified renderer when available, with a readline fallback.
276
+ */
277
+ async captureUserInput(options = {}) {
141
278
  if (this.renderer && typeof this.renderer.captureInput === 'function') {
142
279
  return this.renderer.captureInput(options);
143
280
  }
@@ -158,46 +295,180 @@ export class Display {
158
295
  });
159
296
  });
160
297
  }
161
- rememberToolResult(content, summary) {
162
- const normalized = content.trim();
163
- if (!normalized)
164
- return;
165
- this.lastToolResult = normalized;
166
- this.renderer?.rememberToolResult(normalized, summary);
298
+ /**
299
+ * Show error
300
+ */
301
+ showError(message, error) {
302
+ const details = this.formatErrorDetails(error);
303
+ const parts = [`${theme.error('✗')} ${message}`];
304
+ if (details) {
305
+ parts.push(` ${details}`);
306
+ }
307
+ const output = `${parts.join('\n')}\n`;
308
+ this.notifyBeforeOutput();
309
+ if (!this.enqueueEvent('response', output)) {
310
+ this.outputStream.write(output);
311
+ }
312
+ this.notifyAfterOutput(output);
167
313
  }
168
- expandLastToolResult() {
169
- if (this.renderer?.expandLastToolResult?.()) {
170
- return;
314
+ /**
315
+ * Show warning
316
+ */
317
+ showWarning(message) {
318
+ const output = `${theme.warning('!')} ${message}\n`;
319
+ this.notifyBeforeOutput();
320
+ if (!this.enqueueEvent('response', output)) {
321
+ this.outputStream.write(output);
171
322
  }
172
- if (this.lastToolResult) {
173
- this.emit('tool-result', this.lastToolResult);
323
+ this.notifyAfterOutput(output);
324
+ }
325
+ /**
326
+ * Show info
327
+ */
328
+ showInfo(message) {
329
+ const output = `${theme.info('ℹ')} ${message}\n`;
330
+ this.notifyBeforeOutput();
331
+ if (!this.enqueueEvent('response', output)) {
332
+ this.outputStream.write(output);
174
333
  }
334
+ this.notifyAfterOutput(output);
175
335
  }
176
- showStatusLine(status, elapsedMs) {
336
+ /**
337
+ * Show success
338
+ */
339
+ showSuccess(message) {
340
+ const output = `${theme.success('✓')} ${message}\n`;
341
+ this.notifyBeforeOutput();
342
+ if (!this.enqueueEvent('response', output)) {
343
+ this.outputStream.write(output);
344
+ }
345
+ this.notifyAfterOutput(output);
346
+ }
347
+ /**
348
+ * Show progress badge
349
+ */
350
+ showProgressBadge(label, current, total) {
351
+ const percentage = Math.round((current / total) * 100);
352
+ const barWidth = 20;
353
+ const filled = Math.round((current / total) * barWidth);
354
+ const empty = barWidth - filled;
355
+ const progressBar = `${'█'.repeat(filled)}${'░'.repeat(empty)}`;
356
+ const badge = `[${label}] ${progressBar} ${percentage}%`;
357
+ this.stream(`\r${theme.info(badge)}`);
358
+ if (current >= total) {
359
+ this.stream('\n');
360
+ }
361
+ }
362
+ /**
363
+ * Show status line
364
+ */
365
+ showStatusLine(status, elapsedMs, _context) {
177
366
  const normalized = status?.trim();
178
367
  if (!normalized)
179
368
  return;
180
369
  const elapsed = this.formatElapsed(elapsedMs);
181
- const parts = [normalized];
370
+ const parts = [`• ${normalized}`];
182
371
  if (elapsed) {
183
372
  parts.push(elapsed);
184
373
  }
374
+ if (process.stdout.isTTY) {
375
+ parts.push('esc to interrupt');
376
+ }
377
+ // Status is kept internal; avoid forcing re-render of the overlay.
185
378
  this.renderer?.updateStatusBundle({ main: parts.join(' • ') }, { render: false });
186
379
  }
380
+ /**
381
+ * Show command palette
382
+ */
383
+ showCommandPalette(commands, options) {
384
+ if (!commands || commands.length === 0)
385
+ return;
386
+ const panel = this.buildCommandPalette(commands, options);
387
+ if (!panel.trim())
388
+ return;
389
+ const output = `\n${panel}\n\n`;
390
+ this.notifyBeforeOutput();
391
+ if (!this.enqueueEvent('response', output)) {
392
+ this.outputStream.write(output);
393
+ }
394
+ this.notifyAfterOutput(output);
395
+ }
396
+ /**
397
+ * Show planning step
398
+ */
399
+ showPlanningStep(step, index, total) {
400
+ if (!step?.trim() || index < 1 || total < 1 || index > total)
401
+ return;
402
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_THOUGHT_WIDTH, Math.min(this.getColumnWidth(), DISPLAY_CONSTANTS.MAX_MESSAGE_WIDTH));
403
+ const heading = renderSectionHeading(`Plan ${index}/${total}`, {
404
+ subtitle: step,
405
+ icon: icons.arrow,
406
+ tone: 'info',
407
+ width,
408
+ });
409
+ this.enqueueEvent('raw', `${heading}\n`);
410
+ }
411
+ /**
412
+ * Show thinking block
413
+ */
414
+ showThinkingBlock(content, durationMs) {
415
+ const block = this.buildClaudeStyleThought(content, durationMs);
416
+ this.enqueueEvent('raw', `\n${block}\n\n`);
417
+ }
418
+ /**
419
+ * Clear screen
420
+ */
187
421
  clear() {
188
- // Renderer manages overlay cleanup; nothing to do here.
422
+ // Renderer handles this
189
423
  }
190
- registerOutputInterceptor(_interceptor) {
191
- return () => { };
424
+ /**
425
+ * Update streaming status (routes to renderer)
426
+ */
427
+ updateStreamingStatus(status) {
428
+ this.renderer?.updateStatus(status);
192
429
  }
193
- maybeHandleInlinePanel(content) {
194
- if (!this.inlinePanelHandler)
195
- return false;
430
+ /**
431
+ * Clear streaming status
432
+ */
433
+ clearStreamingStatus() {
434
+ this.renderer?.updateStatus(null);
435
+ }
436
+ /**
437
+ * Check if streaming status is visible
438
+ */
439
+ isStreamingStatusVisible() {
440
+ return false; // Renderer manages this
441
+ }
442
+ parallelAgentStatus(content) {
443
+ if (!content)
444
+ return;
445
+ this.enqueueEvent('streaming', `${content}\n`);
446
+ }
447
+ // ==================== Private Helper Methods ====================
448
+ getColumnWidth() {
449
+ if (typeof this.outputStream.columns === 'number' &&
450
+ Number.isFinite(this.outputStream.columns) &&
451
+ this.outputStream.columns > 0) {
452
+ return this.outputStream.columns;
453
+ }
454
+ return getTerminalColumns();
455
+ }
456
+ formatErrorDetails(error) {
457
+ if (!error)
458
+ return null;
459
+ if (error instanceof Error) {
460
+ if (error.stack)
461
+ return highlightError(error.stack);
462
+ return highlightError(error.message);
463
+ }
464
+ if (typeof error === 'string') {
465
+ return highlightError(error);
466
+ }
196
467
  try {
197
- return this.inlinePanelHandler(content) === true;
468
+ return highlightError(JSON.stringify(error, null, 2));
198
469
  }
199
470
  catch {
200
- return false;
471
+ return null;
201
472
  }
202
473
  }
203
474
  formatElapsed(elapsedMs) {
@@ -212,6 +483,316 @@ export class Display {
212
483
  }
213
484
  return `${seconds}s`;
214
485
  }
486
+ buildChatBox(content, metadata) {
487
+ const normalized = content.trim();
488
+ if (!normalized)
489
+ return '';
490
+ if (isPlainOutputMode()) {
491
+ const body = renderMessageBody(normalized, this.resolveMessageWidth());
492
+ const telemetry = this.formatTelemetryLine(metadata);
493
+ return telemetry ? `${body}\n${telemetry}` : body;
494
+ }
495
+ const width = this.resolveMessageWidth();
496
+ const panel = renderMessagePanel(normalized, {
497
+ width,
498
+ title: 'Assistant',
499
+ icon: icons.assistant,
500
+ accentColor: theme.assistant ?? theme.primary,
501
+ borderColor: theme.ui.border,
502
+ });
503
+ const telemetry = this.formatTelemetryLine(metadata);
504
+ return telemetry ? `${panel}\n${telemetry}` : panel;
505
+ }
506
+ resolveMessageWidth() {
507
+ const columns = this.getColumnWidth();
508
+ return Math.max(DISPLAY_CONSTANTS.MIN_MESSAGE_WIDTH, Math.min(columns - DISPLAY_CONSTANTS.MESSAGE_PADDING, DISPLAY_CONSTANTS.MAX_MESSAGE_WIDTH));
509
+ }
510
+ formatTelemetryLine(metadata) {
511
+ if (!metadata)
512
+ return '';
513
+ const parts = [];
514
+ const elapsed = this.formatElapsed(metadata.elapsedMs);
515
+ if (elapsed) {
516
+ const elapsedLabel = theme.metrics?.elapsedLabel ?? theme.accent;
517
+ const elapsedValue = theme.metrics?.elapsedValue ?? theme.secondary;
518
+ parts.push(`${elapsedLabel('elapsed')} ${elapsedValue(elapsed)}`);
519
+ }
520
+ if (!parts.length)
521
+ return '';
522
+ const separator = theme.ui.muted(' • ');
523
+ return ` ${parts.join(separator)}`;
524
+ }
525
+ buildClaudeStyleThought(content, durationMs) {
526
+ const thinkingStyle = theme.thinking || {
527
+ icon: theme.info,
528
+ text: theme.ui.muted,
529
+ border: theme.ui.border,
530
+ label: theme.info,
531
+ };
532
+ const width = Math.min(this.getColumnWidth() - 4, 70);
533
+ const lines = [];
534
+ // Header
535
+ if (durationMs !== undefined) {
536
+ const elapsed = this.formatElapsedTime(Math.floor(durationMs / 1000));
537
+ lines.push(`${theme.info('∴')} Thought for ${elapsed}`);
538
+ }
539
+ else {
540
+ lines.push(`${theme.info('✻')} ${thinkingStyle.label('Thinking…')}`);
541
+ }
542
+ // Content
543
+ const contentLines = content.split('\n');
544
+ const hasContent = contentLines.some(line => line.trim().length > 0);
545
+ if (hasContent) {
546
+ lines.push('');
547
+ }
548
+ for (const line of contentLines) {
549
+ const trimmed = line.replace(/\s+$/, '');
550
+ if (!trimmed.trim()) {
551
+ lines.push('');
552
+ continue;
553
+ }
554
+ const wrapped = this.wrapLine(trimmed, width - 4);
555
+ for (const wrappedLine of wrapped) {
556
+ lines.push(` ${thinkingStyle.text(wrappedLine)}`);
557
+ }
558
+ }
559
+ return lines.join('\n');
560
+ }
561
+ formatElapsedTime(seconds) {
562
+ if (seconds < 60) {
563
+ return `${seconds}s`;
564
+ }
565
+ const mins = Math.floor(seconds / 60);
566
+ const secs = seconds % 60;
567
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
568
+ }
569
+ buildCommandPalette(commands, options) {
570
+ if (!commands.length)
571
+ return '';
572
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_MESSAGE_WIDTH, Math.min(this.getColumnWidth() - 2, DISPLAY_CONSTANTS.MAX_MESSAGE_WIDTH));
573
+ const indent = ' ';
574
+ const grouped = this.groupPaletteCommands(commands);
575
+ const commandWidth = this.computeCommandColumnWidth(commands, width, indent.length);
576
+ const descWidth = Math.max(DISPLAY_CONSTANTS.MIN_WRAP_WIDTH, width - indent.length - commandWidth - 1);
577
+ const title = options?.title ?? 'Slash commands';
578
+ const intro = options?.intro ?? 'Describe a task or pick a command below:';
579
+ const lines = [];
580
+ lines.push(theme.gradient.primary(title));
581
+ const introLines = this.wrapLine(intro, width);
582
+ for (const line of introLines) {
583
+ lines.push(theme.ui.muted(line));
584
+ }
585
+ lines.push('');
586
+ grouped.forEach(({ category, items }, index) => {
587
+ const label = this.formatPaletteCategory(category);
588
+ if (label) {
589
+ lines.push(theme.bold(label));
590
+ }
591
+ for (const item of items) {
592
+ const wrappedDesc = this.wrapLine(item.description, descWidth);
593
+ const paddedCommand = item.command.padEnd(commandWidth);
594
+ const commandLabel = theme.primary(paddedCommand);
595
+ const firstLine = wrappedDesc[0] ?? '';
596
+ lines.push(`${indent}${commandLabel} ${this.colorizePaletteText(firstLine, item.tone)}`);
597
+ for (const extra of wrappedDesc.slice(1)) {
598
+ lines.push(`${indent}${' '.repeat(commandWidth)} ${this.colorizePaletteText(extra, item.tone)}`);
599
+ }
600
+ }
601
+ if (index < grouped.length - 1) {
602
+ lines.push('');
603
+ }
604
+ });
605
+ return lines.join('\n').trimEnd();
606
+ }
607
+ groupPaletteCommands(commands) {
608
+ const FALLBACK = 'other';
609
+ const groups = new Map();
610
+ for (const item of commands) {
611
+ const key = (item.category ?? FALLBACK).toLowerCase();
612
+ const bucket = groups.get(key) ?? [];
613
+ bucket.push(item);
614
+ groups.set(key, bucket);
615
+ }
616
+ const order = ['configuration', 'workspace', 'diagnostics', 'other'];
617
+ const orderedKeys = [
618
+ ...order.filter((key) => groups.has(key)),
619
+ ...Array.from(groups.keys()).filter((key) => !order.includes(key)),
620
+ ];
621
+ return orderedKeys.map((category) => ({ category, items: groups.get(category) ?? [] }));
622
+ }
623
+ computeCommandColumnWidth(commands, totalWidth, indentWidth) {
624
+ const longest = commands.reduce((max, item) => Math.max(max, this.visibleLength(item.command)), 0);
625
+ const maxAllowed = Math.max(12, Math.min(longest + 2, Math.floor(totalWidth * 0.35)));
626
+ const budget = Math.max(10, totalWidth - indentWidth - DISPLAY_CONSTANTS.MIN_WRAP_WIDTH);
627
+ return Math.min(maxAllowed, budget);
628
+ }
629
+ formatPaletteCategory(category) {
630
+ if (!category)
631
+ return 'Other';
632
+ switch (category.toLowerCase()) {
633
+ case 'configuration': return 'Configuration';
634
+ case 'workspace': return 'Workspace';
635
+ case 'diagnostics': return 'Diagnostics';
636
+ case 'other': return 'Other';
637
+ default: return category[0]?.toUpperCase() + category.slice(1);
638
+ }
639
+ }
640
+ colorizePaletteText(text, tone) {
641
+ switch (tone) {
642
+ case 'warn': return theme.warning(text);
643
+ case 'success': return theme.success(text);
644
+ case 'info': return theme.info(text);
645
+ case 'muted':
646
+ default: return theme.ui.muted(text);
647
+ }
648
+ }
649
+ wrapWithPrefix(text, prefix, options) {
650
+ if (!text) {
651
+ return prefix.trimEnd();
652
+ }
653
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_ACTION_WIDTH, Math.min(this.getColumnWidth(), DISPLAY_CONSTANTS.MAX_ACTION_WIDTH));
654
+ const prefixWidth = this.visibleLength(prefix);
655
+ const available = Math.max(DISPLAY_CONSTANTS.MIN_CONTENT_WIDTH, width - prefixWidth);
656
+ const indent = typeof options?.continuationPrefix === 'string'
657
+ ? options.continuationPrefix
658
+ : ' '.repeat(Math.max(0, prefixWidth));
659
+ const segments = text.split('\n');
660
+ const lines = [];
661
+ let usedPrefix = false;
662
+ for (const segment of segments) {
663
+ if (!segment.trim()) {
664
+ if (usedPrefix) {
665
+ lines.push(indent);
666
+ }
667
+ else {
668
+ lines.push(prefix.trimEnd());
669
+ usedPrefix = true;
670
+ }
671
+ continue;
672
+ }
673
+ const wrapped = this.wrapLine(segment.trim(), available);
674
+ for (const line of wrapped) {
675
+ lines.push(!usedPrefix ? `${prefix}${line}` : `${indent}${line}`);
676
+ usedPrefix = true;
677
+ }
678
+ }
679
+ return lines.join('\n');
680
+ }
681
+ buildWrappedSubActionLines(text, status) {
682
+ const lines = text.split('\n').map((line) => line.trimEnd());
683
+ while (lines.length && !lines[lines.length - 1]?.trim()) {
684
+ lines.pop();
685
+ }
686
+ if (!lines.length)
687
+ return [];
688
+ const rendered = [];
689
+ for (let index = 0; index < lines.length; index += 1) {
690
+ const segment = lines[index] ?? '';
691
+ const isLast = index === lines.length - 1;
692
+ const { prefix, continuation } = this.buildSubActionPrefixes(status, isLast);
693
+ rendered.push(this.wrapWithPrefix(segment, prefix, { continuationPrefix: continuation }));
694
+ }
695
+ return rendered;
696
+ }
697
+ buildSubActionPrefixes(status, isLast) {
698
+ if (isLast) {
699
+ const colorize = this.resolveStatusColor(status);
700
+ return {
701
+ prefix: ` ${colorize(icons.subaction)} `,
702
+ continuation: ' ',
703
+ };
704
+ }
705
+ const branch = theme.ui.muted('│');
706
+ return {
707
+ prefix: ` ${branch} `,
708
+ continuation: ` ${branch} `,
709
+ };
710
+ }
711
+ resolveStatusColor(status) {
712
+ switch (status) {
713
+ case 'success': return theme.success;
714
+ case 'error': return theme.error;
715
+ case 'warning': return theme.warning;
716
+ case 'pending': return theme.info;
717
+ default: return theme.secondary;
718
+ }
719
+ }
720
+ formatActionIcon(status) {
721
+ const colorize = this.resolveStatusColor(status);
722
+ return colorize(`${icons.action}`);
723
+ }
724
+ wrapForRenderer(text) {
725
+ const width = Math.max(DISPLAY_CONSTANTS.MIN_WRAP_WIDTH, Math.min(this.getColumnWidth() - 4, DISPLAY_CONSTANTS.MAX_MESSAGE_WIDTH));
726
+ const lines = [];
727
+ for (const line of text.split('\n')) {
728
+ const trimmed = line.trimEnd();
729
+ const wrapped = this.wrapLine(trimmed, width);
730
+ lines.push(...wrapped);
731
+ }
732
+ return lines.join('\n').trimEnd();
733
+ }
734
+ wrapLine(text, width) {
735
+ if (width <= 0)
736
+ return [text];
737
+ if (!text)
738
+ return [''];
739
+ if (text.length <= width)
740
+ return [text];
741
+ const words = text.split(/\s+/).filter(Boolean);
742
+ if (!words.length)
743
+ return this.chunkWord(text, width);
744
+ const lines = [];
745
+ let current = '';
746
+ for (const word of words) {
747
+ if (!current) {
748
+ if (word.length <= width) {
749
+ current = word;
750
+ }
751
+ else {
752
+ const chunks = this.chunkWord(word, width);
753
+ lines.push(...chunks.slice(0, -1));
754
+ current = chunks[chunks.length - 1] ?? '';
755
+ }
756
+ }
757
+ else if (current.length + 1 + word.length <= width) {
758
+ current = `${current} ${word}`;
759
+ }
760
+ else {
761
+ lines.push(current);
762
+ if (word.length <= width) {
763
+ current = word;
764
+ }
765
+ else {
766
+ const chunks = this.chunkWord(word, width);
767
+ lines.push(...chunks.slice(0, -1));
768
+ current = chunks[chunks.length - 1] ?? '';
769
+ }
770
+ }
771
+ }
772
+ if (current) {
773
+ lines.push(current);
774
+ }
775
+ return lines.length ? lines : [''];
776
+ }
777
+ chunkWord(word, width) {
778
+ if (width <= 0 || !word)
779
+ return word ? [word] : [''];
780
+ const chunks = [];
781
+ for (let i = 0; i < word.length; i += width) {
782
+ chunks.push(word.slice(i, i + width));
783
+ }
784
+ return chunks.length > 0 ? chunks : [''];
785
+ }
786
+ visibleLength(value) {
787
+ if (!value)
788
+ return 0;
789
+ return this.stripAnsi(value).length;
790
+ }
791
+ stripAnsi(value) {
792
+ if (!value)
793
+ return '';
794
+ return value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '');
795
+ }
215
796
  }
216
797
  export const display = new Display();
217
798
  //# sourceMappingURL=display.js.map