diff --git a/packages/tui/src/component/spinner.tsx b/packages/tui/src/component/spinner.tsx
index 5ba9bc31334b..4a57308ba542 100644
--- a/packages/tui/src/component/spinner.tsx
+++ b/packages/tui/src/component/spinner.tsx
@@ -14,7 +14,10 @@ export function Spinner(props: { children?: JSX.Element; color?: RGBA }) {
const config = useConfig().data
const color = () => props.color ?? theme.textMuted
return (
- ⋯ {props.children}}>
+ {props.children ? <>⋯ {props.children}> : "⋯"}}
+ >
diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx
index 788bbbe8e1f6..5168de2729ee 100644
--- a/packages/tui/src/routes/session/index.tsx
+++ b/packages/tui/src/routes/session/index.tsx
@@ -1978,6 +1978,7 @@ function InlineTool(props: {
pending: string
failure?: string
spinner?: boolean
+ status?: JSX.Element
children: JSX.Element
part: SessionMessageAssistantTool
onClick?: () => void
@@ -2028,6 +2029,7 @@ function InlineTool(props: {
pending={props.pending}
failure={props.failure}
spinner={props.spinner}
+ status={props.status}
onMouseOver={() => clickable() && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
@@ -2057,6 +2059,7 @@ export function InlineToolRow(props: {
pending: string
failure?: string
spinner?: boolean
+ status?: JSX.Element
children: JSX.Element
onMouseOver?: () => void
onMouseOut?: () => void
@@ -2066,7 +2069,16 @@ export function InlineToolRow(props: {
-
+ }>
+ {(status) => (
+
+
+
+ {props.children}
+
+
+ )}
+
{props.pending}} when={props.complete || props.failed}>
@@ -2078,13 +2090,28 @@ export function InlineToolRow(props: {
>
{props.icon}
-
+ {props.failed && !props.complete ? (props.failure ?? props.children) : props.children}
+
+ }
>
- {props.failed && !props.complete ? (props.failure ?? props.children) : props.children}
-
+ {(status) => (
+
+ {props.failed && !props.complete ? (props.failure ?? props.children) : props.children}
+
+ )}
+
@@ -2098,6 +2125,32 @@ export function InlineToolRow(props: {
)
}
+function InlineToolLabel(props: { color?: RGBA; denied?: boolean; status: JSX.Element; children: JSX.Element }) {
+ return (
+
+
+ {props.children}
+
+ {props.status}
+
+ )
+}
+
+function StatusBadge(props: { children: string }) {
+ const { theme } = useTheme()
+ return (
+
+ {" "}
+ {props.children}{" "}
+
+ )
+}
+
function BlockTool(props: {
title?: string
path?: { label: string; value: string }
@@ -2241,9 +2294,7 @@ function Shell(props: ToolProps) {
-
- Background
-
+ Background
{expanded() ? "Click to collapse" : "Click to expand"}
@@ -2386,20 +2437,17 @@ function Subagent(props: ToolProps) {
const id = sessionID()
if (id) navigate({ type: "session", sessionID: id })
}}
+ status={
+ props.input.background === true || props.metadata.status === "running" ? (
+ Background
+ ) : undefined
+ }
>
- {formatSubagentTitle(
- Locale.titlecase(stringValue(props.input.agent) ?? stringValue(props.input.subagent_type) ?? "General"),
- description() ?? "Subagent",
- props.input.background === true || props.metadata.status === "running",
- )}
+ {`${Locale.titlecase(stringValue(props.input.agent) ?? stringValue(props.input.subagent_type) ?? "General")} Subagent — ${description() ?? "Subagent"}`}
)
}
-export function formatSubagentTitle(agent: string, description: string, background: boolean) {
- return `${agent} Subagent — ${description}${background ? " [background]" : ""}`
-}
-
export function formatSubagentRetry(attempt: number, message: string) {
return `Retrying (attempt ${attempt}) · ${message}`
}
diff --git a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
index 2ff61fa1c392..24b741eb8de9 100644
--- a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
+++ b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx
@@ -3,7 +3,6 @@ import { For } from "solid-js"
import { testRender, type JSX } from "@opentui/solid"
import {
formatSubagentRetry,
- formatSubagentTitle,
InlineToolRow,
parseApplyPatchFiles,
parseDiagnostics,
@@ -99,7 +98,16 @@ function ReminderAlignmentFixture() {
)
}
+function TrailingStatusFixture() {
+ return (
+ Background }>
+ Explore Subagent — Inspect renderer status styling
+
+ )
+}
+
async function renderFrame(component: () => JSX.Element, options: { width: number; height: number }) {
+ testSetup?.renderer.destroy()
testSetup = await testRender(component, options)
await testSetup.renderOnce()
await testSetup.renderOnce()
@@ -142,6 +150,15 @@ describe("TUI inline tool wrapping", () => {
)
})
+ test("wraps a trailing status as one padded item", async () => {
+ expect(await renderFrame(() => , { width: 70, height: 2 })).toBe(
+ " : Explore Subagent — Inspect renderer status styling Background",
+ )
+ expect(await renderFrame(() => , { width: 62, height: 2 })).toBe(
+ " : Explore Subagent — Inspect renderer status styling\n Background",
+ )
+ })
+
test("filters malformed nested tool wire data", () => {
expect(
parseApplyPatchFiles([
@@ -180,13 +197,6 @@ describe("TUI inline tool wrapping", () => {
).toEqual([{ message: "valid", range: { start: { line: 2, character: 3 } } }])
})
- test("keeps background state attached to the subagent identity", () => {
- expect(formatSubagentTitle("Explore", "Inspect renderer", false)).toBe("Explore Subagent — Inspect renderer")
- expect(formatSubagentTitle("Explore", "Inspect renderer", true)).toBe(
- "Explore Subagent — Inspect renderer [background]",
- )
- })
-
test("keeps retry status ahead of wrapping messages", () => {
expect(formatSubagentRetry(2, "Rate limited by provider")).toBe("Retrying (attempt 2) · Rate limited by provider")
})