diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index c4ee2307370b..a032edfbda0e 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1344,17 +1344,8 @@ function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) { const theme = useTheme() const shortcut = Keymap.useShortcut("session.background") const running = createMemo(() => { - if (!shortcut()) return - const current = props.messages.findLast( - (message): message is SessionMessageAssistant => message.type === "assistant" && !message.time.completed, - ) - const part = current?.content.find((part): part is SessionMessageAssistantTool => { - if (part.type !== "tool" || part.state.status !== "running") return false - const name = canonicalToolName(part.name) - return name === "shell" || name === "subagent" - }) - if (!current || !part) return - return `${current.id}:${part.id}` + if (!shortcut()) return undefined + return backgroundableToolID(props.messages) }) const visible = createDelayedPresence(running, BACKGROUND_TOOL_HINT_DELAY) return ( @@ -1370,6 +1361,19 @@ function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) { ) } +export function backgroundableToolID(messages: SessionMessageInfo[]) { + const current = messages.findLast( + (message): message is SessionMessageAssistant => message.type === "assistant" && !message.time.completed, + ) + const part = current?.content.find((part): part is SessionMessageAssistantTool => { + if (part.type !== "tool" || part.state.status !== "running" || part.state.input.background === true) return false + const name = canonicalToolName(part.name) + return name === "shell" || name === "subagent" + }) + if (!current || !part) return undefined + return `${current.id}:${part.id}` +} + function SessionMessageView(props: { message: SessionMessageInfo }) { return ( diff --git a/packages/tui/test/cli/tui/background-tool-hint.test.ts b/packages/tui/test/cli/tui/background-tool-hint.test.ts new file mode 100644 index 000000000000..89bc13b0fd41 --- /dev/null +++ b/packages/tui/test/cli/tui/background-tool-hint.test.ts @@ -0,0 +1,45 @@ +import { expect, test } from "bun:test" +import type { SessionMessageAssistant, SessionMessageAssistantTool } from "@opencode-ai/client" +import { backgroundableToolID } from "../../../src/routes/session" + +test("shows the hint for running foreground work", () => { + expect(backgroundableToolID([assistant(tool("subagent"))])).toBe("assistant:subagent") + expect(backgroundableToolID([assistant(tool("shell"))])).toBe("assistant:shell") +}) + +test("hides the hint when all running work started in the background", () => { + expect(backgroundableToolID([assistant(tool("subagent", true), tool("shell", true))])).toBeUndefined() +}) + +test("shows the hint when foreground work remains alongside background work", () => { + expect(backgroundableToolID([assistant(tool("subagent", true), tool("shell"))])).toBe("assistant:shell") +}) + +test("ignores running tools that cannot be backgrounded", () => { + expect(backgroundableToolID([assistant(tool("read"))])).toBeUndefined() +}) + +function assistant(...content: SessionMessageAssistantTool[]): SessionMessageAssistant { + return { + type: "assistant", + id: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + content, + time: { created: 1 }, + } +} + +function tool(name: string, background = false): SessionMessageAssistantTool { + return { + type: "tool", + id: name, + name, + state: { + status: "running", + input: background ? { background: true } : {}, + metadata: {}, + }, + time: { created: 1 }, + } +}