feat(#1134): add unread matrix messages as a loose end

When the local matrix daemon has rooms with unread notifications,
get_loose_ends now prepends an UnreadMatrix entry to the output so
agents see pending matrix activity alongside questions/reminders
without message content being exposed.

Changes:
- hive-sh4re: add LooseEnd::UnreadMatrix { rooms: u32 } variant
- hive-matrix-mcp: add DaemonRequest::UnreadCount and handler that
  counts joined rooms with notification_count > 0 (server-side push
  notification counts, cached by matrix-sdk)
- hive-ag3nt/mcp: inject UnreadMatrix entry on self-queries by
  connecting to /run/hive-matrix/socket (HIVE_MATRIX_SOCKET override);
  best-effort — agents without matrix configured are unaffected
This commit is contained in:
atlas 2026-06-03 12:40:41 +02:00
commit 46edc635f2
5 changed files with 114 additions and 14 deletions

View file

@ -343,3 +343,17 @@ pub async fn read_room(client: &Client, room_ref: &str, limit: Option<usize>) ->
.collect();
DaemonResponse::ok(&events)
}
/// Return the number of joined rooms with at least one unread
/// notification according to the server-side push notification counts
/// cached by the matrix-sdk client. Used by the harness to surface
/// unread matrix activity in `get_loose_ends` without exposing
/// message content.
pub fn unread_count(client: &Client) -> DaemonResponse {
let rooms = client
.joined_rooms()
.into_iter()
.filter(|r| r.unread_notification_counts().notification_count > 0)
.count() as u32;
DaemonResponse::ok(&serde_json::json!({ "rooms": rooms }))
}

View file

@ -81,6 +81,12 @@ pub enum DaemonRequest {
#[serde(rename = "join_room")]
JoinRoom { room: String },
/// Return the count of rooms with unread notifications. Used by
/// the harness `get_loose_ends` to surface unread matrix activity
/// without exposing message content.
#[serde(rename = "unread_count")]
UnreadCount,
/// Liveness probe — fast "are you up?" round-trip that doesn't
/// touch matrix-sdk. Not used by the in-tree stdio MCP bridge
/// (which surfaces a daemon-down condition as a normal tool-call

View file

@ -83,5 +83,6 @@ async fn dispatch(req: DaemonRequest, client: &Client) -> DaemonResponse {
DaemonRequest::JoinRoom { room } => handlers::join_room(client, &room).await,
DaemonRequest::ListRoomMembers { room } => handlers::list_room_members(client, &room).await,
DaemonRequest::ReadRoom { room, limit } => handlers::read_room(client, &room, limit).await,
DaemonRequest::UnreadCount => handlers::unread_count(client),
}
}