feat(#551): switch read_room to room.messages() for e2ee decryption, add UTD sentinel, update docs

This commit is contained in:
damocles 2026-06-03 23:26:26 +02:00 committed by mara
commit 78317b9cac
2 changed files with 46 additions and 16 deletions

View file

@ -119,8 +119,15 @@ Initial rollout settings:
engage). The absent
`yes_i_am_very_very_sure_…_open_registration_…` flag keeps the
server closed to anyone without the token.
- `allow_encryption = false` per operator call; E2EE re-enabling is
deferred to a follow-up.
- `allow_encryption = true` — E2EE enabled. Agent clients use the
`e2e-encryption` feature of `matrix-sdk`; crypto keys are stored in
the per-agent sqlite store under the state dir and persist across
restarts (lost on `--purge`). `read_room` decrypts via
`room.messages()` — UTD events surface as `event_type =
"m.room.encrypted"` with `body = "[unable to decrypt]"`.
Cross-signing and automatic key backup are not enabled for the first
pass: static bearer-token bot accounts can't bootstrap cross-signing
without MSC3967.
## Hive Matrix Space

View file

@ -116,6 +116,24 @@ fn extract_body(event: &matrix_sdk::ruma::events::AnyTimelineEvent) -> String {
}
}
/// Same as [`extract_body`] but for the `AnySyncTimelineEvent` variant
/// returned by `TimelineEvent::raw()` (the sdk-level decrypted form from
/// `room.messages()`).
fn extract_body_sync(event: &matrix_sdk::ruma::events::AnySyncTimelineEvent) -> String {
use matrix_sdk::ruma::events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent};
match event {
AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(ev)) => ev
.as_original()
.map_or_else(String::new, |orig| match &orig.content.msgtype {
MessageType::Text(t) => t.body.clone(),
MessageType::Notice(n) => n.body.clone(),
MessageType::Emote(e) => format!("* {}", e.body),
_ => String::new(),
}),
_ => String::new(),
}
}
pub async fn send_message(client: &Client, room_ref: &str, body: &str) -> DaemonResponse {
let room = match resolve_room(client, room_ref).await {
Ok(r) => r,
@ -408,32 +426,37 @@ pub async fn invite_user(client: &Client, room_ref: &str, user_id: &str) -> Daem
}
pub async fn read_room(client: &Client, room_ref: &str, limit: Option<usize>) -> DaemonResponse {
use matrix_sdk::ruma::api::Direction;
use matrix_sdk::ruma::api::client::message::get_message_events;
use matrix_sdk::room::MessagesOptions;
let room = match resolve_room(client, room_ref).await {
Ok(r) => r,
Err(e) => return e,
};
let limit = limit.unwrap_or(50).min(200);
let mut req =
get_message_events::v3::Request::new(room.room_id().to_owned(), Direction::Backward);
req.limit = matrix_sdk::ruma::UInt::try_from(limit as u64)
.unwrap_or(matrix_sdk::ruma::UInt::from(50u32));
let resp = match client.send(req).await {
Ok(r) => r,
Err(e) => return DaemonResponse::error(format!("get_message_events: {e}")),
let limit_val = u32::try_from(limit.unwrap_or(50).min(200)).unwrap_or(50);
let mut opts = MessagesOptions::backward();
opts.limit = matrix_sdk::ruma::UInt::from(limit_val);
// room.messages() transparently decrypts events in encrypted rooms.
// UTD (unable to decrypt) events surface via `ev.kind.is_utd()` and
// get a sentinel body so claude knows decryption failed rather than
// seeing the raw encrypted blob.
let msgs = match room.messages(opts).await {
Ok(m) => m,
Err(e) => return DaemonResponse::error(format!("messages: {e}")),
};
let events: Vec<TimelineEvent> = resp
let events: Vec<TimelineEvent> = msgs
.chunk
.iter()
.filter_map(|raw| {
let parsed = raw.deserialize().ok()?;
.filter_map(|ev| {
let parsed = ev.raw().deserialize().ok()?;
let event_id = parsed.event_id().to_string();
let sender = parsed.sender().to_string();
let origin_server_ts: i64 = parsed.origin_server_ts().0.into();
let event_type = parsed.event_type().to_string();
let body = extract_body(&parsed);
let body = if ev.kind.is_utd() {
"[unable to decrypt]".to_owned()
} else {
extract_body_sync(&parsed)
};
Some(TimelineEvent {
event_id,
sender,