diff --git a/hive-matrix-mcp/src/main.rs b/hive-matrix-mcp/src/main.rs index bcb641c5..3b80bc4a 100644 --- a/hive-matrix-mcp/src/main.rs +++ b/hive-matrix-mcp/src/main.rs @@ -749,6 +749,27 @@ async fn restore_account(cfg: &AccountCfg, is_primary: bool) -> Result matrix_sdk::ruma::api::client::sync::sync_events::v3::Filter { + use matrix_sdk::ruma::api::client::{filter::FilterDefinition, sync::sync_events::v3::Filter}; + let definition: FilterDefinition = serde_json::from_value(serde_json::json!({ + "presence": {"not_types": ["*"]}, + "room": { + "ephemeral": {"not_types": ["m.typing"]}, + "state": {"lazy_load_members": true}, + "timeline": {"limit": 20}, + }, + })) + .expect("the sync filter is a valid filter definition"); + Filter::FilterDefinition(definition) +} + /// `client`'s sync loop, sweeping invites and unread rooms into todos tagged /// `tag` after each sync. Builds the loop only; nothing runs until it is /// polled. Never wipes the todo set: that is `main`'s, once, at startup. @@ -762,17 +783,20 @@ fn account_sync_loop(client: &Client, tag: Option) -> SyncLoop { let unread_notified = Arc::new(tokio::sync::Mutex::new(std::collections::HashSet::new())); Box::pin(async move { sync_client - .sync_with_callback(SyncSettings::default(), move |_response| { - let client = cb_client.clone(); - let invite_notified = invite_notified.clone(); - let unread_notified = unread_notified.clone(); - let tag = tag.clone(); - async move { - timeline::sweep_invites(&client, &invite_notified, tag.as_deref()).await; - timeline::sweep_unread(&client, &unread_notified, tag.as_deref()).await; - matrix_sdk::LoopCtrl::Continue - } - }) + .sync_with_callback( + SyncSettings::default().filter(sync_filter()), + move |_response| { + let client = cb_client.clone(); + let invite_notified = invite_notified.clone(); + let unread_notified = unread_notified.clone(); + let tag = tag.clone(); + async move { + timeline::sweep_invites(&client, &invite_notified, tag.as_deref()).await; + timeline::sweep_unread(&client, &unread_notified, tag.as_deref()).await; + matrix_sdk::LoopCtrl::Continue + } + }, + ) .await .context("matrix-sdk sync loop exited")?; Ok(()) @@ -781,6 +805,28 @@ fn account_sync_loop(client: &Client, tag: Option) -> SyncLoop { #[cfg(test)] mod tests { + /// The filter keeps the sync cheap: no presence, no typing, lazy members, + /// short timelines. Receipts must stay: the unread check reads the + /// agent's own. + #[test] + fn the_sync_filter_drops_the_bulk_and_keeps_receipts() { + use matrix_sdk::ruma::api::client::sync::sync_events::v3::Filter; + + let Filter::FilterDefinition(def) = super::sync_filter() else { + panic!("an inline definition, not a filter id"); + }; + let v = serde_json::to_value(&def).expect("serialize"); + assert_eq!(v["presence"]["not_types"], serde_json::json!(["*"])); + let ephemeral = &v["room"]["ephemeral"]["not_types"]; + assert_eq!(*ephemeral, serde_json::json!(["m.typing"])); + assert!( + !ephemeral.to_string().contains("receipt"), + "receipts are kept" + ); + assert_eq!(v["room"]["state"]["lazy_load_members"], true); + assert_eq!(v["room"]["timeline"]["limit"], 20); + } + use std::time::Duration; use tokio::time::Instant;