agent page: inbox view of last 30 messages addressed to this agent

new wire request AgentRequest::Recent { limit } / ManagerRequest::Recent
(plus matching responses with Vec<InboxRow>). InboxRow moved to
hive-sh4re so it lives on both surfaces without an internal-to-wire
conversion. host-side dispatch in agent_server / manager_server
calls broker.recent_for(name, limit).

per-agent web_ui /api/state grew an inbox: Vec<InboxRow> populated
via the same per-agent socket (best-effort; transport failure
returns empty). frontend renders as a collapsible <details> section
between the state row and the terminal — fmt timestamp / from /
body in a tight grid, capped at 16em scrollable. only visible when
there are rows.
This commit is contained in:
müde 2026-05-15 20:32:19 +02:00
parent bd7d2d4860
commit 538e0446d7
13 changed files with 151 additions and 20 deletions

View file

@ -124,6 +124,10 @@ struct StateSnapshot {
status: &'static str,
/// Present when `status == "needs_login_in_progress"`.
session: Option<SessionView>,
/// Last N messages addressed to this agent, newest-first. Pulled
/// from the broker via the per-agent socket on each render.
/// Empty on transport failure.
inbox: Vec<hive_sh4re::InboxRow>,
}
#[derive(Serialize)]
@ -157,14 +161,47 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
.ok()
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(7000);
let inbox = recent_inbox(&state.socket, state.flavor).await;
axum::Json(StateSnapshot {
label: state.label.clone(),
dashboard_port,
status,
session: session_view,
inbox,
})
}
/// Best-effort: pull the last 30 messages addressed to us via the
/// per-agent / manager socket. Empty list on any transport / decode
/// failure — the inbox section is decorative, not authoritative.
async fn recent_inbox(socket: &std::path::Path, flavor: Flavor) -> Vec<hive_sh4re::InboxRow> {
const LIMIT: u64 = 30;
match flavor {
Flavor::Agent => {
match client::request::<_, hive_sh4re::AgentResponse>(
socket,
&hive_sh4re::AgentRequest::Recent { limit: LIMIT },
)
.await
{
Ok(hive_sh4re::AgentResponse::Recent { rows }) => rows,
_ => Vec::new(),
}
}
Flavor::Manager => {
match client::request::<_, hive_sh4re::ManagerResponse>(
socket,
&hive_sh4re::ManagerRequest::Recent { limit: LIMIT },
)
.await
{
Ok(hive_sh4re::ManagerResponse::Recent { rows }) => rows,
_ => Vec::new(),
}
}
}
}
// ---------------------------------------------------------------------------
// Action handlers
// ---------------------------------------------------------------------------