Skip to content

feat(tui): add bang shell command shortcut#2557

Closed
reidliu41 wants to merge 1 commit into
Hmbown:mainfrom
reidliu41:feat/bang-shell-prefix-new
Closed

feat(tui): add bang shell command shortcut#2557
reidliu41 wants to merge 1 commit into
Hmbown:mainfrom
reidliu41:feat/bang-shell-prefix-new

Conversation

@reidliu41

@reidliu41 reidliu41 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

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_shell execution 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 do
not correspond to a model-issued tool call.

image

Previous pr #1661.

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features
  • cargo test --workspace --all-features

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes

Greptile Summary

This PR adds a ! <command> shortcut in the TUI composer that routes user-submitted shell commands through the existing exec_shell execution path, preserving approval flow, sandbox policy, and transcript rendering without appending results to the model-visible message history.

  • Adds Op::RunShellCommand and handle_run_shell_command in the engine, which emits the standard TurnStarted / ToolCallStarted / ToolCallComplete / TurnComplete event sequence and blocks execution in Plan mode.
  • Introduces is_model_visible_tool_call to gate api_messages writes on whether the tool-call ID is model-issued, and handle_bang_shell_input in the UI layer to parse and dispatch the new op.
  • Test coverage is solid: three async engine integration tests (approval, YOLO skip, Plan-mode block) plus UI-layer unit tests for parsing and dispatch.

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

Filename Overview
crates/tui/src/core/engine.rs Adds handle_run_shell_command method (~240 lines) that routes composer ! commands through exec_shell, approval, snapshot, and event system; missing source field in tool_input causes wrong ExecCell attribution in the transcript
crates/tui/src/core/ops.rs Adds RunShellCommand variant and USER_SHELL_TOOL_ID_PREFIX constant; clean and straightforward
crates/tui/src/tui/app.rs Adds shell_command_from_bang_input parser; bang-shell inputs fall through to input_history and composer_history since looks_like_slash_command_input returns false for ! prefixed lines
crates/tui/src/tui/ui.rs Adds handle_bang_shell_input dispatch, is_model_visible_tool_call guard, and skips api_messages append for user-shell IDs; Ctrl+Enter path does not route through handle_bang_shell_input
crates/tui/src/core/engine/tests.rs Adds three async integration tests covering approval, auto-approve/YOLO, and Plan-mode block — good coverage of the new code path
crates/tui/src/tui/ui/tests.rs Adds unit tests for bang-shell dispatch, empty-command rejection, and model-visibility predicate

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 updated
Loading

Comments Outside Diff (1)

  1. crates/tui/src/tui/ui.rs, line 3540-3582 (link)

    P2 Ctrl+Enter bypasses bang-shell routing

    The Ctrl+Enter branch calls submit_input() and checks only looks_like_slash_command_input, so ! ls entered with Ctrl+Enter is steered into the current model turn as literal text rather than dispatched as a shell command. A user who types ! cmd and presses Ctrl+Enter while a turn is in progress (expecting to queue a shell command) will instead inject that text into the model conversation.

    Fix in Codex Fix in Claude Code Fix in Cursor

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "feat(tui): add bang shell command shortc..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

  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
@gemini-code-assist

Copy link
Copy Markdown
Contributor

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 });

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.

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

Suggested change
let tool_input = json!({ "command": command });
let tool_input = json!({ "command": command, "source": "user" });

Fix in Codex Fix in Claude Code Fix in Cursor

@Hmbown

Hmbown commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Thanks @reidliu41, this is a useful composer affordance and it lines up with #1546. I harvested it into #2504 as c81cdabc0 after upstream CI was green.

While resolving it into the release branch I fixed the two review issues locally:

  • user-started shell tool input now carries source: "user", so transcript attribution and shell-control hints are correct
  • ! <command> is routed through the bang-shell path before Ctrl/Shift+Enter steering can send it as model text

Local verification on the release branch:

  • for filter in bang_shell run_shell_command_op local_bang_shell_tool_ids_are_not_model_visible; do cargo test -p codewhale-tui --all-features --locked "$filter" -- --nocapture || exit 1; done
  • cargo check -p codewhale-tui --all-features --locked
  • cargo fmt --all -- --check
  • git diff --check
  • ./scripts/release/check-versions.sh
  • cargo clippy -p codewhale-tui --all-targets --all-features --locked -- -D warnings

@reidliu41

Copy link
Copy Markdown
Contributor Author

thanks

@reidliu41 reidliu41 closed this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ! shell command prefix in the TUI composer

2 participants