From 5d45eea7eb105c53ccdff9f87fd5406e6d8a89c2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 16 Jul 2026 10:33:34 -0400 Subject: [PATCH 1/2] fix(tui): correct background status rendering --- packages/tui/src/routes/session/index.tsx | 19 ++++++++++++------- .../tui/inline-tool-wrap-snapshot.test.tsx | 8 ++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 373482db8de2..36f15ff00b78 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1340,7 +1340,7 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) { if (state() === "error") return "failed" return state() ?? "finished" } - const heading = () => `${state() === "completed" ? "↳" : "!"} ${actor()} ${status()}` + const heading = () => `${state() === "completed" ? "✓" : "!"} ${actor()} ${status()}` const suffix = () => Locale.truncateWidth(` · ${description()}`, Math.max(0, ctx.width - 3 - Bun.stringWidth(heading()))) const color = () => { @@ -2548,10 +2548,8 @@ function WebSearch(props: ToolProps) { function Subagent(props: ToolProps) { const { navigate } = useRoute() const data = useData() - const input = createMemo(() => (typeof props.part.state.input === "string" ? {} : props.part.state.input)) - const metadata = createMemo(() => (props.part.state.status === "streaming" ? {} : props.part.state.structured)) - const sessionID = createMemo(() => stringValue(metadata().sessionID) ?? stringValue(metadata().sessionId)) - const description = createMemo(() => stringValue(input().description)) + const sessionID = createMemo(() => stringValue(props.metadata.sessionID) ?? stringValue(props.metadata.sessionId)) + const description = createMemo(() => stringValue(props.input.description)) const isRunning = createMemo(() => { const id = sessionID() return props.part.state.status === "running" || Boolean(id && data.session.status(id) === "running") @@ -2569,16 +2567,23 @@ function Subagent(props: ToolProps) { if (id) navigate({ type: "session", sessionID: id }) }} status={ - input().background === true || metadata().status === "running" ? ( + isBackgroundSubagent(props.metadata, props.part.state.status) ? ( Background ) : undefined } > - {`${Locale.titlecase(stringValue(input().agent) ?? stringValue(input().subagent_type) ?? "General")} Subagent — ${description() ?? "Subagent"}`} + {`${Locale.titlecase(stringValue(props.input.agent) ?? stringValue(props.input.subagent_type) ?? "General")} Subagent — ${description() ?? "Subagent"}`} ) } +export function isBackgroundSubagent( + metadata: Record, + status: SessionMessageAssistantTool["state"]["status"], +) { + return status === "completed" && metadata.status === "running" +} + 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 24b741eb8de9..1f44f9260048 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 @@ -4,6 +4,7 @@ import { testRender, type JSX } from "@opentui/solid" import { formatSubagentRetry, InlineToolRow, + isBackgroundSubagent, parseApplyPatchFiles, parseDiagnostics, parseQuestionAnswers, @@ -201,6 +202,13 @@ describe("TUI inline tool wrapping", () => { expect(formatSubagentRetry(2, "Rate limited by provider")).toBe("Retrying (attempt 2) · Rate limited by provider") }) + test("labels only detached or async subagents as background", () => { + expect(isBackgroundSubagent({ status: "running" }, "running")).toBeFalse() + expect(isBackgroundSubagent({ status: "running" }, "completed")).toBeTrue() + expect(isBackgroundSubagent({ status: "running" }, "error")).toBeFalse() + expect(isBackgroundSubagent({ status: "completed" }, "completed")).toBeFalse() + }) + test("snapshots consecutive grep, glob, and read rows at a narrow width", async () => { expect(await renderFrame(() => , { width: 72, height: 12 })).toMatchSnapshot() }) From 1f98326af549181fdf348e7be47d160ef0f7d025 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 16 Jul 2026 10:39:33 -0400 Subject: [PATCH 2/2] fix(tui): preserve background notification icon --- packages/tui/src/routes/session/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 36f15ff00b78..7f8d1c62f7fc 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1340,7 +1340,7 @@ function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) { if (state() === "error") return "failed" return state() ?? "finished" } - const heading = () => `${state() === "completed" ? "✓" : "!"} ${actor()} ${status()}` + const heading = () => `${state() === "completed" ? "↳" : "!"} ${actor()} ${status()}` const suffix = () => Locale.truncateWidth(` · ${description()}`, Math.max(0, ctx.width - 3 - Bun.stringWidth(heading()))) const color = () => {