Commit graph

176 commits

Author SHA1 Message Date
müde
fb669c17c8 dashboard: derive operator inbox from message stream (drop snapshot field + refetch workaround) 2026-05-17 12:28:04 +02:00
müde
1340a654e7 sse: seq plumbing + subscribe-first dedupe dance 2026-05-17 12:26:00 +02:00
müde
8c186d4fb7 dashboard: msgflow uses shared terminal + backfill via /messages/history 2026-05-17 11:56:29 +02:00
müde
0b9e7cbcf6 css: extract terminal pane styles to hive-fr0nt::TERMINAL_CSS 2026-05-17 11:50:39 +02:00
müde
e283e39949 css: route palette + body typography through hive-fr0nt::BASE_CSS 2026-05-17 11:47:45 +02:00
damocles
1770b51845 manager mcp: expose 'remind' tool sharing storage helper with agent surface 2026-05-17 11:43:14 +02:00
damocles
0e6bac8388 limits: unified 1 KiB cap on send/ask + reminder auto-file on overflow 2026-05-17 11:36:12 +02:00
damocles
753409a5ef reminder: fix symlink escape + db bloat cap + handler consistency 2026-05-17 11:26:59 +02:00
müde
9703753a4c dashboard: fan out op-send when recipient is * 2026-05-17 11:09:20 +02:00
damocles
6ce85bd6f2 reminder: file_path delivery + extract scheduler into own module 2026-05-17 11:05:29 +02:00
damocles
271c524e66 agent_server: reminder body size cap + extract Remind/AskOperator handlers 2026-05-17 02:59:51 +02:00
damocles
b86c0a2217 reminder: atomic delivery transaction + per-tick batch cap 2026-05-17 02:59:51 +02:00
damocles
f78c6085b9 fix: subscribe-before-check in recv_blocking to avoid missed-wake race 2026-05-17 02:59:51 +02:00
müde
600ed509f4 forge: ensure core/meta repo + mirror meta commits to forge
startup sweep adds ensure_repo('meta', core_token) after the orgs
so the first push isn't a 404. meta::git_commit now calls
forge::push_meta after every successful commit — token-in-URL
`git push http://core:$token@localhost:3000/core/meta.git` —
gated on the core token file existing (no-op when forge isn't
seeded). push failures log warn, don't bubble up.

no tea needed on the host; git is already on the hive-c0re service
PATH via /run/current-system/sw.
2026-05-17 01:52:00 +02:00
müde
68020a15c9 forge: drop redundant 'core' org — meta repo lives under core user 2026-05-17 01:50:12 +02:00
müde
db87167469 forge: seed core admin user + 'core'/'agents' orgs on startup
new ensure_core_user_and_token mints a site-admin 'core' user with
its token at /var/lib/hyperhive/forge-core-token (root 0600) —
hive-c0re's own forge identity for pushing the meta repo + driving
the admin API. that token then drives ensure_org for 'core' (meta
repo lives here) and 'agents' (per-agent applied config repos).
both org-create calls are idempotent: HTTP 422/409 treated as
success. failures log but don't abort the rest of the sweep.

curl is shelled out from the host — already on the hive-c0re
service PATH via /run/current-system/sw, no new dep.
2026-05-17 01:47:54 +02:00
müde
bf20d99142 kick_agent: use /agents/<name>/state uniformly
manager has /agents bind-mounted too, so /agents/hm1nd/state
resolves there alongside the legacy /state. one canonical path in
the wake message instead of branching on MANAGER_NAME.
2026-05-17 01:43:42 +02:00
müde
90f5162076 kick_agent: use per-recipient state path
manager keeps /state (legacy mount); sub-agents see their state at
/agents/<name>/state. wake message hardcoded /state/ for everyone,
which is wrong for sub-agents post-refactor — they get a path they
can't ls. switch on MANAGER_NAME and format the right path.
2026-05-17 01:43:03 +02:00
damocles
6ba4241a45 show answered question history on dashboard 2026-05-17 01:41:59 +02:00
müde
411cf86632 nix fmt + rustfmt sweep 2026-05-17 01:40:28 +02:00
müde
2b076f8ce4 forge: pass --work-path to admin CLI so app.ini is found
without --work-path, forgejo's admin CLI defaults WorkPath to the
binary's directory (RO nix store), can't find custom/conf/app.ini
there, falls back to defaults, and F3 init mkdir-fails inside the
store. systemd unit sets WORK_PATH for the daemon; mirror it here
for every nixos-container-driven 'forgejo admin' invocation.
2026-05-17 00:42:03 +02:00
müde
dccbd99b0c forge: broaden token scopes for repo create / PRs / orgs / misc
bumped from (read:user,write:repository,write:issue) to also include
write:user (own profile + create repos under own namespace),
write:organization (share namespaces between agents), write:misc
(hooks/attachments). still excludes admin and package scopes.
2026-05-16 20:58:20 +02:00
müde
480d646f69 forge: auto-create a user + token per agent on spawn / startup
new forge module probes the hive-forge nixos-container (no-op when
absent), and ensures every agent + the manager has a forgejo user
named after them with an access token at `<state>/forge-token`
(visible inside the container as `/state/forge-token`).

idempotent: skips user creation when forgejo reports 'already
exists', skips token issuance when the file is present, scopes the
token to read:user,write:repository,write:issue. token-name suffixed
with a clock so re-issuing doesn't collide with a stale name. shells
out via `nixos-container run hive-forge -- runuser -u forgejo --
forgejo admin` (runuser instead of sudo since sudo isn't in the
container by default).

hooks: ensure_all sweeps existing containers at hive-c0re startup
(backgrounded), and the actions.rs spawn task calls ensure_user_for
the new agent right after lifecycle::spawn succeeds. failures log a
warning but don't abort spawn — a missing token is recoverable from
the next startup sweep.
2026-05-16 20:55:13 +02:00
damocles
1023acf69f add get_logs tool to manager mcp surface 2026-05-16 20:45:19 +02:00
müde
313121a6e9 fix: transient state leak via RAII guard
bare set_transient/clear_transient pairs leak the in-memory transient
on task cancellation, panics, or any early return between the two
calls — dashboard then shows the agent stuck in 'rebuilding…'
forever (coder hit this today). add Coordinator::transient_guard
returning a TransientGuard whose Drop clears, and convert every
caller (dashboard lifecycle_action, auto_update::rebuild_agent,
manager_server Update, actions::destroy, actions Spawn task,
migrate phase 4). destroy() now takes &Arc<Coordinator> so it can
hold a guard. existing stuck transients clear on next hive-c0re
restart since transient state is in-memory only.
2026-05-16 19:47:52 +02:00
damocles
1a36c38a54 fix broadcast send for manager, deduplicate into coordinator.broadcast_send 2026-05-16 19:31:53 +02:00
damocles
4a8a668348 feat: add optional description to request_apply_commit and request_spawn 2026-05-16 15:18:32 +02:00
damocles
a6d1464071 refactor: per-agent state paths (/agents/{label}/state), centralize in paths.rs 2026-05-16 15:18:32 +02:00
damocles
ecaa178199 refactor: compute per-agent mount points for /agents/<name>/ structure 2026-05-16 15:18:19 +02:00
damocles
37e56af6ba add /shared mount: new shared directory accessible to all agents 2026-05-16 13:42:41 +02:00
damocles
abcf7a0c41 implement broadcast messaging: send to '*' reaches all agents with hint 2026-05-16 13:16:13 +02:00
damocles
22cea88c7e remove unused broker/coordinator methods 2026-05-16 13:02:53 +02:00
damocles
24eec69418 fix reminder tool issues: error on time overflow, optimize scheduler query 2026-05-16 13:00:56 +02:00
damocles
f38510930a reminder: add background scheduler loop - checks & delivers due reminders every 5s 2026-05-16 12:49:59 +02:00
damocles
4fc9c02934 reminder: add sqlite storage + broker methods + dispatch 2026-05-16 12:49:59 +02:00
damocles
7e9fd8e978 agent: add Remind request + ReminderTiming enum (stub implementation) 2026-05-16 12:49:59 +02:00
damocles
862bc1de44 Revert "agent: add Wake command - co-process self-wake via agent socket"
This reverts commit 68a9b8575b1647643c87bd753767acabf96528c3.
2026-05-16 12:49:59 +02:00
damocles
f0e87f0bc5 agent: add Wake command - co-process self-wake via agent socket 2026-05-16 12:49:59 +02:00
müde
d06b598c56 kick_agent on every rebuild + apply path
agents weren't being woken with the 'you were rebuilt — check
/state/ for notes, --continue intact' system message after
several recent rebuild surfaces:

- auto_update::rebuild_agent — used by the dashboard rebuild
  button, admin-CLI rebuild via lifecycle_action, the startup
  rev-scan, AND the new meta-input update batch loop. kick
  moves *into* rebuild_agent's success arm so all four
  paths benefit. (the dashboard's lifecycle_action extra
  closure was already firing kick — now it's a no-op for the
  rebuild path since rebuild_agent does it.)
- actions::run_apply_commit — apply-commit approve flow built
  + tagged deployed/<id> but never kicked. add kick on
  success with the more specific 'config update applied' hint.
- server.rs::HostRequest::Rebuild — the admin-CLI direct path
  calls lifecycle::rebuild bypassing rebuild_agent. add kick
  on success.

dashboard's restart / start lifecycle_action extras still
kick via their own closures since they don't route through
rebuild_agent. stop / kill / destroy intentionally don't
kick — there's nothing to wake.
2026-05-16 04:20:01 +02:00
müde
78aa830430 meta inputs panel: walk transitive inputs, slash-path names
read_meta_inputs() previously only included direct inputs of
meta's root node — so a manager-added 'inputs.mcp-matrix' in
agent-dmatrix's flake.nix never surfaced in the dashboard
panel even though it's a real fetched input that nix can
update.

now: BFS the flake.lock graph from root to depth 2. emits
one MetaInputView per fetched (non-follows) node, names are
slash-paths from root — 'hyperhive', 'agent-coder',
'agent-dmatrix/mcp-matrix', 'hyperhive/nixpkgs', etc. that's
the same syntax 'nix flake update' accepts for transitive
inputs, so the existing POST /meta-update path needs no
nix-side change.

depth limit of 2 keeps the panel readable — deeper transitives
(nixpkgs's own deps etc.) would explode it; bumping a level-2
entry re-fetches its sub-inputs anyway.

POST /meta-update's 'which agents to rebuild' derivation
updated for the slash names: anything under hyperhive/
fans out to all agents (shared base); 'agent-<n>/...' picks
out the agent name from before the first slash.

read_meta_locked_revs (used by the deployed:<sha> chip per
container) split out into its own straight root-input lookup
since the chip only cares about the agent's own input.
2026-05-16 04:12:04 +02:00
müde
40938d8b54 dashboard: surface silent unwrap_or_default in api_state
every snapshot source backing /api/state used .unwrap_or_default()
— sqlite errors, broker errors, nixos-container list failures,
operator_questions decode crashes all degraded to empty lists
without a log line. the 'pending question doesn't render'
bug we've been chasing was likely a row-decode panic in
OperatorQuestions::pending() being swallowed this way.

new log_default(what, result) replaces each call site: same
default value on Err but emits target=api_state warn with the
source name + dbg error first. five sources covered:
nixos-container list, approvals.pending,
approvals.recent_resolved, broker.recent_for(operator),
questions.pending. next time the question goes missing the
journal will say which source failed and how.

todo updated — pending-question entry now points at the new
log instead of three suspect paths.
2026-05-16 03:49:49 +02:00
müde
266c2c7a77 dashboard: meta flake inputs UI + sequential rebuild loop
new section 'M3T4 1NPUTS' between approvals and message flow:
one row per input in meta/flake.lock (hyperhive first, then
agent-<n> alphabetically). each row shows the input name, the
first 12 chars of the locked sha, a relative timestamp from
locked.lastModified, and the original.url when available.
checkbox per row; submit button is disabled until at least one
box is checked; submitting confirms then POSTs the selected
names to /meta-update.

backend:
- meta::lock_update(inputs: &[String]) — runs 'nix flake update
  <names>' in the meta dir, commits the lock change with a
  combined message ('lock update: hyperhive, agent-coder').
  preserves the existing META_LOCK serialization. existing
  lock_update_for_rebuild / lock_update_hyperhive stay for
  their single-input callers.
- POST /meta-update — comma-separated 'inputs' form field
  (JS joins checkboxes since axum::Form doesn't natively
  decode repeated keys); spawns a background task that runs
  the lock update + per-agent rebuild loop. hyperhive
  selection fans out to all agents; agent-<n> selection only
  rebuilds <n>. each rebuild fires Rebuilt to the manager
  exactly like dashboard / admin-CLI / auto-update.

rebuild loop is sequential — auto_update::run too (was
parallel via tokio::spawn). parallel rebuilds collide on
nix-store's sqlite cache ('sqlite db busy, not using cache')
and the meta META_LOCK contention. nix-daemon serializes the
heavy build steps anyway, so this isn't a throughput loss.
2026-05-16 03:38:07 +02:00
müde
891223219e server: notify manager on admin-socket Rebuild outcomes
HostRequest::Rebuild was the only rebuild path that bypassed
notify_manager. dashboard / auto_update / actions::approve
already emit Rebuilt events on both success + failure, but a
'hive-c0re rebuild <name>' from the host CLI (and the recent
matrix-flake build failure that surfaced in journald) left the
manager in the dark.

mirror auto_update::rebuild_agent's pattern: on success →
Rebuilt{ok:true}, on failure → Rebuilt{ok:false, note=
format!('{e:#}')}. note carries the stderr tail lifecycle::run
collected (the actual nix error: missing prompt file, dep
build failure, etc.), so the manager has enough context to
adjust the agent's agent.nix without ssh-ing to the host.
2026-05-16 03:30:02 +02:00
müde
06af23c8a4 recv: None = peek, positive value = opt-in long-poll
old behavior: omitted wait_seconds fell through to the 30s
RECV_LONG_POLL_DEFAULT — claude calling 'is there anything in
my inbox right now?' between actions blocked the turn for half
a minute. flip the semantics: None (or 0) returns immediately,
positive value parks up to MAX (180s, unchanged). cleaner
'peek vs wait' distinction; tool descriptions + agent/manager
prompts updated to point at the new shape.

harness's own serve loops in hive-ag3nt + hive-m1nd relied on
the old default for their inbox poll. they now explicitly pass
wait_seconds: Some(180) to opt into the full park — same
effective behavior as before, just spelled out.

retires the matching TODO under Turn loop.
2026-05-16 03:22:42 +02:00
müde
90df2106bf agent socket: external wake-up path for in-container MCP servers
new AgentRequest::Wake { from, body } drops a message into
this agent's inbox via the per-agent socket. matrix-style MCP
servers can use it when they receive an external event
(matrix message, webhook, scrape result) to nudge claude
into running a turn. broker.send wakes whatever Recv is
currently long-polling, the harness picks the message up,
formats a wake prompt with the caller's chosen from label
('matrix: new dm', 'webhook: deploy succeeded', etc.).

new `hive-ag3nt wake --from <label> --body <text>` subcommand
on the harness binary so MCP servers can shell out instead of
implementing the line-JSON protocol themselves; body=='-'
reads from stdin for multi-line / quoting-friendly payloads.

identity = socket: anything that can connect to /run/hive/mcp
.sock is implicitly trusted to inject. that's fine because the
bind-mount is the agent's own container; no new auth surface
opens up.

docs/turn-loop.md gets a new 'Waking the agent from inside
the container' section pointing at both paths (CLI + raw
JSON).
2026-05-16 03:15:58 +02:00
müde
96cb9f84c9 dashboard: approval history tab on P3NDING APPR0VALS
new tabs above the approvals list: 'pending · N' and
'history · M'. active tab persists in localStorage so the
operator can park on history if they prefer. on a fresh
dashboard the default is pending (matches the prior shape).

history view shows the last 30 resolved approvals — newest
first by resolved_at — with one row per approval: status
glyph (✓ approved / ✗ denied / ⚠ failed), id, agent, kind,
short sha, status label, and a relative time chip. when the
row has a note (deny reason or build error), it renders
below in a muted block with line wraps preserved.

backend: Approvals::recent_resolved(limit) queries by
status IN ('approved', 'denied', 'failed') ORDER BY
resolved_at DESC. StateSnapshot gets approval_history (a
lean ApprovalHistoryView without diff_html — rendering 30
git diffs per state poll would be expensive and the operator
already saw the diff at decision time). dashboard's
history_view fn projects the sqlite row.

retires the matching TODO entry.
2026-05-16 03:07:50 +02:00
müde
7276e6d5d9 git identity: shorten to 'c0re' across all helpers
lifecycle::GIT_{NAME,EMAIL}, meta::GIT_{NAME,EMAIL}, and the
inline strings migrate.rs uses for its bootstrap commits all
move from 'hive-c0re' / 'hive-c0re@hyperhive' to 'c0re' /
'c0re@hyperhive'. shows up shorter in git log everywhere
(applied + meta repos).
2026-05-16 03:02:44 +02:00
müde
8336017eda lifecycle: annotated tags need a tagger identity
git_tag_annotated planted failed/<id> + denied/<id> as
annotated tags via 'git tag -a' — which produces a git
object and therefore needs user.name + user.email. without a
global git config on the host that fell through to
'fatal: unable to auto-detect email address (got
root@muede-lpt2.(none))' and the tag never landed.

pass the hive-c0re identity inline with -c user.name=… -c
user.email=… (same shape git_commit already uses), so the
applied repo's deny/failure audit tags get planted reliably
without depending on the host user's git config.
2026-05-16 03:00:44 +02:00
müde
c92108a11c lifecycle: fetch into checked-out main with --update-head-ok
setup_applied does `git init --initial-branch=main` then
`git fetch <proposed> main:refs/heads/main` to seed the
applied repo with proposed's initial commit. git's default
safeguard refuses to fetch into the currently-checked-out
branch, even though the working tree is empty (we just init'd).
add --update-head-ok to bypass — the read-tree-reset
immediately after fetches the right state, so the safeguard
the flag bypasses isn't relevant here anyway.

repro from the user: spawn of 'dmatrix' failed with
  fatal: refusing to fetch into branch 'refs/heads/main'
  checked out at '/var/lib/hyperhive/applied/dmatrix'
2026-05-16 02:58:34 +02:00
müde
6f1b664c85 lifecycle: stream nixos-container stdout/stderr line-by-line
run() previously buffered the child's output via .output() and
only logged at exit — a multi-minute 'nixos-container update'
(typical on a fresh hyperhive bump) showed nothing in journald
until the very end. operator watching 'journalctl -u hive-c0re
-f' couldn't tell 'slow nix build' from 'wedged daemon'.

new shape: spawn with piped stdio, pump each line into tracing
as it arrives (stdout → INFO, stderr → WARN), keep a tail of
the last 32 stderr lines for the bail message so the eventual
'failed (status 2)' still carries the actual nix eval error.
target field 'nixos-container', argv-equivalent attached via
the 'cmdline' field so filtering by subcommand works.
2026-05-16 02:57:16 +02:00