From fb3c244ad59a016754416ae7f148f6361d357efb Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 15:35:27 -0500 Subject: [PATCH 1/8] working --- crates/goose/src/agents/execute_commands.rs | 56 ++++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index 7220bba50364..ee717ebf5479 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -53,15 +53,15 @@ impl Agent { } let command_str = trimmed.strip_prefix('/').unwrap_or(&trimmed); - let (command, params) = command_str + let (command, params_str) = command_str .split_once(' ') .map(|(cmd, p)| (cmd, p.trim())) .unwrap_or((command_str, "")); - let params: Vec<&str> = if params.is_empty() { + let params: Vec<&str> = if params_str.is_empty() { vec![] } else { - params.split_whitespace().collect() + params_str.split_whitespace().collect() }; let result = match command { @@ -70,7 +70,7 @@ impl Agent { "compact" => self.handle_compact_command(session_id).await, "clear" => self.handle_clear_command(session_id).await, _ => { - self.handle_recipe_command(command, ¶ms, session_id) + self.handle_recipe_command(command, params_str, session_id) .await } }; @@ -264,7 +264,7 @@ impl Agent { async fn handle_recipe_command( &self, command: &str, - params: &[&str], + params_str: &str, _session_id: &str, ) -> Result> { let full_command = format!("/{}", command); @@ -284,12 +284,52 @@ impl Agent { .parent() .ok_or_else(|| anyhow!("Recipe path has no parent directory"))?; - let param_values: Vec = params.iter().map(|s| s.to_string()).collect(); + let recipe_dir_str = recipe_dir.display().to_string(); + let validation_result = + crate::recipe::validate_recipe::validate_recipe_template_from_content( + &recipe_content, + Some(recipe_dir_str), + ) + .map_err(|e| anyhow!("Failed to parse recipe: {}", e))?; + + let param_values: Vec = if params_str.is_empty() { + vec![] + } else { + let params_without_default = validation_result + .parameters + .as_ref() + .map(|params| params.iter().filter(|p| p.default.is_none()).count()) + .unwrap_or(0); + + if params_without_default == 1 { + vec![params_str.to_string()] + } else if params_without_default == 0 { + vec![params_str.to_string()] + } else { + let param_names: Vec = validation_result + .parameters + .as_ref() + .map(|params| { + params + .iter() + .filter(|p| p.default.is_none()) + .map(|p| p.key.clone()) + .collect() + }) + .unwrap_or_default(); + + return Ok(Some(Message::assistant().with_text(format!( + "Recipe has {} required parameters ({}), but slash commands only support single-parameter recipes with spaces. Please use 'goose run --recipe' with --params instead.", + params_without_default, + param_names.join(", ") + )))); + } + }; let recipe = match build_recipe_from_template_with_positional_params( recipe_content, recipe_dir, - param_values, + param_values.clone(), None:: Result>, ) { Ok(recipe) => recipe, @@ -298,7 +338,7 @@ impl Agent { "Recipe requires {} parameter(s): {}. Provided: {}", parameters.len(), parameters.join(", "), - params.len() + param_values.len() )))); } Err(e) => return Err(anyhow!("Failed to build recipe: {}", e)), From 5296f117eee7f43a6540b57ef355e78f485975db Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 15:37:00 -0500 Subject: [PATCH 2/8] update text --- crates/goose/src/agents/execute_commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index ee717ebf5479..ade65be82db4 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -319,7 +319,7 @@ impl Agent { .unwrap_or_default(); return Ok(Some(Message::assistant().with_text(format!( - "Recipe has {} required parameters ({}), but slash commands only support single-parameter recipes with spaces. Please use 'goose run --recipe' with --params instead.", + "Recipe has {} required parameters ({}), but slash commands only support single-parameter recipes with spaces. Please use 'goose run --recipe' with --params instead or launch from the recipes sidebar.", params_without_default, param_names.join(", ") )))); From 77804906a11a57b6110c0d854f7280655aa9a2c3 Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 15:58:14 -0500 Subject: [PATCH 3/8] fix command duping --- crates/goose/src/agents/execute_commands.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index ade65be82db4..feaddb32a97a 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -301,9 +301,7 @@ impl Agent { .map(|params| params.iter().filter(|p| p.default.is_none()).count()) .unwrap_or(0); - if params_without_default == 1 { - vec![params_str.to_string()] - } else if params_without_default == 0 { + if params_without_default <= 1 { vec![params_str.to_string()] } else { let param_names: Vec = validation_result @@ -318,10 +316,13 @@ impl Agent { }) .unwrap_or_default(); - return Ok(Some(Message::assistant().with_text(format!( - "Recipe has {} required parameters ({}), but slash commands only support single-parameter recipes with spaces. Please use 'goose run --recipe' with --params instead or launch from the recipes sidebar.", + return Ok(Some(Message::user().with_text(format!( + "This recipe requires {} parameters ({}). Custom slash commands only support single-parameter recipes with spaces. Please explain to the user that they should use the CLI instead:\n\n'goose run --recipe {} --params {}=\"...\" --params {}=\"...\"'\n\nOr they can launch the recipe from the recipes sidebar in Goose Desktop, which will prompt for each parameter.", params_without_default, - param_names.join(", ") + param_names.join(", "), + command, + param_names.get(0).unwrap_or(&"param1".to_string()), + param_names.get(1).unwrap_or(&"param2".to_string()) )))); } }; From 157a5b461c7c0e882adef513fce683bb71060781 Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 16:33:05 -0500 Subject: [PATCH 4/8] more structured error handling --- crates/goose/src/agents/agent.rs | 15 +++++++-- crates/goose/src/agents/execute_commands.rs | 37 ++++++++++++--------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 74a4942a1a69..3d2aa822e321 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -793,7 +793,16 @@ impl Agent { .await; match command_result { - Some(response) if response.role == rmcp::model::Role::Assistant => { + Err(e) => { + // Return error directly without adding to conversation + let error_message = Message::assistant() + .with_text(&e.to_string()) + .with_visibility(true, false); + return Ok(Box::pin(stream::once(async move { + Ok(AgentEvent::Message(error_message)) + }))); + } + Ok(Some(response)) if response.role == rmcp::model::Role::Assistant => { SessionManager::add_message( &session_config.id, &user_message.clone().with_visibility(true, false), @@ -826,7 +835,7 @@ impl Agent { } })); } - Some(resolved_message) => { + Ok(Some(resolved_message)) => { SessionManager::add_message( &session_config.id, &user_message.clone().with_visibility(true, false), @@ -838,7 +847,7 @@ impl Agent { ) .await?; } - None => { + Ok(None) => { SessionManager::add_message(&session_config.id, &user_message).await?; } } diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index feaddb32a97a..0e3ffa0212ea 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -41,7 +41,7 @@ pub fn list_commands() -> &'static [CommandDef] { } impl Agent { - pub async fn execute_command(&self, message_text: &str, session_id: &str) -> Option { + pub async fn execute_command(&self, message_text: &str, session_id: &str) -> Result> { let mut trimmed = message_text.trim().to_string(); if COMPACT_TRIGGERS.contains(&trimmed.as_str()) { @@ -49,7 +49,7 @@ impl Agent { } if !trimmed.starts_with('/') { - return None; + return Ok(None); } let command_str = trimmed.strip_prefix('/').unwrap_or(&trimmed); @@ -64,7 +64,7 @@ impl Agent { params_str.split_whitespace().collect() }; - let result = match command { + match command { "prompts" => self.handle_prompts_command(¶ms, session_id).await, "prompt" => self.handle_prompt_command(¶ms, session_id).await, "compact" => self.handle_compact_command(session_id).await, @@ -73,13 +73,6 @@ impl Agent { self.handle_recipe_command(command, params_str, session_id) .await } - }; - - match result { - Ok(msg) => msg, - Err(e) => { - Some(Message::assistant().with_text(format!("Error executing /{}: {}", command, e))) - } } } @@ -316,14 +309,26 @@ impl Agent { }) .unwrap_or_default(); - return Ok(Some(Message::user().with_text(format!( - "This recipe requires {} parameters ({}). Custom slash commands only support single-parameter recipes with spaces. Please explain to the user that they should use the CLI instead:\n\n'goose run --recipe {} --params {}=\"...\" --params {}=\"...\"'\n\nOr they can launch the recipe from the recipes sidebar in Goose Desktop, which will prompt for each parameter.", + let error_message = format!( + "The /{} recipe requires {} parameters: {}.\n\n\ + Slash command recipes only support 1 parameter.\n\n\ + **To use this recipe:**\n\ + • **CLI:** `goose run --recipe {} {}`\n\ + • **Desktop:** Launch from the recipes sidebar to fill in parameters", + command, params_without_default, - param_names.join(", "), + param_names.iter() + .map(|name| format!("**{}**", name)) + .collect::>() + .join(", "), command, - param_names.get(0).unwrap_or(&"param1".to_string()), - param_names.get(1).unwrap_or(&"param2".to_string()) - )))); + param_names.iter() + .map(|name| format!("--params {}=\"...\"", name)) + .collect::>() + .join(" ") + ); + + return Err(anyhow!(error_message)); } }; From 832c7365905143b5a983bace61be881c025403dc Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 16:36:06 -0500 Subject: [PATCH 5/8] rm unnecessary clone --- crates/goose/src/agents/execute_commands.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index 0e3ffa0212ea..cea1b37339f2 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -332,10 +332,12 @@ impl Agent { } }; + let param_values_len = param_values.len(); + let recipe = match build_recipe_from_template_with_positional_params( recipe_content, recipe_dir, - param_values.clone(), + param_values, None:: Result>, ) { Ok(recipe) => recipe, @@ -344,7 +346,7 @@ impl Agent { "Recipe requires {} parameter(s): {}. Provided: {}", parameters.len(), parameters.join(", "), - param_values.len() + param_values_len )))); } Err(e) => return Err(anyhow!("Failed to build recipe: {}", e)), From 32aaf68435fc6e62b1e2ec05349be3ec9e9eac3f Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 16:37:22 -0500 Subject: [PATCH 6/8] Extra comment --- crates/goose/src/agents/agent.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 3d2aa822e321..2c96eb2647be 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -794,7 +794,6 @@ impl Agent { match command_result { Err(e) => { - // Return error directly without adding to conversation let error_message = Message::assistant() .with_text(&e.to_string()) .with_visibility(true, false); From ff25d5745ca1642965b59d678bc803e2b63a344d Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 16:41:40 -0500 Subject: [PATCH 7/8] fmt --- crates/goose/src/agents/execute_commands.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/goose/src/agents/execute_commands.rs b/crates/goose/src/agents/execute_commands.rs index cea1b37339f2..545fcb9b6de9 100644 --- a/crates/goose/src/agents/execute_commands.rs +++ b/crates/goose/src/agents/execute_commands.rs @@ -41,7 +41,11 @@ pub fn list_commands() -> &'static [CommandDef] { } impl Agent { - pub async fn execute_command(&self, message_text: &str, session_id: &str) -> Result> { + pub async fn execute_command( + &self, + message_text: &str, + session_id: &str, + ) -> Result> { let mut trimmed = message_text.trim().to_string(); if COMPACT_TRIGGERS.contains(&trimmed.as_str()) { @@ -317,12 +321,14 @@ impl Agent { • **Desktop:** Launch from the recipes sidebar to fill in parameters", command, params_without_default, - param_names.iter() + param_names + .iter() .map(|name| format!("**{}**", name)) .collect::>() .join(", "), command, - param_names.iter() + param_names + .iter() .map(|name| format!("--params {}=\"...\"", name)) .collect::>() .join(" ") @@ -333,7 +339,7 @@ impl Agent { }; let param_values_len = param_values.len(); - + let recipe = match build_recipe_from_template_with_positional_params( recipe_content, recipe_dir, From ea9407fd98065646eb7af946ed47aa2346227776 Mon Sep 17 00:00:00 2001 From: David Katz Date: Thu, 18 Dec 2025 16:52:24 -0500 Subject: [PATCH 8/8] Fix clippy --- crates/goose/src/agents/agent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 2c96eb2647be..80e7b6e87b7c 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -795,7 +795,7 @@ impl Agent { match command_result { Err(e) => { let error_message = Message::assistant() - .with_text(&e.to_string()) + .with_text(e.to_string()) .with_visibility(true, false); return Ok(Box::pin(stream::once(async move { Ok(AgentEvent::Message(error_message))