feat: multi-account matrix daemon (account-routed mcp surface)

This commit is contained in:
damocles 2026-06-15 20:23:42 +02:00
commit 8e79eb4f26
7 changed files with 613 additions and 156 deletions

View file

@ -27,13 +27,24 @@ use crate::{handlers, wake};
/// `hyperhive_socket`. The wake body summarises ALL rooms with unread
/// notifications at wake time (not just the triggering event) so the
/// agent receives a full picture in one prompt.
pub fn install_message_handler(client: &Client, hyperhive_socket: PathBuf) {
///
/// `account_tag` is `Some(name)` only in multi-account mode (N>1 matrix
/// accounts on this daemon); when set it is prepended to the wake body
/// so the agent knows which account to `read_room` on. In the
/// single-account case it is `None` and the wake body is unchanged.
pub fn install_message_handler(
client: &Client,
hyperhive_socket: PathBuf,
account_tag: Option<String>,
) {
let socket = Arc::new(hyperhive_socket);
let account_tag = Arc::new(account_tag);
let own_user = client.user_id().map(std::borrow::ToOwned::to_owned);
client.add_event_handler({
let socket = socket.clone();
move |event: OriginalSyncRoomMessageEvent, room: Room, client: Client| {
let socket = socket.clone();
let account_tag = account_tag.clone();
let own_user = own_user.clone();
async move {
// INFO so the live host journal shows the handler actually
@ -82,6 +93,7 @@ pub fn install_message_handler(client: &Client, hyperhive_socket: PathBuf) {
} else {
wake::format_unread_summary(&unread)
};
let body = wake::tag_account(account_tag.as_ref().as_deref(), body);
if let Err(e) = wake::send_wake(&socket, &body).await {
tracing::warn!(error = %e, "failed to deliver matrix wake to hyperhive");
} else {
@ -106,7 +118,12 @@ pub fn install_message_handler(client: &Client, hyperhive_socket: PathBuf) {
/// than on every sync tick; it is pruned to the current invite set each
/// pass so a withdrawn-then-reissued invite wakes again. The agent
/// decides whether to accept or reject by calling `resolve_invite`.
pub async fn sweep_invites(client: &Client, socket: &Path, notified: &Mutex<HashSet<OwnedRoomId>>) {
pub async fn sweep_invites(
client: &Client,
socket: &Path,
notified: &Mutex<HashSet<OwnedRoomId>>,
account_tag: Option<&str>,
) {
let current = client.invited_rooms();
let current_ids: HashSet<OwnedRoomId> =
current.iter().map(|r| r.room_id().to_owned()).collect();
@ -134,9 +151,12 @@ pub async fn sweep_invites(client: &Client, socket: &Path, notified: &Mutex<Hash
let room_id = room.room_id().to_owned();
let label = room.name().unwrap_or_else(|| room_id.to_string());
tracing::info!(%room_id, "matrix: pending invite swept, waking agent");
let body = format!(
"[matrix] invited to {label} ({room_id}) — \
use list_invites to see pending invites, resolve_invite to accept or reject"
let body = wake::tag_account(
account_tag,
format!(
"[matrix] invited to {label} ({room_id}) — \
use list_invites to see pending invites, resolve_invite to accept or reject"
),
);
if let Err(e) = wake::send_wake(socket, &body).await {
tracing::warn!(error = %e, "matrix: failed to deliver invite-wake to hyperhive");