feat(tui): add bang shell command shortcut#2557
Conversation
Support `! <command>` and `!command` in the TUI composer to run shell commands through the existing exec_shell path. The shortcut keeps normal approval, sandbox, policy, transcript, and work-panel handling, while avoiding model context pollution from local-only tool results. Refs Hmbown#1546
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
| ); | ||
| let tool_id = turn_id.clone(); | ||
| let tool_name = "exec_shell".to_string(); | ||
| let tool_input = json!({ "command": command }); |
There was a problem hiding this comment.
The
tool_input JSON is missing a "source": "user" field. exec_source_from_input in tool_routing.rs reads that field to populate ExecCell::source; without it, it falls through to ExecSource::Assistant. This has two visible consequences: while the command is running, the transcript shows the misleading "Ctrl+B opens shell controls" hint (gated on source == ExecSource::Assistant), and after it succeeds, the "started by you" attribution (gated on source == ExecSource::User) is never rendered.
| let tool_input = json!({ "command": command }); | |
| let tool_input = json!({ "command": command, "source": "user" }); |
|
Thanks @reidliu41, this is a useful composer affordance and it lines up with #1546. I harvested it into #2504 as While resolving it into the release branch I fixed the two review issues locally:
Local verification on the release branch:
|
|
thanks |
Summary
Closes: #1546
Adds composer support for
! <command>and!command, so users can run an explicit shell command directly from the TUI input box instead of sending it as normal model text.The shortcut routes through the existing
exec_shellexecution path, preserving the current approval flow, sandbox policy, command safety checks, transcript tool cards, and Work/Tasks refresh behavior. It also keeps Plan mode shell execution blocked and skips extra approval prompts when running in auto-approved YOLO mode.Local
!shell results are rendered in the TUI but are not appended to model-visible tool-result history, because they donot correspond to a model-issued tool call.
Previous pr #1661.
Testing
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-featurescargo test --workspace --all-featuresChecklist
Greptile Summary
This PR adds a
! <command>shortcut in the TUI composer that routes user-submitted shell commands through the existingexec_shellexecution path, preserving approval flow, sandbox policy, and transcript rendering without appending results to the model-visible message history.Op::RunShellCommandandhandle_run_shell_commandin the engine, which emits the standardTurnStarted / ToolCallStarted / ToolCallComplete / TurnCompleteevent sequence and blocks execution in Plan mode.is_model_visible_tool_callto gateapi_messageswrites on whether the tool-call ID is model-issued, andhandle_bang_shell_inputin the UI layer to parse and dispatch the new op.Confidence Score: 4/5
Safe to merge with one fix: the missing source field causes user-initiated shell cards to render as assistant-sourced, showing a misleading 'Ctrl+B opens shell controls' hint and suppressing the 'started by you' label.
The new code path correctly sequences events, blocks execution in Plan mode, skips model-history writes for user-shell IDs, and carries good test coverage. The one concrete rendering defect — tool_input missing 'source: user' — produces wrong visual attribution in the transcript for every ! command a user runs. The Ctrl+Enter inconsistency is minor. Everything else (approval flow, snapshot, cancel-token handling, API message isolation) is implemented correctly.
crates/tui/src/core/engine.rs — the handle_run_shell_command tool_input construction needs 'source: user' added.
Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant UI as TUI (ui.rs) participant Engine as Engine (engine.rs) participant Shell as exec_shell tool User->>UI: Type "! cargo test" + Enter UI->>UI: shell_command_from_bang_input() UI->>Engine: "Op::RunShellCommand { command, mode, ... }" UI->>UI: "status_message = "Shell command submitted"" Engine->>Engine: handle_run_shell_command() Engine->>UI: "Event::TurnStarted { turn_id: "user_shell_N" }" Engine->>UI: "Event::ToolCallStarted { id: "user_shell_N", name: "exec_shell" }" alt approval required (non-YOLO, non-auto) Engine->>UI: Event::ApprovalRequired UI->>Engine: approve_tool_call() end Engine->>Shell: execute exec_shell Shell-->>Engine: ToolResult Engine->>UI: "Event::ToolCallComplete { id: "user_shell_N" }" UI->>UI: is_model_visible_tool_call("user_shell_N") → false UI->>UI: pending_tool_uses.retain() [remove entry] Note over UI: api_messages NOT updated Engine->>UI: "Event::TurnComplete { status: Completed }" UI->>UI: flush_active_cell() → transcript updatedComments Outside Diff (1)
crates/tui/src/tui/ui.rs, line 3540-3582 (link)The
Ctrl+Enterbranch callssubmit_input()and checks onlylooks_like_slash_command_input, so! lsentered with Ctrl+Enter is steered into the current model turn as literal text rather than dispatched as a shell command. A user who types! cmdand presses Ctrl+Enter while a turn is in progress (expecting to queue a shell command) will instead inject that text into the model conversation.Reviews (1): Last reviewed commit: "feat(tui): add bang shell command shortc..." | Re-trigger Greptile