diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index 133dedf6a..59627be67 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -1419,6 +1419,7 @@ pub(crate) async fn agent_turn( None, None, None, + &[], ) .await } @@ -1593,6 +1594,7 @@ pub(crate) async fn run_tool_call_loop( cancellation_token: Option, on_delta: Option>, hooks: Option<&crate::hooks::HookRunner>, + excluded_tools: &[String], ) -> Result { let max_iterations = if max_tool_iterations == 0 { DEFAULT_MAX_TOOL_ITERATIONS @@ -1600,8 +1602,11 @@ pub(crate) async fn run_tool_call_loop( max_tool_iterations }; - let tool_specs: Vec = - tools_registry.iter().map(|tool| tool.spec()).collect(); + let tool_specs: Vec = tools_registry + .iter() + .filter(|tool| !excluded_tools.iter().any(|ex| ex == tool.name())) + .map(|tool| tool.spec()) + .collect(); let use_native_tools = provider.supports_native_tools() && !tool_specs.is_empty(); for _iteration in 0..max_iterations { @@ -2252,6 +2257,7 @@ pub async fn run( None, None, None, + &[], ) .await?; final_output = response.clone(); @@ -2372,6 +2378,7 @@ pub async fn run( None, None, None, + &[], ) .await { @@ -2845,6 +2852,7 @@ mod tests { None, None, None, + &[], ) .await .expect_err("provider without vision support should fail"); @@ -2890,6 +2898,7 @@ mod tests { None, None, None, + &[], ) .await .expect_err("oversized payload must fail"); @@ -2929,6 +2938,7 @@ mod tests { None, None, None, + &[], ) .await .expect("valid multimodal payload should pass"); @@ -3050,6 +3060,7 @@ mod tests { None, None, None, + &[], ) .await .expect("parallel execution should complete"); diff --git a/src/channels/mod.rs b/src/channels/mod.rs index 30e2cc197..751a19867 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -218,6 +218,7 @@ struct ChannelRuntimeContext { interrupt_on_new_message: bool, multimodal: crate::config::MultimodalConfig, hooks: Option>, + non_cli_excluded_tools: Arc>, } #[derive(Clone)] @@ -1657,6 +1658,11 @@ async fn process_channel_message( Some(cancellation_token.clone()), delta_tx, ctx.hooks.as_deref(), + if msg.channel == "cli" { + &[] + } else { + ctx.non_cli_excluded_tools.as_ref() + }, ), ) => LlmExecutionResult::Completed(result), }; @@ -2956,6 +2962,13 @@ pub async fn start_channels(config: Config) -> Result<()> { )); } + // Filter out tools excluded for non-CLI channels so the system prompt + // does not advertise them for channel-driven runs. + let excluded = &config.autonomy.non_cli_excluded_tools; + if !excluded.is_empty() { + tool_descs.retain(|(name, _)| !excluded.iter().any(|ex| ex == name)); + } + let bootstrap_max_chars = if config.agent.compact_context { Some(6000) } else { @@ -3107,6 +3120,7 @@ pub async fn start_channels(config: Config) -> Result<()> { } else { None }, + non_cli_excluded_tools: Arc::new(config.autonomy.non_cli_excluded_tools.clone()), }); run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await; @@ -3319,6 +3333,7 @@ mod tests { workspace_dir: Arc::new(std::env::temp_dir()), message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }; assert!(compact_sender_history(&ctx, &sender)); @@ -3797,6 +3812,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -3927,6 +3943,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -3985,6 +4002,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4052,6 +4070,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4140,6 +4159,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4210,6 +4230,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4295,6 +4316,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4365,6 +4387,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4424,6 +4447,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4594,6 +4618,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); let (tx, rx) = tokio::sync::mpsc::channel::(4); @@ -4673,6 +4698,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: true, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); let (tx, rx) = tokio::sync::mpsc::channel::(8); @@ -4764,6 +4790,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: true, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); let (tx, rx) = tokio::sync::mpsc::channel::(8); @@ -4837,6 +4864,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -4895,6 +4923,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -5410,6 +5439,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -5494,6 +5524,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -5578,6 +5609,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); process_channel_message( @@ -6124,6 +6156,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: false, multimodal: crate::config::MultimodalConfig::default(), hooks: None, + non_cli_excluded_tools: Arc::new(Vec::new()), }); // Simulate a photo attachment message with [IMAGE:] marker. diff --git a/src/config/schema.rs b/src/config/schema.rs index 5eb4d1461..cc1ec7d27 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -1843,6 +1843,13 @@ pub struct AutonomyConfig { /// Resolved paths under any of these roots pass `is_resolved_path_allowed`. #[serde(default)] pub allowed_roots: Vec, + + /// Tools to exclude from non-CLI channels (e.g. Telegram, Discord). + /// + /// When a tool is listed here, non-CLI channels will not expose it to the + /// model in tool specs. + #[serde(default)] + pub non_cli_excluded_tools: Vec, } fn default_auto_approve() -> Vec { @@ -1910,6 +1917,7 @@ impl Default for AutonomyConfig { auto_approve: default_auto_approve(), always_ask: default_always_ask(), allowed_roots: Vec::new(), + non_cli_excluded_tools: Vec::new(), } } } @@ -4141,6 +4149,7 @@ default_temperature = 0.7 auto_approve: vec!["file_read".into()], always_ask: vec![], allowed_roots: vec![], + non_cli_excluded_tools: vec![], }, runtime: RuntimeConfig { kind: "docker".into(), diff --git a/src/tools/delegate.rs b/src/tools/delegate.rs index e3b57e329..f3f4dcda4 100644 --- a/src/tools/delegate.rs +++ b/src/tools/delegate.rs @@ -410,6 +410,7 @@ impl DelegateTool { None, None, None, + &[], ), ) .await;