diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js
index 34b4ca5f..d93f8cf3 100644
--- a/hive-ag3nt/assets/app.js
+++ b/hive-ag3nt/assets/app.js
@@ -393,75 +393,6 @@
}
renderStateBadge();
}
- // Open-threads section: same data the get_open_threads MCP tool
- // returns. Best-effort fetch on cold load + after every turn_end
- // (a turn likely answered or asked something). Silent failure
- // keeps the section hidden rather than surfacing an empty banner.
- let lastOpenThreadsCount = 0;
- async function refreshOpenThreads() {
- try {
- const resp = await fetch('/api/open-threads');
- if (!resp.ok) {
- renderOpenThreads([]);
- return;
- }
- const data = await resp.json();
- renderOpenThreads(data.threads || []);
- } catch (err) {
- console.warn('open-threads fetch failed', err);
- renderOpenThreads([]);
- }
- }
- function renderOpenThreads(threads) {
- const root = $('open-threads-section');
- const list = $('open-threads-list');
- const summary = $('open-threads-summary');
- if (!root || !list || !summary) return;
- if (!threads.length) {
- root.hidden = true;
- lastOpenThreadsCount = 0;
- return;
- }
- root.hidden = false;
- summary.textContent = 'open threads · ' + threads.length;
- list.innerHTML = '';
- // Auto-expand on first appearance of any open thread so the
- // operator notices new loose ends; collapse only on operator
- // click (sticky after that).
- if (lastOpenThreadsCount === 0) root.open = true;
- lastOpenThreadsCount = threads.length;
- const fmtAge = (s) => {
- if (s < 60) return s + 's';
- if (s < 3600) return Math.floor(s / 60) + 'm';
- if (s < 86400) return Math.floor(s / 3600) + 'h';
- return Math.floor(s / 86400) + 'd';
- };
- for (const t of threads) {
- const li = el('li');
- if (t.kind === 'approval') {
- li.append(
- el('span', { class: 'inbox-from' }, '◇ approval #' + t.id), ' ',
- el('span', { class: 'inbox-sep' }, t.agent + ' @ ' + (t.commit_ref || '').slice(0, 12)), ' ',
- el('span', { class: 'inbox-ts' }, fmtAge(t.age_seconds || 0) + ' ago'),
- );
- if (t.description) {
- li.append(el('div', { class: 'inbox-body' }, t.description));
- }
- } else if (t.kind === 'question') {
- const target = t.target || 'operator';
- li.append(
- el('span', { class: 'inbox-from' }, '? #' + t.id), ' ',
- el('span', { class: 'inbox-sep' }, t.asker + ' → ' + target), ' ',
- el('span', { class: 'inbox-ts' }, fmtAge(t.age_seconds || 0) + ' ago'),
- el('div', { class: 'inbox-body' }, t.question || ''),
- );
- } else {
- li.append(el('span', { class: 'inbox-body' }, JSON.stringify(t)));
- }
- list.append(li);
- }
- }
-
function renderInbox(rows) {
const root = $('inbox-section');
const list = $('inbox-list');
@@ -611,10 +542,6 @@
renderAliveBadge(s.status);
renderModelChip(s.model);
renderTokenUsage(s.token_usage);
- // Open-threads aren't part of /api/state (kept on the broker
- // db, fetched via the per-agent socket). Cold-load fetches
- // it here; turn_end refreshes it via the renderer below.
- refreshOpenThreads();
// Skip the re-render if nothing structurally changed. The most
// common case is `online` polling itself — without this guard, the
// operator's gets clobbered every cycle.
@@ -803,8 +730,6 @@
openTurnsFromHistory = Math.max(0, openTurnsFromHistory - 1);
} else {
setBannerActive(false); setState('idle');
- // Likely answered/asked/scheduled something — refresh.
- refreshOpenThreads();
}
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
api.row(cls,
diff --git a/hive-ag3nt/assets/index.html b/hive-ag3nt/assets/index.html
index a0c86e7a..204ec135 100644
--- a/hive-ag3nt/assets/index.html
+++ b/hive-ag3nt/assets/index.html
@@ -29,11 +29,6 @@
-
- ▸ open threads
-
-
-
diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs
index c3c093c0..22411de3 100644
--- a/hive-ag3nt/src/web_ui.rs
+++ b/hive-ag3nt/src/web_ui.rs
@@ -105,7 +105,6 @@ pub async fn serve(
.route("/api/compact", post(post_compact))
.route("/api/model", post(post_set_model))
.route("/api/new-session", post(post_new_session))
- .route("/api/open-threads", get(api_open_threads))
.with_state(state);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = bind_with_retry(addr, "web UI").await?;
@@ -232,49 +231,6 @@ struct SessionView {
exit_note: Option
,
}
-/// Proxy this agent's open-threads via the per-agent socket. The
-/// web UI surfaces the result as a collapsible section in the page
-/// so the operator can see at a glance what's pending against the
-/// agent (questions asked by it, peer questions targeting it,
-/// approvals for the manager). Same data the
-/// `mcp__hyperhive__get_open_threads` tool sees from inside the
-/// container.
-async fn api_open_threads(State(state): State) -> Response {
- let threads: Vec = match state.flavor() {
- Flavor::Agent => {
- match client::request::<_, hive_sh4re::AgentResponse>(
- &state.socket,
- &hive_sh4re::AgentRequest::GetOpenThreads,
- )
- .await
- {
- Ok(hive_sh4re::AgentResponse::OpenThreads { threads }) => threads,
- Ok(hive_sh4re::AgentResponse::Err { message }) => {
- return error_response(&format!("get_open_threads: {message}"));
- }
- Ok(other) => return error_response(&format!("unexpected response: {other:?}")),
- Err(e) => return error_response(&format!("transport: {e:#}")),
- }
- }
- Flavor::Manager => {
- match client::request::<_, hive_sh4re::ManagerResponse>(
- &state.socket,
- &hive_sh4re::ManagerRequest::GetOpenThreads,
- )
- .await
- {
- Ok(hive_sh4re::ManagerResponse::OpenThreads { threads }) => threads,
- Ok(hive_sh4re::ManagerResponse::Err { message }) => {
- return error_response(&format!("get_open_threads: {message}"));
- }
- Ok(other) => return error_response(&format!("unexpected response: {other:?}")),
- Err(e) => return error_response(&format!("transport: {e:#}")),
- }
- }
- };
- axum::Json(serde_json::json!({ "threads": threads })).into_response()
-}
-
async fn api_state(State(state): State) -> axum::Json {
// Capture seq *before* any reads so the dedupe contract is
// "events with seq > snapshot.seq are post-snapshot, never missed."
diff --git a/hive-c0re/assets/app.js b/hive-c0re/assets/app.js
index 012aeef7..bd4a3e39 100644
--- a/hive-c0re/assets/app.js
+++ b/hive-c0re/assets/app.js
@@ -335,32 +335,6 @@
if (containersState.delete(ev.name)) renderContainersFromState();
}
- // Derived tombstones + meta_inputs. Both are emitted as full
- // snapshots (not diffs) — the lists are tiny and recomputing
- // avoids ordering races between a same-tick destroy + purge.
- let tombstonesState = [];
- let metaInputsState = [];
- function syncTombstonesFromSnapshot(s) {
- tombstonesState = (s.tombstones || []).slice();
- }
- function syncMetaInputsFromSnapshot(s) {
- metaInputsState = (s.meta_inputs || []).slice();
- }
- function applyTombstonesChanged(ev) {
- tombstonesState = (ev.tombstones || []).slice();
- renderTombstonesFromState();
- }
- function applyMetaInputsChanged(ev) {
- metaInputsState = (ev.inputs || []).slice();
- renderMetaInputsFromState();
- }
- function renderTombstonesFromState() {
- renderTombstones({ tombstones: tombstonesState });
- }
- function renderMetaInputsFromState() {
- renderMetaInputs({ meta_inputs: metaInputsState });
- }
-
// Derived transient state — cold-loaded from /api/state.transients,
// then mutated live by `transient_set` / `transient_cleared`. Keyed
// by agent name so add/remove are O(1). `since_unix` is wall-clock so
@@ -509,14 +483,6 @@
{ class: 'meta', title: 'sha currently locked in /meta/flake.lock' },
`deployed:${c.deployed_sha}`));
}
- if (c.pending_reminders && c.pending_reminders > 0) {
- head.append(el('span',
- {
- class: 'badge badge-reminder',
- title: 'pending reminders queued for this agent — see the reminders section to view / cancel',
- },
- `⏰ ${c.pending_reminders}`));
- }
li.append(head);
// ── line 2: action buttons ───────────────────────────────────
@@ -546,16 +512,14 @@
if (!c.is_manager) {
// DESTR0Y is event-covered (ContainerRemoved); PURG3 also
// wipes tombstone state which isn't event-derived yet, so it
- // Both event-covered now (ContainerRemoved +
- // TombstonesChanged); no /api/state refetch needed.
+ // keeps the post-submit refetch.
actions.append(
form('/destroy/' + c.name, 'btn-destroy', 'DESTR0Y',
'destroy ' + c.name + '? container is removed; state + creds kept.',
{}, { noRefresh: true }),
form('/destroy/' + c.name, 'btn-destroy', 'PURG3',
'PURGE ' + c.name + '? container, config history, claude creds, '
- + 'and notes are all WIPED. no undo.',
- { purge: 'on' }, { noRefresh: true }),
+ + 'and notes are all WIPED. no undo.', { purge: 'on' }),
);
}
li.append(actions);
@@ -719,9 +683,8 @@
actions.append(respawn);
actions.append(form(
'/purge-tombstone/' + t.name, 'btn-destroy', 'PURG3',
- 'PURGE ' + t.name + '? config history, claude creds, '
- + 'and notes are all WIPED. no undo.',
- {}, { noRefresh: true },
+ 'PURGE ' + t.name + '? config history, claude creds, /state/ notes '
+ + 'are all WIPED. no undo.',
));
li.append(actions);
ul.append(li);
@@ -748,7 +711,6 @@
asked_at: ev.asked_at,
deadline_at: ev.deadline_at ?? null,
target: ev.target || null,
- question_refs: ev.question_refs || [],
});
renderQuestions();
}
@@ -767,8 +729,6 @@
answer: ev.answer,
answerer: ev.answerer,
target: existing?.target ?? ev.target ?? null,
- question_refs: existing?.question_refs || [],
- answer_refs: ev.answer_refs || [],
});
if (questionsState.history.length > QUESTION_HISTORY_LIMIT) {
questionsState.history.length = QUESTION_HISTORY_LIMIT;
@@ -860,7 +820,7 @@
head.append(' ', el('span', { class: 'q-ttl' }, txt));
}
const qBody = el('div', { class: 'q-body' });
- const qPreviews = appendLinkified(qBody, q.question, q.question_refs);
+ const qPreviews = appendLinkified(qBody, q.question);
li.append(head, qBody);
for (const d of qPreviews) li.appendChild(d);
const f = el('form', {
@@ -956,9 +916,9 @@
el('span', { class: 'msg-sep' }, 'asked:'),
);
const histBody = el('div', { class: 'q-body' });
- const histBodyPreviews = appendLinkified(histBody, q.question, q.question_refs);
+ const histBodyPreviews = appendLinkified(histBody, q.question);
const ansText = el('span', { class: 'q-answer-text' });
- const histAnsPreviews = appendLinkified(ansText, q.answer || '(none)', q.answer_refs);
+ const histAnsPreviews = appendLinkified(ansText, q.answer || '(none)');
const ansLine = el('div', { class: 'q-answer' },
el('span', { class: 'msg-sep' }, `${q.answerer || '?'}: `),
ansText,
@@ -1254,10 +1214,6 @@
action: '/meta-update',
class: 'meta-inputs-form',
'data-async': '',
- // run_meta_update emits MetaInputsChanged once the lock
- // bump finishes; per-agent rebuilds fire their own
- // ContainerStateChanged. No /api/state refetch needed.
- 'data-no-refresh': '',
'data-confirm': 'update selected meta flake inputs + rebuild affected agents?',
});
const ul = el('ul', { class: 'meta-inputs' });
@@ -1463,8 +1419,6 @@
// `transientsState` + `containersState`, not from `s.*`).
syncTransientsFromSnapshot(s);
syncContainersFromSnapshot(s);
- syncTombstonesFromSnapshot(s);
- syncMetaInputsFromSnapshot(s);
renderContainers(s);
renderTombstones(s);
// Sync the derived approvals + questions stores from the
@@ -1559,8 +1513,6 @@
transient_cleared: (ev) => { applyTransientCleared(ev); },
container_state_changed: (ev) => { applyContainerStateChanged(ev); },
container_removed: (ev) => { applyContainerRemoved(ev); },
- tombstones_changed: (ev) => { applyTombstonesChanged(ev); },
- meta_inputs_changed: (ev) => { applyMetaInputsChanged(ev); },
},
// Both history backfill and live frames flow through here, so the
// inbox section ends up populated correctly on first paint and
diff --git a/hive-c0re/assets/dashboard.css b/hive-c0re/assets/dashboard.css
index 46eeed67..b1ffe7f7 100644
--- a/hive-c0re/assets/dashboard.css
+++ b/hive-c0re/assets/dashboard.css
@@ -119,10 +119,6 @@ a:hover {
color: var(--muted); border-color: var(--purple-dim);
background: rgba(127, 132, 156, 0.08);
}
-.badge-reminder {
- color: var(--cyan); border-color: var(--cyan);
- text-shadow: 0 0 6px rgba(137, 220, 235, 0.4);
-}
.container-row.tombstone {
border-style: dashed;
background: rgba(24, 24, 37, 0.35);
diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs
index edc837e8..32ce5af1 100644
--- a/hive-c0re/src/actions.rs
+++ b/hive-c0re/src/actions.rs
@@ -87,12 +87,8 @@ pub async fn approve(coord: Arc, id: i64) -> Result<()> {
}
// New container row appeared (or didn't, on failure
// before nixos-container create completed) — rescan so
- // dashboards reflect the post-spawn state. Spawn can
- // also consume a tombstone of the same name; emit the
- // fresh list so the operator's dormant-state pane
- // updates without a refetch.
+ // dashboards reflect the post-spawn state.
coord_bg.rescan_containers_and_emit().await;
- crate::dashboard::emit_tombstones_snapshot(&coord_bg).await;
});
Ok(())
}
@@ -364,11 +360,8 @@ pub async fn destroy(coord: &Arc, name: &str, purge: bool) -> Resul
agent: name.to_owned(),
});
// Container row disappeared — rescan so the dashboard fires
- // `ContainerRemoved` for the gone row, then emit the
- // tombstones snapshot (gained one on destroy, lost one on
- // purge — recompute either way).
+ // `ContainerRemoved` for the gone row.
coord.rescan_containers_and_emit().await;
- crate::dashboard::emit_tombstones_snapshot(coord).await;
Ok(())
}
diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs
index 36dc72cd..4c714d34 100644
--- a/hive-c0re/src/auto_update.rs
+++ b/hive-c0re/src/auto_update.rs
@@ -99,8 +99,6 @@ pub async fn rebuild_agent(coord: &Arc, name: &str, current_rev: &s
// shifted — rescan so dashboards drop the "needs update"
// chip without waiting for the next /api/state poll.
coord.rescan_containers_and_emit().await;
- // Lock bump → meta-inputs panel needs to re-render.
- crate::dashboard::emit_meta_inputs_snapshot(coord);
}
Err(e) => {
coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
diff --git a/hive-c0re/src/container_view.rs b/hive-c0re/src/container_view.rs
index a946a88a..869eea87 100644
--- a/hive-c0re/src/container_view.rs
+++ b/hive-c0re/src/container_view.rs
@@ -29,13 +29,6 @@ pub struct ContainerView {
/// for this agent's input.
#[serde(skip_serializing_if = "Option::is_none")]
pub deployed_sha: Option,
- /// Count of this agent's pending reminders. Computed during
- /// `build_all` via `Broker::count_pending_reminders_for`; the
- /// dashboard renders a small chip when > 0. Updates with the
- /// 10s `crash_watch` rescan + every container mutation site;
- /// not real-time on remind/cancel-reminder but close enough.
- #[serde(default)]
- pub pending_reminders: u64,
}
/// Build the full container list. Wraps `lifecycle::list()` and
@@ -60,20 +53,6 @@ pub async fn build_all(coord: &Coordinator) -> Vec {
let deployed_sha = locked
.get(&format!("agent-{logical}"))
.map(|s| s[..s.len().min(12)].to_owned());
- // Recipient name the broker uses for this agent — sub-agents
- // are addressed by logical name, the manager by the
- // MANAGER_AGENT constant. Mirrors the rest of the broker
- // surface so the count matches what `mcp__hyperhive__remind`
- // queued.
- let reminder_recipient = if is_manager {
- hive_sh4re::MANAGER_AGENT
- } else {
- logical.as_str()
- };
- let pending_reminders = coord
- .broker
- .count_pending_reminders_for(reminder_recipient)
- .unwrap_or(0);
out.push(ContainerView {
port: lifecycle::agent_web_port(&logical),
running: lifecycle::is_running(&logical).await,
@@ -83,7 +62,6 @@ pub async fn build_all(coord: &Coordinator) -> Vec {
needs_update,
needs_login,
deployed_sha,
- pending_reminders,
});
}
out
diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs
index 1128c6b6..98e9c778 100644
--- a/hive-c0re/src/coordinator.rs
+++ b/hive-c0re/src/coordinator.rs
@@ -262,7 +262,6 @@ impl Coordinator {
.ok()
.and_then(|d| i64::try_from(d.as_secs()).ok())
.unwrap_or(0);
- let question_refs = crate::dashboard::scan_validated_paths(question);
self.emit_dashboard_event(DashboardEvent::QuestionAdded {
seq: self.next_seq(),
id,
@@ -273,7 +272,6 @@ impl Coordinator {
asked_at,
deadline_at,
target: target.map(str::to_owned),
- question_refs,
});
}
@@ -295,7 +293,6 @@ impl Coordinator {
.ok()
.and_then(|d| i64::try_from(d.as_secs()).ok())
.unwrap_or(0);
- let answer_refs = crate::dashboard::scan_validated_paths(answer);
self.emit_dashboard_event(DashboardEvent::QuestionResolved {
seq: self.next_seq(),
id,
@@ -304,7 +301,6 @@ impl Coordinator {
answered_at,
cancelled,
target: target.map(str::to_owned),
- answer_refs,
});
}
diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs
index da25b15b..c23acb74 100644
--- a/hive-c0re/src/dashboard.rs
+++ b/hive-c0re/src/dashboard.rs
@@ -170,9 +170,9 @@ struct StateSnapshot {
/// fire `HelperEvent::QuestionAnswered` back into the asker's
/// inbox. Peer-to-peer questions live in the same table but never
/// surface here (see `OperatorQuestions::pending`).
- questions: Vec,
+ questions: Vec,
/// Last 20 answered questions, newest-first.
- question_history: Vec,
+ question_history: Vec,
/// State dirs (config history + claude creds + /state/ notes) that
/// survive after a destroy-without-purge. The operator can re-spawn
/// with the same name to resume, or PURG3 to wipe them.
@@ -186,35 +186,6 @@ struct StateSnapshot {
meta_inputs: Vec,
}
-/// OpQuestion + computed `question_refs` / `answer_refs`. Built
-/// from the snapshot read; the live channel attaches the same
-/// fields directly on `QuestionAdded` / `QuestionResolved`.
-#[derive(Serialize)]
-struct QuestionView {
- #[serde(flatten)]
- inner: crate::operator_questions::OpQuestion,
- #[serde(skip_serializing_if = "Vec::is_empty")]
- question_refs: Vec,
- #[serde(skip_serializing_if = "Vec::is_empty")]
- answer_refs: Vec,
-}
-
-impl QuestionView {
- fn from_question(q: crate::operator_questions::OpQuestion) -> Self {
- let question_refs = scan_validated_paths(&q.question);
- let answer_refs = q
- .answer
- .as_deref()
- .map(scan_validated_paths)
- .unwrap_or_default();
- Self {
- inner: q,
- question_refs,
- answer_refs,
- }
- }
-}
-
#[derive(Serialize)]
struct PortConflict {
port: u16,
@@ -222,15 +193,15 @@ struct PortConflict {
agents: Vec,
}
-#[derive(Serialize, Clone, Debug)]
-pub(crate) struct TombstoneView {
- pub name: String,
+#[derive(Serialize)]
+struct TombstoneView {
+ name: String,
/// Bytes used by the state dir tree. Cheap-ish to compute; let the
/// operator know how much they're holding onto.
- pub state_bytes: u64,
+ state_bytes: u64,
/// Mtime (unix seconds) of the state dir; rough "last seen".
- pub last_seen: i64,
- pub has_creds: bool,
+ last_seen: i64,
+ has_creds: bool,
}
#[derive(Serialize)]
@@ -340,21 +311,12 @@ async fn api_state(headers: HeaderMap, State(state): State) -> axum::J
// dashboard now derives it client-side from the message stream
// (terminal backfill + live SSE), so the snapshot stops shipping it.
// Both operator-targeted and peer threads now surface on the
- // dashboard. Client filters by target client-side. Each row is
- // wrapped in QuestionView so the snapshot carries the same
- // file_refs the live event variants attach.
- let questions: Vec =
- log_default("questions.pending_all", state.coord.questions.pending_all())
- .into_iter()
- .map(QuestionView::from_question)
- .collect();
- let question_history: Vec = log_default(
+ // dashboard. Client filters by target client-side.
+ let questions = log_default("questions.pending_all", state.coord.questions.pending_all());
+ let question_history = log_default(
"questions.recent_answered_all",
state.coord.questions.recent_answered_all(20),
- )
- .into_iter()
- .map(QuestionView::from_question)
- .collect();
+ );
axum::Json(StateSnapshot {
seq,
@@ -394,19 +356,19 @@ fn build_port_conflicts(containers: &[ContainerView]) -> Vec {
.collect()
}
-#[derive(Serialize, Clone, Debug)]
-pub(crate) struct MetaInputView {
+#[derive(Serialize, Clone)]
+struct MetaInputView {
/// Input key in meta's `flake.nix` — `hyperhive`, `agent-`, etc.
- pub name: String,
+ name: String,
/// Full locked sha. Not displayed verbatim; the dashboard
/// truncates to the first 12 chars for the chip.
- pub rev: String,
+ rev: String,
/// Unix seconds — `locked.lastModified`. Drives the relative
/// "2h ago" timestamp on each input row.
- pub last_modified: i64,
+ last_modified: i64,
/// `original.url` if available, for the tooltip / row meta text.
#[serde(skip_serializing_if = "Option::is_none")]
- pub url: Option,
+ url: Option,
}
/// Walk `flake.lock`'s `nodes` graph from `root` and emit one
@@ -994,36 +956,6 @@ fn resolve_state_path(raw: &str) -> std::result::Result) {
- let containers = coord.containers_snapshot().await;
- let transient_snapshot = coord.transient_snapshot();
- let tombstones = build_tombstone_views(coord, &containers, &transient_snapshot);
- coord.emit_dashboard_event(
- crate::dashboard_events::DashboardEvent::TombstonesChanged {
- seq: coord.next_seq(),
- tombstones,
- },
- );
-}
-
-/// Snapshot meta/flake.lock's root inputs + emit
-/// `MetaInputsChanged`. Call after any mutation that bumps a lock
-/// (`run_meta_update`, `auto_update::rebuild_agent`).
-pub(crate) fn emit_meta_inputs_snapshot(coord: &Coordinator) {
- let inputs = read_meta_inputs();
- coord.emit_dashboard_event(
- crate::dashboard_events::DashboardEvent::MetaInputsChanged {
- seq: coord.next_seq(),
- inputs,
- },
- );
-}
-
/// Scan `body` for path-shaped tokens, validate each against the
/// allow-list, return the unique set of tokens that resolve to a
/// regular file. Called at broker-message ingest time so the
@@ -1162,10 +1094,9 @@ async fn post_purge_tombstone(
.fail_pending_for_agent(&name, "agent state purged");
if errors.is_empty() {
tracing::info!(%name, "tombstone purged");
- // Fire the post-purge tombstones snapshot so dashboards
- // drop the row live; matching form carries
- // `data-no-refresh`.
- emit_tombstones_snapshot(&state.coord).await;
+ // Tombstones aren't event-derived yet, so the client still
+ // refetches /api/state to see this one disappear (matching
+ // form omits `data-no-refresh`).
(StatusCode::OK, "ok").into_response()
} else {
error_response(&format!("purge {name} partial: {}", errors.join(", ")))
@@ -1217,10 +1148,10 @@ async fn post_meta_update(
let inputs_clone = inputs.clone();
tokio::spawn(async move {
run_meta_update(&coord, &inputs_clone).await;
- // Lock file changed — emit so dashboards refresh the
- // meta-inputs panel without a snapshot poll.
- emit_meta_inputs_snapshot(&coord);
});
+ // Background task — each per-agent rebuild emits its own
+ // `ContainerStateChanged`; the meta inputs panel still relies on
+ // /api/state freshness (matching form omits `data-no-refresh`).
(StatusCode::OK, "ok").into_response()
}
diff --git a/hive-c0re/src/dashboard_events.rs b/hive-c0re/src/dashboard_events.rs
index fe9f57c2..4e531c26 100644
--- a/hive-c0re/src/dashboard_events.rs
+++ b/hive-c0re/src/dashboard_events.rs
@@ -26,7 +26,6 @@
use serde::Serialize;
use crate::container_view::ContainerView;
-use crate::dashboard::{MetaInputView, TombstoneView};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case", tag = "kind")]
@@ -106,11 +105,6 @@ pub enum DashboardEvent {
asked_at: i64,
deadline_at: Option,
target: Option,
- /// Verified file-path tokens that appear in `question`.
- /// Same shape as broker `Sent`/`Delivered` events; the
- /// client linkifies only what hive-c0re vouched for.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- question_refs: Vec,
},
/// A question was answered (operator answer, peer answer,
/// operator override on a peer thread, or ttl watchdog
@@ -125,9 +119,6 @@ pub enum DashboardEvent {
answered_at: i64,
cancelled: bool,
target: Option,
- /// Verified file-path tokens that appear in `answer`.
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- answer_refs: Vec,
},
/// A lifecycle action started for an agent (spawn / start / stop
/// / restart / rebuild / destroy). Clients render a spinner next
@@ -165,25 +156,4 @@ pub enum DashboardEvent {
/// `nixos-container destroy` (operator-driven or otherwise) on the
/// next rescan.
ContainerRemoved { seq: u64, name: String },
- /// Full snapshot of the tombstones list. Emitted on every
- /// mutation that could add / remove a tombstone: destroy
- /// (with or without purge), purge-tombstone, spawn approval
- /// (which can consume a tombstone of the same name). Snapshot
- /// shape (not diff) because the list is tiny (single-digit
- /// typical) and recomputing avoids the add/remove races a
- /// per-row event would have.
- TombstonesChanged {
- seq: u64,
- tombstones: Vec,
- },
- /// Full snapshot of `meta/flake.lock`'s root inputs. Emitted
- /// after every operation that bumps a lock: `meta-update`,
- /// `rebuild_agent` (lock bumps via two-phase staging),
- /// `update-all`. Same snapshot-shape rationale as
- /// `TombstonesChanged` — the list is small (one row per agent
- /// plus their fetched inputs).
- MetaInputsChanged {
- seq: u64,
- inputs: Vec,
- },
}