From 614e8c6d5a5759055b6c7bcd3c8c0481bfa7dc52 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 15 Jul 2026 23:15:14 +0200 Subject: [PATCH] fix(#2515): derive per-agent in-flight badge from node state, not DAG state inFlightOpsByAgent() read e.agent, a DAG-level field that no longer exists (agent moved to per-node when DAGs became agent-per-node). So the SW4RM tab's 'building...'/'meta-updating...' badges never matched any real agent, and multi-agent DAGs (a startup sweep's MetaLock cascade, a hive-wide restart) showed nothing at all on the per-agent cards even while the rebuild queue clearly had them in flight. Derive each agent's in-flight kind+state from its own node(s) within the entry instead: a DAG can be 'running' overall while a given agent's subgraph hasn't started (still queued behind an earlier node in its chain), so per-node state is also more accurate than the old per-DAG state for the badge, not just more available. --- frontend/packages/dashboard/src/swarm.js | 35 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/frontend/packages/dashboard/src/swarm.js b/frontend/packages/dashboard/src/swarm.js index 6f48ee6b..bbc17e67 100644 --- a/frontend/packages/dashboard/src/swarm.js +++ b/frontend/packages/dashboard/src/swarm.js @@ -59,11 +59,18 @@ export function applyRebuildQueueChanged(ev) { // See docs/web-ui.md::Container row for the badge taxonomy. renderContainersFromState(); } -// Map from agent name -> highest-priority in-flight queue entry -// (`running` beats `queued`). Used by the container row renderer -// to surface "building..." / "meta-updating..." badges on the -// SW4RM tab when an op is still in the rebuild queue but no -// operator-initiated transient is set. +// Map from agent name -> highest-priority in-flight op ({ kind, state }) +// (`running` beats `queued`). Used by the container row renderer to +// surface "building..." / "meta-updating..." badges on the SW4RM tab +// when an op is still in the rebuild queue but no operator-initiated +// transient is set. +// +// Agent is per-node, not per-DAG (a DAG can span agents — e.g. the +// startup sweep's MetaLock cascade, or a hive-wide restart), so this +// derives each agent's in-flight state from its own node(s) within the +// entry rather than the DAG's overall `state`/`kind` — a DAG can be +// `running` overall while a given agent's subgraph hasn't started yet +// (still queued behind an earlier node in its chain), and vice versa. function inFlightOpsByAgent() { const out = new Map(); for (const e of rebuildQueueState) { @@ -74,10 +81,20 @@ function inFlightOpsByAgent() { // surfacing if the spawn op happens to land in the queue // while the row exists transiently. if (e.kind === 'spawn') continue; - const cur = out.get(e.agent); - // Prefer running over queued; otherwise keep the first match. - if (!cur || (cur.state === 'queued' && e.state === 'running')) { - out.set(e.agent, e); + const perAgentState = new Map(); + for (const n of e.nodes || []) { + if (!n.agent) continue; + if (n.state !== 'queued' && n.state !== 'running') continue; + const cur = perAgentState.get(n.agent); + if (!cur || (cur === 'queued' && n.state === 'running')) { + perAgentState.set(n.agent, n.state); + } + } + for (const [agent, state] of perAgentState) { + const cur = out.get(agent); + if (!cur || (cur.state === 'queued' && state === 'running')) { + out.set(agent, { kind: e.kind, state }); + } } } return out;