diff --git a/src/channels/mod.rs b/src/channels/mod.rs index 9a5be6e5c..3140bbf86 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -288,9 +288,11 @@ const OPENRC_STATUS_ARGS: [&str; 2] = ["zeroclaw", "status"]; const OPENRC_RESTART_ARGS: [&str; 2] = ["zeroclaw", "restart"]; #[derive(Clone, Copy)] +#[allow(clippy::struct_excessive_bools)] struct InterruptOnNewMessageConfig { telegram: bool, slack: bool, + discord: bool, mattermost: bool, } @@ -299,6 +301,7 @@ impl InterruptOnNewMessageConfig { match channel { "telegram" => self.telegram, "slack" => self.slack, + "discord" => self.discord, "mattermost" => self.mattermost, _ => false, } @@ -4385,6 +4388,11 @@ pub async fn start_channels(config: Config) -> Result<()> { .slack .as_ref() .is_some_and(|sl| sl.interrupt_on_new_message); + let interrupt_on_new_message_discord = config + .channels_config + .discord + .as_ref() + .is_some_and(|dc| dc.interrupt_on_new_message); let interrupt_on_new_message_mattermost = config .channels_config .mattermost @@ -4418,6 +4426,7 @@ pub async fn start_channels(config: Config) -> Result<()> { interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: interrupt_on_new_message, slack: interrupt_on_new_message_slack, + discord: interrupt_on_new_message_discord, mattermost: interrupt_on_new_message_mattermost, }, multimodal: config.multimodal.clone(), @@ -4735,6 +4744,7 @@ mod tests { interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -4848,6 +4858,7 @@ mod tests { interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -4917,6 +4928,7 @@ mod tests { interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -5005,6 +5017,7 @@ mod tests { interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -5545,6 +5558,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, non_cli_excluded_tools: Arc::new(Vec::new()), @@ -5621,6 +5635,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, non_cli_excluded_tools: Arc::new(Vec::new()), @@ -5712,6 +5727,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -5788,6 +5804,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -5874,6 +5891,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -5981,6 +5999,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6069,6 +6088,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6172,6 +6192,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6260,6 +6281,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6338,6 +6360,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6527,6 +6550,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6624,6 +6648,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: true, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6736,6 +6761,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: true, + discord: false, mattermost: false, }, ack_reactions: true, @@ -6845,6 +6871,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: true, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -6936,6 +6963,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -7012,6 +7040,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -7764,6 +7793,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -7890,6 +7920,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -8055,6 +8086,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -8157,6 +8189,7 @@ BTC is currently around $65,000 based on latest tool output."# interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -8724,6 +8757,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -8807,6 +8841,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -8964,6 +8999,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -9071,6 +9107,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -9170,6 +9207,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -9289,6 +9327,7 @@ This is an example JSON object for profile settings."#; interrupt_on_new_message: InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }, multimodal: crate::config::MultimodalConfig::default(), @@ -9417,6 +9456,7 @@ This is an example JSON object for profile settings."#; let cfg = InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: true, }; assert!(cfg.enabled_for_channel("mattermost")); @@ -9427,8 +9467,31 @@ This is an example JSON object for profile settings."#; let cfg = InterruptOnNewMessageConfig { telegram: false, slack: false, + discord: false, mattermost: false, }; assert!(!cfg.enabled_for_channel("mattermost")); } + + #[test] + fn interrupt_on_new_message_enabled_for_discord() { + let cfg = InterruptOnNewMessageConfig { + telegram: false, + slack: false, + discord: true, + mattermost: false, + }; + assert!(cfg.enabled_for_channel("discord")); + } + + #[test] + fn interrupt_on_new_message_disabled_for_discord_by_default() { + let cfg = InterruptOnNewMessageConfig { + telegram: false, + slack: false, + discord: false, + mattermost: false, + }; + assert!(!cfg.enabled_for_channel("discord")); + } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 5b371aa9a..27fb51632 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -63,6 +63,7 @@ mod tests { guild_id: Some("123".into()), allowed_users: vec![], listen_to_bots: false, + interrupt_on_new_message: false, mention_only: false, }; diff --git a/src/config/schema.rs b/src/config/schema.rs index f9b39014d..0f9457857 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -4660,6 +4660,10 @@ pub struct DiscordConfig { /// The bot still ignores its own messages to prevent feedback loops. #[serde(default)] pub listen_to_bots: bool, + /// When true, a newer Discord message from the same sender in the same channel + /// cancels the in-flight request and starts a fresh response with preserved history. + #[serde(default)] + pub interrupt_on_new_message: bool, /// When true, only respond to messages that @-mention the bot. /// Other messages in the guild are silently ignored. #[serde(default)] @@ -9414,6 +9418,7 @@ tool_dispatcher = "xml" guild_id: Some("12345".into()), allowed_users: vec![], listen_to_bots: false, + interrupt_on_new_message: false, mention_only: false, }; let json = serde_json::to_string(&dc).unwrap(); @@ -9429,6 +9434,7 @@ tool_dispatcher = "xml" guild_id: None, allowed_users: vec![], listen_to_bots: false, + interrupt_on_new_message: false, mention_only: false, }; let json = serde_json::to_string(&dc).unwrap(); @@ -9692,6 +9698,20 @@ allowed_users = ["@ops:matrix.org"] assert!(!parsed.mention_only); } + #[test] + async fn discord_config_default_interrupt_on_new_message_is_false() { + let json = r#"{"bot_token":"tok"}"#; + let parsed: DiscordConfig = serde_json::from_str(json).unwrap(); + assert!(!parsed.interrupt_on_new_message); + } + + #[test] + async fn discord_config_deserializes_interrupt_on_new_message_true() { + let json = r#"{"bot_token":"tok","interrupt_on_new_message":true}"#; + let parsed: DiscordConfig = serde_json::from_str(json).unwrap(); + assert!(parsed.interrupt_on_new_message); + } + #[test] async fn discord_config_toml_backward_compat() { let toml_str = r#" diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 5a4371e5d..9ff85bcc6 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -3850,6 +3850,7 @@ fn setup_channels() -> Result { guild_id: if guild.is_empty() { None } else { Some(guild) }, allowed_users, listen_to_bots: false, + interrupt_on_new_message: false, mention_only: false, }); } diff --git a/web/dist/.gitkeep b/web/dist/.gitkeep deleted file mode 100644 index e69de29bb..000000000