swarm.js: drop per-agent pending-badge fallback, transients-only now
mara, on review: "swarm.js should not need to pull in the jobq to do its job" followed by "remove the per agent pending stuff - only show what is running." Deletes queuedOpsByAgent() entirely — no more per-agent badge derived from Pending-state job-queue nodes. A card's pending badges are now driven exclusively by transientsState (i.e. actually-running work); queued-but-not-started work shows nothing on the card until a node starts. jobqNodesState + refreshJobqGraph() stay, now feeding only the queue-summary banner (a separate, still-open question — mara separately asked for a dedicated rollup endpoint for that, tracked apart from this PR). Collapses the now-always-coincident `pending`/`pending-running` row classes into one (`pending-running`) — there's no more queued-only row state to visually distinguish it from. docs/web-ui/dashboard.md's Container-row section rewritten to match: the two-store priority-fallback description is gone, replaced with "transients only."
This commit is contained in:
parent
45710ab739
commit
17f61d1da6
4 changed files with 66 additions and 106 deletions
|
|
@ -883,22 +883,19 @@ fetch entirely.
|
||||||
independent badge rather than being collapsed into one label,
|
independent badge rather than being collapsed into one label,
|
||||||
matching the existing multi-badge convention this line already uses
|
matching the existing multi-badge convention this line already uses
|
||||||
for `paused`/`needs_update`/model/ctx.
|
for `paused`/`needs_update`/model/ctx.
|
||||||
The row visual splits queued vs running: a **queued** entry (no
|
Any pending badge means the row is actually **running** something
|
||||||
transient yet, see below) shows only the pending-state pill (no row
|
right now — there is no separate queued-but-not-started row state
|
||||||
tint, so a long queue doesn't paint half the tab amber); a
|
to visually distinguish it from (see **Pending-badge derivation**
|
||||||
**running** entry (at least one transient) keeps the amber row tint
|
below), so every row carrying ≥1 badge keeps the amber row tint AND
|
||||||
AND draws a **rotating amber ring** around the agent icon, so it's
|
draws a **rotating amber ring** around the agent icon.
|
||||||
obvious at a glance which container is actually moving.
|
|
||||||
|
|
||||||
**Pending-badge derivation:** two separate stores, but no longer a
|
**Pending-badge derivation:** transients only (`transientsState`,
|
||||||
priority *order* between them — the second only ever applies when
|
keyed `agent -> Map<kind, since_unix>`) — a transient is **derived
|
||||||
the first has nothing to say. (1) **Transients**
|
from a job-queue node currently `Running`** against that agent, not
|
||||||
(`transientsState`, keyed `agent -> Map<kind, since_unix>`) — a
|
declared per request, so its label follows the operation as it
|
||||||
transient is **derived from a job-queue node currently `Running`**
|
progresses (a rebuild reads `stop_for_update`, then `swap`, then
|
||||||
against that agent, not declared per request, so its label follows
|
`reconcile` rather than one constant `rebuilding` for its whole
|
||||||
the operation as it progresses (a rebuild reads `stop_for_update`,
|
life). Two consequences for anything rendering it:
|
||||||
then `swap`, then `reconcile` rather than one constant `rebuilding`
|
|
||||||
for its whole life). Two consequences for anything rendering it:
|
|
||||||
|
|
||||||
- The label vocabulary is **open** — it is the node's own wire tag
|
- The label vocabulary is **open** — it is the node's own wire tag
|
||||||
(`NodeKind::as_str`, the same strings `NodeView.kind` carries),
|
(`NodeKind::as_str`, the same strings `NodeView.kind` carries),
|
||||||
|
|
@ -916,24 +913,14 @@ fetch entirely.
|
||||||
their own label directly via `TransientSet`/`TransientCleared`
|
their own label directly via `TransientSet`/`TransientCleared`
|
||||||
events carrying no backing node at all.
|
events carrying no backing node at all.
|
||||||
|
|
||||||
(2) The **job-queue fallback** (`jobqNodesState`, the flat
|
**Queued (not-yet-started) work shows nothing on the card.** An
|
||||||
`GraphNode[]` from `GET /api/jobq/graph` — `swarm.js`'s
|
earlier version of this page had a job-queue-backed fallback badge
|
||||||
`refreshJobqGraph`, same generic endpoint the BU1LDS page's
|
for the `Pending` case (`queuedOpsByAgent()` in swarm.js, reading
|
||||||
`<hive-jobq-graph>` self-fetches, see above) only fires when an
|
`jobqNodesState`); removed per mara, on review of hyperhive#3028's
|
||||||
agent has **zero** transients — since (1) now covers every
|
PR: *"swarm.js should not need to pull in the jobq to do its job"*,
|
||||||
`Running` node unconditionally, a `Running` job-queue node can
|
followed by *"remove the per agent pending stuff - only show what
|
||||||
never usefully reach this fallback by the time it's consulted; the
|
is running."* `jobqNodesState` still exists in swarm.js, but now
|
||||||
fallback exists purely for the **`Pending` (queued, not yet
|
feeds only the queue-summary banner below, not per-card badges.
|
||||||
started)** case, which `running_transients()`'s Running-only test
|
|
||||||
cannot represent. `queuedOpsByAgent()` (swarm.js) reflects this: it
|
|
||||||
only ever looks at `Pending`-state nodes carrying a non-empty
|
|
||||||
`payload.data.agent`, and (mara, on review) the badge shows the
|
|
||||||
node's raw `payload.label` string as-is — no English-phrase lookup
|
|
||||||
translating it first, same opaque-string treatment a transient's
|
|
||||||
own label already gets.
|
|
||||||
`opRunning` (driving the `pending-running` row class + spinner) is
|
|
||||||
simply "does this agent have at least one transient" — a queued-only
|
|
||||||
entry (no transient yet) leaves it false.
|
|
||||||
An **active model badge** (`model · <name>`, blue) appears when the
|
An **active model badge** (`model · <name>`, blue) appears when the
|
||||||
container is running and the harness has persisted a model name
|
container is running and the harness has persisted a model name
|
||||||
(`harness/hyperhive-model`). Read by hive-c0re's `ContainerView`
|
(`harness/hyperhive-model`). Read by hive-c0re's `ContainerView`
|
||||||
|
|
|
||||||
|
|
@ -347,8 +347,11 @@ hive-agent-menu {
|
||||||
.container-row:hover hive-agent-menu {
|
.container-row:hover hive-agent-menu {
|
||||||
--menu-btn-opacity: 1;
|
--menu-btn-opacity: 1;
|
||||||
}
|
}
|
||||||
/* Pending state splits queued vs running. */
|
/* Card actions dim while a row has any pending-state badge (transient-
|
||||||
.container-row.pending .actions { opacity: 0.4; pointer-events: none; }
|
driven only now — see swarm.js's pending-badge derivation comment;
|
||||||
|
there is no separate queued-but-not-running row state to tell apart
|
||||||
|
from this one anymore). */
|
||||||
|
.container-row.pending-running .actions { opacity: 0.4; pointer-events: none; }
|
||||||
.container-row.pending-running {
|
.container-row.pending-running {
|
||||||
border-color: var(--amber);
|
border-color: var(--amber);
|
||||||
background: color-mix(in srgb, var(--amber) 5%, transparent);
|
background: color-mix(in srgb, var(--amber) 5%, transparent);
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,18 @@ const CTX_CAUTION_TOKENS = 100_000; // fallback yellow threshold (~= 50% of 200k
|
||||||
// ─── module-level state ─────────────────────────────────────────────────────
|
// ─── module-level state ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Flat GraphNode[] from GET /api/jobq/graph (hive-jobq-wire's generic
|
// Flat GraphNode[] from GET /api/jobq/graph (hive-jobq-wire's generic
|
||||||
// shape — see hive-jobq-wire/src/lib.rs) — the last DagView/NodeView
|
// shape — see hive-jobq-wire/src/lib.rs) — feeds *only* the queue-summary
|
||||||
// (job_queue-specific) read left on this page, migrated off in favour of
|
// banner in renderContainers (mara, on review: "swarm.js should not need
|
||||||
// the generic endpoint builds.js already established the pattern for
|
// to pull in the jobq to do its job" — the per-agent pending-row fallback
|
||||||
// (see mountJobqGraph there). "Nothing is hidden" on this endpoint: every
|
// that used to be this state's other consumer is gone, see
|
||||||
// retained job group's nodes ride the wire, `Done` included, and each
|
// queuedOpsByAgent's removal below). Once a dedicated rollup endpoint
|
||||||
// group's root is an ordinary node (`parent: null`) whose own `state` IS
|
// exists (status-count pairs, requested separately) the banner moves
|
||||||
// the group's roll-up already — unlike the old DagView, no client-side
|
// onto that instead and this fetch goes away entirely. "Nothing is
|
||||||
// rollup derivation is needed, just an explicit filter for what's still
|
// hidden" on this endpoint: every retained job group's nodes ride the
|
||||||
// active wherever that's wanted (see queuedOpsByAgent + the queue-summary
|
// wire, `Done` included, and each group's root is an ordinary node
|
||||||
// banner in renderContainers).
|
// (`parent: null`) whose own `state` IS the group's roll-up already —
|
||||||
|
// unlike the old DagView, no client-side rollup derivation is needed,
|
||||||
|
// just an explicit filter for what's still active.
|
||||||
let jobqNodesState = [];
|
let jobqNodesState = [];
|
||||||
|
|
||||||
// Keyed container row cache. Maps agent name -> { el: <li>, fingerprint }.
|
// Keyed container row cache. Maps agent name -> { el: <li>, fingerprint }.
|
||||||
|
|
@ -90,44 +92,13 @@ export function applyRebuildQueueChanged() {
|
||||||
// longer reads it. Purely a "something changed, go refetch" signal.
|
// longer reads it. Purely a "something changed, go refetch" signal.
|
||||||
refreshJobqGraph();
|
refreshJobqGraph();
|
||||||
}
|
}
|
||||||
// Map from agent name -> the queued node's raw `payload.label` string,
|
// Per-agent queued-but-not-running badges used to have their own
|
||||||
// backing the SW4RM card's fallback pending badge. Pending only — every
|
// fallback here (`queuedOpsByAgent()`, reading `Pending`-state nodes
|
||||||
// *running* node naming an agent already lights a transient pill (any
|
// off `jobqNodesState`). Removed per mara, on review: "remove the per
|
||||||
// running node, not just a curated "worth it" subset — see
|
// agent pending stuff - only show what is running" — a card's pending
|
||||||
// docs/web-ui.md::Container row), so a Running entry here would
|
// badges are now driven by `transientsState` alone (see below), which
|
||||||
// always be redundant with `transientsState` by the time this is
|
// is exactly "what is running": queued-not-yet-started work shows
|
||||||
// consulted. Queued (not yet started) work is the one state
|
// nothing on the card until a node actually starts.
|
||||||
// transients can't represent, since `running_transients()` on the
|
|
||||||
// backend is a Running-only test.
|
|
||||||
//
|
|
||||||
// The returned label is displayed as-is (mara, on review: "drop
|
|
||||||
// queuedLabelFor - just show what the backend sends") — same opaque-
|
|
||||||
// string treatment `pending`'s transient half already gets, no
|
|
||||||
// English-phrase lookup table translating it first.
|
|
||||||
//
|
|
||||||
// Agent is per-node, not per-group (a group can span agents — e.g. the
|
|
||||||
// startup sweep's MetaLock cascade, or a hive-wide restart), so this
|
|
||||||
// derives each agent's queued state from whichever of its own nodes is
|
|
||||||
// pending, not from the group root. No "skip agentless/spawn nodes"
|
|
||||||
// filter is needed here (an earlier version of this function keyed on
|
|
||||||
// a DAG-level `kind` field that no longer exists on the wire — dead
|
|
||||||
// code since that refactor): a node with no `data.agent` is silently
|
|
||||||
// skipped below, and an agent with no existing container row never
|
|
||||||
// gets its map entry read regardless (see containersState in state.js
|
|
||||||
// — it's populated straight from the real container roster, not a
|
|
||||||
// pre-spawn placeholder).
|
|
||||||
function queuedOpsByAgent() {
|
|
||||||
const out = new Map();
|
|
||||||
for (const n of jobqNodesState) {
|
|
||||||
if (n.state !== 'Pending') continue;
|
|
||||||
const agent = n.payload.data && n.payload.data.agent;
|
|
||||||
if (!agent) continue;
|
|
||||||
// First node found wins — with only one state to consider
|
|
||||||
// (Pending), there's no priority to resolve between groups.
|
|
||||||
if (!out.has(agent)) out.set(agent, n.payload.label);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── transients ─────────────────────────────────────────────────────────────
|
// ─── transients ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -366,9 +337,14 @@ function buildContainerLi(c, node, opts) {
|
||||||
askerCount, targetCount, agentQCount,
|
askerCount, targetCount, agentQCount,
|
||||||
url, containerBase, forgeBase, s,
|
url, containerBase, forgeBase, s,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
// A single `pending-running` class now covers the whole "has at
|
||||||
|
// least one badge" state — there's no more queued-but-not-running
|
||||||
|
// row to distinguish it from (see the pending-badge derivation
|
||||||
|
// comment in renderContainers), so the separate no-tint `pending`
|
||||||
|
// class from before that removal is gone rather than kept as a
|
||||||
|
// class that would now always co-occur with this one.
|
||||||
const li = el('li', {
|
const li = el('li', {
|
||||||
class: 'container-row'
|
class: 'container-row'
|
||||||
+ (pending.length ? ' pending' : '')
|
|
||||||
+ (opRunning ? ' pending-running' : '')
|
+ (opRunning ? ' pending-running' : '')
|
||||||
+ (selected ? ' selected' : ''),
|
+ (selected ? ' selected' : ''),
|
||||||
});
|
});
|
||||||
|
|
@ -704,10 +680,6 @@ export function renderContainers(s) {
|
||||||
const forgeBase = (s && s.forge_public_url) || null;
|
const forgeBase = (s && s.forge_public_url) || null;
|
||||||
const ul = existingUl ?? el('ul', { class: 'containers' });
|
const ul = existingUl ?? el('ul', { class: 'containers' });
|
||||||
const tree = buildAgentTree(containers);
|
const tree = buildAgentTree(containers);
|
||||||
// Queued (not-yet-running) ops per agent name — see
|
|
||||||
// docs/web-ui.md::Container row for why this only covers the
|
|
||||||
// Pending case now (Running is fully covered by transientsState).
|
|
||||||
const queuedOps = queuedOpsByAgent();
|
|
||||||
|
|
||||||
// Build the ordered list of <li> elements, reusing cached rows
|
// Build the ordered list of <li> elements, reusing cached rows
|
||||||
// whose displayed state hasn't changed.
|
// whose displayed state hasn't changed.
|
||||||
|
|
@ -721,20 +693,17 @@ export function renderContainers(s) {
|
||||||
const containerBase = gatewayLinks
|
const containerBase = gatewayLinks
|
||||||
? `/agent/${encodeURIComponent(c.name)}`
|
? `/agent/${encodeURIComponent(c.name)}`
|
||||||
: `http://${hostname}:${c.port}`;
|
: `http://${hostname}:${c.port}`;
|
||||||
// Pending-badge derivation: an agent's transients win outright when
|
// Pending-badge derivation: transients only — "what is running,"
|
||||||
// any exist (rendered one badge per pill — mara: "show all running
|
// full stop (mara, on review: "remove the per agent pending stuff -
|
||||||
// nodes that name the agent"), the rebuild-queue's queued-only
|
// only show what is running"; rendered one badge per pill — mara,
|
||||||
// fallback otherwise. See docs/web-ui.md::Container row.
|
// earlier: "show all running nodes that name the agent"). See
|
||||||
|
// docs/web-ui.md::Container row.
|
||||||
const transientKindsMap = transientsState.get(c.name);
|
const transientKindsMap = transientsState.get(c.name);
|
||||||
// Sorted for stable badge order across renders — Map iteration
|
// Sorted for stable badge order across renders — Map iteration
|
||||||
// order is insertion order, which shifts as pills clear/re-add.
|
// order is insertion order, which shifts as pills clear/re-add.
|
||||||
const transientKinds = transientKindsMap
|
const pending = transientKindsMap
|
||||||
? Array.from(transientKindsMap.keys()).sort() : [];
|
? Array.from(transientKindsMap.keys()).sort() : [];
|
||||||
const queuedKind = transientKinds.length === 0 ? queuedOps.get(c.name) : null;
|
const opRunning = pending.length > 0;
|
||||||
const pending = transientKinds.length > 0
|
|
||||||
? transientKinds
|
|
||||||
: (queuedKind ? [queuedKind] : []);
|
|
||||||
const opRunning = transientKinds.length > 0;
|
|
||||||
const selected = selectionState.has(c.name);
|
const selected = selectionState.has(c.name);
|
||||||
// Pending questions where this agent is the asker (awaiting an
|
// Pending questions where this agent is the asker (awaiting an
|
||||||
// answer) or the target (owes a reply). Derived live from
|
// answer) or the target (owes a reply). Derived live from
|
||||||
|
|
|
||||||
|
|
@ -234,13 +234,14 @@ window.marked = marked;
|
||||||
// `transientsState` + `containersState`, not from `s.*`).
|
// `transientsState` + `containersState`, not from `s.*`).
|
||||||
syncTransientsFromSnapshot(s);
|
syncTransientsFromSnapshot(s);
|
||||||
syncContainersFromSnapshot(s);
|
syncContainersFromSnapshot(s);
|
||||||
// Job-queue graph feeds the SW4RM agent-card badges
|
// Job-queue graph feeds only the SW4RM queue-summary banner now
|
||||||
// (queuedOpsByAgent) + the queue-summary banner; its own detailed
|
// (per-agent card badges are transient-only — see swarm.js); its
|
||||||
// view lives on /builds.html. Self-fetches GET /api/jobq/graph —
|
// own detailed view lives on /builds.html. Self-fetches GET
|
||||||
// not read off `s` (this page's snapshot carries no jobq field) —
|
// /api/jobq/graph — not read off `s` (this page's snapshot
|
||||||
// fire-and-forget: renderContainers below runs off whatever
|
// carries no jobq field) — fire-and-forget: renderContainers
|
||||||
// jobqNodesState already holds, and refreshJobqGraph's own
|
// below runs off whatever jobqNodesState already holds, and
|
||||||
// re-render catches up once the fetch resolves.
|
// refreshJobqGraph's own re-render catches up once the fetch
|
||||||
|
// resolves.
|
||||||
refreshJobqGraph();
|
refreshJobqGraph();
|
||||||
renderContainers(s);
|
renderContainers(s);
|
||||||
// Sync the derived approvals + questions stores from the
|
// Sync the derived approvals + questions stores from the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue