From 323d36f9fa36c8ebfb6c536fd4d9b8918adbe458 Mon Sep 17 00:00:00 2001 From: quangdang46 Date: Mon, 25 May 2026 00:44:56 +0700 Subject: [PATCH] feat(terminal): configurable shell for bash tool (port upstream PR #260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port of upstream PR 1jehuang/jcode#260 (open). Lets users on nu / zsh / fish / pwsh have agent-spawned bash tool calls run through their preferred shell instead of the platform default. Config: [terminal] shell = 'nu' # or 'zsh', 'fish', 'pwsh', '/bin/dash' Env override: JCODE_SHELL=zsh Behavior: - shell unset → platform default (bash on Unix, cmd.exe on Windows) preserves existing behavior - shell set → spawn the named binary with '-c ' - shell name resolved via PATH; absolute paths supported Caveats (intentionally same as upstream): - Most shells accept '-c'; nu, zsh, bash, sh, fish (in some modes), pwsh (with '-Command' which we approximate via -c) - PowerShell users wanting full pwsh syntax should still wrap commands in 'pwsh -Command "..."' inside the agent prompt. Refs upstream PR 1jehuang/jcode#260. --- crates/jcode-config-types/src/lib.rs | 14 ++++++++++++++ src/config.rs | 7 ++++++- src/config/env_overrides.rs | 9 +++++++++ src/tool/bash.rs | 27 +++++++++++++++++++++++---- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/crates/jcode-config-types/src/lib.rs b/crates/jcode-config-types/src/lib.rs index c14262c64..efaa8c6ab 100644 --- a/crates/jcode-config-types/src/lib.rs +++ b/crates/jcode-config-types/src/lib.rs @@ -892,3 +892,17 @@ impl Default for GatewayConfig { } } } + +/// Terminal / shell execution configuration (issue #260 follow-up). +/// +/// When `shell` is set, the bash tool spawns the named shell instead +/// of the platform default (`bash` on Unix, `cmd.exe` on Windows). +/// Useful for users on `nu`, `zsh`, `fish`, or PowerShell who want +/// shell-specific syntax to work in agent-spawned commands. +/// +/// Overridden by the `JCODE_SHELL` env var. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct TerminalConfig { + pub shell: Option, +} diff --git a/src/config.rs b/src/config.rs index 9f5dcb034..8c5912e95 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,8 @@ pub use jcode_config_types::{ DiffDisplayMode, DisplayConfig, FeatureConfig, GatewayConfig, KeybindingsConfig, MarkdownSpacingMode, NamedProviderAuth, NamedProviderConfig, NamedProviderModelConfig, NamedProviderType, NativeScrollbarConfig, ProviderConfig, SafetyConfig, - SessionPickerResumeAction, SwarmSpawnMode, UpdateChannel, WebSearchConfig, WebSearchEngine, + SessionPickerResumeAction, SwarmSpawnMode, TerminalConfig, UpdateChannel, WebSearchConfig, + WebSearchEngine, }; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, HashSet}; @@ -104,6 +105,7 @@ const CONFIG_ENV_KEYS: &[&str] = &[ "JCODE_SCROLL_PROMPT_UP_KEY", "JCODE_SCROLL_UP_FALLBACK_KEY", "JCODE_SCROLL_UP_KEY", + "JCODE_SHELL", "JCODE_SHOW_DIFFS", "JCODE_SHOW_THINKING", "JCODE_SIDE_PANEL_NATIVE_SCROLLBAR", @@ -383,6 +385,9 @@ pub struct Config { /// Auto-judge configuration pub autojudge: AutoJudgeConfig, + + /// Terminal / shell execution configuration (issue #260) + pub terminal: TerminalConfig, } /// Agent Client Protocol adapter configuration. diff --git a/src/config/env_overrides.rs b/src/config/env_overrides.rs index f3dd4a913..9e7a52645 100644 --- a/src/config/env_overrides.rs +++ b/src/config/env_overrides.rs @@ -441,6 +441,15 @@ impl Config { } } + // Terminal (issue #260 follow-up): JCODE_SHELL overrides + // the bash tool's default shell selection. + if let Ok(v) = std::env::var("JCODE_SHELL") { + let trimmed = v.trim().to_string(); + if !trimmed.is_empty() { + self.terminal.shell = Some(trimmed); + } + } + // Provider if let Ok(v) = std::env::var("JCODE_MODEL") { self.provider.default_model = Some(v); diff --git a/src/tool/bash.rs b/src/tool/bash.rs index bf1e61832..8c2e3341d 100644 --- a/src/tool/bash.rs +++ b/src/tool/bash.rs @@ -428,7 +428,23 @@ async fn handle_background_output_line( file.flush().await.ok(); } -fn build_shell_command(cmd_str: &str) -> TokioCommand { +fn build_shell_command(shell: Option<&str>, cmd_str: &str) -> TokioCommand { + // Issue #260 follow-up: when the user configured a custom shell + // via `[terminal] shell = "..."` or JCODE_SHELL, just spawn that + // shell directly with the command string. The shell handles its + // own arg parsing (most shells accept `-c` for inline commands; + // PowerShell uses `-Command`; nu uses `-c`). Falls back to the + // platform default when shell is None. + if let Some(shell_name) = shell { + let mut cmd = TokioCommand::new(shell_name); + // Use `-c` as a reasonable default. Shells that don't accept + // it (e.g. fish-only) should be configured at the script + // level. PowerShell users should set shell = "pwsh" and the + // tool's input will be POSIX-shell-like; for full PowerShell + // syntax, wrap in `pwsh -Command` at the input level. + cmd.arg("-c").arg(cmd_str); + return cmd; + } #[cfg(windows)] { let mut cmd = TokioCommand::new("cmd.exe"); @@ -488,7 +504,7 @@ mod utf8_truncation_tests { #[cfg(windows)] #[tokio::test] async fn build_shell_command_uses_cmd_and_executes_command() { - let output = build_shell_command("echo hello-from-cmd") + let output = build_shell_command(None, "echo hello-from-cmd") .output() .await .expect("run cmd command"); @@ -627,7 +643,10 @@ impl BashTool { let has_stdin_channel = ctx.stdin_request_tx.is_some(); - let mut command = build_shell_command(¶ms.command); + let mut command = build_shell_command( + crate::config::config().terminal.shell.as_deref(), + ¶ms.command, + ); command .kill_on_drop(true) .stdout(Stdio::piped()) @@ -900,7 +919,7 @@ impl BashTool { notify, wake, move |output_path| async move { - let mut cmd = build_shell_command(&command); + let mut cmd = build_shell_command(crate::config::config().terminal.shell.as_deref(), &command); #[cfg(unix)] unsafe { cmd.pre_exec(|| {