From bfacba20cbe13cf4b0f208d855ed144e3d49f7f9 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Sat, 28 Feb 2026 20:18:40 -0500 Subject: [PATCH] feat(config): add ProgressMode enum for streaming channel draft updates --- src/agent/loop_.rs | 188 +++++++++++++++++++++-------------- src/channels/mod.rs | 89 ++++++++++++++++- src/config/mod.rs | 19 ++-- src/config/schema.rs | 46 +++++++++ src/daemon/mod.rs | 2 + src/integrations/registry.rs | 5 +- src/migration.rs | 5 +- src/onboard/wizard.rs | 5 +- 8 files changed, 266 insertions(+), 93 deletions(-) diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index 1164d092b..18b35167c 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -1,5 +1,5 @@ use crate::approval::{ApprovalManager, ApprovalRequest, ApprovalResponse}; -use crate::config::Config; +use crate::config::{Config, ProgressMode}; use crate::memory::{self, Memory, MemoryCategory}; use crate::multimodal; use crate::observability::{self, runtime_trace, Observer, ObserverEvent}; @@ -290,6 +290,7 @@ tokio::task_local! { static TOOL_LOOP_NON_CLI_APPROVAL_CONTEXT: Option; static LOOP_DETECTION_CONFIG: LoopDetectionConfig; static SAFETY_HEARTBEAT_CONFIG: Option; + static TOOL_LOOP_PROGRESS_MODE: ProgressMode; } /// Configuration for periodic safety-constraint re-injection (heartbeat). @@ -305,6 +306,14 @@ fn should_inject_safety_heartbeat(counter: usize, interval: usize) -> bool { interval > 0 && counter > 0 && counter % interval == 0 } +fn should_emit_verbose_progress(mode: ProgressMode) -> bool { + mode == ProgressMode::Verbose +} + +fn should_emit_tool_progress(mode: ProgressMode) -> bool { + mode != ProgressMode::Off +} + /// Extract a short hint from tool call arguments for progress display. fn truncate_tool_args_for_progress(name: &str, args: &serde_json::Value, max_len: usize) -> String { let hint = match name { @@ -654,27 +663,31 @@ pub(crate) async fn run_tool_call_loop_with_reply_target( on_delta: Option>, hooks: Option<&crate::hooks::HookRunner>, excluded_tools: &[String], + progress_mode: ProgressMode, ) -> Result { - TOOL_LOOP_REPLY_TARGET + TOOL_LOOP_PROGRESS_MODE .scope( - reply_target.map(str::to_string), - run_tool_call_loop( - provider, - history, - tools_registry, - observer, - provider_name, - model, - temperature, - silent, - approval, - channel_name, - multimodal_config, - max_tool_iterations, - cancellation_token, - on_delta, - hooks, - excluded_tools, + progress_mode, + TOOL_LOOP_REPLY_TARGET.scope( + reply_target.map(str::to_string), + run_tool_call_loop( + provider, + history, + tools_registry, + observer, + provider_name, + model, + temperature, + silent, + approval, + channel_name, + multimodal_config, + max_tool_iterations, + cancellation_token, + on_delta, + hooks, + excluded_tools, + ), ), ) .await @@ -809,6 +822,9 @@ pub(crate) async fn run_tool_call_loop( .try_with(Clone::clone) .ok() .flatten(); + let progress_mode = TOOL_LOOP_PROGRESS_MODE + .try_with(|mode| *mode) + .unwrap_or(ProgressMode::Verbose); let bypass_non_cli_approval_for_turn = approval.is_some_and(|mgr| channel_name != "cli" && mgr.consume_non_cli_allow_all_once()); if bypass_non_cli_approval_for_turn { @@ -870,13 +886,15 @@ pub(crate) async fn run_tool_call_loop( } // ── Progress: LLM thinking ──────────────────────────── - if let Some(ref tx) = on_delta { - let phase = if iteration == 0 { - "\u{1f914} Thinking...\n".to_string() - } else { - format!("\u{1f914} Thinking (round {})...\n", iteration + 1) - }; - let _ = tx.send(format!("{DRAFT_PROGRESS_SENTINEL}{phase}")).await; + if should_emit_verbose_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let phase = if iteration == 0 { + "\u{1f914} Thinking...\n".to_string() + } else { + format!("\u{1f914} Thinking (round {})...\n", iteration + 1) + }; + let _ = tx.send(format!("{DRAFT_PROGRESS_SENTINEL}{phase}")).await; + } } observer.record_event(&ObserverEvent::LlmRequest { @@ -1078,15 +1096,17 @@ pub(crate) async fn run_tool_call_loop( }; // ── Progress: LLM responded ───────────────────────────── - if let Some(ref tx) = on_delta { - let llm_secs = llm_started_at.elapsed().as_secs(); - if !tool_calls.is_empty() { - let _ = tx - .send(format!( - "{DRAFT_PROGRESS_SENTINEL}\u{1f4ac} Got {} tool call(s) ({llm_secs}s)\n", - tool_calls.len() - )) - .await; + if should_emit_verbose_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let llm_secs = llm_started_at.elapsed().as_secs(); + if !tool_calls.is_empty() { + let _ = tx + .send(format!( + "{DRAFT_PROGRESS_SENTINEL}\u{1f4ac} Got {} tool call(s) ({llm_secs}s)\n", + tool_calls.len() + )) + .await; + } } } @@ -1120,12 +1140,14 @@ pub(crate) async fn run_tool_call_loop( }), ); - if let Some(ref tx) = on_delta { - let _ = tx - .send(format!( - "{DRAFT_PROGRESS_SENTINEL}\u{21bb} Retrying: response deferred action without a tool call\n" - )) - .await; + if should_emit_verbose_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let _ = tx + .send(format!( + "{DRAFT_PROGRESS_SENTINEL}\u{21bb} Retrying: response deferred action without a tool call\n" + )) + .await; + } } continue; @@ -1422,18 +1444,19 @@ pub(crate) async fn run_tool_call_loop( }), ); - // ── Progress: tool start ──────────────────────────── - if let Some(ref tx) = on_delta { - let hint = truncate_tool_args_for_progress(&tool_name, &tool_args, 60); - let progress = if hint.is_empty() { - format!("\u{23f3} {}\n", tool_name) - } else { - format!("\u{23f3} {}: {hint}\n", tool_name) - }; - tracing::debug!(tool = %tool_name, "Sending progress start to draft"); - let _ = tx - .send(format!("{DRAFT_PROGRESS_SENTINEL}{progress}")) - .await; + if should_emit_tool_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let hint = truncate_tool_args_for_progress(&tool_name, &tool_args, 60); + let progress = if hint.is_empty() { + format!("\u{23f3} {}\n", tool_name) + } else { + format!("\u{23f3} {}: {hint}\n", tool_name) + }; + tracing::debug!(tool = %tool_name, "Sending progress start to draft"); + let _ = tx + .send(format!("{DRAFT_PROGRESS_SENTINEL}{progress}")) + .await; + } } executable_indices.push(idx); @@ -1514,21 +1537,22 @@ pub(crate) async fn run_tool_call_loop( .await; } - // ── Progress: tool completion ─────────────────────── - if let Some(ref tx) = on_delta { - let secs = outcome.duration.as_secs(); - let icon = if outcome.success { - "\u{2705}" - } else { - "\u{274c}" - }; - tracing::debug!(tool = %call.name, secs, "Sending progress complete to draft"); - let _ = tx - .send(format!( - "{DRAFT_PROGRESS_SENTINEL}{icon} {} ({secs}s)\n", - call.name - )) - .await; + if should_emit_tool_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let secs = outcome.duration.as_secs(); + let icon = if outcome.success { + "\u{2705}" + } else { + "\u{274c}" + }; + tracing::debug!(tool = %call.name, secs, "Sending progress complete to draft"); + let _ = tx + .send(format!( + "{DRAFT_PROGRESS_SENTINEL}{icon} {} ({secs}s)\n", + call.name + )) + .await; + } } // ── Loop detection: record call ────────────────────── @@ -1597,12 +1621,14 @@ pub(crate) async fn run_tool_call_loop( Some("loop pattern detected, injecting self-correction prompt"), serde_json::json!({ "iteration": iteration + 1, "warning": &warning }), ); - if let Some(ref tx) = on_delta { - let _ = tx - .send(format!( - "{DRAFT_PROGRESS_SENTINEL}\u{26a0}\u{fe0f} Loop detected, attempting self-correction\n" - )) - .await; + if should_emit_verbose_progress(progress_mode) { + if let Some(ref tx) = on_delta { + let _ = tx + .send(format!( + "{DRAFT_PROGRESS_SENTINEL}\u{26a0}\u{fe0f} Loop detected, attempting self-correction\n" + )) + .await; + } } loop_detection_prompt = Some(warning); } @@ -5644,4 +5670,16 @@ Let me check the result."#; assert_eq!(parsed["content"].as_str(), Some("answer")); assert!(parsed.get("reasoning_content").is_none()); } + + #[test] + fn progress_mode_gates_work_as_expected() { + assert!(should_emit_verbose_progress(ProgressMode::Verbose)); + assert!(!should_emit_verbose_progress(ProgressMode::Compact)); + assert!(!should_emit_verbose_progress(ProgressMode::Off)); + + assert!(should_emit_tool_progress(ProgressMode::Verbose)); + assert!(should_emit_tool_progress(ProgressMode::Compact)); + assert!(!should_emit_tool_progress(ProgressMode::Off)); + } + } diff --git a/src/channels/mod.rs b/src/channels/mod.rs index b77ccf550..7c3502fbe 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -82,7 +82,7 @@ use crate::agent::loop_::{ }; use crate::agent::session::{resolve_session_id, shared_session_manager, Session, SessionManager}; use crate::approval::{ApprovalManager, ApprovalResponse, PendingApprovalError}; -use crate::config::{Config, NonCliNaturalLanguageApprovalMode}; +use crate::config::{Config, NonCliNaturalLanguageApprovalMode, ProgressMode}; use crate::identity; use crate::memory::{self, Memory}; use crate::observability::{self, runtime_trace, Observer}; @@ -166,6 +166,23 @@ fn clear_live_channels() { .clear(); } +fn runtime_telegram_progress_mode_store() -> &'static Mutex { + static STORE: OnceLock> = OnceLock::new(); + STORE.get_or_init(|| Mutex::new(ProgressMode::default())) +} + +fn set_runtime_telegram_progress_mode(mode: ProgressMode) { + *runtime_telegram_progress_mode_store() + .lock() + .unwrap_or_else(|e| e.into_inner()) = mode; +} + +fn runtime_telegram_progress_mode() -> ProgressMode { + *runtime_telegram_progress_mode_store() + .lock() + .unwrap_or_else(|e| e.into_inner()) +} + pub(crate) fn get_live_channel(name: &str) -> Option> { live_channels_registry() .lock() @@ -683,6 +700,27 @@ fn split_internal_progress_delta(delta: &str) -> (bool, &str) { } } +fn effective_progress_mode_for_message( + channel_name: &str, + expose_internal_tool_details: bool, +) -> ProgressMode { + if channel_name.eq_ignore_ascii_case("cli") || expose_internal_tool_details { + ProgressMode::Verbose + } else if channel_name.eq_ignore_ascii_case("telegram") { + runtime_telegram_progress_mode() + } else { + ProgressMode::Off + } +} + +fn is_verbose_only_progress_line(delta: &str) -> bool { + let trimmed = delta.trim_start(); + trimmed.starts_with("\u{1f914} Thinking") + || trimmed.starts_with("\u{1f4ac} Got ") + || trimmed.starts_with("\u{21bb} Retrying") + || trimmed.starts_with("\u{26a0}\u{fe0f} Loop detected") +} + fn build_channel_system_prompt( base_prompt: &str, channel_name: &str, @@ -3460,6 +3498,8 @@ or tune thresholds in config.", let expose_internal_tool_details = msg.channel == "cli" || should_expose_internal_tool_details(&msg.content); + let progress_mode = + effective_progress_mode_for_message(msg.channel.as_str(), expose_internal_tool_details); let excluded_tools_snapshot = if msg.channel == "cli" { Vec::new() } else { @@ -3527,7 +3567,7 @@ or tune thresholds in config.", let channel = Arc::clone(channel_ref); let reply_target = msg.reply_target.clone(); let draft_id = draft_id_ref.to_string(); - let suppress_internal_progress = !expose_internal_tool_details; + let mode = progress_mode; Some(tokio::spawn(async move { let mut accumulated = String::new(); while let Some(delta) = rx.recv().await { @@ -3536,10 +3576,15 @@ or tune thresholds in config.", continue; } let (is_internal_progress, visible_delta) = split_internal_progress_delta(&delta); - if suppress_internal_progress && is_internal_progress { - continue; + if is_internal_progress { + if mode == ProgressMode::Off { + continue; + } + if mode == ProgressMode::Compact && is_verbose_only_progress_line(visible_delta) + { + continue; + } } - accumulated.push_str(visible_delta); if let Err(e) = channel .update_draft(&reply_target, &draft_id, &accumulated) @@ -3605,6 +3650,7 @@ or tune thresholds in config.", delta_tx, ctx.hooks.as_deref(), &excluded_tools_snapshot, + progress_mode, ), ) => LlmExecutionResult::Completed(result), }; @@ -5482,6 +5528,13 @@ pub async fn start_channels(config: Config) -> Result<()> { .telegram .as_ref() .is_some_and(|tg| tg.interrupt_on_new_message); + let telegram_progress_mode = config + .channels_config + .telegram + .as_ref() + .map(|tg| tg.progress_mode) + .unwrap_or_default(); + set_runtime_telegram_progress_mode(telegram_progress_mode); let session_manager = shared_session_manager(&config.agent.session, &config.workspace_dir)? .map(|mgr| mgr as Arc); @@ -11160,6 +11213,32 @@ Done reminder set for 1:38 AM."#; assert_eq!(plain, "final answer"); } + #[test] + fn effective_progress_mode_defaults_non_telegram_to_off() { + assert_eq!( + effective_progress_mode_for_message("draft-streaming-channel", false), + ProgressMode::Off + ); + assert_eq!( + effective_progress_mode_for_message("draft-streaming-channel", true), + ProgressMode::Verbose + ); + } + + #[test] + fn effective_progress_mode_uses_telegram_runtime_setting() { + set_runtime_telegram_progress_mode(ProgressMode::Compact); + assert_eq!( + effective_progress_mode_for_message("telegram", false), + ProgressMode::Compact + ); + set_runtime_telegram_progress_mode(ProgressMode::Off); + assert_eq!( + effective_progress_mode_for_message("telegram", false), + ProgressMode::Off + ); + } + #[test] fn build_channel_system_prompt_includes_visibility_policy() { let hidden = build_channel_system_prompt("base", "telegram", "chat", false); diff --git a/src/config/mod.rs b/src/config/mod.rs index 5c8279a29..24025a15c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -18,15 +18,15 @@ pub use schema::{ MatrixConfig, MemoryConfig, ModelRouteConfig, MultimodalConfig, NextcloudTalkConfig, NonCliNaturalLanguageApprovalMode, ObservabilityConfig, OtpChallengeDelivery, OtpConfig, OtpMethod, OutboundLeakGuardAction, OutboundLeakGuardConfig, PeripheralBoardConfig, - PeripheralsConfig, PerplexityFilterConfig, PluginEntryConfig, PluginsConfig, ProviderConfig, - ProxyConfig, ProxyScope, QdrantConfig, QueryClassificationConfig, ReliabilityConfig, - ResearchPhaseConfig, ResearchTrigger, ResourceLimitsConfig, RuntimeConfig, SandboxBackend, - SandboxConfig, SchedulerConfig, SecretsConfig, SecurityConfig, SecurityRoleConfig, - SkillsConfig, SkillsPromptInjectionMode, SlackConfig, StorageConfig, StorageProviderConfig, - StorageProviderSection, StreamMode, SyscallAnomalyConfig, TelegramConfig, TranscriptionConfig, - TunnelConfig, UrlAccessConfig, WasmCapabilityEscalationMode, WasmConfig, WasmModuleHashPolicy, - WasmRuntimeConfig, WasmSecurityConfig, WebFetchConfig, WebSearchConfig, WebhookConfig, - DEFAULT_MODEL_FALLBACK, + PeripheralsConfig, PerplexityFilterConfig, PluginEntryConfig, PluginsConfig, ProgressMode, + ProviderConfig, ProxyConfig, ProxyScope, QdrantConfig, QueryClassificationConfig, + ReliabilityConfig, ResearchPhaseConfig, ResearchTrigger, ResourceLimitsConfig, RuntimeConfig, + SandboxBackend, SandboxConfig, SchedulerConfig, SecretsConfig, SecurityConfig, + SecurityRoleConfig, SkillsConfig, SkillsPromptInjectionMode, SlackConfig, StorageConfig, + StorageProviderConfig, StorageProviderSection, StreamMode, SyscallAnomalyConfig, + TelegramConfig, TranscriptionConfig, TunnelConfig, UrlAccessConfig, + WasmCapabilityEscalationMode, WasmConfig, WasmModuleHashPolicy, WasmRuntimeConfig, + WasmSecurityConfig, WebFetchConfig, WebSearchConfig, WebhookConfig, DEFAULT_MODEL_FALLBACK, }; pub fn name_and_presence(channel: Option<&T>) -> (&'static str, bool) { @@ -55,6 +55,7 @@ mod tests { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), group_reply: None, base_url: None, ack_enabled: true, diff --git a/src/config/schema.rs b/src/config/schema.rs index 2fdb34128..d4d971d84 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -4285,6 +4285,19 @@ pub enum StreamMode { Partial, } +/// Progress verbosity for channels that support draft streaming. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ProgressMode { + /// Show all progress lines (thinking rounds, tool-count lines, tool lifecycle). + Verbose, + /// Show only tool lifecycle lines (start + completion). + #[default] + Compact, + /// Suppress progress lines and stream only final answer text. + Off, +} + fn default_draft_update_interval_ms() -> u64 { 1000 } @@ -4530,6 +4543,9 @@ pub struct TelegramConfig { /// Direct messages are always processed. #[serde(default)] pub mention_only: bool, + /// Draft progress verbosity for streaming updates. + #[serde(default)] + pub progress_mode: ProgressMode, /// Group-chat trigger controls. #[serde(default)] pub group_reply: Option, @@ -8972,6 +8988,7 @@ mod tests { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, @@ -9399,6 +9416,7 @@ ws_url = "ws://127.0.0.1:3002" draft_update_interval_ms: default_draft_update_interval_ms(), interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, @@ -9880,6 +9898,7 @@ tool_dispatcher = "xml" draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, @@ -10064,6 +10083,7 @@ tool_dispatcher = "xml" draft_update_interval_ms: 500, interrupt_on_new_message: true, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, @@ -10082,6 +10102,7 @@ tool_dispatcher = "xml" let json = r#"{"bot_token":"tok","allowed_users":[]}"#; let parsed: TelegramConfig = serde_json::from_str(json).unwrap(); assert_eq!(parsed.stream_mode, StreamMode::Off); + assert_eq!(parsed.progress_mode, ProgressMode::Compact); assert_eq!(parsed.draft_update_interval_ms, 1000); assert!(!parsed.interrupt_on_new_message); assert!(parsed.base_url.is_none()); @@ -10099,6 +10120,31 @@ tool_dispatcher = "xml" assert_eq!(parsed.base_url, Some("https://tapi.bale.ai".to_string())); } + #[test] + async fn progress_mode_deserializes_variants() { + let verbose: ProgressMode = serde_json::from_str(r#""verbose""#).unwrap(); + let compact: ProgressMode = serde_json::from_str(r#""compact""#).unwrap(); + let off: ProgressMode = serde_json::from_str(r#""off""#).unwrap(); + + assert_eq!(verbose, ProgressMode::Verbose); + assert_eq!(compact, ProgressMode::Compact); + assert_eq!(off, ProgressMode::Off); + } + + #[test] + async fn telegram_config_deserializes_progress_mode_verbose() { + let json = r#"{"bot_token":"tok","allowed_users":[],"progress_mode":"verbose"}"#; + let parsed: TelegramConfig = serde_json::from_str(json).unwrap(); + assert_eq!(parsed.progress_mode, ProgressMode::Verbose); + } + + #[test] + async fn telegram_config_deserializes_progress_mode_off() { + let json = r#"{"bot_token":"tok","allowed_users":[],"progress_mode":"off"}"#; + let parsed: TelegramConfig = serde_json::from_str(json).unwrap(); + assert_eq!(parsed.progress_mode, ProgressMode::Off); + } + #[test] async fn telegram_group_reply_config_overrides_legacy_mention_only() { let json = r#"{ diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 4f3d9d63e..1d948e51b 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -498,6 +498,7 @@ mod tests { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: crate::config::ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, @@ -667,6 +668,7 @@ mod tests { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: crate::config::ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, diff --git a/src/integrations/registry.rs b/src/integrations/registry.rs index 5f0529ce7..23dd2857b 100644 --- a/src/integrations/registry.rs +++ b/src/integrations/registry.rs @@ -770,7 +770,9 @@ pub fn all_integrations() -> Vec { #[cfg(test)] mod tests { use super::*; - use crate::config::schema::{IMessageConfig, MatrixConfig, StreamMode, TelegramConfig}; + use crate::config::schema::{ + IMessageConfig, MatrixConfig, ProgressMode, StreamMode, TelegramConfig, + }; use crate::config::Config; #[test] @@ -837,6 +839,7 @@ mod tests { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, diff --git a/src/migration.rs b/src/migration.rs index a253afdc8..721e7be42 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -1289,7 +1289,9 @@ fn backup_target_config(config_path: &Path) -> Result> { #[cfg(test)] mod tests { use super::*; - use crate::config::{Config, DelegateAgentConfig, MemoryConfig, StreamMode, TelegramConfig}; + use crate::config::{ + Config, DelegateAgentConfig, MemoryConfig, ProgressMode, StreamMode, TelegramConfig, + }; use crate::memory::{Memory, SqliteMemory}; use rusqlite::params; use serde_json::json; @@ -1454,6 +1456,7 @@ mod tests { draft_update_interval_ms: 1_500, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), ack_enabled: true, group_reply: None, base_url: None, diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index bb256afeb..f92359b87 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -1,7 +1,7 @@ use crate::config::schema::{ default_nostr_relays, DingTalkConfig, IrcConfig, LarkReceiveMode, LinqConfig, - NextcloudTalkConfig, NostrConfig, QQConfig, QQEnvironment, QQReceiveMode, SignalConfig, - StreamMode, WhatsAppConfig, + NextcloudTalkConfig, NostrConfig, ProgressMode, QQConfig, QQEnvironment, QQReceiveMode, + SignalConfig, StreamMode, WhatsAppConfig, }; use crate::config::{ AutonomyConfig, BrowserConfig, ChannelsConfig, ComposioConfig, Config, DiscordConfig, @@ -4436,6 +4436,7 @@ fn setup_channels() -> Result { draft_update_interval_ms: 1000, interrupt_on_new_message: false, mention_only: false, + progress_mode: ProgressMode::default(), group_reply: None, base_url: None, ack_enabled: true,