erosolar-cli 2.1.168 → 2.1.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agents/erosolar-code.rules.json +2 -2
- package/agents/general.rules.json +21 -3
- package/dist/capabilities/statusCapability.js +2 -2
- package/dist/capabilities/statusCapability.js.map +1 -1
- package/dist/contracts/agent-schemas.json +5 -5
- package/dist/core/agent.d.ts +70 -24
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +424 -248
- package/dist/core/agent.js.map +1 -1
- package/dist/core/preferences.d.ts +1 -0
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +8 -1
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/reliabilityPrompt.d.ts +9 -0
- package/dist/core/reliabilityPrompt.d.ts.map +1 -0
- package/dist/core/reliabilityPrompt.js +31 -0
- package/dist/core/reliabilityPrompt.js.map +1 -0
- package/dist/core/schemaValidator.js +3 -3
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts +0 -11
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +33 -164
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +9 -114
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/updateChecker.d.ts +61 -1
- package/dist/core/updateChecker.d.ts.map +1 -1
- package/dist/core/updateChecker.js +147 -3
- package/dist/core/updateChecker.js.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +0 -39
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +0 -2
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
- package/dist/providers/openaiResponsesProvider.js +79 -74
- package/dist/providers/openaiResponsesProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +6 -3
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/runtime/agentSession.d.ts +0 -2
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +2 -2
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +11 -12
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +269 -193
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +4 -15
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/subagents/taskRunner.js +2 -1
- package/dist/subagents/taskRunner.js.map +1 -1
- package/dist/tools/bashTools.d.ts.map +1 -1
- package/dist/tools/bashTools.js +101 -8
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/diffUtils.d.ts +8 -2
- package/dist/tools/diffUtils.d.ts.map +1 -1
- package/dist/tools/diffUtils.js +72 -13
- package/dist/tools/diffUtils.js.map +1 -1
- package/dist/tools/grepTools.d.ts.map +1 -1
- package/dist/tools/grepTools.js +10 -2
- package/dist/tools/grepTools.js.map +1 -1
- package/dist/tools/searchTools.d.ts.map +1 -1
- package/dist/tools/searchTools.js +4 -2
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/ui/PromptController.d.ts +2 -3
- package/dist/ui/PromptController.d.ts.map +1 -1
- package/dist/ui/PromptController.js +2 -3
- package/dist/ui/PromptController.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +71 -18
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +237 -139
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/UnifiedUIController.d.ts +0 -1
- package/dist/ui/UnifiedUIController.d.ts.map +1 -1
- package/dist/ui/UnifiedUIController.js +0 -1
- package/dist/ui/UnifiedUIController.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.d.ts +122 -7
- package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +823 -130
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/animatedStatus.d.ts +129 -0
- package/dist/ui/animatedStatus.d.ts.map +1 -0
- package/dist/ui/animatedStatus.js +384 -0
- package/dist/ui/animatedStatus.js.map +1 -0
- package/dist/ui/display.d.ts +13 -48
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +22 -105
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +0 -1
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/unified/index.d.ts +1 -1
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +0 -2
- package/dist/ui/unified/index.js.map +1 -1
- package/package.json +1 -2
- package/dist/StringUtils.d.ts +0 -8
- package/dist/StringUtils.d.ts.map +0 -1
- package/dist/StringUtils.js +0 -11
- package/dist/StringUtils.js.map +0 -1
- package/dist/core/aiFlowSupervisor.d.ts +0 -44
- package/dist/core/aiFlowSupervisor.d.ts.map +0 -1
- package/dist/core/aiFlowSupervisor.js +0 -299
- package/dist/core/aiFlowSupervisor.js.map +0 -1
- package/dist/core/cliTestHarness.d.ts +0 -200
- package/dist/core/cliTestHarness.d.ts.map +0 -1
- package/dist/core/cliTestHarness.js +0 -549
- package/dist/core/cliTestHarness.js.map +0 -1
- package/dist/core/testUtils.d.ts +0 -121
- package/dist/core/testUtils.d.ts.map +0 -1
- package/dist/core/testUtils.js +0 -235
- package/dist/core/testUtils.js.map +0 -1
- package/dist/core/toolValidation.d.ts +0 -116
- package/dist/core/toolValidation.d.ts.map +0 -1
- package/dist/core/toolValidation.js +0 -282
- package/dist/core/toolValidation.js.map +0 -1
- package/dist/ui/compactRenderer.d.ts +0 -139
- package/dist/ui/compactRenderer.d.ts.map +0 -1
- package/dist/ui/compactRenderer.js +0 -398
- package/dist/ui/compactRenderer.js.map +0 -1
- package/dist/ui/streamingFormatter.d.ts +0 -30
- package/dist/ui/streamingFormatter.d.ts.map +0 -1
- package/dist/ui/streamingFormatter.js +0 -91
- package/dist/ui/streamingFormatter.js.map +0 -1
- package/dist/utils/errorUtils.d.ts +0 -16
- package/dist/utils/errorUtils.d.ts.map +0 -1
- package/dist/utils/errorUtils.js +0 -66
- package/dist/utils/errorUtils.js.map +0 -1
|
@@ -13,15 +13,13 @@ 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';
|
|
17
16
|
export class ShellUIAdapter {
|
|
18
17
|
uiController;
|
|
19
18
|
display;
|
|
20
19
|
config;
|
|
21
|
-
isProcessing = false;
|
|
22
|
-
contextUsage = 0;
|
|
23
20
|
fileChangeCallback;
|
|
24
21
|
_toolStatusCallback;
|
|
22
|
+
_activityCallback;
|
|
25
23
|
profileSwitcherTimeout;
|
|
26
24
|
updateCoordinator;
|
|
27
25
|
// Track tool operations for compact rendering
|
|
@@ -38,6 +36,16 @@ export class ShellUIAdapter {
|
|
|
38
36
|
activeToolStatus = null;
|
|
39
37
|
activeToolHeartbeatId = null;
|
|
40
38
|
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;
|
|
41
49
|
constructor(writeStream, display, config = {}, updateCoordinator) {
|
|
42
50
|
this.display = display;
|
|
43
51
|
this.config = {
|
|
@@ -138,6 +146,12 @@ export class ShellUIAdapter {
|
|
|
138
146
|
else {
|
|
139
147
|
this.logToolProgress(call, progress);
|
|
140
148
|
}
|
|
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
|
+
}
|
|
141
155
|
// Update status bar with progress - use in-place update for supported terminals
|
|
142
156
|
if (this._toolStatusCallback) {
|
|
143
157
|
const description = this.getToolActionDescription(call, progress);
|
|
@@ -167,12 +181,10 @@ export class ShellUIAdapter {
|
|
|
167
181
|
const now = Date.now();
|
|
168
182
|
// Complete the operation tracking
|
|
169
183
|
const op = this.pendingOperations.get(call.id);
|
|
170
|
-
let durationMs;
|
|
171
184
|
if (op) {
|
|
172
185
|
op.status = 'success';
|
|
173
186
|
op.completedAt = now;
|
|
174
187
|
op.summary = this.extractResultSummary(call, output);
|
|
175
|
-
durationMs = op.startedAt ? Math.max(0, now - op.startedAt) : undefined;
|
|
176
188
|
this.pendingOperations.delete(call.id);
|
|
177
189
|
this.completedOperations.push(op);
|
|
178
190
|
// Keep only recent completed operations
|
|
@@ -191,19 +203,22 @@ export class ShellUIAdapter {
|
|
|
191
203
|
this.fileChangeCallback(fileChange.path, fileChange.type, fileChange.additions, fileChange.removals);
|
|
192
204
|
}
|
|
193
205
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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);
|
|
207
222
|
}
|
|
208
223
|
// Surface any captured preflight warnings in a structured block
|
|
209
224
|
this.flushToolWarnings(call.id, call.name);
|
|
@@ -217,12 +232,10 @@ export class ShellUIAdapter {
|
|
|
217
232
|
onToolError: (call, message) => {
|
|
218
233
|
// Complete the operation tracking with error
|
|
219
234
|
const op = this.pendingOperations.get(call.id);
|
|
220
|
-
let durationMs;
|
|
221
235
|
if (op) {
|
|
222
236
|
op.status = 'error';
|
|
223
237
|
op.completedAt = Date.now();
|
|
224
238
|
op.summary = message;
|
|
225
|
-
durationMs = op.startedAt ? Math.max(0, (op.completedAt ?? Date.now()) - op.startedAt) : undefined;
|
|
226
239
|
this.pendingOperations.delete(call.id);
|
|
227
240
|
this.completedOperations.push(op);
|
|
228
241
|
}
|
|
@@ -230,19 +243,16 @@ export class ShellUIAdapter {
|
|
|
230
243
|
this.logExploreCompletion(call.id, false);
|
|
231
244
|
}
|
|
232
245
|
this.clearToolState(call.id);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Erosolar-CLI style: Display error with ⎿ prefix (error color)
|
|
244
|
-
this.displayToolResultSummary(call, message, false);
|
|
245
|
-
}
|
|
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);
|
|
246
256
|
}
|
|
247
257
|
this.flushToolWarnings(call.id, call.name);
|
|
248
258
|
// Clear status bar after showing error
|
|
@@ -310,17 +320,27 @@ export class ShellUIAdapter {
|
|
|
310
320
|
* Shows the top two tools and how many more were used.
|
|
311
321
|
*/
|
|
312
322
|
showToolUsageSummary() {
|
|
323
|
+
// Compute summary for metadata but don't display it
|
|
324
|
+
// The tool calls are already visible in the conversation flow
|
|
313
325
|
this.lastToolUsageSummary = this.formatToolUsageSummary();
|
|
314
326
|
this.lastToolUsageSummaryPlain = this.formatToolUsageSummary({ plain: true });
|
|
315
|
-
|
|
316
|
-
if (!summary) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
this.display.stream(`\n${summary}\n`);
|
|
327
|
+
// Don't stream the summary - it's redundant after the assistant response
|
|
320
328
|
}
|
|
321
329
|
getToolUsageSummary(options = {}) {
|
|
322
330
|
return options.plain ? this.lastToolUsageSummaryPlain : this.lastToolUsageSummary;
|
|
323
331
|
}
|
|
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
|
+
}
|
|
324
344
|
formatToolUsageSummary(options = {}) {
|
|
325
345
|
const plain = options.plain === true;
|
|
326
346
|
if (this.toolUsageCounts.size === 0) {
|
|
@@ -439,7 +459,6 @@ export class ShellUIAdapter {
|
|
|
439
459
|
case 'Write':
|
|
440
460
|
case 'write_file': {
|
|
441
461
|
const path = this.extractPath(args, ['file_path', 'path']);
|
|
442
|
-
const lines = this.countLines(output);
|
|
443
462
|
return path ? `${this.truncatePath(path, 40)} written` : 'file written';
|
|
444
463
|
}
|
|
445
464
|
case 'Edit':
|
|
@@ -504,8 +523,8 @@ export class ShellUIAdapter {
|
|
|
504
523
|
return;
|
|
505
524
|
}
|
|
506
525
|
const prefix = success ? theme.success(icons.subaction) : theme.error(icons.subaction);
|
|
507
|
-
|
|
508
|
-
this.display.stream(` ${prefix} ${
|
|
526
|
+
// Just show the result summary - tool name was already shown on the call line
|
|
527
|
+
this.display.stream(` ${prefix} ${summary}\n`);
|
|
509
528
|
}
|
|
510
529
|
/**
|
|
511
530
|
* Display edit results with full colored diff output.
|
|
@@ -523,12 +542,10 @@ export class ShellUIAdapter {
|
|
|
523
542
|
if (success) {
|
|
524
543
|
const diffSection = this.extractEditDiffSection(output);
|
|
525
544
|
if (diffSection) {
|
|
526
|
-
|
|
527
|
-
|
|
545
|
+
// Show diff with single trailing newline
|
|
546
|
+
this.display.stream(`${diffSection}\n`);
|
|
528
547
|
}
|
|
529
548
|
}
|
|
530
|
-
// Add spacing when no diff section is available
|
|
531
|
-
this.display.stream('\n');
|
|
532
549
|
}
|
|
533
550
|
logToolProgress(call, progress) {
|
|
534
551
|
const description = this.getToolActionDescription(call, progress);
|
|
@@ -551,12 +568,97 @@ export class ShellUIAdapter {
|
|
|
551
568
|
}
|
|
552
569
|
const separator = theme.ui.muted(' — ');
|
|
553
570
|
const line = parts.filter(Boolean).join(separator);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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);
|
|
559
596
|
}
|
|
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);
|
|
560
662
|
}
|
|
561
663
|
logExploreProgress(call, progress) {
|
|
562
664
|
const previous = this.exploreProgressState.get(call.id) ?? { lastCurrent: 0 };
|
|
@@ -570,8 +672,14 @@ export class ShellUIAdapter {
|
|
|
570
672
|
}
|
|
571
673
|
const line = parts.join(theme.ui.muted(' • '));
|
|
572
674
|
if (line && line !== previous.lastLine) {
|
|
573
|
-
//
|
|
574
|
-
|
|
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
|
+
}
|
|
575
683
|
}
|
|
576
684
|
this.exploreProgressState.set(call.id, {
|
|
577
685
|
total,
|
|
@@ -585,6 +693,10 @@ export class ShellUIAdapter {
|
|
|
585
693
|
const summary = success
|
|
586
694
|
? `${theme.success('✓')} Indexed ${total} file${total === 1 ? '' : 's'}`
|
|
587
695
|
: `${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
|
+
}
|
|
588
700
|
this.display.stream(`${summary}\n`);
|
|
589
701
|
}
|
|
590
702
|
/**
|
|
@@ -594,12 +706,13 @@ export class ShellUIAdapter {
|
|
|
594
706
|
clearToolState(callId) {
|
|
595
707
|
this.toolProgressSnapshots.delete(callId);
|
|
596
708
|
this.exploreProgressState.delete(callId);
|
|
709
|
+
this.finalizeScrollingOutput(callId);
|
|
597
710
|
}
|
|
598
711
|
/**
|
|
599
712
|
* Display tool call start with Erosolar-CLI style prefix
|
|
600
713
|
* - ✢ for Task/agent tools (active task indicator)
|
|
601
714
|
* - ⏺ for other tools
|
|
602
|
-
*
|
|
715
|
+
* Uses scrolling display to show only the last N tool calls
|
|
603
716
|
*/
|
|
604
717
|
displayToolCallStart(call, isCacheHit = false) {
|
|
605
718
|
const args = call.arguments || {};
|
|
@@ -622,8 +735,41 @@ export class ShellUIAdapter {
|
|
|
622
735
|
}
|
|
623
736
|
// Erosolar-CLI style: bullet + optional task marker + ToolName(args)
|
|
624
737
|
const line = `${bullet}${taskMarker ? taskMarker : ''} ${displayName}${argsStr}${cacheHint}`;
|
|
625
|
-
//
|
|
626
|
-
this.
|
|
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;
|
|
627
773
|
}
|
|
628
774
|
/**
|
|
629
775
|
* Format tool arguments inline for display (Erosolar-CLI style)
|
|
@@ -772,8 +918,8 @@ export class ShellUIAdapter {
|
|
|
772
918
|
* Start processing a request
|
|
773
919
|
*/
|
|
774
920
|
startProcessing(message = 'Working on your request') {
|
|
775
|
-
this.isProcessing = true;
|
|
776
921
|
this.resetToolUsageSummary();
|
|
922
|
+
this.clearToolCallDisplay(); // Clear previous tool call display
|
|
777
923
|
this.uiController.startProcessing();
|
|
778
924
|
this.uiController.setBaseStatus(message, 'info');
|
|
779
925
|
}
|
|
@@ -781,20 +927,17 @@ export class ShellUIAdapter {
|
|
|
781
927
|
* End processing
|
|
782
928
|
*/
|
|
783
929
|
endProcessing(message = 'Ready for prompts') {
|
|
784
|
-
this.isProcessing = false;
|
|
785
930
|
this.uiController.endProcessing();
|
|
786
931
|
this.showToolUsageSummary();
|
|
787
932
|
this.uiController.setBaseStatus(message, 'success');
|
|
933
|
+
// Clear tool call scrolling state for next request
|
|
934
|
+
this.clearToolCallDisplay();
|
|
788
935
|
}
|
|
789
936
|
/**
|
|
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.
|
|
937
|
+
* Update context usage (no-op - status handled by display.showStatusLine)
|
|
794
938
|
*/
|
|
795
|
-
updateContextUsage(
|
|
796
|
-
|
|
797
|
-
// Status updates are internal; avoid forcing overlay rerender
|
|
939
|
+
updateContextUsage(_percentage) {
|
|
940
|
+
// No-op - context display is handled by display.showStatusLine()
|
|
798
941
|
}
|
|
799
942
|
/**
|
|
800
943
|
* Show a user interrupt
|
|
@@ -814,7 +957,7 @@ export class ShellUIAdapter {
|
|
|
814
957
|
/**
|
|
815
958
|
* Show profile switcher (Shift+Tab)
|
|
816
959
|
*/
|
|
817
|
-
showProfileSwitcher(profiles,
|
|
960
|
+
showProfileSwitcher(profiles, _currentProfile) {
|
|
818
961
|
this.updateCoordinator.enqueue({
|
|
819
962
|
lane: 'overlay',
|
|
820
963
|
description: 'profile-switcher',
|
|
@@ -1026,6 +1169,12 @@ export class ShellUIAdapter {
|
|
|
1026
1169
|
callback(this.buildRunningStatusLine(this.activeToolStatus.description, this.activeToolStatus.startedAt));
|
|
1027
1170
|
}
|
|
1028
1171
|
}
|
|
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
|
+
}
|
|
1029
1178
|
/**
|
|
1030
1179
|
* Keep a live status line ticking while an SSE tool or long-running tool is active.
|
|
1031
1180
|
* Uses a heartbeat to animate the spinner and update elapsed time without spamming writes.
|
|
@@ -1146,79 +1295,10 @@ export class ShellUIAdapter {
|
|
|
1146
1295
|
const prefix = success
|
|
1147
1296
|
? theme.success(icons.subaction)
|
|
1148
1297
|
: theme.error(icons.subaction);
|
|
1149
|
-
//
|
|
1150
|
-
this.display.stream(` ${prefix} ${summary}\n
|
|
1298
|
+
// Single newline - next tool call adds its own spacing
|
|
1299
|
+
this.display.stream(` ${prefix} ${summary}\n`);
|
|
1151
1300
|
}
|
|
1152
1301
|
}
|
|
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
|
-
}
|
|
1222
1302
|
/**
|
|
1223
1303
|
* Format tool result summary in Erosolar-CLI style
|
|
1224
1304
|
* Returns compact summary like "Found 4 files" or "Read 50 lines"
|
|
@@ -1331,14 +1411,32 @@ export class ShellUIAdapter {
|
|
|
1331
1411
|
}
|
|
1332
1412
|
return 'Todos updated';
|
|
1333
1413
|
}
|
|
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
|
+
}
|
|
1334
1430
|
default: {
|
|
1335
|
-
// Generic result
|
|
1431
|
+
// Generic result - show more info for better context
|
|
1336
1432
|
const lines = output.trim().split('\n').filter(l => l).length;
|
|
1337
1433
|
if (lines === 0) {
|
|
1338
|
-
return '
|
|
1434
|
+
return theme.ui.muted('No output');
|
|
1339
1435
|
}
|
|
1340
1436
|
if (lines <= 3) {
|
|
1341
|
-
|
|
1437
|
+
// Show the actual output if short
|
|
1438
|
+
const shortOutput = output.trim().substring(0, 60);
|
|
1439
|
+
return shortOutput.length < output.trim().length ? `${shortOutput}…` : shortOutput;
|
|
1342
1440
|
}
|
|
1343
1441
|
return `${theme.info(String(lines))} lines`;
|
|
1344
1442
|
}
|