diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 6d77b0ea58fd..b101005dc209 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -214,13 +214,7 @@ export function Session() { const foregroundTasks = createMemo(() => sync.data.capabilities.experimentalBackgroundSubagents ? messages().flatMap((message) => - (sync.data.part[message.id] ?? []).filter( - (part): part is ToolPart => - part.type === "tool" && - part.tool === "task" && - part.state.status === "running" && - part.state.metadata?.background !== true, - ), + (sync.data.part[message.id] ?? []).filter(isForegroundRunningTask), ) : [], ) @@ -1500,13 +1494,7 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las - x.type === "tool" && - x.tool === "task" && - x.state.status === "running" && - x.state.metadata?.background !== true, - ) + props.parts.some(isForegroundRunningTask) } > ยท @@ -2708,3 +2696,13 @@ export function parseDiagnostics(value: unknown, filePath: string) { }) .slice(0, 3) } + +export function isForegroundRunningTask(part: Part): part is ToolPart { + return ( + part.type === "tool" && + part.tool === "task" && + part.state.status === "running" && + part.state.metadata?.background !== true && + part.state.input?.background !== true + ) +} 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..ee4d56539775 --- /dev/null +++ b/packages/tui/test/cli/tui/background-tool-hint.test.ts @@ -0,0 +1,79 @@ +import { expect, test } from "bun:test" +import type { Part, ToolPart } from "@opencode-ai/sdk/v2" +import { isForegroundRunningTask } from "../../../src/routes/session" + +function taskPart(opts: { background?: boolean; metadataBackground?: boolean } = {}): Part { + return { + id: "part-1", + sessionID: "session-1", + messageID: "message-1", + type: "tool", + callID: "call-1", + tool: "task", + state: { + status: "running", + input: opts.background ? { background: true } : {}, + metadata: opts.metadataBackground ? { background: true } : undefined, + time: { start: Date.now() }, + }, + } satisfies ToolPart as Part +} + +function nonTaskPart(tool: string): Part { + return { + id: "part-2", + sessionID: "session-1", + messageID: "message-1", + type: "tool", + callID: "call-2", + tool, + state: { + status: "running", + input: {}, + time: { start: Date.now() }, + }, + } satisfies ToolPart as Part +} + +test("returns true for a foreground running task", () => { + expect(isForegroundRunningTask(taskPart())).toBeTrue() +}) + +test("returns false when input.background is true", () => { + expect(isForegroundRunningTask(taskPart({ background: true }))).toBeFalse() +}) + +test("returns false when metadata.background is true", () => { + expect(isForegroundRunningTask(taskPart({ metadataBackground: true }))).toBeFalse() +}) + +test("returns false when both input and metadata mark background", () => { + expect(isForegroundRunningTask(taskPart({ background: true, metadataBackground: true }))).toBeFalse() +}) + +test("returns false for non-task tools", () => { + expect(isForegroundRunningTask(nonTaskPart("read"))).toBeFalse() + expect(isForegroundRunningTask(nonTaskPart("write"))).toBeFalse() + expect(isForegroundRunningTask(nonTaskPart("bash"))).toBeFalse() +}) + +test("returns false for a completed task", () => { + const part = { + id: "part-3", + sessionID: "session-1", + messageID: "message-1", + type: "tool" as const, + callID: "call-3", + tool: "task", + state: { + status: "completed" as const, + input: {}, + output: "", + title: "", + metadata: {}, + time: { start: Date.now(), end: Date.now() }, + content: [], + }, + } as Part + expect(isForegroundRunningTask(part)).toBeFalse() +})