diff --git a/Cargo.lock b/Cargo.lock index aa9df5b7ea6ebfed5fbf6eef8e7ac095b27fbe8a..641489952f680cb54be23718a10c9be9d7bc51b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,7 @@ dependencies = [ "serde_json", "serde_json_lenient", "settings", + "shlex", "smol", "streaming_diff", "task", diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml index 47d9f6d6a27a2ad5102e831094912208e66a9b43..028db95c10a8c7a319bb05927dcabd0564a14683 100644 --- a/crates/agent_ui/Cargo.toml +++ b/crates/agent_ui/Cargo.toml @@ -80,6 +80,7 @@ serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true settings.workspace = true +shlex.workspace = true smol.workspace = true streaming_diff.workspace = true task.workspace = true diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index cf5284f643cfe3d58ff62a4fa549a84f0a62db69..dd86de920d58f576741f5d0a709eb329352535dc 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -9,7 +9,7 @@ use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting}; use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Context as _, Result, anyhow, bail}; use arrayvec::ArrayVec; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; @@ -1582,6 +1582,19 @@ impl AcpThreadView { window.spawn(cx, async move |cx| { let mut task = login.clone(); + task.command = task + .command + .map(|command| anyhow::Ok(shlex::try_quote(&command)?.to_string())) + .transpose()?; + task.args = task + .args + .iter() + .map(|arg| { + Ok(shlex::try_quote(arg) + .context("Failed to quote argument")? + .to_string()) + }) + .collect::>>()?; task.full_label = task.label.clone(); task.id = task::TaskId(format!("external-agent-{}-login", task.label)); task.command_label = task.label.clone(); @@ -1591,7 +1604,7 @@ impl AcpThreadView { task.shell = shell; let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(&login, window, cx) + terminal_panel.spawn_task(&task, window, cx) })?; let terminal = terminal.await?; @@ -5669,23 +5682,6 @@ pub(crate) mod tests { }); } - #[gpui::test] - async fn test_spawn_external_agent_login_handles_spaces(cx: &mut TestAppContext) { - init_test(cx); - - // Verify paths with spaces aren't pre-quoted - let path_with_spaces = "/Users/test/Library/Application Support/Zed/cli.js"; - let login_task = task::SpawnInTerminal { - command: Some("node".to_string()), - args: vec![path_with_spaces.to_string(), "/login".to_string()], - ..Default::default() - }; - - // Args should be passed as-is, not pre-quoted - assert!(!login_task.args[0].starts_with('"')); - assert!(!login_task.args[0].starts_with('\'')); - } - #[gpui::test] async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) { init_test(cx);