From 47a13fd8f018c9e7707d027a463ff4963bab18ef Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 09:27:41 +0200 Subject: [PATCH] fix(vscode): stabilize shell command streaming (#10127) --- .changeset/stable-shell-streaming.md | 5 ++++ .../src/components/chat/AssistantMessage.tsx | 29 ++++++++++++------- .../webview-ui/src/context/session.tsx | 9 ++++-- 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 .changeset/stable-shell-streaming.md diff --git a/.changeset/stable-shell-streaming.md b/.changeset/stable-shell-streaming.md new file mode 100644 index 00000000000..bdae1f250a5 --- /dev/null +++ b/.changeset/stable-shell-streaming.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Keep shell command blocks stable while output streams so pending animations and expanded state do not reset. diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 998d610d579..9acf713193d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -67,19 +67,26 @@ interface AssistantMessageProps { feedback?: MessageFeedbackControls } +type ToolStateProps = { + input?: Record + metadata?: Record + output?: string + status?: string +} + function TodoToolCard(props: { part: ToolPart }) { const render = ToolRegistry.render(props.part.tool) - const state = props.part.state as any + const state = () => props.part.state as ToolStateProps return ( {(renderFn) => ( @@ -90,23 +97,23 @@ function TodoToolCard(props: { part: ToolPart }) { function BashToolCard(props: { part: ToolPart; defaultOpen: boolean }) { const render = ToolRegistry.render(props.part.tool) - const state = props.part.state as any + const state = () => props.part.state as ToolStateProps return ( {(card) => ( >} - input={state?.input ?? {}} - metadata={state?.metadata ?? {}} + input={state()?.input ?? {}} + metadata={state()?.metadata ?? {}} partMetadata={props.part.metadata ?? {}} tool={props.part.tool} partID={props.part.id} callID={props.part.callID} - output={state?.output} - status={state?.status} + output={state()?.output} + status={state()?.status} defaultOpen={props.defaultOpen} animate - reveal={state?.status === "pending" || state?.status === "running"} + reveal={state()?.status === "pending" || state()?.status === "running"} /> )} diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 8ebe0d41edf..2da1f032c68 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -1192,8 +1192,13 @@ export const SessionProvider: ParentComponent = (props) => { // Append text delta to text or reasoning parts ;(existing as { text: string }).text += delta.textDelta } else { - // Replace entire part - parts[effectiveMessageID][existingIndex] = part + // Preserve the proxy identity so Solid does not remount tool UI + // during streaming updates and restart pending animations. + const target = existing as unknown as Record + for (const key of Object.keys(target)) { + if (!(key in part)) delete target[key] + } + Object.assign(existing, part) } } else { // Add new part