Skip to content

ui: Add "Show tool call in progress" setting - #30681

Merged
ggerganov merged 6 commits into
masterfrom
allozaur/feat/settings-show-tool-call-in-progress
Oct 22, 2026
Merged

ggerganov merged 6 commits into
masterfrom
allozaur/feat/settings-show-tool-call-in-progress

Conversation

@allozaur

@allozaur allozaur commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

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_command folded itself the moment it started, its command appeared only once the args JSON was complete, and write_file / edit_file showed an empty block until a path had streamed.

Details

  • New showToolCallInProgress checkbox in Settings > Display (default true), listed above Always show tool call content; that one's help text now spells out the split — in-progress covers the streaming/executing phases, always show keeps finished calls expanded and wins on its own.
  • Default expansion moves into a pure isSectionExpandedByDefault() in $lib/utils so 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_command creates an empty tool result before reading output, so its section already reads TOOL_CALL while live. A section whose toolCallId matches the executing call therefore stays expanded regardless of type, and releases when the loop reports the call done.
  • That command now streams into the terminal title from the first character (args parsed partially), with a blinking cursor until the JSON parses; then "Executing command..." (was "Running...") holds until the first output chunk so the block never goes blank. The prompt shows the conversation cwd until the result message carries the real x-tool-cwd (new conversationCwd prop).
  • write_file / edit_file no longer bail out when the args carry no path — it's display-only, so content/edits render with filePath / fileName undefined (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 shared extractToolArgPath() / fileNameFromPath().
  • New unit + client tests for the setting, the expansion truth table, the streaming titles, and the path-less bodies; existing parser tests updated.

Notes

tools/ui only — 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

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES, llama-ui (ggml-org/Qwen3.8-Flash-Next:Q8_0)1, Delta (zai-org/GLM-5.3-Flash)

Footnotes

  1. https://huggingface.co/datasets/allozaur/llama-ui-traces/blob/main/2026-10-16_17-14-02_conv_add1290b_create_new_branch_in.jsonl ↩

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)
@allozaur
allozaur marked this pull request as ready for review October 16, 2026 18:32
@allozaur
allozaur requested a review from a team as a code owner October 16, 2026 18:32
Copilot AI balanced review requested due to automatic review settings October 16, 2026 18:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

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)
@allozaur
allozaur requested a balanced review from Copilot October 19, 2026 07:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
2 resolved since last review

Review effort: Lite

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

Triaging the Copilot pass:

  • :has() browser support: fine, it is Baseline since Dec 2023 and the UI already uses it (markdown-content.css, table cells).
  • JSON.parse in commandStreaming: fine, it only runs while the model is typing the call, on a few hundred bytes.
  • "No edits" unreachable in edit_file: don't care, showing the raw args of a malformed edit is more useful anyway.
  • Empty write_file content: the only real one, cosmetic. If you feel like it, keep content undefined in the parser when the key is missing and test !== undefined in the template, so an intentionally empty file renders as an empty block with "0 bytes". Here or in a follow-up, your call.

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)
@allozaur allozaur added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Oct 21, 2026
@ggerganov
ggerganov merged commit 84746c5 into master Oct 22, 2026
6 checks passed
jiangchuanso added a commit to jiangchuanso/llama.cpp-zh-el8 that referenced this pull request Oct 22, 2026
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
edwardyoon pushed a commit to edwardyoon/focus-llama that referenced this pull request Oct 24, 2026
* 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)
@allozaur
allozaur deleted the allozaur/feat/settings-show-tool-call-in-progress branch October 25, 2026 08:45
auRose94 pushed a commit to auRose94/llama.cpp that referenced this pull request Oct 28, 2026
* 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. server/ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants