From a906903e13ab48fd4922368d713233eeb4b08974 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Tue, 26 May 2026 14:36:32 -0300 Subject: [PATCH] fix(core): restrict modal questions to supported hosts --- .../templates/default.md | 2 +- codex-rs/core/src/tools/spec_plan.rs | 10 +++++-- codex-rs/core/src/tools/spec_plan_tests.rs | 29 +++++++++++++++++++ .../src/collaboration_mode_presets_tests.rs | 7 +++-- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/codex-rs/collaboration-mode-templates/templates/default.md b/codex-rs/collaboration-mode-templates/templates/default.md index 715982c396..d893884128 100644 --- a/codex-rs/collaboration-mode-templates/templates/default.md +++ b/codex-rs/collaboration-mode-templates/templates/default.md @@ -8,4 +8,4 @@ Your active mode changes only when new developer instructions with a different ` Use the `request_user_input` tool only when it is listed in the available tools for this turn. -In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message. +In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, use `request_user_input` when it is available. If the tool is unavailable, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message. diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 55e6dea0e6..bf46235994 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -568,9 +568,13 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut planned_tools.add(UpdateGoalHandler); } - planned_tools.add(RequestUserInputHandler { - available_modes: request_user_input_available_modes(), - }); + if !turn_context.session_source.is_non_root_agent() + && !matches!(&turn_context.session_source, SessionSource::Exec) + { + planned_tools.add(RequestUserInputHandler { + available_modes: request_user_input_available_modes(), + }); + } if features.enabled(Feature::RequestPermissionsTool) { planned_tools.add(RequestPermissionsHandler); diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index f1c756d1a2..cb0927b4c2 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -442,6 +442,33 @@ async fn host_context_gates_goal_and_agent_job_tools() { worker_agent_job.assert_visible_contains(&["spawn_agents_on_csv", "report_agent_job_result"]); } +#[tokio::test] +async fn request_user_input_is_hidden_from_unsupported_session_sources() { + let interactive_root = probe(|turn| { + turn.session_source = SessionSource::VSCode; + }) + .await; + interactive_root.assert_visible_contains(&["request_user_input"]); + + let exec = probe(|turn| { + turn.session_source = SessionSource::Exec; + }) + .await; + exec.assert_visible_lacks(&["request_user_input"]); + + let sub_agent = probe(|turn| { + turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { + parent_thread_id: Default::default(), + depth: 1, + agent_path: None, + agent_nickname: None, + agent_role: None, + }); + }) + .await; + sub_agent.assert_visible_lacks(&["request_user_input"]); +} + #[tokio::test] async fn mcp_and_tool_search_follow_direct_and_deferred_tool_exposure() { let direct_mcp = probe_with( @@ -700,6 +727,7 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() { #[tokio::test] async fn code_mode_only_keeps_request_user_input_as_a_direct_modal_tool() { let plan = probe(|turn| { + turn.session_source = SessionSource::VSCode; set_features(turn, &[Feature::CodeMode, Feature::CodeModeOnly]); }) .await; @@ -897,6 +925,7 @@ async fn multi_agent_v2_namespace_is_ignored_without_provider_namespace_support( #[tokio::test] async fn code_mode_only_can_expose_namespaced_multi_agent_v2_as_normal_tools() { let plan = probe(|turn| { + turn.session_source = SessionSource::VSCode; set_features( turn, &[ diff --git a/codex-rs/models-manager/src/collaboration_mode_presets_tests.rs b/codex-rs/models-manager/src/collaboration_mode_presets_tests.rs index 4e21d6d350..5de3672c20 100644 --- a/codex-rs/models-manager/src/collaboration_mode_presets_tests.rs +++ b/codex-rs/models-manager/src/collaboration_mode_presets_tests.rs @@ -30,7 +30,8 @@ fn default_mode_instructions_replace_mode_names_placeholder() { assert!(default_instructions.contains( "Use the `request_user_input` tool only when it is listed in the available tools" )); - assert!( - default_instructions.contains("ask the user directly with a concise plain-text question") - ); + assert!(default_instructions.contains("use `request_user_input` when it is available")); + assert!(default_instructions.contains( + "If the tool is unavailable, ask the user directly with a concise plain-text question" + )); }