Repository navigation
ui: Add "Show tool call in progress" setting - #30681
Conversation
Add the showToolCallInProgress key and its checkbox to Settings > Display, right next to its "always show" counterpart. Upstream dropped a setting of this exact name in 43b5e63, folding it into alwaysShowToolCallContent because the old name promised expansion that outlives the call; this brings back the narrower, in-progress-only meaning. The entry is registry-driven, so SETTING_CONFIG_DEFAULT, the dialog field and the ui_settings admin baseline pick it up without extra wiring. It ships enabled, mirroring Show thought in progress, and the always-show help text is reworded so the two toggles read as a pair rather than contradicting each other. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)
Drive the default expansion of TOOL_CALL_STREAMING and TOOL_CALL_PENDING from showToolCallInProgress and leave TOOL_CALL, the terminal state, to alwaysShowToolCallContent. A finished call now folds back on its own once its result lands, which is exactly how the reasoning block behaves when the thought completes. Manual toggling keeps precedence over both defaults in either direction: isExpanded() only consults the settings while expandedStates holds no entry for the section. Client tests pin the truth table for the two toggles, including the automatic fold-back and both manual overrides. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)
There was a problem hiding this comment.
🟡 Changes recommended
Streaming shell calls collapse before execution finishes, hiding live output.
2 open findings
What changed in this PR
Adds a default-enabled Display setting to expand tool calls while running, similar to reasoning blocks.
Changes:
- Registers the setting and updates help text.
- Adjusts expansion defaults while preserving manual choices.
- Adds settings and component tests.
| File | Description |
|---|---|
| tools/ui/tests/unit/settings-tool-call-in-progress.test.ts | Checks setting registration and default. |
| tools/ui/tests/client/tool-call-in-progress.svelte.test.ts | Tests expansion settings and manual toggles. |
| tools/ui/src/lib/constants/settings.constants.ts | Adds checkbox and help text. |
| tools/ui/src/lib/constants/settings-keys.constants.ts | Defines the setting key. |
| tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte | Applies progress-based expansion defaults. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
There's a catch with exec_shell_command: to stream the terminal output live, it creates an empty result message first and fills it as the command writes, so the UI sees a result and folds the block right when the command starts, hiding the live output. The component already knows which call is actually running (currentlyExecutingToolCallId), so keeping the block open while its id matches would fix it. |
A call the agentic loop reports as running now outranks its section type. exec_shell_command persists an empty result before reading any output, so a live call is already TOOL_CALL and folded back, which hid the streaming output the setting is meant to show. Move the expansion default to isSectionExpandedByDefault so the running-id rule is covered by a truth table, and test the pending -> finished transition instead of only the finished state: a block that only ever got mounted finished cannot prove it folds back on its own. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)
The exec parser read its args strictly, so while the model was still typing the call the block title stayed empty and the body claimed "Running...". Parse the args partially (the read_file / edit_file / write_file pattern), keep a terminal cursor while the args JSON is incomplete, and stand the conversation cwd in for the x-tool-cwd that only arrives with the result message, so the prompt is there from the first character. The body stays empty while the args stream, shows "Executing command..." once the call is executing, and the wrapper drops its padding for empty phases so the title sits flush with the frame. Assisted by: Delta (zai-org/GLM-5.3)
write_file and edit_file refused to render until their args carried a path, so a call that streams its body first (or a same-named tool with other arg names) showed an empty block. Meta now carries optional fileName/filePath, path alias reading moves to shared helpers, and the blocks fall back to the raw args JSON when no known content key is present. Assisted by: Delta (zai-org/GLM-5.3)
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
13 open issues
{:else if writeFileBody?.content}treats an empty string as “no content”, which can mis-render… · New{:else if writeFileBody?.content}treats an empty string as “no content”, which can mis-render… · New{:else if writeFileBody?.content}treats an empty string as “no content”, which can mis-render… · New{:else if writeFileBody?.content}treats an empty string as “no content”, which can mis-render… · New{:else if writeFileBody?.content}treats an empty string as “no content”, which can mis-render… · New Thesection.toolArgsfallback will typically be hit wheneveredits.lengthis0, which makes… · New Thesection.toolArgsfallback will typically be hit wheneveredits.lengthis0, which makes… · New Thesection.toolArgsfallback will typically be hit wheneveredits.lengthis0, which makes… · New Thesection.toolArgsfallback will typically be hit wheneveredits.lengthis0, which makes… · New Thesection.toolArgsfallback will typically be hit wheneveredits.lengthis0, which makes… · NewcommandStreamingcan re-run very frequently during args streaming, andJSON.parseon a growing… · NewcommandStreamingcan re-run very frequently during args streaming, andJSON.parseon a growing… · New Using:has()can cause cross-browser issues depending on the project’s supported browser matrix… · New
2 resolved since last review
Review effort: Lite
|
Triaging the Copilot pass:
|
A call carrying {"content":""} fell through to the raw-args fallback, so
a finished write of an empty file showed the args JSON. The parser now
leaves content undefined when the key is missing and the block tests
!== undefined, so an empty write renders as an empty code block with its
byte count.
Assisted by: DeepSeek-V4.1-Flash (deepseek-ai)
All conflicts came from upstream ggml-org#30681 (Show tool call in progress setting) landing on the zh-CN i18n layer of the three ToolCallBlock renderers, resolved the usual way: imports merged, the upstream snippet signature change (children(meta, _ctx) -> children(_meta, ctx)) picked up so the stats read from the file-level deriveds, and the strings kept under t(). The upstream UI additions are wired into t() as well: the new "Show tool call in progress" setting with its help texts, and the "Receiving arguments..." / "No content" fallbacks; the stale help text of "Always show tool call content" was replaced with the rewritten one. Assisted-by: CodeBuddy
* feat(ui): add Show tool call in progress display setting Add the showToolCallInProgress key and its checkbox to Settings > Display, right next to its "always show" counterpart. Upstream dropped a setting of this exact name in 43b5e63, folding it into alwaysShowToolCallContent because the old name promised expansion that outlives the call; this brings back the narrower, in-progress-only meaning. The entry is registry-driven, so SETTING_CONFIG_DEFAULT, the dialog field and the ui_settings admin baseline pick it up without extra wiring. It ships enabled, mirroring Show thought in progress, and the always-show help text is reworded so the two toggles read as a pair rather than contradicting each other. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * feat(ui): expand tool call blocks while they are in progress Drive the default expansion of TOOL_CALL_STREAMING and TOOL_CALL_PENDING from showToolCallInProgress and leave TOOL_CALL, the terminal state, to alwaysShowToolCallContent. A finished call now folds back on its own once its result lands, which is exactly how the reasoning block behaves when the thought completes. Manual toggling keeps precedence over both defaults in either direction: isExpanded() only consults the settings while expandedStates holds no entry for the section. Client tests pin the truth table for the two toggles, including the automatic fold-back and both manual overrides. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * fix(ui): keep running tool calls expanded A call the agentic loop reports as running now outranks its section type. exec_shell_command persists an empty result before reading any output, so a live call is already TOOL_CALL and folded back, which hid the streaming output the setting is meant to show. Move the expansion default to isSectionExpandedByDefault so the running-id rule is covered by a truth table, and test the pending -> finished transition instead of only the finished state: a block that only ever got mounted finished cannot prove it folds back on its own. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * feat(ui): stream the exec command from the first character The exec parser read its args strictly, so while the model was still typing the call the block title stayed empty and the body claimed "Running...". Parse the args partially (the read_file / edit_file / write_file pattern), keep a terminal cursor while the args JSON is incomplete, and stand the conversation cwd in for the x-tool-cwd that only arrives with the result message, so the prompt is there from the first character. The body stays empty while the args stream, shows "Executing command..." once the call is executing, and the wrapper drops its padding for empty phases so the title sits flush with the frame. Assisted by: Delta (zai-org/GLM-5.3) * fix(ui): stream file tool bodies even before a path arrives write_file and edit_file refused to render until their args carried a path, so a call that streams its body first (or a same-named tool with other arg names) showed an empty block. Meta now carries optional fileName/filePath, path alias reading moves to shared helpers, and the blocks fall back to the raw args JSON when no known content key is present. Assisted by: Delta (zai-org/GLM-5.3) * fix(ui): keep an empty write_file body instead of the raw args A call carrying {"content":""} fell through to the raw-args fallback, so a finished write of an empty file showed the args JSON. The parser now leaves content undefined when the key is missing and the block tests !== undefined, so an empty write renders as an empty code block with its byte count. Assisted by: DeepSeek-V4.1-Flash (deepseek-ai)
* feat(ui): add Show tool call in progress display setting Add the showToolCallInProgress key and its checkbox to Settings > Display, right next to its "always show" counterpart. Upstream dropped a setting of this exact name in 43b5e63, folding it into alwaysShowToolCallContent because the old name promised expansion that outlives the call; this brings back the narrower, in-progress-only meaning. The entry is registry-driven, so SETTING_CONFIG_DEFAULT, the dialog field and the ui_settings admin baseline pick it up without extra wiring. It ships enabled, mirroring Show thought in progress, and the always-show help text is reworded so the two toggles read as a pair rather than contradicting each other. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * feat(ui): expand tool call blocks while they are in progress Drive the default expansion of TOOL_CALL_STREAMING and TOOL_CALL_PENDING from showToolCallInProgress and leave TOOL_CALL, the terminal state, to alwaysShowToolCallContent. A finished call now folds back on its own once its result lands, which is exactly how the reasoning block behaves when the thought completes. Manual toggling keeps precedence over both defaults in either direction: isExpanded() only consults the settings while expandedStates holds no entry for the section. Client tests pin the truth table for the two toggles, including the automatic fold-back and both manual overrides. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * fix(ui): keep running tool calls expanded A call the agentic loop reports as running now outranks its section type. exec_shell_command persists an empty result before reading any output, so a live call is already TOOL_CALL and folded back, which hid the streaming output the setting is meant to show. Move the expansion default to isSectionExpandedByDefault so the running-id rule is covered by a truth table, and test the pending -> finished transition instead of only the finished state: a block that only ever got mounted finished cannot prove it folds back on its own. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0) * feat(ui): stream the exec command from the first character The exec parser read its args strictly, so while the model was still typing the call the block title stayed empty and the body claimed "Running...". Parse the args partially (the read_file / edit_file / write_file pattern), keep a terminal cursor while the args JSON is incomplete, and stand the conversation cwd in for the x-tool-cwd that only arrives with the result message, so the prompt is there from the first character. The body stays empty while the args stream, shows "Executing command..." once the call is executing, and the wrapper drops its padding for empty phases so the title sits flush with the frame. Assisted by: Delta (zai-org/GLM-5.3) * fix(ui): stream file tool bodies even before a path arrives write_file and edit_file refused to render until their args carried a path, so a call that streams its body first (or a same-named tool with other arg names) showed an empty block. Meta now carries optional fileName/filePath, path alias reading moves to shared helpers, and the blocks fall back to the raw args JSON when no known content key is present. Assisted by: Delta (zai-org/GLM-5.3) * fix(ui): keep an empty write_file body instead of the raw args A call carrying {"content":""} fell through to the raw-args fallback, so a finished write of an empty file showed the args JSON. The parser now leaves content undefined when the key is missing and the block tests !== undefined, so an empty write renders as an empty code block with its byte count. Assisted by: DeepSeek-V4.1-Flash (deepseek-ai)


Overview
Tool call blocks stay folded while the model writes the call and while it runs; they only open on their own with
Always show tool call content, which then leaves them open forever.Adds Show tool call in progress (Settings > Display, on by default): a tool call is expanded by default while its arguments stream and while it executes, then folds back on its own once its result is in — the tool-call counterpart of
Show thought in progress. A block you open or close by hand keeps your choice.Because in-flight calls are now on screen by default, they have to render while in flight. The rest of the PR closes the cases where they didn't: a running
exec_shell_commandfolded itself the moment it started, its command appeared only once the args JSON was complete, andwrite_file/edit_fileshowed an empty block until a path had streamed.Details
showToolCallInProgresscheckbox in Settings > Display (defaulttrue), listed aboveAlways show tool call content; that one's help text now spells out the split — in-progress covers the streaming/executing phases,always showkeeps finished calls expanded and wins on its own.isSectionExpandedByDefault()in$lib/utilsso the rules are testable without DOM. A manual toggle always overrides the default, both ways. Existing installs inherit the default via the config merge, no migration.exec_shell_commandcreates an empty tool result before reading output, so its section already readsTOOL_CALLwhile live. A section whosetoolCallIdmatches the executing call therefore stays expanded regardless of type, and releases when the loop reports the call done.x-tool-cwd(newconversationCwdprop).write_file/edit_fileno longer bail out when the args carry no path — it's display-only, so content/edits render withfilePath/fileNameundefined (now optional), falling back to pretty-printed raw args JSON, then "Receiving arguments..." while nothing has arrived, then "No content" / "No edits". Path-alias and basename handling collapses into sharedextractToolArgPath()/fileNameFromPath().Notes
tools/uionly — no server, API, or storage changes. Only default expansion is affected; nothing per-block is persisted, and collapsed blocks still skip parsing the args/result blobs.Requirements
llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)1,Delta (zai-org/GLM-5.3-Flash)Footnotes
https://huggingface.co/datasets/allozaur/llama-ui-traces/blob/main/2026-10-16_17-14-02_conv_add1290b_create_new_branch_in.jsonl ↩