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

@ -301,44 +301,6 @@ impl Broker {
.map_err(Into::into)
}
/// Unacknowledged messages addressed to `recipient`, newest-first.
/// Backs the dashboard's operator inbox: the operator never
/// `recv`s over an agent socket, so messages to `"operator"` sit in
/// the broker with `acked_at IS NULL` until the operator hits "mark
/// all read" (which calls [`Broker::mark_all_read`]). This read
/// mirrors that filter EXACTLY — `recipient = ?1 AND acked_at IS
/// NULL`, with no `delivered_at` condition — so everything listed
/// here is precisely what `mark_all_read` will clear (operator rows
/// may never get `delivered_at` set). Returned as
/// [`MessageEvent::Sent`] so the dashboard reuses its live renderer.
///
/// # Errors
///
/// Returns `Err` if the `SQLite` prepare or query fails.
pub fn unread_for_recipient(&self, recipient: &str, limit: u64) -> Result<Vec<MessageEvent>> {
let conn = self.conn.lock().unwrap();
let limit_i = i64::try_from(limit.min(i64::MAX as u64)).unwrap_or(i64::MAX);
let mut stmt = conn.prepare(
"SELECT id, sender, recipient, body, sent_at, in_reply_to
FROM messages
WHERE recipient = ?1 AND acked_at IS NULL
ORDER BY id DESC
LIMIT ?2",
)?;
let rows = stmt.query_map(params![recipient, limit_i], |row| {
Ok(MessageEvent::Sent {
id: row.get(0)?,
from: row.get(1)?,
to: row.get(2)?,
body: row.get(3)?,
at: row.get(4)?,
in_reply_to: row.get(5)?,
})
})?;
rows.collect::<rusqlite::Result<Vec<_>>>()
.map_err(Into::into)
}
/// Number of undelivered messages addressed to `recipient`. Non-mutating
/// — used by the harness to surface "N unread" in tool-result status
/// lines without popping the queue.