Watch
0
0
Fork
You've already forked hyperhive
0

matrix-mcp: sync through a filter that drops presence, typing and unbounded state

This commit is contained in:
damocles 2026-11-07 10:56:28 +01:00
commit e436d356f3

View file

@ -749,6 +749,27 @@ async fn restore_account(cfg: &AccountCfg, is_primary: bool) -> Result<Option<Cl
Ok(Some(client))
}
/// The server-side sync filter every account uses. The default (no filter)
/// ships presence, typing notices, full member state and unbounded
/// timelines in every delta, about 50 MB/h on a busy public homeserver.
/// This keeps what the daemon uses: room messages (short timelines; unread
/// scanning pages back with `/messages`), read receipts (the agent's own
/// receipt marks what it has read), invites and account data. Members are
/// lazy-loaded: `Room::members` fetches the full list on demand.
fn sync_filter() -> 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<String>) -> 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<String>) -> 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;