fix(tui): collapse execute child details (#41624)

This commit is contained in:
Kit Langton
2026-08-10 21:20:51 -04:00
committed by GitHub
Unverified
parent 753fa4cd60
commit 8d989f6829
2 changed files with 78 additions and 9 deletions
+61 -9
View File
@@ -5,6 +5,7 @@ import {
createMemo,
createSignal,
For,
Index,
Match,
on,
onCleanup,
@@ -12,6 +13,7 @@ import {
Show,
Switch,
useContext,
type Accessor,
} from "solid-js"
import path from "node:path"
import { EOL, tmpdir } from "node:os"
@@ -3019,6 +3021,63 @@ function executeCalls(value: unknown): ExecuteCall[] {
})
}
export function executeCallSummary(call: ExecuteCall) {
const args = primitiveInputSummary(call.input ?? {}).replace(/\s+/g, " ")
return `↳ ${call.tool}${call.status === "error" ? " (failed)" : ""}${args ? ` ${args}` : ""}`
}
function ExecuteCallView(props: { call: Accessor<ExecuteCall> }) {
const theme = useTheme()
const renderer = useRenderer()
const [expanded, setExpanded] = createSignal(false)
const [hover, setHover] = createSignal(false)
const input = createMemo(() => Object.entries(props.call().input ?? {}))
const expandable = createMemo(() => input().length > 0)
const title = createMemo(() => `↳ ${props.call().tool}${props.call().status === "error" ? " (failed)" : ""}`)
return (
<box
paddingLeft={3 + INLINE_TOOL_ICON_WIDTH}
onMouseOver={() => expandable() && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
if (!expandable() || renderer.getSelection()?.getSelectedText()) return
setExpanded((value) => !value)
}}
>
<text
wrapMode="none"
truncate
fg={
props.call().status === "error"
? theme.text.feedback.error.default
: hover()
? theme.text.default
: theme.text.subdued
}
>
{expanded() ? title() : executeCallSummary(props.call())}
</text>
<Show when={expanded()}>
<box paddingLeft={2}>
<For each={input()}>
{([key, value]) => (
<box flexDirection="row">
<text flexShrink={0} fg={theme.text.subdued}>
{key}:{" "}
</text>
<text flexGrow={1} wrapMode="word" fg={theme.text.default}>
{typeof value === "string" ? value : JSON.stringify(value, null, 2)}
</text>
</box>
)}
</For>
</box>
</Show>
</box>
)
}
// The `execute` tool streams child tool calls through metadata, not a child session like Task.
function Execute(props: ToolProps) {
const ctx = use()
@@ -3029,14 +3088,6 @@ function Execute(props: ToolProps) {
const hasRuntimeError = createMemo(() => props.metadata.error === true || props.part.state.status === "error")
const outputPreview = createMemo(() => collapseToolOutput(output(), 4, 4 * Math.max(20, ctx.width - 6)).output)
const showOutput = createMemo(() => output() && hasRuntimeError())
const content = createMemo(() => {
const lines = ["execute"]
for (const call of calls()) {
const args = primitiveInputSummary(call.input ?? {})
lines.push(`↳ ${call.tool}${args ? ` ${args}` : ""}${call.status === "error" ? " (failed)" : ""}`)
}
return lines.join("\n")
})
return (
<>
@@ -3048,8 +3099,9 @@ function Execute(props: ToolProps) {
complete={true}
part={props.part}
>
{content()}
execute
</InlineTool>
<Index each={calls()}>{(call) => <ExecuteCallView call={call} />}</Index>
<Show when={showOutput()}>
<box paddingLeft={3}>
<For each={outputPreview().split("\n")}>
@@ -3,6 +3,7 @@ import { For } from "solid-js"
import { testRender, type JSX } from "@opentui/solid"
import {
InlineToolRow,
executeCallSummary,
isBackgroundSubagent,
parseApplyPatchFiles,
parseDiagnostics,
@@ -182,6 +183,22 @@ describe("TUI inline tool wrapping", () => {
expect(parseQuestionAnswers({})).toBeUndefined()
})
test("summarizes execute calls on one line", () => {
expect(
executeCallSummary({
tool: "session.prompt",
status: "completed",
input: { sessionID: "ses_example", notify: true },
}),
).toBe("↳ session.prompt [sessionID=ses_example, notify=true]")
expect(executeCallSummary({ tool: "session.get", status: "error", input: { nested: { hidden: true } } })).toBe(
"↳ session.get (failed)",
)
expect(
executeCallSummary({ tool: "session.prompt", status: "completed", input: { text: "first line\nsecond line" } }),
).toBe("↳ session.prompt [text=first line second line]")
})
test("ignores diagnostics with malformed nested ranges", () => {
expect(
parseDiagnostics(