From 52c1e0021c3e8ed2c46a23f3b8067a6485a4f471 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 27 Mar 2025 14:27:18 -0400 Subject: [PATCH] Allow Bash tool to Just Work with more `cd` inputs (#27501) Release Notes: - N/A --- crates/assistant_tools/src/bash_tool.rs | 43 ++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/assistant_tools/src/bash_tool.rs b/crates/assistant_tools/src/bash_tool.rs index 0e9a3a1cb4..6853e07b29 100644 --- a/crates/assistant_tools/src/bash_tool.rs +++ b/crates/assistant_tools/src/bash_tool.rs @@ -5,6 +5,7 @@ use language_model::LanguageModelRequestMessage; use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::path::Path; use std::sync::Arc; use ui::IconName; use util::command::new_smol_command; @@ -68,10 +69,44 @@ impl Tool for BashTool { Err(err) => return Task::ready(Err(anyhow!(err))), }; - let Some(worktree) = project.read(cx).worktree_for_root_name(&input.cd, cx) else { - return Task::ready(Err(anyhow!("Working directory not found in the project"))); + let project = project.read(cx); + let input_path = Path::new(&input.cd); + let working_dir = if input.cd == "." { + // Accept "." as meaning "the one worktree" if we only have one worktree. + let mut worktrees = project.worktrees(cx); + + let only_worktree = match worktrees.next() { + Some(worktree) => worktree, + None => return Task::ready(Err(anyhow!("No worktrees found in the project"))), + }; + + if worktrees.next().is_some() { + return Task::ready(Err(anyhow!("'.' is ambiguous in multi-root workspaces. Please specify a root directory explicitly."))); + } + + only_worktree.read(cx).abs_path() + } else if input_path.is_absolute() { + // Absolute paths are allowed, but only if they're in one of the project's worktrees. + if !project + .worktrees(cx) + .any(|worktree| input_path.starts_with(&worktree.read(cx).abs_path())) + { + return Task::ready(Err(anyhow!( + "The absolute path must be within one of the project's worktrees" + ))); + } + + input_path.into() + } else { + let Some(worktree) = project.worktree_for_root_name(&input.cd, cx) else { + return Task::ready(Err(anyhow!( + "`cd` directory {} not found in the project", + &input.cd + ))); + }; + + worktree.read(cx).abs_path() }; - let working_directory = worktree.read(cx).abs_path(); cx.spawn(async move |_| { // Add 2>&1 to merge stderr into stdout for proper interleaving. @@ -80,7 +115,7 @@ impl Tool for BashTool { let output = new_smol_command("bash") .arg("-c") .arg(&command) - .current_dir(working_directory) + .current_dir(working_dir) .output() .await .context("Failed to execute bash command")?;