Commit graph

1,755 commits

Author SHA1 Message Date
damocles
f751c4495f refactor(#1474): group journalctl read args into a journalquery struct 2026-06-08 21:58:08 +02:00
atlas
98660d134a refactor(c0re): pass hive-c0re serve config via a --config file, shrink ExecStart
The systemd ExecStart carried every host-level setting as its own flag —
nine of them, including two escaped JSON blobs (the context-window map and
the model-price table). Collapse them into a single `--config <file>` JSON.

- Reuse the existing HiveEnv as the container-injected config shape (add
  Deserialize + Default), and add a ServeConfig wrapper = flattened HiveEnv
  plus the hive-c0re-local model_prices table (kept out of HiveEnv since it
  is never injected into containers). serde(default) lets any field be
  omitted and fall back to its canonical default.
- clap: add --config; the per-setting flags become optional overrides
  (config file is the base, explicit flags win — preserves hivectl/debug
  ergonomics and bare `hive-c0re serve`).
- Coordinator::open and cmd_serve now take the bundled HiveEnv, which drops
  their too_many_arguments clippy allows. cmd_serve keeps a single
  too_many_lines allow (inherent daemon-boot orchestration, not arg-driven).
- nix: write the config as JSON to the store + pass --config, so ExecStart
  is one short line.
- Add a round-trip test proving the flatten + per-field defaults work.

Closes the ExecStart-length issue.
2026-06-08 21:57:46 +02:00
iris
09787dd1c3 refactor(frontend): base16 theme contract in a swappable colors.css
Split the palette into two standalone stylesheets:
- colors.css — the 16 base16 slots (--base00..--base0F). This is the
  entire theme swap contract; a generator (e.g. a stylix base16 scheme,
  which is natively base00-base0F) replaces only this file.
- theme.css — the semantic layer (--bg, --purple, …) derived from the
  base16 slots via var()/color-mix. Never changes on a swap.

Every page links colors.css then theme.css; theme.css does NOT @import
colors.css (that would re-bake the slots into it) — they're separate
dist outputs so a swap touches just colors.css. Pixel-identical refactor:
base16 defaults are Catppuccin Mocha and the three off-slot vars
(--crust, --muted, --subtext0) derive via color-mix reproducing their
exact prior hexes.

Wires colors.css through both build.mjs CSS entry lists, the
@hive/shared exports map, and all 7 page templates. css-vars.md + the
frontend.nix output-list comment updated.
2026-06-08 21:24:07 +02:00
iris
742d4a5c6d fix(frontend): var-derive hardcoded palette colours for theme-swap safety
Replace ~92 hardcoded Catppuccin rgba literals across the dashboard,
agent, and shared terminal stylesheets with color-mix() over the theme
:root vars, so a stylix-generated theme.css recolours the whole UI, not
just the elements that already referenced the vars. Pixel-identical
under the default palette (same RGB + alpha; color-mix(in srgb, C N%,
transparent) == rgba(C, N/100)).

Also fixes four var(--mauve, #cba6f7) usages: --mauve is undefined (the
var is --purple), so they were silently falling back to the hardcoded
hex and would not have recoloured on a theme swap. The bare crust hex in
terminal.css and the checkerboard-gradient hexes in common.css are
var-derived too.

Pure black/white drop-shadow/frost scrims are left as literals (not
theme colours). Two off-palette surface tints (dim-purple schedule rows,
warm-amber inline-button hover) are also left, with a comment, pending a
dedicated named var. Correct-var fallbacks like var(--red, #f38ba8) are
left as-is since they already recolour via the var.
2026-06-08 21:24:07 +02:00
damocles
4e0f2ac9ca feat(stats): record api-resolved model id in turn-stats 2026-06-08 21:18:02 +02:00
damocles
8b66bf78df fix(#1462): buffer transient wakes so they survive when no recv is parked 2026-06-08 21:07:55 +02:00
damocles
05002acda4 chore(#1474): document the too_many_arguments keeps in hive-forge + hive-ag3nt 2026-06-08 21:01:32 +02:00
iris
5d0f3d060b feat(dashboard): generic server-warnings banner on every page (#1518)
Per operator request: instead of a disk-specific alert, surface a generic
server-warnings banner at the very top of every page, so new system
warnings can be added backend-side with no frontend change.

- hive-c0re `host_stats`: `server_warnings() -> Vec<ServerWarning>`
  (`{ kind, level, message }`). The threshold logic lives server-side; the
  host disk-pressure check (a `statvfs` probe of `/nix`: ≥85% used → warn,
  ≥95% → crit) is the first and only producer today. No new deps (libc).
- `/api/state` carries `server_warnings` (replaces the disk-specific
  field). Empty when all clear.
- frontend: `renderServerWarnings` / `initServerWarnings` in `common.js`
  inject a sticky top-of-<body> banner and render the list, coloured by
  `level`. Wired on every page — dashboard (live, via refreshState),
  FL0W, L0GS, H0M3. No per-warning frontend code; adding a warning kind
  is a pure backend change.

cargo check/clippy/fmt + npm run build green. Closes #1518.
2026-06-08 20:55:15 +02:00
iris
d9d2a52221 feat(stats): hive-wide "favorite tools" rollup on ST4TS
Swarm-level companion to the per-agent favorite-tools doughnut (#1433).
Aggregates each agent's bash_commands(ts, head) table (written by
hive-bash-mcp) across the whole hive and surfaces the top-10 most-run
command heads on the dashboard ST4TS tab, alongside the existing model
mix.

- hive_stats.rs: AgentAgg gains a `bash` head→count map, filled by a new
  guarded `read_bash_heads()` that reuses read_agent's read-only
  connection. A missing `bash_commands` table (capture hasn't run for
  that agent) or any read error yields an empty map — isolated from
  read_agent's error path so it never drops an agent from the rollup.
  HiveStats gains `bash_mix: Vec<KeyCount>` (busiest-first, top 10).
  Unit tests cover the per-head tally + window cutoff and the
  missing-table degrade-to-empty path (in-memory sqlite).
- dashboard: a "favorite tools (bash commands across the swarm)" CSS-bar
  list on the ST4TS pane, mirroring the model-mix bars. Header + list
  stay hidden until bash_mix has data, so a fresh hive shows no empty
  block. (Dashboard ships no chart lib — bars, not a doughnut.)
- docs: dashboard.md ST4TS section documents the new rollup.

Closes #1449. Inert until the hive-bash-mcp capture (#1448, merged) has
recorded data across agents.
2026-06-08 20:46:19 +02:00
iris
bd0b3efd8f chore: post-#1442 cleanup — fix stale price example + drop dead CSS
Two small follow-ups after the modelPrices PR (#1442):
- nix/modules/hive-c0re.nix: the `modelPrices` option `example` still
  showed the old 5-minute sonnet `cache_write = 3.75`; the default is now
  the 1-hour TTL `6.0`. Bump the example to match so it doesn't mislead.
- frontend/packages/agent/agent.css: remove the dead `.stats-empty-note`
  selector (no element uses it — the stats empty state paints on canvas).
2026-06-08 20:24:39 +02:00
iris
b066af010b review: use 1-hour cache-TTL prices (the subscription default) 2026-06-08 20:03:47 +02:00
iris
a9560ebb51 review: update default model prices to current Anthropic list pricing
Per operator request on the PR: the built-in/default prices were the old
Claude 3 numbers (opus 15/75, etc.). Update opus + haiku to the current
Claude 4.x family list pricing (cache_write = the default 5-minute cache
TTL); sonnet was already correct:

- opus:   input 5, output 25, cache_read 0.5,  cache_write 6.25
- sonnet: input 3, output 15, cache_read 0.3,  cache_write 3.75  (unchanged)
- haiku:  input 1, output 5,  cache_read 0.1,  cache_write 1.25

Updated in both builtin_prices (hive_stats.rs) and the nix modelPrices
default (hive-c0re.nix), with cross-reference "keep in sync" notes on
both sides. Also addresses the earlier reviewer note: dropped the
over-strong "single source of truth" wording in the --model-prices arg
doc (the nix default does mirror the numbers in production).
2026-06-08 20:02:31 +02:00
iris
60caef73f9 review: address damocles nits on modelPrices
- modelPrices submodule fields use lib.types.numbers.nonnegative
  instead of lib.types.float: accepts bare ints (15) as well as floats
  (15.0) and rejects negative prices for free.
- Collapse the triple-sourced default: hive-c0re serve --model-prices
  now defaults to "{}" so builtin_prices() is the single in-code
  fallback. The nix option default still carries the full
  opus/sonnet/haiku table to self-document prices for operators.
2026-06-08 20:02:31 +02:00
iris
cc8f58fb24 feat(stats): make ST4TS model price table operator-tunable
The hive-wide cost estimate on the dashboard's ST4TS tab used a
hard-coded model->price table in hive_stats.rs. Anthropic list pricing
drifts, so move the table to a nix option operators can keep current
without a code change.

- New `services.hyperhive.modelPrices` option: attrset of model-family
  short name -> { input, output, cache_read, cache_write } USD per
  million tokens. Passed to `hive-c0re serve --model-prices <json>`.
- hive_stats: `Prices` is now public + Deserialize; add `PriceTable`
  type and `resolve_prices` (longest case-insensitive substring key
  wins) with the old hard-coded table preserved as `builtin_prices`
  fallback for any model not covered.
- Coordinator holds the parsed table (hive-c0re-local, not injected
  into containers, so not part of HiveEnv); `/api/stats-hive` reads it.
- Docs: dashboard.md ST4TS cost note updated; option self-documents
  via nixosOptionsDoc.

Closes #1434
2026-06-08 20:02:31 +02:00
damocles
f5d9f325c6 refactor(#1474): replace approvals lookup 7-tuple with named struct, drop type_complexity allow 2026-06-08 19:56:32 +02:00
atlas
b7eb0f3930 docs(ci): recommend host-level nix store GC + min-free for CI disk pressure
Store GC is a host-level concern (the CI runner builds through the host
nix-daemon; the container shares the host store with no daemon of its own).
A service module should not change the host's global nix-daemon options, so
document the daily GC + disk-pressure min-free/max-free as a recommendation
for the operator's own host config instead. Notes the remote-builder caveat:
GC must be applied wherever the builder's store lives.
2026-06-08 19:32:14 +02:00
atlas
0d92a6028c fix(matrix): address argus review — drop in-code issue tag + dead nameservers
- remove the (#1500) issue tag from the resolv.conf source comment
  (no-NNN-in-code rule; context lives in the commit/PR/issue link)
- drop networking.nameservers from the network.enable branch: resolvconf
  is disabled, so nothing reads it to synthesise resolv.conf — the static
  environment.etc."resolv.conf" is the sole source. eval output unchanged
  (nameserver <bridgeIp> + options edns0), confirming it was dead config.
2026-06-08 18:06:20 +02:00
atlas
2efd9c4944 fix(matrix): write a static resolv.conf for tuwunel (eval-proven; #1500)
#1485's simplified fix turned off useHostResolvConf and trusted resolvconf
to honour networking.nameservers, but that is a runtime resolvconf behaviour
we couldn't verify at eval time — and it STILL came up with an empty
/etc/resolv.conf in practice, so tuwunel kept failing the resolver init and
matrix stayed down (#1500).

Take resolvconf out of the loop entirely: resolvconf.enable = false plus an
explicit environment.etc."resolv.conf" that writes nameserver <bridgeIp>
statically. Nothing regenerates it out from under tuwunel.

Eval-proven (unlike the prior variant): on a host with matrix+network on, the
generated container environment.etc."resolv.conf".text is
"nameserver <bridgeIp>\noptions edns0\n".
2026-06-08 18:06:20 +02:00
damocles
b5410d4613 chore: drop two more stale clippy allows (ContainerView bools, journal too_many_lines) 2026-06-08 08:17:33 +02:00
damocles
97f8781150 chore: drop the stale doc_markdown allow on OpQuestion (#1474) 2026-06-07 23:17:57 +02:00
iris
022fd5015c docs: reflect the H0M3 nav restructure in dashboard.md (#1504)
Follow-up to the #1464 step-2 PRs now on main (route swap + page-link
removal). Brings docs/web-ui/dashboard.md in line with the shipped UI:

- Tab strip no longer lists the FL0W / L0GS / M4TR1X "→" page-links —
  those are separate pages reached from the H0M3 hub now. The strip is
  in-page tabs only (SW4RM / Y3R C4LL / SYST3M / P3RM1SS10NS / SCH3DUL3S /
  ST4TS / P33RS / S3TT1NGS).
- Document the dashboard's new "← home" back-link in the chrome header.
- FL0W / L0GS page sections: they use a slim "← home" header now, not the
  dashboard tab strip (dropped the stale "reuses the dashboard chrome" +
  cross-page-tab-link descriptions).
- M4TR1X / L0GS page intros: reachable from the H0M3 hub tile, not a
  tab-strip "→" entry.
- S3TT1NGS-on-FL0W note: settings live only on the dashboard tab, reached
  via the FL0W page's "← home" → Dashboard.

Docs-only; matches the merged frontend. Closes #1504.
2026-06-07 22:00:47 +02:00
iris
91fff60c34 refactor(dashboard): drop FL0W/L0GS/M4TR1X page-links from the tab strip (#1464)
Follow-up to the route swap (#1501): now that every separate page is
reachable from the H0M3 hub, the dashboard tab strip should hold only
real in-page tabs, not the "→" links out to other pages (mara: "remove
links to the pages from dashboard - only actual tabs stay here").

- dashboard.html: remove the `tab-matrix`, `tab-flow`, and `tab-logs`
  `.tab-link` entries (and the stale `tab-count-flow` pill slot — the
  operator inbox moved to Y3R C4LL). The dashboard strip is now SW4RM /
  Y3R C4LL / SYST3M / P3RM1SS10NS / SCH3DUL3S / P33RS / ST4TS / S3TT1NGS.
- tabs.js: drop the now-dead `tab-matrix` matrix_gui_enabled gate (the
  H0M3 Matrix tile is gated by home.js instead) and the obsolete
  `#tab-count-flow` comment. Kept the contextual deep-links into the log
  viewer (an agent's logs, a build entry's log) — those are functional
  content links, not navigation chrome.
- flow.css: remove the dead `.dashboard-chrome.flow-chrome` +
  `.tabbar .tab.active.tab-link` rules — the flow page uses a slim
  back-link header, not the dashboard tabbar, and `.tab-link` no longer
  exists anywhere.

npm run build green (flow.css 1.8kb). No residual references.
2026-06-07 21:57:26 +02:00
damocles
7059ebdebf docs(nix): refresh the stale dashboard output-layout comment in frontend.nix 2026-06-07 21:52:37 +02:00
atlas
36e5e19d8e refactor(gateway): extract the _ vhost agent + dashboard location groups
Step 3 of the hive-gateway.nix vhost cleanup. Lift the /agent/ catch-all
(+ its two internal error-page targets) and the catch-all dashboard proxy
out of the inline //-chain into agentLocations / dashboardProxyLocation
bindings. The default _ server's locations now read as a flat composition
of named groups — matrixRedirectLocations // wellKnownLocations //
agentLocations // dashboardProxyLocation // <auth optionalAttrs> — instead
of a deep nested literal. The auth-401 group stays inline (a self-contained
lib.optionalAttrs already).

Pure readability refactor, eval-identical: generated virtualHosts toJSON is
byte-identical before/after (8888 bytes, diff empty).
2026-06-07 21:52:23 +02:00
iris
90b7d61792 docs: fix dead /home.html alias in the H0M3 page heading
The route swap (#1501) renamed the source home.html → index.html (served
at /), so /home.html 404s. Drop the stale alias from the dashboard.md
section heading and align it with the leet-speak heading convention
(## FL0W page / ## L0GS page): "## Home page (`/`, `/home.html`)" →
"## H0M3 page (`/`)".

Trailing fix from the #1495 docs PR (the heading nit both argus and I
flagged, which an AGit push issue prevented from landing before merge).
2026-06-07 21:24:31 +02:00
lexis
60fb6a29c3 docs: fix front-matter for dashboard route at /dashboard.html (post-H0M3 routing swap) 2026-06-07 21:13:31 +02:00
lexis
a5268560e1 docs: add operator inbox + flow agent filter + flow collapse + home page docs (follow-up to #1469 #1472 #1473 #1464) 2026-06-07 21:13:17 +02:00
iris
8ddab401f9 feat(dashboard): serve H0M3 at /, relocate dashboard to /dashboard.html (#1464)
Step 2 of the nav restructure: make the H0M3 menu hub the landing page
at / and move the dashboard SPA to /dashboard.html, with every surface
linking back to the hub.

Mechanism (frontend-only, no host-side change — confirmed against the
ServeDir route table): the dashboard's ServeDir fallback serves
index.html at / via append_index_html_on_directories, and there is no
SPA path-routing catch-all to break. The dashboard SPA is served as the
plain file dashboard.html so it never shadows the exact-match
/dashboard/stream + /dashboard/history SSE routes registered before the
fallback.

- Swap the HTML entry files: the H0M3 page becomes index.html (loads
  home.js → served at /), and the dashboard SPA becomes dashboard.html
  (loads tabs.js → served at /dashboard.html). build.mjs copies the new
  set; JS bundle names are unchanged (referenced by absolute /static/
  paths, independent of the HTML filename).
- H0M3 Dashboard tile now points at /dashboard.html.
- The dashboard gains a "← home" back-link in its chrome; flow.html and
  logs.html relabel their back-link from "← dashboard" to "← home"
  (href stays / — which is the hub now). Pages link to the hub, not to
  each other.
- Agent page (app.js + stats.js): the "↑ dashboard" link now targets
  /dashboard.html. The API base (rebuild / answer-question /
  mark-all-read POSTs) stays the origin root, unchanged.
- Comment-only: tabs.js / flow.js / common.js references to the
  dashboard's old index.html filename updated to dashboard.html.

Note for review: git renders the file swap as a deleted home.html + an
added dashboard.html + a heavily-modified index.html, because index.html
exists on both sides with swapped content. It's a content swap, not a
rewrite — the built dist/ is verified (index.html→home.js, dashboard.html
→tabs.js).

Deferred to a follow-up: removing the FL0W / L0GS / M4TR1X "→" page-links
from the dashboard tab strip (touches tabs.js gating/overflow), and the
shared reusable chrome component (a later step).
2026-06-07 21:00:34 +02:00
atlas
d7e4028a16 refactor(gateway): extract the _ vhost matrix location groups into named bindings
Step 2 of the hive-gateway.nix vhost cleanup (follows the sub-domain
vhost extraction). Lift the two deepest-nested location groups of the
default _ server — the /matrix/ 301 redirect and the
.well-known/matrix/{client,server} discovery JSON, each carrying its own
let block — out of the inline //-chain into matrixRedirectLocations /
wellKnownLocations bindings. The _ vhost locations now open with
matrixRedirectLocations // wellKnownLocations // ... instead of two ~60-line
nested literals. agent/dashboard/auth groups stay inline (a later step).

Pure readability refactor, eval-identical: the generated
services.nginx.virtualHosts toJSON is byte-identical before/after (8888
bytes, diff empty) on a host with matrix+forge+gui+auth+tls enabled.
2026-06-07 20:40:55 +02:00
atlas
a03aafb004 refactor(gateway): extract forge + matrix sub-domain vhosts into named bindings
Step 1 of the hive-gateway.nix vhost-builder cleanup. The two
sub-domain vhosts (forge, matrix) were inline `lib.optionalAttrs`
blocks `//`-appended to the virtualHosts attrset, burying the
top-level structure. Lift them into `forgeVhost` / `matrixVhost`
bindings in the config-closure let so the composition reads as
`{ "_" = ...; } // forgeVhost // matrixVhost` — the three vhosts are
now visible at a glance. Also fixes a garbled merge-mangled comment on
the nginx-reload host-trigger.

Pure readability refactor, eval-identical: verified the generated
`services.nginx.virtualHosts` toJSON is byte-identical before/after
(8888 bytes, diff empty) on a host with matrix+forge+gui+auth+tls all
enabled. The deeper `_` vhost location-group extraction is a follow-up.
2026-06-07 20:40:55 +02:00
iris
e949129e75 refactor(flow): drop operator-inbox UI, now lives on Y3R C4LL (#1483)
The operator inbox moved to the dashboard's Y3R C4LL tab in #1469
(◆ 1NB0X ◆ section, with per-message + mark-all read). FL0W goes back
to being the pure event firehose, so remove its now-redundant inbox UI:

- flow.js: drop the operatorInbox store, inboxAppendFromEvent,
  buildInboxListNode, renderInbox, the inbox-pill click wiring, and the
  onAnyEvent hook that fed them. The side panel was only used for the
  inbox flyout on this page, so drop Panel.bind() + the Panel import too.
- flow.html: remove the inbox pill, the offscreen inbox-section div, and
  the side-panel markup.
- flow.css: remove the .flow-pill* and .flow-inbox-headless rules.
- home.html: Flow tile desc → "live all-agents message firehose".
- docs/web-ui/dashboard.md: drop the FL0W 0PER4T0R 1NB0X section, point
  at Y3R C4LL, and fix the count-pill + /op-send descriptions.

The agent filter, sent→delivered collapse, compose box, and OS
notifications on operator-bound traffic are unaffected.
2026-06-07 20:40:20 +02:00
damocles
f9fe3280f9 chore: justify the remaining keep-only cast + serde-default allows with reasons 2026-06-06 14:25:41 +02:00
atlas
30be7cd632 fix(dashboard): rustfmt api_operator_inbox match expression
The operator-inbox handler landed with an unformatted long match line
(`match state.coord.broker.unread_for_recipient(...)`) that rustfmt
wants wrapped. It was force-merged during the CI outage so the formatting
gate didn't catch it, and now the treefmt check fails for every PR based
on current main. Pure formatting, no logic change.
2026-06-06 13:49:53 +02:00
atlas
812a072e1c fix(matrix): point the tuwunel container resolver at the bridge dnsmasq
The hive-matrix nixos-container came up with an EMPTY /etc/resolv.conf
even with networking.nameservers set, so tuwunel hard-failed at boot
(no nameservers found). The nixos-container default useHostResolvConf=true
puts in-container resolvconf in host-tracking mode: it ignores
networking.nameservers and never receives the host resolv.conf across the
shared-netns boundary, so resolvconf regenerates an empty file.

When the hive network module is on, turn off host-tracking (mkForce, to
beat the module default) so resolvconf honours networking.nameservers,
pointing the resolver at the gateway-container dnsmasq at bridgeIp.
Network module off -> inherit the host resolv.conf.
2026-06-06 13:27:23 +02:00
damocles
c819eab947 chore: justify the cast_precision_loss allows with reasons (#1474 slice) 2026-06-06 13:10:00 +02:00
atlas
09bcf9fd7a chore(ci): drop issue tag from hive-ci-prefetch 401 log string
The runner's prefetch 401/403 diagnostic baked a literal issue tag into
its runtime journal output. Per the hive-wide rule (no issue/PR #NNN tags
in code), strip it — the message already explains the stale-core-token
condition and the operator remedy in full prose, so nothing is lost.
2026-06-06 12:34:51 +02:00
iris
7d00928c69 feat(dashboard): operator inbox with mark-as-read on Y3R C4LL
Agents that `send(to: "operator")` were easy to miss — they only
surfaced on the FL0W firehose with no read-state (#1469). Surface them
on the Y3R C4LL ("things waiting on you") tab as a proper inbox.

Backend:
- broker: `unread_for_recipient(recipient, limit)` — unacked messages
  for a recipient, newest-first. Mirrors `mark_all_read`'s filter
  EXACTLY (`recipient = ?1 AND acked_at IS NULL`, no `delivered_at`
  condition) so everything listed is exactly what mark-read clears —
  operator rows never get `delivered_at` set (no agent-socket recv).
- dashboard: `GET /api/operator-inbox` → `{ messages: [...] }` (id,
  from, body, at, in_reply_to, validated file_refs). Mark-read reuses
  the existing `POST /api/agent/operator/mark-all-read` (the route
  format-validates the name; "operator" passes; `mark_all_read`
  already acks `to="operator"` rows).

Frontend (Y3R C4LL):
- New ◆ 1NB0X ◆ section listing unread messages (sender · time · body,
  path-linkified) + a "✓ mark all read" button.
- Cold-loaded on page load + on tab activation; appended live from the
  broker `sent` stream (deduped on row id); cleared on mark-all-read.
- Unread count folds into the Y3R C4LL tab pill + the browser-title
  `(N)` prefix, so messages are visible from any tab.

Removing the now-redundant FL0W operator-inbox UI is a clean follow-up
(deferred to avoid a flow.js conflict with the in-flight #1473).
Backend (broker + route) is host-side — @damocles to review per plan.

Closes #1469.
2026-06-06 12:34:31 +02:00
iris
4c77eefc51 feat(flow): filter the timeline by agent
Adds an agent filter to the FL0W header (#1473): a select listing the
live agents narrows the timeline to messages involving the chosen agent
(matched on `from` OR `to`). Each message row now carries
`data-from`/`data-to`; non-matching rows get `.flow-hidden`. New rows
pick up the active filter at render time; changing the filter re-scans
existing rows. The selection persists in localStorage so a reload or
tab-switch keeps the view. The dropdown is populated from the live
container list (and stays current on container add/remove); a saved
selection survives even if that agent isn't currently listed.

Pure frontend; composes with the sent+delivered collapse (the surviving
collapsed row keeps its `data-from`/`data-to`). Closes #1473.
2026-06-06 12:05:02 +02:00
atlas
d9c7b7fcc1 fix(ci): hive-ci prefetch — correct partOf unit name + 401-harden token fetch
Two bugs that together kept the runner registration token from
refreshing (#1475):

1. Unit name: the prefetch's before/wantedBy/partOf targeted
   nixos-container@hive-ci.service, but a declarative containers.<n> is
   the host unit container@<n>.service (confirmed against the live
   container@hive-matrix.service during the #1465 incident). The wrong
   name made all three silent no-ops, so the partOf never bound — the
   RemainAfterExit oneshot stayed 'active (exited)' and never re-ran on
   nixos-container restart, leaving the stale token in place. Corrected
   to container@hive-ci.service.

2. 401-hardening: the registration-token fetch used a bare curl -sf | jq,
   so a forge-core-token that is stale/invalid for the current forge
   (e.g. after a forge rebuild) 401s and fails silently every attempt for
   the full 60s loop, then exits with a misleading 'core token absent or
   forge unreachable'. Now capture the HTTP status and fail fast + loudly
   on 401/403 with a clear message pointing at re-minting the core token.
2026-06-06 11:58:13 +02:00
atlas
c5e23fd3a3 fix(matrix): correct container ordering unit name to container@<n>.service
The host systemd unit for a declarative nixos-container is
container@<name>.service (confirmed from the live
container@hive-matrix.service status), not nixos-container@. The prior
commit's nixos-container@ ordering targeted a non-existent unit (a
silent no-op). Use container@hive-{matrix,gateway}.service so the
after-ordering actually takes effect.
2026-06-06 11:51:37 +02:00
iris
a2fe3f2e01 feat(flow): collapse back-to-back sent+delivered into one line
When a message directly wakes its recipient, the broker emits `sent`
then `delivered` for the same row id ~instantly, rendering two
near-identical lines on the FL0W timeline. Track each recent `sent`
row and, when its `delivered` lands within COLLAPSE_SECS (3s), upgrade
that row in place (arrow → green ✓, title "sent + delivered") instead
of adding a second line. A delivery that arrives later (recipient was
busy) still renders as its own row, so genuine delivery latency stays
visible.

`Sent` and `Delivered` share the same broker row id
(dashboard_events.rs), so matching is exact. The recentSent map is
bounded to 256 entries. Reply threading is unaffected (msgRowMap still
resolves the surviving row).

Closes #1472.
2026-06-06 11:50:01 +02:00
iris
a5f1337876 fix(home): un-leet the menu link labels
Per mara on #1464: keep l33t only in page headlines, not link labels
(we were overdoing it). H0M3's tiles now read Dashboard / Flow / Logs /
Matrix; the page banner headline stays H0M3.
2026-06-06 11:49:31 +02:00
iris
b0eb824cfa feat(frontend): add the H0M3 menu-hub page (#1464 step 1)
First step of the dashboard-tab consolidation (#1464): a static menu
page linking to every top-level surface, so the tab strip can later
shed its `→` page-links and the dashboard can graduate Stats/Settings
to their own pages.

- dashboard/src/home.{html,css,js}: a responsive grid of link tiles
  (D4SHB04RD, FL0W, L0GS, M4TR1X). Pure portal — no tabbar/SSE. Colours
  from the shared theme.css, base typography from common.css. The
  Matrix tile is hidden until home.js confirms `matrix_gui_enabled`
  (same gating as the dashboard's M4TR1X tab); home.js also fills the
  swarm/hive identity line.
- build.mjs: emit home.{html,css,js}.

Served at `/home.html` for now (additive — reachable via the existing
ServeDir, links to surfaces at their current routes). Promoting it to
`/` (and relocating the dashboard to `/dashboard` + wiring `← home`
back-links) is the next step — a route swap that touches hive-c0re's
static router, coordinating with damocles. Deliberately decoupled so
this page ships standalone without conflicting with the in-flight
tabs.js change (#1449/#1451).

Part of #1464.
2026-06-06 11:49:31 +02:00
damocles
3cac374c60 forge: revalidate the core token against the live forge before trusting it 2026-06-06 11:26:59 +02:00
damocles
2a840c14a2 chore: drop stale dead_code allows + the unused operator_questions get method 2026-06-06 11:14:35 +02:00
atlas
43776afbfd fix(matrix): order hive-matrix container start after the gateway (resolver)
Per operator review on the PR: when the network module is on, the
matrix container's resolver is the dnsmasq in the gateway container, so
order the matrix container start after the gateway container. This is
robustness for tuwunel's lazy federation lookups, not a boot
requirement — the boot fix is the resolv.conf nameserver line (the
failure was a parse error on an empty resolv.conf, not connectivity).
Soft 'after' (not 'requires') keeps lifecycles decoupled; network.enable
asserts gateway.enable so the gateway container unit always exists.
2026-06-06 11:02:25 +02:00
atlas
38f2435767 fix(matrix): give hive-matrix container a DNS resolver so tuwunel can boot
tuwunel hard-fails to start when /etc/resolv.conf has no nameserver
line (Failed to configure DNS resolver: no nameservers found in
config -> exit 1 -> systemd start-limit). The declarative
containers.hive-matrix generates its own resolv.conf via resolvconf
and, unlike agent containers whose resolv.conf is written by
hive-c0re's lifecycle, has no nameserver source -> it comes up empty
(just 'options edns0'). Defaulting network.enable on surfaced this:
the host DNS moved to the bridge dnsmasq but the container was never
pointed at it, so the homeserver could not boot, taking down matrix
for all agents.

Point the container at the hive resolver (the dnsmasq the network
module runs at bridgeIp) when the network module is enabled; the
container always shares the host netns (privateNetwork = false) so it
reaches bridgeIp whether or not isolateContainers is set. With the
network module off, inherit the host resolv.conf.
2026-06-06 10:50:55 +02:00
damocles
b7529f2e00 feat(matrix): log message-handler firing + wake delivery to localise the no-wake bug 2026-06-06 09:19:02 +02:00
damocles
7529c67f6b docs: slim CLAUDE.md to a lean repo index (drop the drifting per-file map) 2026-06-06 08:56:02 +02:00
lexis
f32684b242 docs: add endpoint name for container_stats (GET /api/container-resources) for parity 2026-06-06 08:52:02 +02:00