Watch
0
0
Fork
You've already forked hyperhive
0

matrix-mcp: send_dm and open_dm accept a bare agent name

This commit is contained in:
damocles 2026-11-07 11:02:21 +01:00 • committed by mara
commit 66f4984223
2 changed files with 35 additions and 4 deletions

View file

@ -511,6 +511,19 @@ fn recipient_among(
})
}
/// A bare agent name (`iris`, no `@` and no `:`) means that agent's account
/// on the caller's own homeserver, `@iris:<own server>`; anything else is
/// passed through for the normal user id parse. Lets a simple agent DM a peer
/// by the name it knows it by.
fn expand_user_id(own_server: Option<&str>, input: &str) -> String {
match own_server {
Some(server) if !input.is_empty() && !input.contains(['@', ':']) => {
format!("@{input}:{server}")
}
_ => input.to_owned(),
}
}
/// Find the existing DM room with `user_id` or create one, and say where the
/// recipient stands in it. Shared by `send_dm` and `open_dm`.
/// `direct_targets()` (cached state) is used rather than the async
@ -523,6 +536,8 @@ async fn resolve_or_create_dm(
client: &Client,
user_id: &str,
) -> Result<(matrix_sdk::Room, Recipient), DaemonResponse> {
let own = client.user_id().map(|u| u.server_name().to_string());
let user_id = expand_user_id(own.as_deref(), user_id);
let uid: OwnedUserId = user_id
.parse()
.map_err(|e| DaemonResponse::error(format!("invalid user_id {user_id}: {e}")))?;
@ -568,6 +583,9 @@ fn recipient_fields(recipient: Recipient) -> serde_json::Value {
}
pub async fn send_dm(client: &Client, user_id: &str, body: &str) -> DaemonResponse {
let own = client.user_id().map(|u| u.server_name().to_string());
let user_id = expand_user_id(own.as_deref(), user_id);
let user_id = user_id.as_str();
let (room, recipient) = match resolve_or_create_dm(client, user_id).await {
Ok(r) => r,
Err(e) => return e,
@ -1321,9 +1339,9 @@ mod utd_body_tests {
#[cfg(test)]
mod unread_tests {
use super::{
ParsedEvent, TimelineEvent, attachment_marker, collapse_edits, extract_body_sync,
extract_in_reply_to, is_intentional, media_parts, replacement_of, safe_file_name,
unread_verdict,
ParsedEvent, TimelineEvent, attachment_marker, collapse_edits, expand_user_id,
extract_body_sync, extract_in_reply_to, is_intentional, media_parts, replacement_of,
safe_file_name, unread_verdict,
};
use matrix_sdk::ruma::events::AnySyncTimelineEvent;
use matrix_sdk::ruma::events::room::message::MessageType;
@ -1368,6 +1386,18 @@ mod unread_tests {
.map(|(id, sender, body)| (id.to_string(), sender.to_string(), body))
}
#[test]
fn a_bare_agent_name_means_that_agent_on_the_own_homeserver() {
let own = Some("pr1ma.darkest.space");
assert_eq!(expand_user_id(own, "iris"), "@iris:pr1ma.darkest.space");
// Full ids and anything with a server part pass through untouched.
assert_eq!(expand_user_id(own, "@mara:matrix.org"), "@mara:matrix.org");
assert_eq!(expand_user_id(own, "iris:other.org"), "iris:other.org");
// No own server known, or nothing given: nothing to expand with.
assert_eq!(expand_user_id(None, "iris"), "iris");
assert_eq!(expand_user_id(own, ""), "");
}
#[test]
fn an_empty_room_is_caught_up() {
assert_eq!(verdict(None, &[]), None);

View file

@ -280,7 +280,8 @@ impl MatrixMcp {
}
#[tool(description = "Open (or reuse) a direct message room with `user_id` \
(@user:server) and post `body` to it. If the DM room already exists \
(@user:server, or just an agent's name such as `iris` for that agent \
on this homeserver) and post `body` to it. If the DM room already exists \
and has unread messages, the send is rejected with a hint — read_room \
then mark_read the latest event first.")]
async fn send_dm(&self, Parameters(args): Parameters<SendDmArgs>) -> String {