From 5b442796cad598a77cb7d99748d8eac30885a620 Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 27 Jun 2026 11:51:34 +0200 Subject: [PATCH] feat(#2039): list matrix identities in get_agent_meta --- hive-ag3nt/src/mcp.rs | 20 ++++++++++++++++++++ hive-c0re/src/socket_server.rs | 15 +++++++++++++++ hive-sh4re/src/lib.rs | 24 ++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 05b189bb..60ab993b 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -58,6 +58,7 @@ pub enum SocketReply { status_set_at: Option, hive_name: Option, swarm_name: Option, + matrix_accounts: Vec, }, /// `create_repo` result — the new repo's full name + clone URL. RepoCreated { @@ -109,6 +110,7 @@ impl From for SocketReply { status_set_at, hive_name, swarm_name, + matrix_accounts, } => Self::AgentMeta { name, running, @@ -117,6 +119,7 @@ impl From for SocketReply { status_set_at, hive_name, swarm_name, + matrix_accounts, }, hive_sh4re::Response::RepoCreated { full_name, @@ -452,6 +455,7 @@ pub fn format_agent_meta(resp: Result) -> String { status_set_at, hive_name, swarm_name, + matrix_accounts, }) => { let rev = hyperhive_rev.as_deref().unwrap_or(""); let run = if running { "yes" } else { "no" }; @@ -498,6 +502,22 @@ pub fn format_agent_meta(resp: Result) -> String { } } } + // Matrix identities the agent can act as (the `account` arg on + // the matrix tools). Listed only when matrix is provisioned, so + // non-matrix agents don't see an empty line. + if !matrix_accounts.is_empty() { + use std::fmt::Write as _; + out.push_str("\nmatrix_accounts:"); + for acct in &matrix_accounts { + let uid = acct.user_id.as_deref().unwrap_or("?"); + let primary = if acct.is_primary { " [primary]" } else { "" }; + let _ = write!( + out, + "\n {} ({uid}) on {}{primary}", + acct.name, acct.homeserver + ); + } + } out } Ok(SocketReply::Err(m)) => format!("get_agent_meta failed: {m}"), diff --git a/hive-c0re/src/socket_server.rs b/hive-c0re/src/socket_server.rs index 4fcde8c1..75bcf0be 100644 --- a/hive-c0re/src/socket_server.rs +++ b/hive-c0re/src/socket_server.rs @@ -417,9 +417,24 @@ async fn handle_get_agent_meta( status_set_at, hive_name, swarm_name, + matrix_accounts: read_agent_matrix_identities(target), } } +/// Read the target agent's matrix identities from the daemon's +/// `matrix-accounts.json` snapshot (under the agent's state dir). +/// Best-effort: an absent / unparseable snapshot (no matrix provisioning, +/// or the daemon not up yet) yields an empty list. The `MatrixIdentity` +/// serde shape matches the snapshot entries; the snapshot's `live` field is +/// ignored (only live accounts are written). +fn read_agent_matrix_identities(agent: &str) -> Vec { + let path = Coordinator::agent_notes_dir(agent).join("matrix-accounts.json"); + std::fs::read_to_string(&path) + .ok() + .and_then(|s| serde_json::from_str::>(&s).ok()) + .unwrap_or_default() +} + /// `Status` — count of pending (unread) inbox messages for `agent`. fn handle_status(coord: &Arc, agent: &str) -> hive_sh4re::Response { match coord.broker.count_pending(agent) { diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 155cfd5e..4e965e70 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -845,6 +845,10 @@ pub enum Response { hive_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] swarm_name: Option, + /// Matrix identities this agent can act as (one per configured + + /// live account). Empty for agents with no matrix provisioning. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + matrix_accounts: Vec, }, /// `GetLogs` result: journal lines for the requested container. /// Returned on the manager socket only. @@ -918,6 +922,26 @@ pub struct AgentStatusRow { pub parent: Option, } +/// One matrix identity an agent can act as, surfaced in `GetAgentMeta`'s +/// `matrix_accounts`. Field names match the daemon's `matrix-accounts.json` +/// snapshot (written by `hive-matrix-mcp`'s account registry) so hive-c0re +/// deserializes the snapshot straight into `Vec`; the +/// snapshot's `live` field is ignored here (only live accounts are listed). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MatrixIdentity { + /// Logical account name (the `account` arg on the matrix MCP tools). + pub name: String, + /// Matrix user id (`@user:server`). `None` if the session restored + /// without a known user id yet. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_id: Option, + /// Homeserver base URL this account is on. + pub homeserver: String, + /// Whether this is the primary account (selected when a matrix tool + /// call omits `account`). + pub is_primary: bool, +} + /// Serde default for the `running` field; keeps wire backwards-compat /// with pre-running-field payloads. See /// `docs/conventions.md::Agent metadata`.