diff --git a/server/src/config.rs b/server/src/config.rs index 275821d5..b1d830d7 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -70,6 +70,13 @@ pub struct ProjectConfig { /// Default: `true`. #[serde(default = "default_discord_status_consumer")] pub discord_status_consumer: bool, + /// Whether the WhatsApp bot subscribes to the status broadcaster and forwards + /// pipeline events to all active senders. + /// Set to `false` to silence WhatsApp status notifications without affecting + /// other consumers (web UI, Matrix, Slack, Discord, agent context). + /// Default: `true`. + #[serde(default = "default_whatsapp_status_consumer")] + pub whatsapp_status_consumer: bool, /// IANA timezone name (e.g. `"Europe/London"`, `"America/New_York"`). /// When set, timer HH:MM inputs are interpreted in this timezone instead /// of the container/host local time. Falls back to `chrono::Local` when absent. @@ -166,6 +173,10 @@ fn default_discord_status_consumer() -> bool { true } +fn default_whatsapp_status_consumer() -> bool { + true +} + fn default_max_mesh_peers() -> usize { 3 } @@ -299,6 +310,7 @@ impl Default for ProjectConfig { matrix_status_consumer: default_matrix_status_consumer(), slack_status_consumer: default_slack_status_consumer(), discord_status_consumer: default_discord_status_consumer(), + whatsapp_status_consumer: default_whatsapp_status_consumer(), timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -384,6 +396,7 @@ impl ProjectConfig { matrix_status_consumer: default_matrix_status_consumer(), slack_status_consumer: default_slack_status_consumer(), discord_status_consumer: default_discord_status_consumer(), + whatsapp_status_consumer: default_whatsapp_status_consumer(), timezone: legacy.timezone, rendezvous: None, trusted_keys: Vec::new(), @@ -420,6 +433,7 @@ impl ProjectConfig { matrix_status_consumer: default_matrix_status_consumer(), slack_status_consumer: default_slack_status_consumer(), discord_status_consumer: default_discord_status_consumer(), + whatsapp_status_consumer: default_whatsapp_status_consumer(), timezone: legacy.timezone, rendezvous: None, trusted_keys: Vec::new(), @@ -444,6 +458,7 @@ impl ProjectConfig { matrix_status_consumer: default_matrix_status_consumer(), slack_status_consumer: default_slack_status_consumer(), discord_status_consumer: default_discord_status_consumer(), + whatsapp_status_consumer: default_whatsapp_status_consumer(), timezone: legacy.timezone, rendezvous: None, trusted_keys: Vec::new(), diff --git a/server/src/main.rs b/server/src/main.rs index 266df7c7..ed3ce2c7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -752,6 +752,39 @@ async fn main() -> Result<(), std::io::Error> { watcher_rx_for_whatsapp, root.clone(), ); + + // Subscribe to the status broadcaster if the whatsapp_status_consumer toggle + // is enabled (default: true). Formats each StatusEvent via the common + // formatter and sends the resulting text to all active WhatsApp senders. + // The task exits automatically when the broadcaster is dropped on shutdown. + { + use crate::service::status::format::format_status_event; + + let status_enabled = config::ProjectConfig::load(root) + .map(|c| c.whatsapp_status_consumer) + .unwrap_or(true); + + if status_enabled { + let mut sub = ctx.services.status.subscribe(); + let transport = Arc::clone(&ctx.transport) as Arc; + let ambient_rooms = Arc::clone(&ctx.services.ambient_rooms); + tokio::spawn(async move { + while let Some(event) = sub.recv().await { + let plain = format_status_event(&event); + let rooms: Vec = + ambient_rooms.lock().unwrap().iter().cloned().collect(); + for room in &rooms { + if let Err(e) = transport.send_message(room, &plain, "").await { + crate::slog!( + "[whatsapp] Failed to send status event to {room}: {e}" + ); + } + } + } + crate::slog!("[whatsapp] Status subscriber task exiting — broadcaster dropped"); + }); + } + } } else { drop(watcher_rx_for_whatsapp); } diff --git a/server/src/worktree.rs b/server/src/worktree.rs index a7efd762..8ed1c892 100644 --- a/server/src/worktree.rs +++ b/server/src/worktree.rs @@ -532,6 +532,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -565,6 +566,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -598,6 +600,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -631,6 +634,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -663,6 +667,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -702,6 +707,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -782,6 +788,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -820,6 +827,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -905,6 +913,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -946,6 +955,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -977,6 +987,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(), @@ -1014,6 +1025,7 @@ mod tests { matrix_status_consumer: true, slack_status_consumer: true, discord_status_consumer: true, + whatsapp_status_consumer: true, timezone: None, rendezvous: None, trusted_keys: Vec::new(),