Watch
0
0
Fork
You've already forked hyperhive
0

agents: remove send(operator) and the operator inbox; alert on turn failures

Agents reach the operator over matrix now (#5115). A `send` to
`operator` is refused, by the broker and by the MCP allow-list check,
with an error that names the matrix MCP tools. The dashboard's Y3R C4LL
tab goes with it: the operator inbox, `GET /api/operator-inbox`,
`Broker::unread_for_recipient`, and the browser notifications that only
ever fired on operator-bound messages.

The harness no longer posts turn failures and plugin-install failures
to the operator inbox. Both are already logged at warn; a new Grafana
rule, `hh-agent-turn-failed`, fires per agent on the harness's
`turn failed` line.

Dashboard-to-agent messaging (op-send, the agent web UI's message box,
mark-all-read for agent inboxes) is unchanged.

Decisions per mara on #5115: q1 a, q2 a plus a grafana alert, q3 keep
dashboard-to-agent messaging and leave swarm-level code alone.
This commit is contained in:
atlas 2026-10-12 00:44:11 +02:00 • committed by mara
commit 7e6a4c7727
37 changed files with 246 additions and 848 deletions

View file

@ -1,6 +1,6 @@
//! Remaining single-endpoint dashboard handlers: the operator inbox
//! (`Y3R C4LL`) + mark-all-read, operator compose (`op-send`),
//! spawn-request, hive-wide turn stats, and container resources.
//! Remaining single-endpoint dashboard handlers: mark-all-read, operator
//! compose (`op-send`), spawn-request, hive-wide turn stats, and container
//! resources.
use axum::{
extract::{Form, Path as AxumPath, State},
@ -10,80 +10,10 @@ use axum::{
use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use super::{AppState, Ident, error_response, scan_validated_paths};
use super::{AppState, Ident, error_response};
use crate::container_stats::ContainerResource;
use crate::hive_stats::HiveStats;
#[derive(Serialize, ToSchema)]
pub(super) struct OperatorInboxItem {
id: i64,
from: String,
body: String,
at: chrono::DateTime<chrono::Utc>,
in_reply_to: Option<i64>,
file_refs: Vec<String>,
}
#[derive(Serialize, ToSchema)]
pub(super) struct OperatorInboxBody {
messages: Vec<OperatorInboxItem>,
}
/// Unread operator-directed messages for the dashboard's Y3R C4LL inbox.
///
/// Returns messages addressed to `"operator"` that haven't been
/// acked yet (the operator clears them via the existing
/// `POST /api/agent/operator/mark-all-read`). Newest-first; path-shaped
/// tokens are validated so the client renders file links like the
/// terminal does.
#[utoipa::path(
get,
path = "/api/operator-inbox",
responses(
(status = 200, description = "unread operator-directed messages", body = OperatorInboxBody),
(status = 500, description = "broker read failed"),
),
tag = "misc_api"
)]
pub(super) async fn api_operator_inbox(State(state): State<AppState>) -> Response {
const INBOX_LIMIT: u64 = 100;
match state
.coord
.broker
.unread_for_recipient("operator", INBOX_LIMIT)
{
Ok(messages) => {
let messages: Vec<OperatorInboxItem> = messages
.into_iter()
.filter_map(|m| {
let crate::broker::MessageEvent::Sent {
id,
from,
body,
at,
in_reply_to,
..
} = m
else {
return None;
};
let file_refs = scan_validated_paths(&body);
Some(OperatorInboxItem {
id,
from,
at: hive_sh4re::wire_time::from_secs(at),
body,
in_reply_to,
file_refs,
})
})
.collect();
axum::Json(OperatorInboxBody { messages }).into_response()
}
Err(e) => error_response(&format!("operator-inbox failed: {e:#}")),
}
}
#[derive(Deserialize, IntoParams)]
pub(super) struct StatsHiveQuery {
/// Stats window; defaults to `24h`.
@ -227,7 +157,7 @@ pub(super) async fn post_op_send(
// 200 instead of 303 → the client doesn't refetch /api/state. The
// broker `send` already emitted a `MessageEvent` which the
// dashboard channel forwarder mirrors as `DashboardEvent::Sent`,
// and the page's terminal + inbox derive from that stream — so the
// and the page's terminal derives from that stream — so the
// operator's send shows up the same way an agent's send does, with
// no full-state refresh in between.
(axum::http::StatusCode::OK, "ok").into_response()