diff --git a/src/config/prompts.rs b/src/config/prompts.rs index 54786ed..8af226b 100644 --- a/src/config/prompts.rs +++ b/src/config/prompts.rs @@ -207,7 +207,7 @@ pub(in crate::config) const DEFAULT_USER_INTERACTION_INSTRUCTIONS: &str = indoc! ## User Interaction You have built-in tools to interact with the user directly: - - `user__ask --question \"...\" --options [\"A\", \"B\", \"C\"]`: Present a selection prompt. Returns the chosen option. + - `user__select --question \"...\" --options [\"A\", \"B\", \"C\"]`: Present a single-select list of named options. Use this — not `user__confirm` — whenever there are 2+ named options. Returns the chosen option. - `user__confirm --question \"...\"`: Ask a yes/no question. Returns \"yes\" or \"no\". - `user__input --question \"...\"`: Request free-form text input from the user. - `user__checkbox --question \"...\" --options [\"A\", \"B\", \"C\"]`: Multi-select prompt. Returns an array of selected options. diff --git a/src/config/request_context.rs b/src/config/request_context.rs index cd160a9..06e7f41 100644 --- a/src/config/request_context.rs +++ b/src/config/request_context.rs @@ -4784,7 +4784,7 @@ mod tests { let fns = ctx.select_functions(&role).unwrap(); let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); assert!(names.contains(&"todo__init")); - assert!(names.contains(&"user__ask")); + assert!(names.contains(&"user__select")); } #[test] @@ -4848,7 +4848,7 @@ mod tests { let fns = ctx.select_functions(&role).unwrap(); let names: Vec<&str> = fns.iter().map(|f| f.name.as_str()).collect(); - assert!(names.contains(&"user__ask")); + assert!(names.contains(&"user__select")); assert!(!names.contains(&"skill__list")); } @@ -4933,7 +4933,7 @@ mod tests { "teammate tools must survive an agent tool filter, got: {names:?}" ); assert!( - names.contains(&"user__ask"), + names.contains(&"user__select"), "user__ tools must survive an agent tool filter, got: {names:?}" ); } diff --git a/src/function/mod.rs b/src/function/mod.rs index fcc82de..e4974e3 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -1875,7 +1875,7 @@ mod tests { fn functions_append_user_interaction_adds_declarations() { let mut f = Functions::default(); f.append_user_interaction_functions(); - assert!(f.contains("user__ask")); + assert!(f.contains("user__select")); assert!(f.contains("user__confirm")); assert!(f.contains("user__input")); assert!(f.contains("user__checkbox")); diff --git a/src/function/user_interaction.rs b/src/function/user_interaction.rs index 92c407c..ce98d5b 100644 --- a/src/function/user_interaction.rs +++ b/src/function/user_interaction.rs @@ -17,8 +17,11 @@ const CUSTOM_MULTI_CHOICE_ANSWER_OPTION: &str = "Other (custom)"; pub fn user_interaction_function_declarations() -> Vec { vec![ FunctionDeclaration { - name: format!("{USER_FUNCTION_PREFIX}ask"), - description: "Ask the user to select one option from a list. Returns the selected option. Indicate the recommended choice if there is one.".to_string(), + name: format!("{USER_FUNCTION_PREFIX}select"), + description: "Present a list of named options and ask the user to pick exactly one. \ + Indicate the recommended choice if there is one. \ + Use this — not `confirm` — whenever there are 2+ named options to choose \ + between. Returns the selected option.".to_string(), parameters: JsonSchema { type_value: Some("object".to_string()), properties: Some(IndexMap::from([ @@ -50,7 +53,9 @@ pub fn user_interaction_function_declarations() -> Vec { }, FunctionDeclaration { name: format!("{USER_FUNCTION_PREFIX}confirm"), - description: "Ask the user a yes/no question. Returns \"yes\" or \"no\".".to_string(), + description: "Ask a genuinely binary yes/no question with no other choices. Do NOT \ + use for \"A or B?\" situations — use `select` instead. Returns \"yes\" \ + or \"no\".".to_string(), parameters: JsonSchema { type_value: Some("object".to_string()), properties: Some(IndexMap::from([( @@ -68,7 +73,8 @@ pub fn user_interaction_function_declarations() -> Vec { }, FunctionDeclaration { name: format!("{USER_FUNCTION_PREFIX}input"), - description: "Ask the user for free-form text input. Returns the text entered.".to_string(), + description: "Collect free-form text from the user when no predefined options exist. \ + Returns the text entered.".to_string(), parameters: JsonSchema { type_value: Some("object".to_string()), properties: Some(IndexMap::from([( @@ -86,7 +92,9 @@ pub fn user_interaction_function_declarations() -> Vec { }, FunctionDeclaration { name: format!("{USER_FUNCTION_PREFIX}checkbox"), - description: "Ask the user to select one or more options from a list. Returns an array of selected options.".to_string(), + description: "Ask the user to pick one or more options from a list (multi-select). \ + Use when multiple answers are valid simultaneously. Returns an array \ + of selected options.".to_string(), parameters: JsonSchema { type_value: Some("object".to_string()), properties: Some(IndexMap::from([ @@ -139,7 +147,7 @@ pub async fn handle_user_tool( fn handle_direct(action: &str, args: &Value) -> Result { match action { - "ask" => handle_direct_ask(args), + "select" => handle_direct_ask(args), "confirm" => handle_direct_confirm(args), "input" => handle_direct_input(args), "checkbox" => handle_direct_checkbox(args), diff --git a/src/graph/user_interaction.rs b/src/graph/user_interaction.rs index 7bef499..91d0693 100644 --- a/src/graph/user_interaction.rs +++ b/src/graph/user_interaction.rs @@ -27,7 +27,7 @@ impl ApprovalNodeExecutor { &json!({ "question": question, "options": node.options }), ) .await - .context("user__ask failed")?; + .context("user__select failed")?; if let Some(err) = response.get("error").and_then(Value::as_str) { bail!("Approval interaction failed: {err}");