fix(app): stream running shell tool output (#45106)

This commit is contained in:
Luke Parker
2026-08-26 10:58:15 +10:00
committed by GitHub
parent c4dcf72e13
commit 5a54eb4afc
3 changed files with 40 additions and 2 deletions
@@ -6,6 +6,7 @@ import { LocalProvider } from "@/providers/models/selection"
import type { ServerConnection } from "@/runtime/server/registry"
import { sessionHref } from "@/shell/routes/session"
import { useData } from "@/runtime/server/current"
import { useServerSDK } from "@/runtime/server/client"
import { useTabs } from "@/shell/tabs/tabs"
export function SessionUIProvider(
@@ -17,6 +18,7 @@ export function SessionUIProvider(
const navigate = useNavigate()
const params = useParams()
const data = useData()
const serverSDK = useServerSDK()
const tabs = useTabs()
const directory = () => props.directory
const href = (sessionID: string) => sessionHref(props.server, sessionID)
@@ -53,6 +55,7 @@ export function SessionUIProvider(
data={sessionUIData()}
directory={directory()}
sessionID={params.id}
shellOutput={(input) => serverSDK.api.shell.output(input)}
onNavigateToSession={navigateToSession}
onSessionHref={href}
>
+9 -1
View File
@@ -1,4 +1,10 @@
import type { FileDiffInfo, SessionInfo, SessionStatus } from "@opencode-ai/client/promise"
import type {
FileDiffInfo,
SessionInfo,
SessionStatus,
ShellOutputInput,
ShellOutputOutput,
} from "@opencode-ai/client/promise"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
@@ -40,6 +46,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
data: Data
directory: string
sessionID?: string
shellOutput?: (input: ShellOutputInput) => Promise<ShellOutputOutput>
onNavigateToSession?: NavigateToSessionFn
onSessionHref?: SessionHrefFn
}) => {
@@ -55,6 +62,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
navigateToSession: props.onNavigateToSession,
sessionHref: props.onSessionHref,
shellOutput: props.shellOutput,
}
},
})
@@ -6,6 +6,7 @@ import {
createSignal,
For,
Match,
onCleanup,
onMount,
Show,
Switch,
@@ -1294,15 +1295,41 @@ ToolRegistry.register({
name: "shell",
render(props) {
const i18n = useI18n()
const data = useData()
const streaming = () => props.status === "streaming"
const pending = () => streaming() || props.status === "running" || props.metadata.status === "running"
const sawStreaming = streaming()
const [streamed, setStreamed] = createSignal("")
createEffect(() => {
const id = props.metadata.shellID
const shellOutput = data.shellOutput
if (typeof id !== "string" || !pending() || !shellOutput) return
const directory = data.directory
let cursor = 0
let loading = false
let disposed = false
const load = async () => {
if (loading) return
loading = true
const response = await shellOutput({ id, location: { directory }, cursor }).catch(() => undefined)
if (disposed) return
if (response?.data.output) setStreamed((output) => output + response.data.output)
if (response) cursor = response.data.cursor
loading = false
}
void load()
const interval = setInterval(() => void load(), 1_000)
onCleanup(() => {
disposed = true
clearInterval(interval)
})
})
const command = () => {
if (typeof props.input.command === "string") return props.input.command
if (typeof props.metadata.command === "string") return props.metadata.command
return ""
}
const output = createMemo(() => stripAnsi(props.output ?? "").replace(/\r\n?/g, "\n"))
const output = createMemo(() => stripAnsi((pending() && streamed()) || props.output || "").replace(/\r\n?/g, "\n"))
return (
<BasicTool
{...props}