argus on #622 comment 7419 🟡:
> monospace h2 on index — the CSS styles `h2` with `ui-monospace`
> font (intended for option-name headings in the per-option pages).
> on the index page, "host options" / "agent options" / "Regenerate"
> headings also get monospace treatment. cosmetic; reads as
> intentional if not, easy to scope.
Index page now uses h3 for section headers, leaves h2 free for the
auto-generated option-name headings on host.html / agent.html where
the monospace styling is appropriate.
(Other argus nit — stale namespace text in indexHTML's option
listings — will be addressed when this branch rebases post-#615
merge, same as #620.)
mara on mara/internal-requests#8:
> nix/docs.nix currently only emits CommonMark via doc.optionsCommonMark;
> add HTML output alongside.
Renders host + agent option pages as standalone HTML using cmark-gfm
(stock nixpkgs, no pandoc). Each page is wrapped in a minimal
inline-CSS template — no external stylesheets, no second HTTP fetch.
New bundle layout (consumed by nginx at `hyperhive.darkest.space/options/`):
index.html — landing page with cross-links + regenerate snippet
host.html — services.hive-c0re.* / hyperhive.{domain,forge,matrix}.*
agent.html — hyperhive.{model,allowedRecipients,extraMcpServers,…}
host.md — same content, CommonMark source-of-truth
agent.md — same content, CommonMark source-of-truth
All asset paths inside the rendered HTML are relative (`./host.html`
etc.) per mara's spec — the bundle mounts at any URL prefix without
rebuild. Forge source links from `transformOptions` are preserved
as proper `<a href>` (verified: `forge.darkest.space/.../nix/...`).
`packages.<system>.docs` now emits HTML primarily; `docs-host` and
`docs-agent` outputs flip from .md to .html (the .md content is still
in the `docs` bundle for callers that want the source shape).
The native nixos-render-docs `options html` subcommand doesn't exist
(only `manpage` / `commonmark` / `asciidoc`). The `manual html` path
exists but needs a full manual structure for what we're treating as
two standalone pages — overkill. cmark-gfm over the existing
CommonMark output is the leanest path.
Verified:
nix flake check --no-build
nix build .#docs # bundled site (5 files)
nix build .#docs-host # standalone HTML page
nix build .#docs-agent # standalone HTML page
Per [mara on PR #615 comment 7349](http://localhost:3000/hyperhive/hyperhive/pulls/615#issuecomment-7349):
> follow nix conventions, services.hyperhive it is. the earlier we
> change this, the less breakage.
Renames the entire host-side option tree under `services.hyperhive.*`:
- `services.hive-c0re.*` → `services.hyperhive.c0re.*`
- `hyperhive.enable` → `services.hyperhive.enable`
- `hyperhive.domain` → `services.hyperhive.domain`
- `hyperhive.forge.*` → `services.hyperhive.forge.*`
- `hyperhive.matrix.*` → `services.hyperhive.matrix.*`
Per mara's "earlier = less breakage", the previous `services.hive-c0re.enable`
deprecation alias is dropped. Operators get a clear eval error on the
old paths pointing at the rename. Single migration moment.
Per-agent options in `nix/templates/harness-base.nix` (`hyperhive.model`,
`hyperhive.allowedRecipients`, etc.) stay at `hyperhive.*` — they're
container-level config, not services in the NixOS sense.
Verified via `nix flake check --no-build` + an end-to-end NixOS eval
exercising every renamed path.
Follow-up needed: rust source comments referencing the old NixOS
option names (`hive-c0re/src/{meta,coordinator,main,dashboard}.rs`)
should be updated in a separate pure-rust PR to keep this one
strictly nix-only.
- Move options.services.hive-c0re → options.hyperhive.c0re
- Add options.hyperhive.enable to auto-enable c0re + subsystems
- Add deprecation alias for services.hive-c0re.enable (backward compat)
- Update doc references in README, flake.nix, docs, harness-base.nix
- Simplifies config: 'hyperhive.enable = true' now enables everything
Existing operator configs using services.hive-c0re.enable will
continue to work but emit a deprecation warning. Aligns the option
namespace with the existing hyperhive.* family (matrix, forge, domain).
fixes#612
argus review notes on #618:
- `walk` in `pickSubtrees` was leftover from an earlier traversal
design; `pick` does everything we need. drop it.
- `checks.docs` was re-importing `nix/docs.nix` independently of
`packages.docs`; the comment claimed they shared eval but they
didn't (nix's lazy eval + import caching made the *result*
identical, not the eval). switch to `inherit (self.packages.\${system}) docs;`
so the check is literally the package output, no second import.
Auto-generate CommonMark references for hyperhive's two NixOS module
surfaces via `pkgs.nixosOptionsDoc`:
- `packages.<system>.docs-host` — operator-facing options exposed by
`hyperhive.nixosModules.default` (`services.hive-c0re.*`,
`hyperhive.domain`, `hyperhive.forge.*`, `hyperhive.matrix.*`).
- `packages.<system>.docs-agent` — per-agent options declared in
`nix/templates/harness-base.nix` (model, allowedRecipients,
extraMcpServers, frontend, forge, matrix, gui, …).
- `packages.<system>.docs` — both pages plus a thin `README.md`
index, bundled for publishing.
Declaration links are rewritten to point at the forge source tree
instead of nix-store paths.
Host options come from a stubbed `nixosSystem` eval that force-disables
all hyperhive subsystems — only the *declarations* feed the doc
renderer, no heavy build inputs end up in the closure. Agent options
reuse `nixosConfigurations.agent-base.options` (already evaluated).
Also wired as `checks.<system>.docs` so CI fails fast on eval breakage.
Frontend half of #607 v0 — adds the `◆ M4TR1X ◆ →` page-link entry
to the dashboard tab strip between SCH3DUL3S and FL0W →. Same
`tab-link` class as FL0W → since /matrix/ is its own SPA, not an
in-place pane swap.
Hidden via the `hidden` attribute by default; tabs.js's `refreshState`
flips it based on `s.matrix_gui_enabled` (added in #610). When the
operator hasn't set `hyperhive.matrix.gui.enable = true` the snapshot
returns false and the tab strip doesn't surface a dead link.
docs/web-ui.md updated:
- chrome-header tab-strip list now includes M4TR1X → with the
hidden-when-disabled note
- new "M4TR1X page" section under FL0W explaining the same-origin
serve via hive-c0re ServeDir + the manual homeserver URL pick on
first login (with #609 tracking the post-#15 nginx-front re-root +
.well-known/matrix/client auto-discovery)
The N3W SCH3DUL3 vertical form is gone. The schedules table now
carries an always-visible inline create row at the bottom — fill the
cells, click + to POST /api/schedules, the new schedule appears
above on the next refresh.
Per-column inputs match the display semantics:
- `next` → `<input type="datetime-local">` (defaults to now+5min)
- `every` → 4 mini d/h/m/s number inputs (blank/all-zero = one-shot)
- `body` → `<textarea rows=1>` expanding to 4em on focus-within
so multi-line prompts still fit (closes mara's "prompt may still be
multi-line" requirement)
- per-agent columns → checkbox (whole cell clickable via padded label)
- actions → + submit + ⌫ reset (clears the carry without POSTing)
`newScheduleCarry` module-scope object survives the paintAtomic
re-render so mid-typed values stick across schedule-list refreshes
(same pattern as `scheduleEditCarry` from #474).
H2 collapses from "N3W SCH3DUL3" + "QU3U3D SCH3DUL3S" down to a
single "SCH3DUL3S" since the section now does both. Empty-state
branch on `renderSchedulesList` is gone — the table always renders
because the create row is always there.
`#schedule-new-section` div + `renderScheduleNewForm` /
`readScheduleFormCarry` / `submitNewSchedule` and the
`.schedule-new-form` CSS selector all removed. The edit-form path
(#474 `renderScheduleEditForm` colspan'd row on `✎` toggle) is
unchanged — "edit mode is inline in the table" already shipped
there, this PR just adds the same mental model for create.
Validations match the old form: empty targets / empty body /
invalid datetime / non-integer interval each surface an `alert()`.
The + button shows a spinner while the POST is in flight, then
clears the carry + triggers `refreshSchedules()`.
The sticky bulk-action bar (#443) was visible on every tab whenever the
selection was non-empty. On Y3R C4LL / SYST3M / SCH3DUL3S the operator
sees a floating bar without the agent cards next to it for cross-
reference — which is what mara called out in #596.
Fix: gate the bar on `document.body.dataset.activeTab === 'swarm'` in
addition to the existing non-empty-selection check. Selection state
itself stays in memory, so the bar reappears on return to SW4RM if any
agents are still ticked. The `activateTab` hook now sets the data
attribute and re-runs `renderSelectionBar` so the toggle takes effect
on hashchange without waiting for the next SSE update.
mara on #563: 'we fixed the agent to not start turns in that
state (it fell back to online before), but this does not show on
dashboard properly'.
Root cause: the per-agent harness flips LoginState::NeedsLogin in
memory on three entry paths (cold-boot without a session, 401
mid-turn, /api/logout) and parks in wait_for_login. But
wait_for_login itself never called bus.emit_status('needs_login_idle')
at entry — only the /api/logout handler does that today. So:
- Cold boot: agent has no session, harness shows 'needs login'
on its own web UI (via LoginState mutex), but the dashboard's
needs_login field stays false because the
{state_dir}/hyperhive-needs-login sentinel was never written.
- 401 mid-turn: same — the 'after a turn failed' path in
hive-ag3nt.rs / hive-m1nd.rs flips LoginState directly without
emitting status, then calls wait_for_login, which now waits
silently with no sentinel write.
Fix: hoist the emit_status('needs_login_idle') call into
wait_for_login itself. All three entry paths get the sentinel
write for free; the /api/logout handler's explicit call (web_ui.rs
line 966) becomes redundant but idempotent — no behaviour change
there. The 'online' clear at session refresh stays exactly where
it was at the loop's exit.
Both hive-ag3nt and hive-m1nd binaries share wait_for_login, so the
manager harness benefits without a separate change.
Cargo's default test runner parallelises tests within a binary,
so the original 'tests run serially' comment was wrong — two
`with_env` calls running concurrently would race the
process-wide HIVE_LABEL / HYPERHIVE_HIVE_DOMAIN state.
Added a module-scope `static ENV_LOCK: Mutex<()>` and acquire it
at the top of `with_env` so each set / run / restore window is
exclusive. Poison recovery via `unwrap_or_else(into_inner)` so a
single test panic doesn't cascade through the rest of the module.
Lighter than pulling in serial_test for one module. No new deps.
First chunk of #589 v0 phase A: plumbing the hive-qualified
'name@hive' form through the per-agent surfaces that the harness
itself owns. Broker from/to + dashboard rendering + container_view
follow in subsequent PRs once damocles ships the HYPERHIVE_HIVE_DOMAIN
env var in harness-base.nix.
- new hive_ag3nt::identity module: label() / hive_domain() /
qualified_label() / qualify(label). Reads HYPERHIVE_HIVE_DOMAIN
(set by hive-c0re.nix module from hyperhive.domain) — when unset
or empty, qualified_label degrades to just the short label so
existing single-hive deployments are unchanged. Six unit tests
cover the set / unset / empty / arbitrary-label paths.
- prompt::render gains {qualified_label} substitution alongside
the existing {label}. system.md template uses both: the agent
intro now reads 'You are hyperhive agent iris (qualified:
iris@darkest.space) in a multi-agent system. ... When you're
talking to or about a peer on a different hive, use the
qualified form (name@hive) so the operator + the manager can
disambiguate'. Manager flavor gets the same treatment.
- /api/state gains qualified_label: String. Always present, equals
label when no domain is configured.
- frontend setHeader takes the qualified_label, drives the browser
tab title (so two tabs from different hives are
distinguishable in the tab bar) while the glyphic #title stays
short for the cinematic header.
Gated on env var presence — no behaviour change for single-hive
deployments. Pairs with damocles's upcoming harness-base.nix
HYPERHIVE_HIVE_DOMAIN ship; safe to land in either order.
Browsers may silently ignore autocomplete='off' on type='password'
inputs (UA override to help users save credentials). For an OAuth
code, the semantic value is 'one-time-code' which is honoured + has
the side benefit of suppressing the 'save password?' prompt that
would otherwise fire on form submit.
The login-in-progress screen's OAuth-code input was a plain text
field — anyone shoulder-surfing or capturing a screenshot of the
agent web UI would see the code in cleartext. Same risk applies
to dashboard share-screens during live demos.
Changes:
- input switches to type='password' so the pasted code renders
as bullets by default. Placeholder updated to '(hidden)' so the
operator knows the masking is intentional, not a browser quirk.
- new 'reveal' button (👁) next to the input flips the type back
to text on press, so the operator can sanity-check the paste
before submitting if she wants. aria-pressed reflects state.
- CSS for the reveal button mirrors the existing .btn-login amber
family — quiet by default, amber border/glow when pressed.
- spellcheck='false' on the input so browsers don't try to
underline the random-looking string as a typo.
The on-screen OAuth URL stays visible (the operator needs to
click it). The code is the secret leg — only the operator's
browser holds it, the URL is what was posted publicly to claude's
OAuth provider.
After PR #585 (closes#584) narrowed the backend wipe to just
.credentials.json + mcp-needs-auth-cache.json (preserving
projects/<hash>/*.jsonl session history + everything else under
~/.claude/), the dialog wording I shipped in 9d58ec3 (which
described a full-dir wipe to satisfy argus's review against the
PRE-#585 backend) is now stale.
Updated to match what the backend actually does:
- title + slash desc + both confirm dialogs now say 'rotate OAuth
credentials, --continue session history preserved'
- confirms explicitly name the two files deleted (.credentials.json,
mcp-needs-auth-cache.json) so the operator knows exactly what's
going + reassures that the conversation context survives
- 'agent picks up where it left off on the next turn after re-login'
tail makes the recoverability concrete
No code-path changes — just the wording. Backend behavior + endpoint
shape unchanged.
argus #583 review caught: the prior dialog wording said 'prior
--continue context is not affected (only the OAuth creds)', but
that's false — paths::claude_dir() is /root/.claude and
remove_dir_all wipes the projects/<hash>/*.jsonl session history
along with the OAuth creds.
Updated both the overflow-menu confirm and the /logout slash
command confirm to explicitly say the projects/*.jsonl session
history (--continue context) goes too, plus a 'no undo' tail so
the operator can't read past the consequence list. SLASH_COMMANDS
desc + overflow-item title also corrected so /help + tooltips
match.
Pairs with damocles PR #582 (POST /api/logout backend on the
per-agent web UI). Three additions to the agent's app.js:
- New `postLogout` helper next to postCancelTurn / postCompact /
postNewSession. Same postSimple shape.
- New entry in SLASH_COMMANDS so /help lists /logout.
- New /logout case in handleSlashCommand with window.confirm.
- New '🔓 logout' item in populateOverflowMenu's overflow popover,
mirroring the new-session item's pattern (confirm before POST,
disable button while in-flight, closeOverflowMenu before fire).
Confirm dialog spells out the consequences (SIGINT, creds wiped,
park in needs-login) so the operator doesn't accidentally drop a
production session. Tooltip on the menu item links the action back
to the credentials directory + post-logout state.
Wire-level: POST /api/logout, no body. Backend returns 200 with a
text body describing the wipe outcome — postSimple ignores it
(success → no terminal note; failure → red turn-end-fail row).
Backend route POST /api/rebuild-queue/{id}/cancel already exists
(refuses Running / terminal entries with {cancelled: false}). This
adds the operator-facing affordance:
- small circular X button on the right edge of each row whose
state === 'queued'. Running / done / failed rows don't render
it, so the operator never clicks a button that the backend
would refuse.
- uses the same data-async + data-confirm pattern as the
reminder cancel form — global submit handler does POST +
spinner + error toast for free.
- successful cancel flips the row queued -> cancelled via the
live RebuildQueueChanged snapshot, so the button disappears
on the next paint without an explicit refresh.
CSS keeps it quiet by default (muted border, transparent
background) and lights red on hover / focus, matching the
.btn-deny family without claiming a full button-width slot
that would push the row layout around.
Pairs with damocles PR #566 (broker primitive + dashboard
`POST /api/agent/{name}/mark-all-read` route). The agent's
per-container inbox side-panel now gets a header row with a
`✓ mark all read` button that:
- confirms via a one-line dialog (the action is destructive: any
pending broker message for this agent is acked, the harness
won't receive a wake-prompt for them)
- POSTs to the host dashboard (cross-origin, same pattern as the
existing operator-answer flow on this page)
- surfaces `{ marked: N }` in an inline status pill, then triggers
a `refreshState` so any state-derived surfaces re-read fresh
- stays out of the way when the inbox is empty (only renders above
a non-empty rows list)
Note: `recent_for` returns the most-recent-N messages regardless of
ack state, so clicking does NOT visually empty the rows list. The
status pill ("✓ marked N as read") is the operator-facing
confirmation; the next `turn_start` will show `0 unread` in its
badge. Tooltip on the button calls this out so the operator isn't
surprised the row list stays put.
CSS mirrors the existing answer-form button family (mauve hover on
bg-elev background) so it reads as a peer affordance, with a
border-bottom separating it from the message list.
Per mara's review on PR #561: the previous commit kept
`./hive-ag3nt/prompts` in `cleanSrc` because
`hive-ag3nt::prompt::tests` had a compile-time
`include_str!("../prompts/system.md")`. That meant a prompt edit
still busted the cargo cache.
This change:
- Replaces the test-side `include_str!` with a runtime read from
`$HIVE_ASSETS_DIR/prompts/system.md` (with a CARGO_MANIFEST_DIR
fallback for plain `cargo test` from a checked-out repo).
- Drops `./hive-ag3nt/prompts` from `cleanSrc` — it's now
`craneLib.cleanCargoSource ./.` (Cargo.* + *.rs only).
- Sets `doCheck = false` on `packages.default` and lifts
`cargo test` into a separate `checks.cargo-test` derivation
that carries the `hyperhive-assets` build input. That scopes the
asset rebuild blast radius to the test check — `nix flake check`
still exercises the suite, but the binary derivation no longer
carries the assets dep.
Verified cache-invariance matrix (via `echo '' >> <f>; nix eval
.#default.outPath`):
| edit | default | cargo-test | clippy |
|-------------------------|---------|------------|--------|
| README.md | stable | stable | stable |
| branding/hyperhive.svg | stable | CHANGED | stable |
| nix/modules/* | stable | stable | stable |
| prompts/system.md | stable | CHANGED | stable |
| hive-c0re/src/main.rs | CHANGED | CHANGED | CHANGED |
(`cargo-test` CHANGED on prompts/branding is correct — tests
read the production template + need the assets output.)
After the asset-split in the previous commit the rust derivations
have no compile-time dependency on `branding/*` and the only
remaining reference to `hive-ag3nt/prompts/` is a `#[cfg(test)]`
`include_str!` of `system.md` for the prompt-renderer tests. So we
can finally narrow the src input down from `./.` (the post-naersk-
port shape) to a fileset:
fileset = lib.fileset.unions [
(craneLib.fileset.commonCargoSources ./.) # *.rs + Cargo.{toml,lock}
./hive-ag3nt/prompts # cfg(test) include_str!
];
Same `cleanSrc` is fed into all three derivations
(`buildDepsOnly`, `buildPackage`, `cargoClippy`) so the input hash
stays consistent across the chain (no surprise cache misses
between stages of the same nix build).
Verified the cache-invalidation contract by `echo '' >> <file>`
and re-evaluating `.#default.outPath`:
README.md → unchanged ✓
branding/hyperhive.{svg,png} → unchanged ✓
hive-c0re/src/main.rs → invalidates ✓
hive-ag3nt/prompts/system.md → invalidates ✓ (cfg(test))
branding/agent-configs.svg → unchanged ✓
(assets derivation rebuilds
independently)
End state: a tweak to nix modules, frontend JS, docs, README, or
any branding asset rebuilds nothing rust-side. Only Rust source
changes and prompt edits invalidate the cargo cache — and the
prompt edit is gated to tests, so the production binary derivation
is invariant to it (a follow-up could move the `include_str!` into
its own test-only fixture if even that residual coupling matters,
but the operator-visible cost today is zero).
Closes#555.