diff --git a/docs/config-reference.md b/docs/config-reference.md index dbc5221..cfd65fb 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -50,6 +50,10 @@ Notes: Top-level channel options are configured under `channels_config`. +| Key | Default | Purpose | +|---|---|---| +| `message_timeout_secs` | `300` | Timeout in seconds for processing a single channel message (LLM + tools) | + Examples: - `[channels_config.telegram]` @@ -57,6 +61,12 @@ Examples: - `[channels_config.whatsapp]` - `[channels_config.email]` +Notes: + +- Default `300s` is optimized for on-device LLMs (Ollama) which are slower than cloud APIs. +- If using cloud APIs (OpenAI, Anthropic, etc.), you can reduce this to `60` or lower. +- When a timeout occurs, users receive: `⚠️ Request timed out while waiting for the model. Please try again.` + See detailed channel matrix and allowlist behavior in [channels-reference.md](channels-reference.md). ## Security-Relevant Defaults diff --git a/src/channels/mod.rs b/src/channels/mod.rs index 0fff1ec..67654a7 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -60,8 +60,8 @@ const BOOTSTRAP_MAX_CHARS: usize = 20_000; const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2; const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60; -/// Timeout for processing a single channel message (LLM + tools). -/// 300s for on-device LLMs (Ollama) which are slower than cloud APIs. +/// Default timeout for processing a single channel message (LLM + tools). +/// Used as fallback when not configured in channels_config.message_timeout_secs. const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300; const CHANNEL_PARALLELISM_PER_CHANNEL: usize = 4; const CHANNEL_MIN_IN_FLIGHT_MESSAGES: usize = 8; @@ -120,6 +120,7 @@ struct ChannelRuntimeContext { reliability: Arc, provider_runtime_options: providers::ProviderRuntimeOptions, workspace_dir: Arc, + message_timeout_secs: u64, } fn conversation_memory_key(msg: &traits::ChannelMessage) -> String { @@ -696,7 +697,7 @@ async fn process_channel_message(ctx: Arc, msg: traits::C }; let llm_result = tokio::time::timeout( - Duration::from_secs(CHANNEL_MESSAGE_TIMEOUT_SECS), + Duration::from_secs(ctx.message_timeout_secs), run_tool_call_loop( active_provider.as_ref(), &mut history, @@ -787,10 +788,7 @@ async fn process_channel_message(ctx: Arc, msg: traits::C } } Err(_) => { - let timeout_msg = format!( - "LLM response timed out after {}s", - CHANNEL_MESSAGE_TIMEOUT_SECS - ); + let timeout_msg = format!("LLM response timed out after {}s", ctx.message_timeout_secs); eprintln!( " ❌ {} (elapsed: {}ms)", timeout_msg, @@ -1835,6 +1833,7 @@ pub async fn start_channels(config: Config) -> Result<()> { reliability: Arc::new(config.reliability.clone()), provider_runtime_options, workspace_dir: Arc::new(config.workspace_dir.clone()), + message_timeout_secs: config.channels_config.message_timeout_secs, }); run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await; @@ -2225,6 +2224,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2277,6 +2277,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2338,6 +2339,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2420,6 +2422,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2478,6 +2481,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2531,6 +2535,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -2635,6 +2640,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); let (tx, rx) = tokio::sync::mpsc::channel::(4); @@ -2705,6 +2711,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( @@ -3095,6 +3102,7 @@ mod tests { reliability: Arc::new(crate::config::ReliabilityConfig::default()), provider_runtime_options: providers::ProviderRuntimeOptions::default(), workspace_dir: Arc::new(std::env::temp_dir()), + message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS, }); process_channel_message( diff --git a/src/config/schema.rs b/src/config/schema.rs index 8d9138f..7803da4 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -1979,6 +1979,14 @@ pub struct ChannelsConfig { pub lark: Option, pub dingtalk: Option, pub qq: Option, + /// Timeout in seconds for processing a single channel message (LLM + tools). + /// Default: 300s for on-device LLMs (Ollama) which are slower than cloud APIs. + #[serde(default = "default_channel_message_timeout_secs")] + pub message_timeout_secs: u64, +} + +fn default_channel_message_timeout_secs() -> u64 { + 300 } impl Default for ChannelsConfig { @@ -1999,6 +2007,7 @@ impl Default for ChannelsConfig { lark: None, dingtalk: None, qq: None, + message_timeout_secs: default_channel_message_timeout_secs(), } } } @@ -3242,6 +3251,7 @@ default_temperature = 0.7 lark: None, dingtalk: None, qq: None, + message_timeout_secs: 300, }, memory: MemoryConfig::default(), storage: StorageConfig::default(), @@ -3746,6 +3756,7 @@ allowed_users = ["@ops:matrix.org"] lark: None, dingtalk: None, qq: None, + message_timeout_secs: 300, }; let toml_str = toml::to_string_pretty(&c).unwrap(); let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap(); @@ -3909,6 +3920,7 @@ channel_id = "C123" lark: None, dingtalk: None, qq: None, + message_timeout_secs: 300, }; let toml_str = toml::to_string_pretty(&c).unwrap(); let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap(); diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index de1895f..99f1860 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -2452,23 +2452,7 @@ fn setup_channels() -> Result { print_bullet("CLI is always available. Connect more channels now."); println!(); - let mut config = ChannelsConfig { - cli: true, - telegram: None, - discord: None, - slack: None, - mattermost: None, - webhook: None, - imessage: None, - matrix: None, - signal: None, - whatsapp: None, - email: None, - irc: None, - lark: None, - dingtalk: None, - qq: None, - }; + let mut config = ChannelsConfig::default(); loop { let options = vec![