container row: pending-reminder count chip ( N)

ContainerView gains pending_reminders: u64; computed during
build_all via Broker::count_pending_reminders_for, mapping
manager → MANAGER_AGENT recipient + sub-agents → logical name.
Updates on every rescan (mutation sites + crash_watch's 10s
poll); accept 10s staleness on background remind / scheduler
delivery — live updates on operator cancel via /api/state path.

client renders a small cyan chip on the row when the count > 0;
tooltip points the operator at the reminders section to view
or cancel.
This commit is contained in:
müde 2026-05-17 23:52:56 +02:00
parent aed43ce4df
commit 087a5366fb
3 changed files with 34 additions and 0 deletions

View file

@ -509,6 +509,14 @@
{ class: 'meta', title: 'sha currently locked in /meta/flake.lock' }, { class: 'meta', title: 'sha currently locked in /meta/flake.lock' },
`deployed:${c.deployed_sha}`)); `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); li.append(head);
// ── line 2: action buttons ─────────────────────────────────── // ── line 2: action buttons ───────────────────────────────────

View file

@ -119,6 +119,10 @@ a:hover {
color: var(--muted); border-color: var(--purple-dim); color: var(--muted); border-color: var(--purple-dim);
background: rgba(127, 132, 156, 0.08); 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 { .container-row.tombstone {
border-style: dashed; border-style: dashed;
background: rgba(24, 24, 37, 0.35); background: rgba(24, 24, 37, 0.35);

View file

@ -29,6 +29,13 @@ pub struct ContainerView {
/// for this agent's input. /// for this agent's input.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub deployed_sha: Option<String>, pub deployed_sha: Option<String>,
/// 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 /// Build the full container list. Wraps `lifecycle::list()` and
@ -53,6 +60,20 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
let deployed_sha = locked let deployed_sha = locked
.get(&format!("agent-{logical}")) .get(&format!("agent-{logical}"))
.map(|s| s[..s.len().min(12)].to_owned()); .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 { out.push(ContainerView {
port: lifecycle::agent_web_port(&logical), port: lifecycle::agent_web_port(&logical),
running: lifecycle::is_running(&logical).await, running: lifecycle::is_running(&logical).await,
@ -62,6 +83,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
needs_update, needs_update,
needs_login, needs_login,
deployed_sha, deployed_sha,
pending_reminders,
}); });
} }
out out