huskies: merge 649_story_migrate_whatsapp_transport_to_status_broadcaster

This commit is contained in:
dave
2026-04-27 14:14:21 +00:00
parent 6c8043d866
commit cbb0a50729
3 changed files with 60 additions and 0 deletions
+15
View File
@@ -70,6 +70,13 @@ pub struct ProjectConfig {
/// Default: `true`. /// Default: `true`.
#[serde(default = "default_discord_status_consumer")] #[serde(default = "default_discord_status_consumer")]
pub discord_status_consumer: bool, 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"`). /// IANA timezone name (e.g. `"Europe/London"`, `"America/New_York"`).
/// When set, timer HH:MM inputs are interpreted in this timezone instead /// 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. /// of the container/host local time. Falls back to `chrono::Local` when absent.
@@ -166,6 +173,10 @@ fn default_discord_status_consumer() -> bool {
true true
} }
fn default_whatsapp_status_consumer() -> bool {
true
}
fn default_max_mesh_peers() -> usize { fn default_max_mesh_peers() -> usize {
3 3
} }
@@ -299,6 +310,7 @@ impl Default for ProjectConfig {
matrix_status_consumer: default_matrix_status_consumer(), matrix_status_consumer: default_matrix_status_consumer(),
slack_status_consumer: default_slack_status_consumer(), slack_status_consumer: default_slack_status_consumer(),
discord_status_consumer: default_discord_status_consumer(), discord_status_consumer: default_discord_status_consumer(),
whatsapp_status_consumer: default_whatsapp_status_consumer(),
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -384,6 +396,7 @@ impl ProjectConfig {
matrix_status_consumer: default_matrix_status_consumer(), matrix_status_consumer: default_matrix_status_consumer(),
slack_status_consumer: default_slack_status_consumer(), slack_status_consumer: default_slack_status_consumer(),
discord_status_consumer: default_discord_status_consumer(), discord_status_consumer: default_discord_status_consumer(),
whatsapp_status_consumer: default_whatsapp_status_consumer(),
timezone: legacy.timezone, timezone: legacy.timezone,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -420,6 +433,7 @@ impl ProjectConfig {
matrix_status_consumer: default_matrix_status_consumer(), matrix_status_consumer: default_matrix_status_consumer(),
slack_status_consumer: default_slack_status_consumer(), slack_status_consumer: default_slack_status_consumer(),
discord_status_consumer: default_discord_status_consumer(), discord_status_consumer: default_discord_status_consumer(),
whatsapp_status_consumer: default_whatsapp_status_consumer(),
timezone: legacy.timezone, timezone: legacy.timezone,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -444,6 +458,7 @@ impl ProjectConfig {
matrix_status_consumer: default_matrix_status_consumer(), matrix_status_consumer: default_matrix_status_consumer(),
slack_status_consumer: default_slack_status_consumer(), slack_status_consumer: default_slack_status_consumer(),
discord_status_consumer: default_discord_status_consumer(), discord_status_consumer: default_discord_status_consumer(),
whatsapp_status_consumer: default_whatsapp_status_consumer(),
timezone: legacy.timezone, timezone: legacy.timezone,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
+33
View File
@@ -752,6 +752,39 @@ async fn main() -> Result<(), std::io::Error> {
watcher_rx_for_whatsapp, watcher_rx_for_whatsapp,
root.clone(), 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<dyn crate::chat::ChatTransport>;
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<String> =
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 { } else {
drop(watcher_rx_for_whatsapp); drop(watcher_rx_for_whatsapp);
} }
+12
View File
@@ -532,6 +532,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -565,6 +566,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -598,6 +600,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -631,6 +634,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -663,6 +667,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -702,6 +707,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -782,6 +788,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -820,6 +827,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -905,6 +913,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -946,6 +955,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -977,6 +987,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),
@@ -1014,6 +1025,7 @@ mod tests {
matrix_status_consumer: true, matrix_status_consumer: true,
slack_status_consumer: true, slack_status_consumer: true,
discord_status_consumer: true, discord_status_consumer: true,
whatsapp_status_consumer: true,
timezone: None, timezone: None,
rendezvous: None, rendezvous: None,
trusted_keys: Vec::new(), trusted_keys: Vec::new(),