Remove the depth-2 cap in walk_meta_inputs so every fetched input
at every depth is surfaced, not just two levels (issue #275). The
uncapped walk needs a guard: a visited-node set makes it a spanning
tree — each fetched node walked once, at its shallowest path — so
shared subtrees don't re-walk and a cycle can't recurse forever.
A two-pass walk (claim a node's direct inputs before descending)
keeps shallow inputs at a shallow path.
Frontend: renderMetaInputs indents each row by its slash-path depth
and shows the leaf segment (full path on hover), plus a select-all /
select-none control so a long input list isn't ticked box by box.
extract per-agent forge logic from ensure_all() into sync_agent()
so both the startup sweep and rebuild_agent call identical code.
rebuild now runs: ensure_user_for + ensure_config_repo + push_config
+ meta_read_access + ensure_meta_remote — same as the boot sweep.
missing tokens and drift in any forge state are fixed by rebuild,
not just hive reboot.
if forge_after_first_spawn fails transiently on first spawn the
token is missing. rebuild_agent now calls ensure_user_for so
a manual rebuild (or the startup auto-update scan) recovers
the missing token — no full hive reboot needed.
post_meta_update returns 200 immediately and runs the nix flake
update + agent-rebuild ripple in a background task, so the META
INPUTS panel looked idle for the whole multi-minute window (#259).
Track in-flight runs with a Coordinator atomic counter, exposed via
an RAII MetaUpdateGuard held across run_meta_update. Surface it as
the meta_update_running snapshot field plus a MetaUpdateRunning SSE
event (flipped only when the count crosses 0, so concurrent runs
flip the flag once). The panel shows a pulsing in-progress banner
and disables the update button while a run is active.
A stopped or mid-transient (restarting / rebuilding) container's
web server isn't answering, so its <url>/icon background-image
just failed to an empty box on the card.
When the container isn't reachable (not running, or a transient
is in flight) the icon now falls back to the dimmed hyperhive
mark — /favicon.svg, served by the dashboard itself so it's
always loadable — greyscaled + lowered opacity via the
.icon-unreachable class.
closes#195
Follow-up to #188. Two additions to the side-panel file preview:
- Markdown files get a rendered/plain tabbed view (was: always
rendered, no way to see source) — same tab pattern as SVG.
- Raster images (png/jpg/gif/webp/bmp/ico/avif) render as an
<img>. /api/state-file previously from_utf8_lossy-stringified
every file and served text/plain, which corrupts binary; it
now serves image files as raw bytes with their real
content-type (over-cap images are rejected, not truncated —
a clipped binary is corrupt).
buildSvgPanel generalised to buildTabbedPreview, shared by SVG +
markdown. .svg-host/.svg-render renamed .preview-host/.img-preview
since they now back images + md too.
closes#192
SVG files in the side-panel file preview showed only raw source.
Add a rendered/source tabbed view: 'rendered' (default) shows the
image, 'source' shows the markup.
The image loads via an <img> data: URI — <img>-loaded SVG runs in
the browser's secure static mode (scripts + external fetches
disabled), so an untrusted SVG from an agent's state dir can't
execute code in the dashboard origin. Tabs reuse the existing
diff-base-tab styling; a checkerboard backs the image so
transparent regions read clearly.
closes#188
The agent icon was a 26px <img> inline in the card head, hidden via
onerror when a stopped container's web server didn't answer — which
collapsed the slot and shifted the row.
Restructure the live container card as icon-left / body-right:
- the icon is a background-image div with aspect-ratio 1 and
align-self stretch — full card height, square, and (being a
background) it has no intrinsic size, so loading or failing it
can never reflow the row;
- a failed load (stopped container) falls through to a placeholder
fill instead of collapsing;
- the three content lines move into a .card-body column.
Tombstone rows keep the plain stacked layout (:not(.tombstone)).
closes#177
The main dashboard had no favicon — PR #145 added them to the
per-agent pages but missed hive-c0re's index. Serve branding/
hyperhive.svg at /favicon.svg and declare it in the index head.
The dashboard represents the whole hive, so it uses the project
mark (per-agent pages keep their own configurable /icon).
closes#173
The dashboard cold-loaded its derived stores (approvals, questions,
containers, …) from /api/state once, then relied solely on live SSE
events. Events that fired during a disconnect window (reconnect,
hive-c0re restart) are never replayed, so the dashboard drifted stale
until a manual reload.
- terminal.js: add onStreamOpen, fired on every EventSource open
(initial + reconnect); the dashboard wires it to refreshState() so
every connection epoch re-syncs the authoritative snapshot.
- terminal.js: seq-dedupe only event kinds that actually appeared in
the history replay. Mutation events are never in /dashboard/history,
so deduping them against the broker-history seq wrongly dropped ones
that fired between the /api/state snapshot and the history fetch.
- app.js: make applyApprovalResolved / applyQuestionResolved
idempotent (guard the history unshift by id) so a re-sync
overlapping a live event can't double a history row.
closes#163
row_to_approval matched only apply_commit + spawn, so any approvals
row with kind=init_config (added by 80dd5bb's two-step spawn) failed
to deserialize. pending() / recent_resolved() collect all-or-nothing
via collect::<Result<Vec>>(), so one bad row errored the whole query;
api_state's log_default then swallowed the error and returned an empty
list — every pending approval vanished from the dashboard (issue #160).
- add the missing init_config arm to row_to_approval
- collect_lenient(): skip + log unparseable rows so a single bad row
can never blank the whole approvals list again
- dashboard: label init_config approvals 'init' (was mislabeled
'spawn' by the apply-vs-other fallthrough)
closes#160
Consumes the GET /icon endpoint from #139:
- Dashboard: each container card shows the agent's icon next to its
name (26px). Loaded from <agent-url>/icon; onerror hides it for a
stopped container whose web server isn't answering.
- Per-agent web UI: the agent's icon next to the page title (40px),
and /icon as the favicon on the index, stats, and screen pages.
/icon always returns an image (configured SVG or the default
hyperhive logo), so no presence check is needed.
Closes#140
CountPendingReminders and ReminderRollup were hardcoded to
MANAGER_AGENT. Both now take agent: Option<String> — None keeps the
current behavior (manager's own), Some(name) returns that agent's
reminder stats. The broker functions already take an agent name, so
this is a thin wire-protocol change. Callers (web UI stats page,
post-turn counts) pass None.
Closes#122
GetLooseEnds now takes agent: Option<String>:
- None = manager's own loose ends (default; the bug fix)
- Some("*") = hive-wide view (every approval/question/reminder)
- Some("name") = that agent's loose ends
The get_loose_ends MCP tool exposes this as an optional agent arg, so
the manager can still scan the whole swarm on demand. The web UI and
post-turn counts pass None (manager's own).
hive_wide returns ALL agents' reminders and questions, causing other
agents' reminders (e.g. triage) to appear on the manager's web page
and in the get_loose_ends MCP tool. The MCP tool spec says it shows
your own pending items — switch to for_agent(MANAGER_AGENT) which
includes all approvals (manager is sole submitter), questions where
the manager is asker/target, and only the manager's own reminders.
Fixes#118
agent users were created with {name}@hive.local but git commits use
{name}@hyperhive (set by meta::render_flake). forgejo matches by email,
so no profile link appeared on any commit.
- extract agent_email() helper returning {name}@hyperhive
- use it in ensure_user_exists (new users)
- add ensure_user_email() that runs gitea admin user edit to patch
existing users; called from ensure_all on every startup sweep
Closes#64
- MessageEvent and DashboardEvent Sent/Delivered now carry id and in_reply_to
- broker.send() includes last_insert_rowid in the emitted event
- recent_all() and recv_batch() include id and in_reply_to from the DB
- deliver_reminders_batch() tracks per-row rowids within the transaction
- dashboard message flow: reply rows are indented with a border-left and a
clickable '↳ reply' tag that scroll-jumps + briefly highlights the parent
- per-agent inbox: reply messages get a '↳ reply ·' prefix and indent
Closes#26
- add rate_limited: Arc<AtomicBool> to Bus; set/cleared by emit_status
- write/remove sentinel file hyperhive-rate-limited in state dir so host-side
dashboard can detect it without a live socket call
- api_state returns status=rate_limited when flag is set (cold-load accurate)
- ALIVE_LABELS gains rate_limited entry (⊘ red chip) on per-agent page
- ContainerView gains rate_limited: bool read from sentinel file
- dashboard container row shows ⊘ rate limited badge (red) ahead of needs_login
Closes#24
Reads the most recent turn's context-window token count directly from
each agent's hyperhive-turn-stats.sqlite (same path the host-side
stats_vacuum uses). Adds ctx_tokens: Option<u64> to ContainerView;
populated in build_all via a single best-effort SQL query.
Dashboard app.js renders a 'ctx·Nk' badge colour-coded by harness
watermarks: green <100k (safe), yellow 100-150k (near auto-reset),
red ≥150k (compact territory). Badge only shown when ctx_tokens
is present (agent has run at least one turn).
Closes#17