fix(#1643): filter destroyed-agent targets from dashboard schedule view

This commit is contained in:
damocles 2026-06-13 15:58:44 +02:00
commit e08122a206
4 changed files with 117 additions and 8 deletions

View file

@ -2,7 +2,7 @@
//! socket, and the per-agent sockets: the broker, configured `agent_flake`,
//! and the map of registered agent sockets.
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
@ -512,7 +512,7 @@ impl Coordinator {
/// after each tick that fires or rearms a row) so the dashboard's
/// scheduled-prompts tab updates live without polling.
pub fn emit_schedules_snapshot(self: &Arc<Self>) {
let schedules = match self.scheduled_prompts.list() {
let mut schedules: Vec<hive_sh4re::WireSchedule> = match self.scheduled_prompts.list() {
Ok(rows) => rows
.into_iter()
.map(crate::manager_server::schedule_to_wire_public)
@ -522,12 +522,34 @@ impl Coordinator {
return;
}
};
// Strip ghost targets (destroyed agents) so the dashboard doesn't
// render dead columns. Best-effort: if the roster cache is
// momentarily contended we emit unfiltered rather than block this
// sync path — the next snapshot / page reload corrects it.
if let Some(live) = self.live_container_names_blocking() {
crate::manager_server::filter_ghost_schedule_targets(&mut schedules, &live);
}
self.emit_dashboard_event(DashboardEvent::SchedulesChanged {
seq: self.next_seq(),
schedules,
});
}
/// Best-effort synchronous snapshot of live container names (the
/// logical agent names from the last `nixos-container list` scan).
/// Returns `None` when the roster cache lock is momentarily
/// contended, so a transient miss is treated as "roster unknown,
/// don't filter" rather than hiding live schedule targets. The async
/// `containers_snapshot` is the reliable path for request handlers;
/// this exists for the sync `emit_schedules_snapshot` SSE emit.
#[must_use]
pub fn live_container_names_blocking(&self) -> Option<HashSet<String>> {
self.last_containers
.try_lock()
.ok()
.map(|m| m.keys().cloned().collect())
}
/// Emit a `RemindersChanged` snapshot event. Called from every
/// reminder mutation site (agent `remind` calls, operator cancel /
/// retry, and the scheduler after each delivery batch) so the