feat(shell_dispatcher): isolate shell dispatcher layer#2290
feat(shell_dispatcher): isolate shell dispatcher layer#2290aboimpinto wants to merge 10 commits into
Conversation
Introduces a central shell abstraction that replaces hardcoded
Command::new("cmd") / Command::new("sh") across the execution path.
- Shell detection at startup (pwsh -> powershell -> cmd -> sh)
- Correct quoting per shell (PowerShell uses -NoProfile -Command)
- Scope guards restore crossterm raw mode on all spawn paths (Hmbown#1690)
- Direct program+args builder for sandbox ExecEnv integration
Files:
- crates/tui/src/shell_dispatcher.rs (new, 12 tests)
- crates/tui/src/main.rs (register module)
- crates/tui/src/eval.rs (exec_shell delegates to dispatcher)
- crates/tui/src/sandbox/mod.rs (CommandSpec::shell uses dispatcher)
- crates/tui/src/tools/shell.rs (raw mode guards on all spawn paths)
Closes Hmbown#1690
Refs Hmbown#1779
… logging - find_exe(): fall back to known install dirs when PATH lookup fails (C:\Program Files\PowerShell\7, System32\WindowsPowerShell\v1.0) - Encoding prefix: use idiomatic [Console]::OutputEncoding for PowerShell instead of cmd.exe chcp 65001 >NUL - Execution logging: write exec via <ShellKind> entries to SHELL_DISPATCHER_LOG when set - System prompt: use ShellDispatcher detection instead of raw $SHELL so model knows it has PowerShell and generates native cmdlets - display_command(): strip PowerShell encoding prefix for display - Add tests for find_exe PATH + known-dir fallback Refs Hmbown#1779
There was a problem hiding this comment.
Code Review
This pull request introduces a new ShellDispatcher module to centralize and abstract shell detection and command execution across Windows and Unix platforms. It replaces scattered, platform-specific shell invocation logic and ensures terminal raw-mode is correctly saved and restored during foreground execution. The review feedback highlights several cross-platform issues: the $SHELL environment variable detection should be restricted to non-Windows platforms to avoid issues with Unix-style paths, Windows-only helper functions should be conditionally compiled to prevent dead code warnings on Unix, and Windows-specific tests should be marked with #[cfg(windows)] to avoid test failures on Unix systems.
|
Addressed the macOS CI failure and the platform-gating review comments in this push:
I also resolved the review threads that this commit directly covers. Paulo Aboim Pinto |
This is the first PR in the split stack for the former #1848 work.
Scope:
This PR intentionally stays narrow and does not include the later plugin/registry security surface called out in #1848. Those follow-up concerns stay in the later stack layers.
Follow-up PRs will add the ExternalTool layer and then the pluggable tool registry on top of this base.
Greptile Summary
This PR introduces a
ShellDispatcherabstraction layer that centralises shell detection and command construction, replacing scatteredCommand::new(\"sh\")/Command::new(\"cmd\")calls acrosseval.rs,sandbox/mod.rs, andtools/shell.rs. Detection at startup picks the right shell (Bash, Pwsh, WindowsPowerShell, Cmd, or a POSIX Custom path) from$SHELLor probe-based fallbacks, and all callers route throughglobal_dispatcher().ShellKindenum with platform-awarebinary(),command_flag(), andneeds_command_flag()helpers;build_command/build_command_partsencapsulate per-shell quoting rules including Windowsraw_argforcmd.exe /C.run_foreground,execute_sync_sandboxed, andexecute_interactive_sandboxedso the TUI terminal state is correctly restored after child processes exit (issue [Windows] Terminal input breaks after exec_shell runs a batch script that spawns long-lived child processes #1690).sandbox/mod.rsnow delegates shell selection to the dispatcher and layers the UTF-8 encoding prefix (chcp 65001/[Console]::OutputEncoding) per shell family instead of unconditionally prepending the cmd-only wrapper.Confidence Score: 5/5
Safe to merge; the dispatcher correctly handles all shell families and the raw-mode guards match the fix already applied to run_foreground.
The core detection, command-building, and raw-mode save/restore logic all look correct. The two findings are a redundant branch in display_command and a cfg-gate inconsistency on is_powershell(), neither of which affects runtime behaviour.
crates/tui/src/sandbox/mod.rs (redundant display_command branch) and crates/tui/src/shell_dispatcher.rs (is_powershell cfg gate).
Important Files Changed
is_powershell()is cfg(test)-only which creates an asymmetry with the non-gatedneeds_command_flag().display_commandhas a redundant first branch for sh/bash that is entirely subsumed by the second branch.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[global_dispatcher] -->|LazyLock, once| B[ShellDispatcher::detect] B --> C{Platform} C -->|Windows| D{$SHELL set?} D -->|contains bash| E[ShellKind::Bash] D -->|contains pwsh| F[ShellKind::Pwsh] D -->|contains powershell| G[ShellKind::WindowsPowerShell] D -->|not set / no match| H{pwsh.exe on PATH?} H -->|yes| F H -->|no| I{powershell.exe on PATH?} I -->|yes| G I -->|no| J[ShellKind::Cmd] C -->|Unix| K{$SHELL set?} K -->|contains bash| E K -->|contains pwsh| F K -->|contains powershell| G K -->|other path| L[ShellKind::Custom] K -->|not set| M[ShellKind::Sh] E & F & G & J & L & M --> N[ShellDispatcher] N -->|build_command| O[std::process::Command] N -->|build_command_parts| P[program + args Vec] N -->|run_foreground| Q[raw-mode guard + output] P --> R[CommandSpec::shell in sandbox] Q --> S[exec_shell in eval.rs]Comments Outside Diff (2)
crates/tui/src/sandbox/mod.rs, line 155-186 (link)display_commandregressed forbashand custom POSIX shellsThe first branch only matches
program == "sh", but after this PR the detected shell on most Linux/macOS machines isShellKind::Bash(programbash) orShellKind::Custom(program/bin/zsh,/usr/bin/fish, etc.). None of those match the"sh"literal, so the else-branch fires and returnsbash -c echo hello(or/bin/zsh -c echo hello) everywhere a bareecho hellowas previously shown. The PowerShell andcmdbranches were extended to cover their new variants, but the POSIX branch was not widened to coverbashor any-c-flag shell.crates/tui/src/sandbox/mod.rs, line 159-190 (link)display_commandstill only matches"sh", breaking bash/custom POSIX shellsThe first branch checks
self.program == "sh"(exact match). After this PR, Unix detection yieldsShellKind::Bash(program"bash") orShellKind::Custom(program/bin/zsh,/usr/bin/fish, etc.) on virtually every Linux/macOS machine where$SHELLis set. Both cases fall through to the else-branch and returnbash -c echo helloinstead ofecho hello.Two tests will fail on any standard CI runner (GitHub Actions Ubuntu/macOS sets
SHELL=/bin/bash):test_command_spec_shellassertsdisplay_command() == "echo hello"but getsbash -c echo hellotest_command_spec_shell_quoted_arg_not_split(line 649) assertsspec.program == "sh"but receives"bash"The POSIX branch needs to be widened to cover
"bash"and any-c-flag shell, just as thecmd/PowerShell branches were already extended for their new variants.Reviews (6): Last reviewed commit: "fix(tui): use raw args for cmd payloads" | Re-trigger Greptile