From 5f9107d0fab593c5aea95321a3ef32fac119ceb1 Mon Sep 17 00:00:00 2001 From: Felix Ding Date: Mon, 6 Oct 2025 10:31:25 -0700 Subject: [PATCH 1/4] chore(mcp): upgrades rmcp (#3109) --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 503fb48ea5..0dba427eda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5364,9 +5364,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534fd1cd0601e798ac30545ff2b7f4a62c6f14edd4aaed1cc5eb1e85f69f09af" +checksum = "583d060e99feb3a3683fb48a1e4bf5f8d4a50951f429726f330ee5ff548837f8" dependencies = [ "base64 0.22.1", "chrono", @@ -5393,9 +5393,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba777eb0e5f53a757e36f0e287441da0ab766564ba7201600eeb92a4753022e" +checksum = "421d8b0ba302f479214889486f9550e63feca3af310f1190efcf6e2016802693" dependencies = [ "darling 0.21.3", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 2d80bc05f4..1739aa74e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,7 @@ winreg = "0.55.0" schemars = "1.0.4" jsonschema = "0.30.0" zip = "2.2.0" -rmcp = { version = "0.7.0", features = ["client", "transport-sse-client-reqwest", "reqwest", "transport-streamable-http-client-reqwest", "transport-child-process", "tower", "auth"] } +rmcp = { version = "0.8.0", features = ["client", "transport-sse-client-reqwest", "reqwest", "transport-streamable-http-client-reqwest", "transport-child-process", "tower", "auth"] } [workspace.lints.rust] future_incompatible = "warn" From 38622d752e00b5a12be9ec2612c9ce6f8fac1e18 Mon Sep 17 00:00:00 2001 From: Felix Ding Date: Mon, 6 Oct 2025 10:32:04 -0700 Subject: [PATCH 2/4] fix(delegate): adds envs to delegate task subcommand (#3094) --- crates/chat-cli/src/cli/chat/tools/delegate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/chat-cli/src/cli/chat/tools/delegate.rs b/crates/chat-cli/src/cli/chat/tools/delegate.rs index 991b999452..f0f8005ad4 100644 --- a/crates/chat-cli/src/cli/chat/tools/delegate.rs +++ b/crates/chat-cli/src/cli/chat/tools/delegate.rs @@ -326,12 +326,17 @@ pub async fn spawn_agent_process(os: &Os, agent: &str, task: &str) -> Result Date: Mon, 6 Oct 2025 20:00:01 +0100 Subject: [PATCH 3/4] =?UTF-8?q?fix(agent):=20Parse=20EDITOR=20environment?= =?UTF-8?q?=20variable=20to=20agent=20edit/create=20co=E2=80=A6=20(#3104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(agent): Parse EDITOR environment variable to agent edit/create commands Fix bug where 'q agent edit', '/agent edit', 'q agent create' and '/agent create' commands failed when EDITOR contained arguments (e.g., 'emacsclient -nw'). The commands were passing the entire string to Command::new() which expects only the executable name. Now using shlex::split() to properly parse the command and arguments, matching the implementation in the /editor chat command. 🤖 Assisted by Amazon Q Developer * Fixed formatting issues --- .../src/cli/agent/root_command_args.rs | 15 +------ crates/chat-cli/src/cli/chat/cli/profile.rs | 17 ++------ crates/chat-cli/src/util/editor.rs | 39 +++++++++++++++++++ crates/chat-cli/src/util/mod.rs | 1 + 4 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 crates/chat-cli/src/util/editor.rs diff --git a/crates/chat-cli/src/cli/agent/root_command_args.rs b/crates/chat-cli/src/cli/agent/root_command_args.rs index 0f02028e50..20d4e62803 100644 --- a/crates/chat-cli/src/cli/agent/root_command_args.rs +++ b/crates/chat-cli/src/cli/agent/root_command_args.rs @@ -116,13 +116,8 @@ impl AgentArgs { Some(AgentSubcommands::Create { name, directory, from }) => { let mut agents = Agents::load(os, None, true, &mut stderr, mcp_enabled).await.0; let path_with_file_name = create_agent(os, &mut agents, name.clone(), directory, from).await?; - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); - let mut cmd = std::process::Command::new(editor_cmd); - let status = cmd.arg(&path_with_file_name).status()?; - if !status.success() { - bail!("Editor process did not exit with success"); - } + crate::util::editor::launch_editor(&path_with_file_name)?; let Ok(content) = os.fs.read(&path_with_file_name).await else { bail!( @@ -148,13 +143,7 @@ impl AgentArgs { let _agents = Agents::load(os, None, true, &mut stderr, mcp_enabled).await.0; let (_agent, path_with_file_name) = Agent::get_agent_by_name(os, &name).await?; - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); - let mut cmd = std::process::Command::new(editor_cmd); - - let status = cmd.arg(&path_with_file_name).status()?; - if !status.success() { - bail!("Editor process did not exit with success"); - } + crate::util::editor::launch_editor(&path_with_file_name)?; let Ok(content) = os.fs.read(&path_with_file_name).await else { bail!( diff --git a/crates/chat-cli/src/cli/chat/cli/profile.rs b/crates/chat-cli/src/cli/chat/cli/profile.rs index 83a7b634cd..90c5679713 100644 --- a/crates/chat-cli/src/cli/chat/cli/profile.rs +++ b/crates/chat-cli/src/cli/chat/cli/profile.rs @@ -195,13 +195,9 @@ impl AgentSubcommand { let path_with_file_name = create_agent(os, &mut agents, name.clone(), directory, from) .await .map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?; - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); - let mut cmd = std::process::Command::new(editor_cmd); - let status = cmd.arg(&path_with_file_name).status()?; - if !status.success() { - return Err(ChatError::Custom("Editor process did not exit with success".into())); - } + crate::util::editor::launch_editor(&path_with_file_name) + .map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?; let new_agent = Agent::load( os, @@ -253,13 +249,8 @@ impl AgentSubcommand { .await .map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?; - let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); - let mut cmd = std::process::Command::new(editor_cmd); - - let status = cmd.arg(&path_with_file_name).status()?; - if !status.success() { - return Err(ChatError::Custom("Editor process did not exit with success".into())); - } + crate::util::editor::launch_editor(&path_with_file_name) + .map_err(|e| ChatError::Custom(Cow::Owned(e.to_string())))?; let updated_agent = Agent::load( os, diff --git a/crates/chat-cli/src/util/editor.rs b/crates/chat-cli/src/util/editor.rs new file mode 100644 index 0000000000..a7aa9baa6e --- /dev/null +++ b/crates/chat-cli/src/util/editor.rs @@ -0,0 +1,39 @@ +use std::path::Path; +use std::process::Command; + +/// Launch the user's preferred editor with the given file path. +/// +/// This function properly parses the EDITOR environment variable to handle +/// editors that require arguments (e.g., "emacsclient -nw"). +/// +/// # Arguments +/// * `file_path` - Path to the file to open in the editor +/// +/// # Returns +/// * `Ok(())` if the editor was launched successfully and exited with success +/// * `Err` if the editor failed to launch or exited with an error +pub fn launch_editor(file_path: &Path) -> eyre::Result<()> { + let editor_cmd = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); + + // Parse the editor command to handle arguments + let mut parts = shlex::split(&editor_cmd).ok_or_else(|| eyre::eyre!("Failed to parse EDITOR command"))?; + + if parts.is_empty() { + eyre::bail!("EDITOR environment variable is empty"); + } + + let editor_bin = parts.remove(0); + + let mut cmd = Command::new(editor_bin); + for arg in parts { + cmd.arg(arg); + } + + let status = cmd.arg(file_path).status()?; + + if !status.success() { + eyre::bail!("Editor process did not exit with success"); + } + + Ok(()) +} diff --git a/crates/chat-cli/src/util/mod.rs b/crates/chat-cli/src/util/mod.rs index 48d8c94c97..b0b9333143 100644 --- a/crates/chat-cli/src/util/mod.rs +++ b/crates/chat-cli/src/util/mod.rs @@ -1,5 +1,6 @@ pub mod consts; pub mod directories; +pub mod editor; pub mod knowledge_store; pub mod open; pub mod pattern_matching; From 71ba5f17e0cbdef5e4176cfc0403f88ae41494a2 Mon Sep 17 00:00:00 2001 From: nirajchowdhary <226941436+nirajchowdhary@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:33:58 -0700 Subject: [PATCH 4/4] fix: make q settings delete functionality user friendly (#3042) --- crates/chat-cli/src/cli/settings.rs | 47 +++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/crates/chat-cli/src/cli/settings.rs b/crates/chat-cli/src/cli/settings.rs index 0993fb5dc3..2360668f3d 100644 --- a/crates/chat-cli/src/cli/settings.rs +++ b/crates/chat-cli/src/cli/settings.rs @@ -6,6 +6,7 @@ use clap::{ Args, Subcommand, }; +use crossterm::style::Stylize; use eyre::{ Result, WrapErr, @@ -37,7 +38,7 @@ pub enum SettingsSubcommands { #[derive(Clone, Debug, Args, PartialEq, Eq)] #[command(subcommand_negates_reqs = true)] #[command(args_conflicts_with_subcommands = true)] -#[command(group(ArgGroup::new("vals").requires("key").args(&["value", "delete", "format"])))] +#[command(group(ArgGroup::new("vals").requires("key").args(&["value", "format"])))] pub struct SettingsArgs { #[command(subcommand)] cmd: Option, @@ -45,7 +46,7 @@ pub struct SettingsArgs { key: Option, /// value value: Option, - /// Delete a value + /// Delete a key (No value needed) #[arg(long, short)] delete: bool, /// Format of the output @@ -87,11 +88,26 @@ impl SettingsArgs { }, None => { let Some(key) = &self.key else { + if self.delete { + return Err(eyre::eyre!( + "the argument {} requires a {}\n Usage: q settings {} {}", + "'--delete'".yellow(), + "".green(), + "--delete".yellow(), + "".green() + )); + } return Ok(ExitCode::SUCCESS); }; let key = Setting::try_from(key.as_str())?; match (&self.value, self.delete) { + (Some(_), true) => Err(eyre::eyre!( + "the argument {} cannot be used with {}\n Usage: q settings {} {key}", + "'--delete'".yellow(), + "'[VALUE]'".yellow(), + "--delete".yellow() + )), (None, false) => match os.database.settings.get(key) { Some(value) => { match self.format { @@ -147,9 +163,34 @@ impl SettingsArgs { Ok(ExitCode::SUCCESS) }, - _ => Ok(ExitCode::SUCCESS), } }, } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_delete_with_value_error() { + let mut os = Os::new().await.unwrap(); + + let settings_args = SettingsArgs { + cmd: None, + key: Some("chat.defaultAgent".to_string()), + value: Some("test_value".to_string()), + delete: true, + format: OutputFormat::Plain, + }; + + let result = settings_args.execute(&mut os).await; + + assert!(result.is_err()); + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("the argument")); + assert!(error_msg.contains("--delete")); + assert!(error_msg.contains("Usage:")); + } +}