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:
parent
2bf809cbe5
commit
7e6a4c7727
37 changed files with 246 additions and 848 deletions
|
|
@ -4,14 +4,14 @@
|
|||
// dashboard event stream (subscribed via `openStream` from common.js). See
|
||||
// docs/web-ui/shape.md::Shape (shared by both) for the broader contract.
|
||||
//
|
||||
// Pure helpers (DOM, side panel, OS notifications, path linkification)
|
||||
// live in `./common.js`; the flow-page surface (operator inbox, broker
|
||||
// Pure helpers (DOM, side panel, path linkification)
|
||||
// live in `./common.js`; the flow-page surface (broker
|
||||
// terminal, @-mention composer) lives in `./flow.js` — `flow.html` and
|
||||
// `dashboard.html` each load their own bundle.
|
||||
// SW4RM (containers) domain lives in `./swarm.js`.
|
||||
|
||||
import { marked } from "marked";
|
||||
import { $, NOTIF, openStream, renderServerWarnings } from "./common.js";
|
||||
import { $, openStream, renderServerWarnings } from "./common.js";
|
||||
import { el } from "@hive/shared/dom.js";
|
||||
import { bindAsyncForms } from "@hive/shared/forms.js";
|
||||
import { createTabStrip } from "@hive/shared/tabs.js";
|
||||
|
|
@ -24,12 +24,6 @@ import {
|
|||
initPermissions,
|
||||
} from "./permissions.js";
|
||||
import { refreshSchedules, activeScheduleCount } from "./schedules.js";
|
||||
import {
|
||||
initCall,
|
||||
refreshOperatorInbox,
|
||||
operatorInboxAppendFromEvent,
|
||||
operatorInboxCount,
|
||||
} from "./call.js";
|
||||
import {
|
||||
initJobqRollup,
|
||||
syncTransientsFromSnapshot,
|
||||
|
|
@ -138,10 +132,7 @@ window.marked = marked;
|
|||
const label = swarm && hive ? `${swarm} / ${hive}` : hive || swarm;
|
||||
hiveId.textContent = label;
|
||||
hiveId.hidden = false;
|
||||
// Preserve any (N) call-count prefix already applied by
|
||||
// refreshTabCounts so the title doesn't flicker on reload.
|
||||
const existingPrefix = document.title.match(/^(\(\d+\) )/)?.[1] || "";
|
||||
document.title = existingPrefix + label + " // h1ve-c0re";
|
||||
document.title = label + " // h1ve-c0re";
|
||||
}
|
||||
}
|
||||
const openDetails = snapshotOpenDetails();
|
||||
|
|
@ -181,22 +172,10 @@ window.marked = marked;
|
|||
}
|
||||
}
|
||||
refreshState();
|
||||
// Cold-load the operator inbox so the Y3R C4LL pill + browser
|
||||
// title reflect unread agent->operator messages immediately, before the
|
||||
// operator opens the tab. Live updates arrive via the broker stream.
|
||||
refreshOperatorInbox();
|
||||
// NOTIF.bind() wires the enable/mute/unmute toggle buttons that now
|
||||
// live in the Y3R C4LL tab's ◆ PR3F3R3NC3S ◆ section. NOTIF.show()
|
||||
// (inbox notifications) reads the same browser permission +
|
||||
// localStorage mute flag regardless of which page fires it — bind()
|
||||
// only needs to run once, here, where the buttons live. The side
|
||||
// panel (<hive-side-panel>) wires its own internals in
|
||||
// connectedCallback — no bind() step needed for it.
|
||||
NOTIF.bind();
|
||||
|
||||
// ─── live updates: dashboard event stream ──────────────────────────────
|
||||
// The dashboard subscribes to /dashboard/stream for live mutation
|
||||
// events so the SW4RM / Y3R C4LL / SYST3M panes update without an
|
||||
// events so the SW4RM / SYST3M panes update without an
|
||||
// operator action triggering a refreshState.
|
||||
//
|
||||
// Bare `EventSource` (no terminal infrastructure needed — the
|
||||
|
|
@ -209,7 +188,7 @@ window.marked = marked;
|
|||
//
|
||||
// Both /dashboard.html and /flow.html subscribe to `/api/dashboard/stream`
|
||||
// and filter client-side — the dashboard ignores broker traffic
|
||||
// and the inbox ignores mutation events.
|
||||
// and FL0W ignores mutation events.
|
||||
const MUTATION_HANDLERS = {
|
||||
transient_set: applyTransientSet,
|
||||
transient_cleared: applyTransientCleared,
|
||||
|
|
@ -231,14 +210,10 @@ window.marked = marked;
|
|||
// EventSource-shaped facade with a graceful direct-EventSource
|
||||
// fallback when SharedWorker isn't supported.
|
||||
//
|
||||
// kinds= matches MUTATION_HANDLERS below verbatim, plus `sent`
|
||||
// (checked separately, just above, for the operator inbox) —
|
||||
// narrows this from all 12 wire kinds down to the 8 this page
|
||||
// actually acts on. This page was one of 4 unfiltered
|
||||
// `/api/dashboard/stream` subscribers before this (subscription
|
||||
// discipline, part 1 of the dashboard-event-stream-split issue).
|
||||
// kinds= matches MUTATION_HANDLERS verbatim, so the stream carries
|
||||
// only the 7 wire kinds this page acts on.
|
||||
const es = openStream(
|
||||
"/api/dashboard/stream?kinds=sent," +
|
||||
"/api/dashboard/stream?kinds=" +
|
||||
"transient_set,transient_cleared," +
|
||||
"container_state_changed,container_removed,rebuild_queue_changed," +
|
||||
"capabilities_changed,tool_groups_changed",
|
||||
|
|
@ -250,12 +225,6 @@ window.marked = marked;
|
|||
} catch {
|
||||
return;
|
||||
}
|
||||
// Broker `sent` frames aren't mutation events, but the operator
|
||||
// inbox cares about ones addressed to "operator".
|
||||
if (ev.kind === "sent" && ev.to === "operator") {
|
||||
operatorInboxAppendFromEvent(ev);
|
||||
return;
|
||||
}
|
||||
const h = MUTATION_HANDLERS[ev.kind];
|
||||
if (!h) return; // broker rows + future kinds — dashboard doesn't care
|
||||
try {
|
||||
|
|
@ -279,7 +248,7 @@ window.marked = marked;
|
|||
})();
|
||||
|
||||
// ─── tab routing ───────────────────────────────────────────────────────
|
||||
// Hash-based: `#swarm` / `#call` / `#system` / `#permissions` /
|
||||
// Hash-based: `#swarm` / `#system` / `#permissions` /
|
||||
// `#schedules` activate the matching pane on the dashboard. Empty
|
||||
// hash defaults to SW4RM. FL0W and ST4TS are NOT tabs —
|
||||
// they're separate pages (`/flow.html`, `/stats.html`) reached from
|
||||
|
|
@ -311,9 +280,6 @@ window.marked = marked;
|
|||
fetchAndRenderCapabilities();
|
||||
fetchAndRenderToolGroups();
|
||||
}
|
||||
if (target === "call") {
|
||||
refreshOperatorInbox();
|
||||
}
|
||||
}
|
||||
|
||||
// Wire the shared tab strip now that activateTab + the lazy-load fns it
|
||||
|
|
@ -322,11 +288,6 @@ window.marked = marked;
|
|||
// fires activateTab for the per-tab side-effects on every change.
|
||||
createTabStrip($("tabbar"), { defaultId: "swarm", onShow: activateTab });
|
||||
|
||||
// Register the Y3R C4LL domain's count callback (call.js) — its live
|
||||
// mutations (inbox stream append, mark-read) trigger a tab-count refresh
|
||||
// through this instead of reaching back into the coordinator directly.
|
||||
initCall({ onCountsChanged: refreshTabCounts });
|
||||
|
||||
// Tab count pills — pure derived data from the existing state
|
||||
// stores so SSE-driven updates flow through without extra plumbing.
|
||||
// Set `hidden` when the count is zero so the pill doesn't draw
|
||||
|
|
@ -349,15 +310,6 @@ window.marked = marked;
|
|||
if (c.needs_update) swarm++;
|
||||
}
|
||||
setTabCount("swarm", swarm);
|
||||
// Y3R C4LL — unread agent->operator messages.
|
||||
const callCount = operatorInboxCount();
|
||||
setTabCount("call", callCount);
|
||||
// Browser tab title prefix — lets the operator see the pending
|
||||
// call count without switching to the window. Strips any existing
|
||||
// `(N) ` prefix before re-applying so identity-title updates
|
||||
// (which run once on state load, not every tick) compose cleanly.
|
||||
const rawTitle = document.title.replace(/^\(\d+\) /, "");
|
||||
document.title = callCount > 0 ? `(${callCount}) ${rawTitle}` : rawTitle;
|
||||
// SCH3DUL3S — count of schedules with at least one still-active
|
||||
// target: the ones left to recreate with `swarmctl schedule`.
|
||||
setTabCount("schedules", activeScheduleCount());
|
||||
|
|
|
|||
Loading…
Reference in a new issue