feat(#1137): rich unread summary in loose ends and wake signal
- hive-sh4re: UnreadMatrix gains summary: String field (per-room breakdown) - hive-matrix-mcp/protocol: add RoomUnread struct + UnreadSummary request - hive-matrix-mcp/handlers: collect_unread() fetches per-room data; single-unread rooms include truncated last-message body + sender; multi-unread rooms carry count only - hive-matrix-mcp/wake: format_unread_summary() builds wake body from RoomUnread slice; terse one-liner for single-room/single-message, bulleted list for multi-room; always appends read-hint - hive-matrix-mcp/timeline: wake body now covers all rooms with unread at fire time, not just the triggering event; falls back to per-event teaser if notification counts haven't updated yet - hive-ag3nt/mcp: matrix_unread_summary() replaces matrix_unread_rooms(); UnreadMatrix loose end carries per-room summary lines; render shows room breakdown with sender: body for single-unread rooms
This commit is contained in:
parent
46edc635f2
commit
68e30b857c
19 changed files with 421 additions and 108 deletions
|
|
@ -72,10 +72,51 @@ pub fn format_wake_body(sender: &str, room: &str, text: &str) -> String {
|
|||
format!("[matrix] {sender} in {room}: {truncated}")
|
||||
}
|
||||
|
||||
/// Format a wake-message body from a list of per-room unread summaries.
|
||||
/// Single-room / single-message case collapses to the terse one-liner
|
||||
/// format; multiple rooms expand to a bulleted list. Always appends a
|
||||
/// read-hint line so the agent knows which tools to reach for.
|
||||
#[must_use]
|
||||
pub fn format_unread_summary(rooms: &[crate::protocol::RoomUnread]) -> String {
|
||||
use std::fmt::Write as _;
|
||||
if rooms.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
// Terse path: exactly one room, exactly one unread with body.
|
||||
if rooms.len() == 1 {
|
||||
let r = &rooms[0];
|
||||
if r.count == 1 {
|
||||
if let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender) {
|
||||
return format!(
|
||||
"[matrix] {sender} in {label}: {body} — use read_room to view, mark_read to clear",
|
||||
label = r.label
|
||||
);
|
||||
}
|
||||
}
|
||||
return format!(
|
||||
"[matrix] {} unread in {} — use read_room to view, mark_read to clear",
|
||||
r.count, r.label
|
||||
);
|
||||
}
|
||||
// Multi-room path.
|
||||
let mut out = String::from("[matrix] unread messages:");
|
||||
for r in rooms {
|
||||
if r.count == 1 {
|
||||
if let (Some(body), Some(sender)) = (&r.last_body, &r.last_sender) {
|
||||
let _ = write!(out, "\n- {}: {sender}: {body}", r.label);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let _ = write!(out, "\n- {}: {} unread", r.label, r.count);
|
||||
}
|
||||
out.push_str("\nUse list_rooms + read_room to view, mark_read to clear.");
|
||||
out
|
||||
}
|
||||
|
||||
/// Truncate `s` to `max` Unicode chars, appending `…` when cut.
|
||||
/// Char-based not byte-based so multi-byte content (most chat) doesn't
|
||||
/// get cut mid-codepoint.
|
||||
fn truncate_chars(s: &str, max: usize) -> String {
|
||||
pub fn truncate_chars(s: &str, max: usize) -> String {
|
||||
let mut end = s.len();
|
||||
for (count, (i, _)) in s.char_indices().enumerate() {
|
||||
if count == max {
|
||||
|
|
|
|||
Loading…
Reference in a new issue