Commit graph

320 commits

Author SHA1 Message Date
damocles
bc872fffb9 lifecycle: drop defensive is_running check per mara@#372 2026-05-24 13:16:31 +02:00
damocles
0540f213f1 lifecycle: use nixos-container restart for the running case (mara@#372) 2026-05-24 13:16:31 +02:00
damocles
48420890e0 lifecycle: preserve stopped state across rebuild (closes #371) 2026-05-24 13:16:31 +02:00
damocles
3fa12bf363 rebuild_queue: dedup MetaUpdate on (kind, agent, inputs) (closes #365) 2026-05-24 11:43:47 +02:00
damocles
0b03d5bcfb topology: meta-repo agent hierarchy + ContainerView.parent (#361) 2026-05-24 04:47:55 +02:00
damocles
4d7c767eb0 meta: emit nixpkgs follows for agents that declare it (closes #355) 2026-05-24 02:45:11 +02:00
damocles
09c51c87aa meta: collapse hyperhive's nixpkgs into meta's via follows (#317) 2026-05-23 14:51:40 +02:00
iris
229c4292e9 frontend: cut over Rust binaries to ServeDir; delete legacy assets
Phase 4 of #273 — the actual switch. Both axum routers now serve
their static surface via `tower_http::services::ServeDir` mounted
as a fallback service, reading the dist path from `HIVE_STATIC_DIR`
(set by Phase 3's NixOS module wiring).

Deletes:
- `hive-c0re/assets/{index.html, app.js, dashboard.css}`
- `hive-ag3nt/assets/{index.html, app.js, agent.css, stats.html,
   stats.js, screen.html}`
- The whole `hive-fr0nt/` crate (workspace member dropped, both
  hive-c0re and hive-ag3nt drop their `hive-fr0nt.workspace = true`
  dep). Its contents now live as `@hive/shared` under
  `frontend/packages/shared/`.

Rust changes:
- `hive-c0re/src/dashboard.rs`: remove `serve_index`, `serve_css`,
  `serve_app_js`, `serve_shared_js`, `serve_marked_js`,
  `serve_favicon` (all six `include_str!` handlers); replace their
  routes with a single `.fallback_service(ServeDir::new(static_dir))`
  on the router. Fail closed (anyhow::bail) if `HIVE_STATIC_DIR` is
  unset or not a directory at startup.
- `hive-ag3nt/src/web_ui.rs`: remove `serve_index`, `serve_css`,
  `serve_app_js`, `serve_shared_js`, `serve_marked_js`,
  `serve_stats`, `serve_stats_js`, `serve_screen`; same
  `fallback_service` pattern. `serve_icon` stays (consumes
  `/etc/hyperhive/icon.svg` + `branding/hyperhive.svg` fallback,
  neither of which lives under the frontend dist).
- `AgentLink` URLs for stats/screen switched from `/stats` / `/screen`
  to `/stats.html` / `/screen.html` since ServeDir doesn't auto-
  append the extension and the on-disk filename is the natural URL
  post-cutover.
- `Cargo.toml` (workspace): drop `hive-fr0nt` member + workspace
  dep, add `tower-http = { version = "0.6", features = ["fs"] }`.
- `hive-c0re/Cargo.toml` + `hive-ag3nt/Cargo.toml`: drop the
  `hive-fr0nt.workspace = true` dep, add `tower-http.workspace =
  true`.

Docs updated:
- `CLAUDE.md`: file map reflects `frontend/` (was `hive-fr0nt/` +
  `assets/`) and the ServeDir/HIVE_STATIC_DIR shape.
- `docs/web-ui.md` 'Shape (shared by both)' section: describes the
  ServeDir fallback + bundled-by-esbuild surface, no more
  `include_str!` references.
- `docs/terminal-rendering.md`: src paths point at
  `frontend/packages/{agent,shared}/src/`; marked is the npm dep,
  not vendored UMD.

Validation:
- `cargo check --workspace` — clean (5 warnings, all pre-existing
  in `rebuild_queue.rs`, none on changed files).
- `cargo clippy --workspace --all-targets` — clean (11 warnings,
  same pre-existing source).
- `cd frontend && npm run build` from the prior commit's lockfile
  produces the dist directories the new routers consume:
    dashboard: `dist/{index.html, static/{app.js, dashboard.css}}`
    agent:     `dist/{index.html, stats.html, screen.html,
                       static/{app.js, stats.js, agent.css}}`
  (favicon.svg lands in dashboard/ during the nix build —
  `nix/frontend.nix` install phase copies `branding/hyperhive.svg`
  there, since it's outside the npm tree.)

Refs #273.
2026-05-23 14:51:01 +02:00
damocles
d81b430136 rebuild_queue: pre-enqueue meta-update cascade at submit time (closes #347) 2026-05-23 12:36:06 +02:00
iris
a2ed35cd19 dashboard: pin agent icon at a fixed size
`.container-icon` had `align-self: stretch` + `aspect-ratio: 1`, so
the square's width tracked the body's height. As soon as state pills
(rate-limited / needs-login / needs-update / ctx) wrapped the head
row, the body grew taller and the icon grew with it — two cards
with different state ended up with visibly different-sized icons
(issue #344).

Pin the icon at 5em; height follows from aspect-ratio. Card-level
`align-items` drops to flex-start so a row taller than the icon
doesn't stretch the icon back out. The card body still flows
however many lines it needs.

Closes #344.
2026-05-23 12:12:49 +02:00
damocles
47d2f766c9 rebuild_queue: dashboard panel + snapshot field + SSE event wireup 2026-05-23 12:05:09 +02:00
damocles
11db5c2a8f rebuild_queue: switch dashboard / auto-update / manager call sites to enqueue 2026-05-23 12:05:09 +02:00
damocles
37f6bc4b6b rebuild_queue: wire worker into coordinator + dashboard event 2026-05-23 12:05:09 +02:00
damocles
5890e6796a rebuild_queue: add module with types + dedup + cancellation 2026-05-23 12:05:09 +02:00
damocles
73871f18c3 set_status: consolidate whoami into get_agent_meta with optional name 2026-05-23 11:32:33 +02:00
damocles
77fdaf0d1e set_status: add get_agent_meta tool for inter-agent status queries 2026-05-23 11:32:33 +02:00
damocles
8e8e8a771f set_status: add status_set_at timestamp (mtime of status file) 2026-05-23 11:32:33 +02:00
damocles
fe2933b213 feat: add set_status MCP tool and status field to whoami/dashboard (closes #325) 2026-05-23 11:32:33 +02:00
iris
6f3b56ad84 dashboard: tick question TTL chip every second
The ` MM:SS` chip on an asked-with-timeout question was rendered
once and then frozen — the operator saw stale info (e.g. 48s
sitting unchanged for the whole TTL window) (issue #335).

Stamp the deadline onto the chip as `data-deadline` and run a
single page-wide setInterval that refreshes every `.q-ttl[data-
deadline]`'s textContent each second. No re-render of the
questions section; no new state on the client. No-op when no
chips are on screen.

Also pulls the bucketed seconds-to-string logic into a
`formatTtl` helper so the renderer and the ticker share one
source of truth.

Closes #335.
2026-05-23 10:44:05 +02:00
iris
5887111327 dashboard: stop nav-link icons cramming against the head row's right edge
The per-container nav strip's <a> elements had class "meta nav-link".
`.container-row .head .meta { margin-left: auto }` then matched every
link, so as flex siblings the first one absorbed all the available
space and the rest packed against it on the right — the icons looked
like they overlapped (issue #333).

Drop `meta` from the link class. Add a `.nav-strip` rule that is
inline-flex with a 0.35em gap so the icons sit on a fixed cadence
regardless of how many backend-supplied links land. Give .nav-link a
real hit target (0.15em / 0.35em padding) + a subtle hover so the
icons read as interactive.
2026-05-23 02:43:12 +02:00
iris
2c087f53ed dashboard: same bind retry policy as the agent half
dashboard.rs had the same 12-attempt cap shape as the per-agent
bind_with_retry. Apply the same fix — retry forever with the 2s-capped
backoff, WARN early then INFO once we're clearly stuck on a stale
socket, INFO on success when we did have to retry. Mirrors the
agent change in this PR.
2026-05-23 02:32:33 +02:00
iris
222a5b4dc6 dashboard+agent: agent backend owns its nav links; dashboard proxies
The previous take put a shared NavLink wire type in hive-sh4re and
duplicated the link-building logic across crates. Per @mara on #326:
that doesn't fit the eventual frontend/backend split goal (#273).
The agent backend is the natural source of truth for what links its
own page exposes; hive-c0re just passes the list through to the
dashboard.

* hive-ag3nt/src/web_ui.rs: agent_links now also serves the
  config-repo link + reads agent-declared dashboardLinks extras
  from {state_dir}/hyperhive-dashboard-links.json. AgentLink gains a
  kind enum (Container | Forge | External) so the frontend can build
  the right href no matter which surface is rendering. The host
  header is no longer used — URLs are paths for Container/Forge,
  absolute for External.

* hive-c0re/src/dashboard.rs: new GET /api/agent/{name}/links route,
  a same-origin proxy that fetches the agent's /api/state and
  forwards just the links field. No shared wire type — hive-c0re
  treats the payload as opaque JSON (serde_json::Value). All failure
  modes degrade to an empty list so the dashboard still renders.

* hive-c0re/assets/app.js: container card head row gets an async-
  populated icon-only nav strip from the proxy. The hardcoded stats
  link, the standalone config-repo trigger, and the extras block are
  gone. The deployed:<sha> chip stays — the agent harness can't know
  its own deployed sha, so this chip is how the operator sees what's
  live alongside the agent's (root-only) config link.

* hive-ag3nt/assets/app.js: agent page meta-links rendered via
  el() / textContent (DOM build) so agent-declared icon / label / url
  strings never reach innerHTML. kind-based href resolution mirrors
  the dashboard side.

* docs/web-ui.md: dashboard + per-agent sections updated for the new
  architecture.

Closes #262.
2026-05-23 02:11:40 +02:00
iris
ce539559d5 forge: use base64 crate for avatar payload
Per @mara on #328: the hand-rolled encoder was over-cautious. Swap
for base64 = 0.22 from crates.io — a standard, widely-trusted dep,
no maintenance surface to carry. Drops the 15-line encoder and its
two RFC 4648 unit tests.
2026-05-23 01:15:16 +02:00
iris
dbb2ca4393 forge: upload hyperhive logo as the core user's avatar
The 'core' Forgejo user (hive-c0re's identity for commits in
core/meta + agent-configs/*) was showing the default hash identicon.
Adds a one-shot ensure_core_avatar in the ensure_all bootstrap that
POSTs the branding PNG to the admin avatar API and writes a marker
file (CORE_AVATAR_MARKER) so subsequent startups skip the call
(delete the marker to re-upload). Best-effort: a non-2xx is logged
and swallowed, doesn't gate startup.

PNG bytes baked in via include_bytes! from branding/hyperhive.png.
Base64 is hand-rolled (one small image in one cold path, not worth
a new workspace dep) with RFC 4648 §10 test vectors.

Closes #320.
2026-05-23 01:05:58 +02:00
iris
7f97acf19e dashboard: render META INPUTS as a full tree with bulk select
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.
2026-05-22 23:50:20 +02:00
iris
4a27ef7304 dashboard: derive ctx badge thresholds from the model context window 2026-05-22 22:32:32 +02:00
damocles
3e94914569 fix #289: full forge sync on rebuild (extract sync_agent, use in rebuild_agent)
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.
2026-05-22 22:18:05 +02:00
damocles
7ad9809207 fix #289: re-seed forge token in rebuild_agent
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.
2026-05-22 22:18:05 +02:00
iris
2f1b846baf dashboard: show meta-update progress in the META INPUTS panel
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.
2026-05-22 21:54:28 +02:00
damocles
3f08051bda lifecycle: seed applied repo at template commit, not main, for first-spawn diff 2026-05-22 21:29:52 +02:00
iris
a9a10b631f dashboard: show when an approval was requested (closes #272) 2026-05-22 20:06:45 +02:00
damocles
748536203b refactor: split long functions per review feedback; remove all #[allow] attributes 2026-05-22 19:24:44 +02:00
damocles
bbe2112dc9 fix: use try_from for i64/u64 casts; split format_notification into helpers 2026-05-22 19:14:22 +02:00
damocles
484cea62c7 fix #265: resolve all remaining clippy warnings (cast, too_many_lines, doc nits) 2026-05-22 19:11:40 +02:00
damocles
30d82148e0 clippy: apply auto-fixable warnings across workspace (closes #265 partial) 2026-05-22 18:55:57 +02:00
damocles
b283768f26 forge: replace curl shell-outs with reqwest http helper (closes #249) 2026-05-22 16:26:44 +02:00
damocles
bbe4cdb872 add meta read access and remote for agents on forge 2026-05-22 15:28:06 +02:00
damocles
7426654a74 crash_watch: track prev_sub_agents to fix needs_login for newly spawned agents 2026-05-22 12:31:18 +02:00
damocles
55fe2856b9 actions: emit needs_login after first spawn, fix update_meta_inputs arity 2026-05-22 12:31:18 +02:00
damocles
66f1568e8f apply_commit handles first-time spawns, request_spawn deprecated 2026-05-22 10:52:02 +02:00
damocles
3e098c56ff update_meta_inputs: require operator approval, rename to request_update_meta_inputs 2026-05-22 10:49:20 +02:00
damocles
597e4ba03a manager: add update_meta_inputs tool to update flake.lock on demand (closes #235) 2026-05-22 10:49:20 +02:00
iris
15e44955a8 web: clickable links in terminal rows and dashboard messages (issue #233) 2026-05-22 01:17:38 +02:00
damocles
66c481a07a harness+dashboard: declarative dashboardLinks option (closes #191) 2026-05-21 23:20:13 +02:00
iris
ab1f8d6e33 dashboard: icon fallback on real img load failure, not container-state guess 2026-05-21 22:01:38 +02:00
iris
16f614f45d dashboard: dimmed default icon for unreachable containers
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
2026-05-21 21:56:39 +02:00
iris
f42ba9b561 dashboard file preview: markdown tabs + raster image rendering
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
2026-05-21 21:49:15 +02:00
iris
a8ab91ecd8 dashboard: render SVG file previews
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
2026-05-21 20:40:30 +02:00
damocles
433bc85b91 auto_update: rebuild all on startup, needs_update = applied HEAD vs deployed sha 2026-05-21 19:47:01 +02:00
iris
9abcda280a dashboard: full-height square agent icon, icon-left card layout
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
2026-05-21 19:19:47 +02:00