From 610e618bc5419b2d646adfeca185fd6fef0e803f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 4 Jul 2026 11:33:45 -0400 Subject: [PATCH] fix(tui): clear completed background shell status (#35320) --- packages/core/src/tool/shell.ts | 8 +++++--- packages/core/test/tool-shell.test.ts | 8 ++++++-- packages/tui/src/routes/session/index.tsx | 15 +++++++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/core/src/tool/shell.ts b/packages/core/src/tool/shell.ts index cb7edb3113..c3644e4810 100644 --- a/packages/core/src/tool/shell.ts +++ b/packages/core/src/tool/shell.ts @@ -18,8 +18,9 @@ export const DEFAULT_TIMEOUT_MS = 2 * 60 * 1_000 export const MAX_TIMEOUT_MS = 10 * 60 * 1_000 export const MAX_CAPTURE_BYTES = 1024 * 1024 -const BACKGROUND_STARTED = - "The command has not completed; it is now running in the background." +const BACKGROUND_STARTED = "The command was moved to the background." +const BACKGROUND_INSTRUCTION = + "You will be notified automatically when the command finishes. DO NOT sleep, poll, or proactively check on its progress." export const Input = Schema.Struct({ command: Schema.String.annotate({ description: "Shell command string to execute" }), @@ -54,10 +55,11 @@ const Output = Schema.Struct({ type Output = typeof Output.Type const modelOutput = (output: Output): string | undefined => { - if (output.status === "running") return undefined const warnings = output.warnings?.length ? `\n\nWarnings:\n${output.warnings.map((warning) => `- ${warning}`).join("\n")}` : "" + if (output.status === "running") + return `${warnings.trimStart()}${warnings ? "\n\n" : ""}${BACKGROUND_INSTRUCTION}` if (output.timeout) return `${warnings.trimStart()}${warnings ? "\n\n" : ""}Command timed out before completion.` return `${warnings.trimStart()}${warnings ? "\n\n" : ""}Command exited with code ${output.exit}.` } diff --git a/packages/core/test/tool-shell.test.ts b/packages/core/test/tool-shell.test.ts index 217414aa2c..117eba27f1 100644 --- a/packages/core/test/tool-shell.test.ts +++ b/packages/core/test/tool-shell.test.ts @@ -480,9 +480,13 @@ describe("ShellTool", () => { const structured = settled.output?.structured as Record | undefined const shellID = typeof structured?.shellID === "string" ? structured.shellID : undefined expect(settled.output?.structured).toMatchObject({ truncated: false }) - expect(settled.output?.content[0]).toMatchObject({ + expect(settled.output?.content[0]).toEqual({ type: "text", - text: expect.stringContaining("running in the background"), + text: "The command was moved to the background.", + }) + expect(settled.output?.content[1]).toMatchObject({ + type: "text", + text: expect.stringContaining("DO NOT sleep, poll"), }) expect(shellID).toStartWith("sh_") diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 604cd95e89..dd9fd34d07 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -2080,14 +2080,16 @@ function Shell(props: ToolProps) { return request?.source?.type === "tool" && request.source.callID === props.part.id }) const color = createMemo(() => (permission() ? theme.warning : theme.text)) - const isRunning = createMemo(() => { - if (props.part.state.status === "running") return true - const shellID = stringValue(props.metadata.shellID) - return Boolean(shellID && data.shell.get(shellID)) + const shellID = createMemo(() => stringValue(props.metadata.shellID)) + const backgroundRunning = createMemo(() => { + const id = shellID() + return Boolean(id && data.shell.get(id)) }) + const isRunning = createMemo(() => props.part.state.status === "running" || backgroundRunning()) const command = createMemo(() => stringValue(props.input.command)) const output = createMemo(() => { if (props.part.state.status === "pending") return "" + if (shellID()) return "" const content = props.part.state.content[0] return stripAnsi(content?.type === "text" ? content.text.trim() : "") }) @@ -2130,6 +2132,11 @@ function Shell(props: ToolProps) { + + + Backgrounded + + {expanded() ? "Click to collapse" : "Click to expand"}