Commit graph

1,597 commits

Author SHA1 Message Date
iris
e24cfba124 feat(dashboard): preserve call-count prefix on hive-identity title update
On refreshState, the hive-identity title update (fired once when
hive_name/swarm_name are configured) now reads any existing (N) prefix
and preserves it, so the title doesn't briefly drop back to the bare
identity string before refreshTabCounts re-applies the prefix.
2026-06-05 13:12:33 +02:00
iris
fa62417328 feat(dashboard): prefix browser tab title with pending call count
When approvals or questions are waiting, document.title is prefixed
with (N) so the operator can see activity in a background browser
tab without switching windows. Clears to the base title when zero.

Implemented in refreshTabCounts (1s tick): strips any existing
prefix before re-applying so the hive-identity title update (once
on state load) composes cleanly.
2026-06-05 13:12:33 +02:00
iris
d4d8fa6d7a docs(web-ui): expand Y3R C4LL questions section — filter chips, peer override, answer form, history
The M1ND H4S QU3STI0NS paragraph was a minimal stub that omitted
most of the questions UI:

- Filter chips row (all · N / @operator / @peer / per-participant)
  with localStorage persistence
- Peer-targeted questions (agent-to-agent asks) and the
  operator's ability to override-answer them (0V3RR1D3 button)
- Full answer-form anatomy (textarea, option list, merge logic,
  Enter-to-submit, separate cancel form)
- Answered-history collapsible (◆ answ3red (N) details element)

Also clarify the P3NDING APPR0VALS paragraph: explicitly mention
the pending/history tab pair (it was documented in the Approval
card section but not called out at the tab level).
2026-06-05 13:12:12 +02:00
atlas
20c5039156 style: treefmt main — fix CI formatting check
Full `nix flake check` (CI) runs the treefmt formatting derivation. While the
hive-ci runner was offline (#1221), PRs merged without it, leaving 5 files
unformatted: hive-ag3nt/src/web_ui.rs, hive-c0re/src/bin/hivectl.rs,
hive-c0re/src/knowledge.rs, hive-c0re/src/matrix.rs,
hive-forge/src/verbs/attachment_get.rs. `nix fmt` output, pure formatting.
2026-06-05 13:09:56 +02:00
iris
c70823755b docs(web-ui): document BUILD tab auto-scroll, elapsed-time ticker, SSE list refresh
The BUILD sub-tab description was written before the auto-scroll,
live elapsed-time ticker, and SSE-driven list refresh were added.
Update it to cover:

- Live elapsed-time chip: ticks every second via setInterval,
  cleared on finish/error
- Sticky-bottom auto-scroll: follows new output while the operator
  is at the bottom; silently appends when scrolled up
- SSE-driven list refresh: rebuild_queue_changed fires a 2s-debounced
  fetchBuild() while on the BUILD tab; refresh button still works for
  immediate re-fetch
2026-06-05 13:09:07 +02:00
iris
2a5444fdc8 feat(dashboard): live-tick agent status-age chip via data-set-at + 30s interval
Stamp data-set-at on .status-age spans so the '(set N ago)' label
stays accurate as time passes. Previously the age was computed once
when the async dashboard-state fetch completed and never updated.

With the keyed container row cache, rows persist much longer between
rebuilds (no more full rebuild on every SSE event), making stale
status-age labels more noticeable. The 30s ticker fixes this without
requiring a full row rebuild or re-fetch.
2026-06-05 13:08:53 +02:00
iris
f011638b5e perf(dashboard): keyed container row cache — skip rebuild + async fetch for unchanged rows
Maintain a module-level containerRowCache (Map<name, {el, fingerprint}>)
that preserves <li> elements across renderContainers calls. Each row's
fingerprint encodes everything that affects its rendered output:
container running/login/update/reminder state, derived pending/opRunning
labels, tree position (depth, isLast, ancestorIsLast), selection, agent
question counts, and link-base context.

When the fingerprint is unchanged the existing DOM node is reused:
- no replaceChildren wipe for stable rows
- the async dashboard-state fetch (nav strip, ctx badge, status text)
  is skipped — previously-fetched data stays in place
- DOM order is reconciled via insertBefore with zero layout work for
  in-place nodes

Before this change every SSE event (container_changed, rebuild_queue_
changed, transient_set/cleared, question_added/resolved) caused a full
wipe + rebuild of the entire container list, triggering N concurrent
/api/dashboard-state fetches where N is the number of running agents.

After this change only the rows whose state actually changed are
rebuilt; the rest survive intact across re-renders.
2026-06-05 13:08:53 +02:00
iris
59798f72f1 fix(dashboard): auto-reset stale per-agent questions filter on re-render
If the operator had an agent:foo filter active and all of foo's questions
were resolved, foo's chip disappears from the filter row on the next
render — but the stored filter value is still agent:foo. The section
then shows "no questions match this filter" with no active chip visible,
leaving the operator confused.

Fix: compute the set of valid filter values (all, operator, peer, plus
one agent:<name> per current participant) before rendering. If the stored
value is not in the set, silently reset it to 'all'. Write directly to
localStorage rather than via setQuestionsFilter() to avoid a re-entrant
renderQuestions() call.
2026-06-05 13:06:07 +02:00
iris
6c6d6f67e7 fix(dashboard): preserve q-history details open state on SSE-triggered renderQuestions
When a question_added or question_resolved SSE event fires, the handler
calls renderQuestions() directly — bypassing the snapshotOpenDetails /
restoreOpenDetails pair that refreshState wraps around it. This caused
the answered-history <details> (data-restore-key="q-history") to
collapse every time a question arrived or was resolved while the
operator had it open.

Fix: call snapshotOpenDetails() at the start of renderQuestions() and
restoreOpenDetails() at the end. The double-restore in the refreshState
path is harmless — re-opening an already-open <details> is idempotent,
and restoreOpenDetails is a no-op when the snapshot set is empty.
2026-06-05 13:05:33 +02:00
iris
defcb5df0b fix(agent): use gateway-relative dashboard URL when behind hive-gateway
When the agent page is accessed via hive-gateway (path is /agent/<name>/)
the dashboard lives at the same origin's root (/), not at the direct
TCP port. Both app.js (overflow menu / setHeader) and stats.js
(dashboard-link anchor) previously hardcoded http://hostname:port which
is wrong or unreachable behind HTTPS TLS termination.

Detection: if location.pathname starts with '/agent/', use
location.origin + '/' as the dashboard base. Otherwise fall back to the
original direct-TCP construction for non-gateway deploys.
2026-06-05 13:05:01 +02:00
iris
10f22defee docs(web-ui): fix stale container-row 'Line 3' + document overflow menu
The dashboard.md 'Line 3: drill-in triggers' section described inline
↳ logs and ↳ build logs side-panel affordances that were removed from
the container row some time ago (navigation links were consolidated onto
the Line 1 nav strip; journal + build-log viewing moved to /logs.html).

Replace that stale section with accurate documentation of the per-agent
⋮ overflow menu (restart/stop/start/rebuild + journal-logs deep-link +
destroy/purge + deployed-sha forge link).

Also update the AGENT and SYSTEM sub-tab descriptions to cover:
- localStorage persistence of agent/unit selection across page loads
- ?agent= / ?unit= URL params for deep-linking (used by the ⋮ menu)
- 'fetched N ago' chip after each successful fetch (ticks every 30 s)
2026-06-05 13:04:38 +02:00
iris
f87a674e14 fix(flow): flashError was inserting at top of terminal (invisible)
The compose box's send-error path used:
  flow.insertBefore(row, flow.firstChild)

This inserts the error before the first (oldest) child — at the top of
the terminal's scroll area — which is completely out of view since the
operator is at the bottom watching new messages.

Changed to flow.append(row) + scroll the wrap element to the bottom so
the error appears where the operator is looking. Also prefixed the
message with '✗ ' so it's visually distinct from regular flow rows.
2026-06-05 13:02:48 +02:00
iris
435be4302e fix(dashboard): init_config kind label in approval history + remove dead ticker loop
Two small fixes in tabs.js:

1. renderApprovalHistory showed 'spawn' for init_config approvals.
   The pending-approvals path already handles the three kinds correctly
   (apply / init / spawn); history used a two-branch ternary that fell
   through to 'spawn' for init_config. Fixed to match the pending path.

2. The 1s interval that ticks rebuild-queue elapsed badges contained a
   second loop polling '.build-logs-runtime[data-bl-elapsed]'. No code
   in tabs.js ever sets that attribute — the logs page manages its own
   elapsed timers inside logs.js. Removed the dead loop.
2026-06-05 13:02:33 +02:00
iris
5fe750eaad feat(logs): auto-scroll live build output, elapsed-time ticker, SSE-driven list refresh
Three UX improvements for the live build log viewer:

1. Auto-scroll (sticky-bottom): live build output now scrolls to the
   bottom as new lines arrive. Stops auto-scrolling when the operator
   manually scrolls up; resumes when they scroll back to the bottom.
   Same intent-tracking pattern used by the terminal pane.

2. Elapsed-time ticker: running builds show a live seconds/minutes
   counter in the row header that ticks every second. Stops and shows
   the final duration when the build finishes (done frame received)
   or errors out.

3. SSE-driven list refresh: subscribes to /dashboard/stream and
   debounces a fetchBuild() call (2s) whenever rebuild_queue_changed
   fires while the BUILD tab is active. New log entries and status
   changes appear without a manual refresh.
2026-06-05 12:06:47 +02:00
iris
97857cfdc5 docs(coordinator): note CapabilitiesChanged/ToolGroupsChanged SSE in PermChange description 2026-06-05 12:06:32 +02:00
iris
f174c5d32c fix(dashboard): guard capabilities/tool-groups SSE handlers against in-progress edits
- Add 'capabilities-section' and 'tool-groups-section' to
  MANAGED_SECTION_IDS so operatorIsTyping() covers them too
- applyCapabilitiesChanged and applyToolGroupsChanged skip re-render
  when the operator has focus inside the section, preventing the
  table from being torn down under an in-progress checkbox edit.
  Tab-activation re-fetch is the recovery path for any missed event.
2026-06-05 12:06:32 +02:00
iris
13b0844c41 fix(dashboard): SYST3M tab count was always 0 (PascalCase vs snake_case)
refreshTabCounts() compared entry.state against 'Queued' / 'Running'
(PascalCase) but QueueState serialises as snake_case per
#[serde(rename_all = "snake_case")] — wire values are 'queued' /
'running'. Tab badge was always 0 regardless of rebuild-queue depth.
2026-06-05 12:06:32 +02:00
iris
04b313b5e7 docs(dashboard): note SSE coverage in P3RM1SS10NS tab description 2026-06-05 12:06:32 +02:00
iris
2080fd3866 feat: live SSE updates for P3RM1SS10NS tab (capabilities + tool groups)
Add CapabilitiesChanged and ToolGroupsChanged DashboardEvent variants
so the P3RM1SS10NS tab reflects perm changes without the operator
navigating away and back.

Backend:
- DashboardEvent::CapabilitiesChanged { seq, caps, descriptions,
  assignments } — same payload shape as GET /api/capabilities
- DashboardEvent::ToolGroupsChanged { seq, groups, descriptions,
  assignments } — same payload shape as GET /api/tool-groups
- Coordinator::emit_capabilities_snapshot() and
  emit_tool_groups_snapshot() — read from the JSON files and broadcast
- rebuild_queue.rs PermChange worker: emit after each successful
  commit_capabilities / commit_tool_groups call

Frontend:
- applyCapabilitiesChanged(ev): calls renderCapabilities(root, ev)
- applyToolGroupsChanged(ev): calls renderToolGroups(root, ev)
- Both registered in MUTATION_HANDLERS
- activateTab comment updated (SSE now covers perm changes)

Docs: dashboard.md and CLAUDE.md updated.

This completes SSE coverage for all dashboard sections: SW4RM,
Y3R C4LL, SYST3M, SCH3DUL3S/reminders, and P3RM1SS10NS all
derive live updates from /dashboard/stream.
2026-06-05 12:06:32 +02:00
iris
d0b038e17d fix(dashboard): refresh reminders on SCH3DUL3S tab activation + update stale comments
- activateTab('schedules') now calls both refreshSchedules() and
  refreshReminders() since both sections live on the same tab.
  (The previous SYST3M/system target was wrong.)
- Update index.html comment to reflect schedules_changed SSE coverage
- Update index.html reminders comment to mention reminders_changed SSE
- Update tabs.js reminders section comment to reflect SSE coverage
2026-06-05 12:06:32 +02:00
iris
a1e46e2b3d feat: live SSE updates for the SYST3M reminders section
Add RemindersChanged SSE event so the pending-reminders list in the
SYST3M tab updates live without polling.

Backend emission sites (every path that mutates the reminders table):
- agent_server: store_remind (remind MCP call)
- dashboard.rs: post_cancel_reminder, post_retry_reminder
- questions.rs: cancel_loose_end Reminder kind
- reminder_scheduler: after each delivery batch (any_delivered)

Coordinator gets emit_reminders_snapshot() mirroring the existing
emit_schedules_snapshot() pattern: lists PendingReminder rows from the
broker and emits DashboardEvent::RemindersChanged.

Frontend: applyRemindersChanged(ev) calls renderReminders(ev.reminders)
and is registered as reminders_changed in MUTATION_HANDLERS.

Docs: dashboard.md reminders_changed entry; CLAUDE.md file map updated.
2026-06-05 12:06:32 +02:00
iris
68108fe5f8 fix: emit SchedulesChanged from manager-server + approval paths
Agent-triggered schedule mutations (cancel_schedule, fire_schedule_now,
edit_schedule MCP tools) go through manager_server.rs, not the HTTP API
handlers. Approval-resolved SchedulePrompt inserts go through actions.rs.
Neither was emitting SchedulesChanged.

- manager_server.rs: add emit_schedules_snapshot() on Ok in
  handle_cancel_schedule, handle_fire_schedule_now, handle_edit_schedule
- actions.rs: emit_schedules_snapshot() after successful
  run_approval_schedule_prompt (covers request_schedule_prompt approval
  resolving)

Coverage is now complete: every path that writes a scheduled_prompts row
emits the SSE snapshot.
2026-06-05 12:06:32 +02:00
iris
76c4a67b1c feat: live SSE updates for the SCH3DUL3S tab
Add `SchedulesChanged` to the dashboard event channel so the
operator's schedule list updates in real time without requiring a
tab-activation or form-submit refresh.

Backend:
- `dashboard_events.rs`: new `SchedulesChanged { seq, schedules }`
  variant carrying a full `Vec<WireSchedule>` snapshot (same
  snapshot-over-diff rationale as `RebuildQueueChanged`).
- `coordinator.rs`: `emit_schedules_snapshot()` helper — queries the
  scheduled_prompts list, converts to wire shape, broadcasts the event.
- `dashboard.rs`: call `emit_schedules_snapshot()` at the end of each
  operator API handler that mutates a schedule:
  `post_schedule_new`, `post_schedule_fire_now`,
  `patch_schedule`, `post_schedule_cancel`.
- `scheduled_prompts_worker.rs`: call `emit_schedules_snapshot()`
  after each tick that fires schedules, so `last_fired_at_unix`,
  `next_fire_at_unix`, and reaped one-shots surface live.

Frontend:
- `tabs.js`: add `applySchedulesChanged(ev)` — replaces
  `schedulesState` from the snapshot and calls `renderSchedulesList()`.
  Registered in `MUTATION_HANDLERS` as `schedules_changed`.
  Tab-activation re-fetch kept as safety net for approval-path
  inserts and disconnect windows; comment updated to reflect this.

Docs:
- `docs/web-ui/dashboard.md`: document `schedules_changed` event.
- `CLAUDE.md`: add `SchedulesChanged` to the file-map entry.
2026-06-05 12:06:32 +02:00
atlas
b411a81c1a fix: point hivectl agents at the correct host socket path
hivectl's DEFAULT_HOST_SOCKET was /run/hive/host.sock, but hive-c0re binds
/run/hyperhive/host.sock (main.rs default + startup log). The doc comment
even claimed it matched hive-c0re's default — it didn't. So 'hivectl agents
restart' / 'restart-all' failed with 'connect to /run/hive/host.sock: No such
file or directory' unless the operator passed --socket explicitly.

Point the default at /run/hyperhive/host.sock.
2026-06-05 09:55:24 +02:00
atlas
3858740488 refactor: drop speculative prose markers, keep only backtick extraction
Per operator review on the PR: the prose marker list ('new password is:',
' to:', 'changed to:', etc.) was speculative — built from a misread
screenshot, not a real reply. The conduit admin bot always code-spans the
password, so the backtick anchor is the verified, complete format. Removing
the markers leaves a ~15-line function that's honest about what it parses.

If a non-code-span format ever appears, extract_new_password returns None and
the diagnostic logging in admin_room_send_and_poll records the raw body, so a
real format change is visible — far better than a speculative marker silently
mis-parsing it. Tests now cover the live format, symbol passwords, the
code-spanned-user-id error guard, and the no-codespan / non-password cases.
2026-06-05 01:53:05 +02:00
atlas
2d8a68e4d1 fix: extract matrix password from backtick code span (real conduit format)
The live conduit admin-room reply (observed directly in #admins) is:

  Successfully reset the password for user @x:server: `<password>`

The delimiter is ': ' after the user id, NOT ' to:' — so the ' to: ' marker
added earlier never matched the real format, and extract_new_password returned
None for every reply, timing out auto-recovery for every agent.

conduit always renders the password as a backtick code span, so anchor on
that directly: take the content of the first backtick pair when the message
is a password-reset success. Guard against a code-spanned matrix user id in
an error message (a real password has no whitespace and isn't @localpart:server).
Prose markers stay as a fallback for hypothetical non-code-span builds.

Tests cover the exact live format and the code-spanned-user-id error case.
2026-06-05 01:49:31 +02:00
iris
da52afcf53 docs(agent): update stats page description (Chart.js bundled, reminder chips) 2026-06-05 01:44:01 +02:00
iris
0d32048abe feat(stats): surface reminder_stats in the summary chip row
The backend already fetches ReminderStats from the broker RPC and
includes it in the /api/stats snapshot, but the frontend was not
rendering it. Add three chips to the summary row when the field is
present: reminders scheduled / delivered / pending.
2026-06-05 01:14:45 +02:00
damocles
d60414cde1 docs: move hive-forge cli reference to docs/tools/forge.md 2026-06-05 00:57:18 +02:00
damocles
881f598c64 docs: add attachment-get to forge cheatsheet 2026-06-05 00:57:18 +02:00
damocles
80157dd26c docs: add timeline, attach-issue, attach-comment, subscription to forge cheatsheet 2026-06-05 00:57:18 +02:00
damocles
2fff9485b9 fix: cancel_loose_end approval kind is root-agent-only, no own-approval carve-out 2026-06-05 00:48:05 +02:00
damocles
32d815df81 docs: clarify execution tool group is vestigial — bash always available via extraMcpServers 2026-06-05 00:48:05 +02:00
damocles
4d4bfb2b33 chore: remove stale TODO in stats.rs (ReminderRollup RPC already implemented) 2026-06-05 00:48:05 +02:00
damocles
457a3e3ae5 docs: add running field to get_agent_meta description in system.md and mcp.rs 2026-06-05 00:48:05 +02:00
damocles
29bc40a1d7 docs: document get_host_journal in scheduling.md and turn-loop.md 2026-06-05 00:48:05 +02:00
damocles
af4021f336 fix(#1088): sync system.md and mcp.rs tool descriptions against implementation 2026-06-05 00:48:05 +02:00
atlas
93d28ce0b1 fix: strip code-span backticks from extracted matrix password
mara observed the tuwunel reply renders the new password as a code span,
so the plain message body carries literal backticks:

  Successfully reset password for @user:server to: `<password>`

The previous extraction stopped at the first whitespace, capturing the
surrounding backticks ("`<password>`") and producing a login string that
doesn't match the password the bot actually set — recovery would still
fail after parsing.

Strip a leading backtick after the marker and stop the token at the first
whitespace OR closing backtick. Generated passwords contain neither, so a
real password is never truncated mid-token. Two regression tests cover the
backtick-wrapped form, including trailing prose after the closing backtick.
2026-06-05 00:47:37 +02:00
atlas
defface0a5 fix: parse tuwunel 'reset password for X to: <pw>' admin reply
extract_new_password had markers for 'is:', 'changed to:', 'reset to:',
'set to:' etc. but none match tuwunel's actual reset-password reply:

  Successfully reset password for @user:server to: <password>

Here the verb ('reset') is not adjacent to 'to:', so every marker missed
and the function returned None for every polled event. The admin-room poll
then ran its full 15s loop without a match and the auto-recovery timed out
on every agent — even though the bot replied correctly and the backward
poll found the message. This is why matrix password auto-recovery kept
looping despite the poll-strategy fix.

Add a generic ' to: ' marker (with surrounding spaces) placed after the
specific verb markers and before the bare 'password:' last resort. Matrix
user ids and server names can't contain ' to: ', so it only ever anchors
on the prose delimiter. Two regression tests cover the exact production
wording.
2026-06-05 00:47:37 +02:00
damocles
7f7ee7dbef fix: correct doc comment — path goes to stdout not stderr 2026-06-05 00:46:49 +02:00
damocles
98f96e5435 feat(#1315): hive-forge attachment-get verb - download attachments by uuid or url 2026-06-05 00:46:49 +02:00
iris
86165f07c8 docs: update hivectl verb list in CLAUDE.md (add gateway, agents, choom) 2026-06-05 00:46:17 +02:00
iris
33be854c50 docs: fix stale /api/agent/{name}/links reference in gateway.md 2026-06-05 00:46:17 +02:00
iris
30c2d94f13 docs: fix remaining stale AgentLink comment (per damocles review) 2026-06-05 00:46:17 +02:00
iris
4769d7ce5a docs: remove stale /api/agent/{name}/links references
The c0re-proxy endpoint was removed in the dashboard-state refactor.
Links are now served by agent_links() via two agent-local endpoints:
StateSnapshot.links (GET /api/state, per-agent page) and
DashboardState.links (GET /api/dashboard-state, dashboard card).

Update docs/web-ui/agent.md and the StateSnapshot.links doc comment
to reflect the current source of truth.
2026-06-05 00:46:17 +02:00
damocles
bc1c4d1cc5 docs: update get_host_journal container param to mention hive-ci and other siblings 2026-06-05 00:40:45 +02:00
damocles
12eac03324 fix(#1310): add hive-ci to SIBLING_CONTAINERS so agents can read its journal 2026-06-05 00:40:45 +02:00
atlas
4f446269ae fix: re-run hive-ci-prefetch on every container restart via partOf
The hive-ci-prefetch oneshot has RemainAfterExit=true and is wired to the
container only via wantedBy + before. Once it runs successfully it stays
'active (exited)' indefinitely, so systemd skips it on subsequent container
restarts. The runner-token file it wrote is never refreshed.

This breaks the runner after its first registration: the token written on
the first successful boot is either a placeholder (forge-core-token wasn't
ready yet) or a registration token that has since been consumed/rotated.
On the next container restart prefetch does not re-run, the stale token
persists, and the in-container register service fails with
'invalid_argument: runner registration token not found' — exactly the
symptom in the field (worked briefly, two orphan runners registered, then
permanently offline).

Add partOf = [ nixos-container@hive-ci.service ] so a container stop/restart
propagates to the prefetch unit, forcing it to re-run and fetch a fresh
registration token before the container comes back up. before= still orders
it ahead of the container start within the same transaction.
2026-06-05 00:39:13 +02:00
damocles
84e468dadb fix(#1309): retry forge token read at startup instead of giving up immediately 2026-06-04 21:15:03 +02:00
damocles
cf6479b3a9 fix(#1307) add appearance:none to .btn to strip browser UA button chrome 2026-06-04 21:04:45 +02:00