Compare commits

...
Author SHA1 Message Date
Aiden Cline a67a8235e1 fix(tui): normalize shell progress output 2026-07-19 05:03:47 +00:00
Aiden Cline 8454f94dbe fix(tui): collapse direct shell output 2026-07-19 04:54:32 +00:00
3 changed files with 20 additions and 4 deletions
+3 -3
View File
@@ -66,7 +66,7 @@ import { useConfig } from "../../config"
import { useClipboard } from "../../context/clipboard"
import { nextThinkingMode, reasoningSummary, type ThinkingMode } from "../../context/thinking"
import { getScrollAcceleration } from "../../util/scroll"
import { collapseToolOutput } from "../../util/collapse-tool-output"
import { collapseToolOutput, normalizeShellOutput } from "../../util/collapse-tool-output"
import { usePluginRuntime } from "../../plugin/runtime"
import { PluginSlot } from "../../plugin/context"
import { Keymap, type KeymapCommand } from "../../context/keymap"
@@ -2448,7 +2448,7 @@ function Shell(props: ToolProps) {
limit: 1024 * 1024,
location: location ? { directory: location.directory, workspace: location.workspaceID } : undefined,
})
.then((response) => setBackgroundOutput(stripAnsi(response.data.output.trim())))
.then((response) => setBackgroundOutput(normalizeShellOutput(response.data.output.trim())))
.catch(() => undefined)
loading = false
}
@@ -2461,7 +2461,7 @@ function Shell(props: ToolProps) {
if (props.part.state.status === "streaming") return ""
if (shellID()) return expanded() ? backgroundOutput() : ""
const content = props.part.state.content[0]
return stripAnsi(content?.type === "text" ? content.text.trim() : "")
return normalizeShellOutput(content?.type === "text" ? content.text.trim() : "")
})
const maxLines = 10
const maxChars = createMemo(() => maxLines * Math.max(20, ctx.width - 6))
@@ -1,3 +1,5 @@
import stripAnsi from "strip-ansi"
export function collapseToolOutput(output: string, maxLines: number, maxChars: number) {
const lines = output.split("\n")
if (lines.length <= maxLines && Array.from(output).length <= maxChars) {
@@ -19,3 +21,7 @@ export function collapseToolOutput(output: string, maxLines: number, maxChars: n
return { output: preview, overflow: true }
}
export function normalizeShellOutput(output: string) {
return stripAnsi(output).replace(/\r\n?/g, "\n")
}
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { collapseToolOutput } from "../../../src/util/collapse-tool-output"
import { collapseToolOutput, normalizeShellOutput } from "../../../src/util/collapse-tool-output"
test("limits command input and output to the same line budget", () => {
const command = Array.from({ length: 8 }, (_, index) => `command ${index + 1}`).join("\n")
@@ -12,3 +12,13 @@ test("limits command input and output to the same line budget", () => {
expect(collapsed.output).toContain("command 8\n\noutput 1…")
expect(collapsed.output).not.toContain("output 2")
})
test("normalizes carriage-return shell progress before collapsing", () => {
const output = Array.from({ length: 30 }, (_, index) => `progress ${index + 1}`).join("\r")
const collapsed = collapseToolOutput(normalizeShellOutput(output), 10, 1_000)
expect(collapsed.overflow).toBe(true)
expect(collapsed.output.split("\n")).toHaveLength(10)
expect(collapsed.output).toContain("progress 10…")
expect(collapsed.output).not.toContain("progress 11")
})