Commit graph

1,543 commits

Author SHA1 Message Date
atlas
e20a311786 tidy: is_whitespace() already covers newline, remove redundant arm 2026-06-04 20:13:47 +02:00
atlas
254fd1f9f1 fix: admin-room poll strategy — backward fetch anchored on sent event_id
The forward-pagination approach (dir=b anchor → dir=f poll) fails in
production: commands sent as @hive time out consistently even though
tuwunel responds. Root cause is likely a pagination-token direction
incompatibility in some tuwunel builds where 'end' from dir=b cannot
be used as 'from' for dir=f.

New strategy: send the command, capture the event_id from the PUT
response, then poll dir=b&limit=20 each tick. Events come back
newest-first; walk until we hit our own event_id, then stop — anything
before that marker arrived after our command. Simpler, avoids stored
tokens entirely.

Also:
- check formatted_body in addition to body (some admin bots put content
  only in HTML formatted_body)
- add more extract_new_password patterns: 'changed to:', 'reset to:',
  'set to:', 'new password:', 'password:' to handle different tuwunel
  version response formats
- add unit tests for new patterns

Fixes #1283.
2026-06-04 20:13:47 +02:00
damocles
9f8694e3cf fix(#1249): break sticky-bottom lock when user scrolls up during snap animation 2026-06-04 20:10:37 +02:00
iris
0723737d89 fix(#1029): use getBoundingClientRect for tab overflow cutoff
The old measurement used tabbar.clientWidth which includes horizontal
padding (~2em / ~30px total), causing an over-allocation of the same
amount. Flex gap between tabs (0.2em) was also not counted in the
accumulated offsetWidth, adding another ~20px of error. Together these
caused the algorithm to leave too many tabs visible, overflowing the
bar and pushing the ⋮ button off the right edge of the screen.

Replace the clientWidth + cumulative-offsetWidth approach with
getBoundingClientRect: the cutoff is the tabbar's actual right edge
minus its right padding, minus a reserve for the overflow wrapper.
Individual tabs are overflowed once their right edge exceeds the
cutoff; subsequent tabs are overflowed unconditionally to keep the
visible set contiguous and left-anchored.
2026-06-04 20:10:19 +02:00
iris
0f77fc3598 fix(#1297): prevent scroll jump on loadMore prepend
Two cooperating fixes:

1. overflow-anchor: none on .live — disables browser scroll anchoring
   so the manual scrollTop compensation in loadMore() is the sole
   mechanism. Without this the browser auto-adjusts scrollTop when
   rows are prepended above the viewport, then our += delta doubles
   the compensation, causing the erratic jump.

2. updateLoadMoreBtn() moved before the beforeH baseline capture —
   if histHasMore becomes false the load-more button is removed here
   rather than after the delta is applied, so the button height is
   already baked into beforeH and doesn't shift the viewport post-
   compensation.
2026-06-04 20:08:29 +02:00
damocles
ab65245619 feat(#1298): add hivectl choom verb — interactive claude session in agent container 2026-06-04 20:08:04 +02:00
iris
70832fcbf1 fix(#1293): override global li padding on agent menu separator
common.css sets `li { padding: 0.5em 0 }` globally. The .agent-menu-sep
li inherits this, bloating the 1px separator into a ~16px thick bar.

Add `padding: 0` to .agent-menu-sep to reset it.
2026-06-04 20:06:18 +02:00
iris
e0ea22f3ee refactor(#1292): remove agent-owned fields from ContainerView
Now that the dashboard fetches agent-owned state directly from
GET /api/dashboard-state (via gateway), hive-c0re no longer needs
to read those fields from disk on the agent's behalf.

Removed from ContainerView:
  ctx_tokens, context_window_tokens, rate_limited,
  extra_links, status_text, status_set_at

Removed from container_view.rs:
  DashboardLink struct, build_nav_links, read_dashboard_links,
  read_status, is_rate_limited, read_last_turn, resolve_ctx_window
  (and the resolve_ctx_window unit tests)

Removed from dashboard.rs:
  GET /api/agent/{name}/links route + get_agent_links handler

dashboard JS (tabs.js):
  Merged rate_limited, ctx-window badge, and status-text rendering
  into the async dashboard-state fetch block. c0re still provides
  needs_login (auth sentinel on host), needs_update, pending_reminders,
  running, deployed_sha, parent — all genuinely host-side fields.
2026-06-04 19:39:06 +02:00
iris
40845c21cd feat(#1292): add GET /api/dashboard-state to hive-ag3nt
hive-ag3nt now exposes a lean DashboardState endpoint that returns the
agent-owned fields the dashboard card needs:

  - status_text / status_set_at  (from hyperhive-status on disk)
  - ctx_tokens / context_window_tokens  (from Bus)
  - rate_limited  (from Bus)
  - links  (from agent_links — includes screen link for GUI agents,
            which c0re's disk-based fallback cannot determine)

The dashboard fetches `${containerBase}/api/dashboard-state` instead of
the previous c0re proxy `/api/agent/{name}/links`. For gateway deployments
this is a same-origin call to the agent via the gateway's unix-socket
upstream; for direct TCP it hits the agent port directly. Both paths fail
gracefully (empty strip) when the agent is starting up.

The main behaviour fix: GUI agents now show the screen link in the dashboard
nav strip. c0re's build_nav_links reads /etc/hyperhive/gui.json from outside
the container (not possible), so it always omitted the screen link.
2026-06-04 19:16:40 +02:00
iris
750f64b7f0 feat(#1290): show per-agent loose-end question count in dashboard
Add a ' N' badge to each container row showing the count of pending
questions where the agent is the asker (awaiting an answer) or the
target (owes a reply). Derived live from questionsState — no backend
field needed.

Details:
- New .badge-loose-ends CSS class (purple, matching Q33R1ES section colour)
- Badge tooltip breaks down the count: 'N asked, N to answer'
- renderContainersFromState() called on QuestionAdded/QuestionResolved
  so the badge updates instantly when question state changes
- Existing  reminders badge kept separate (different signal)
2026-06-04 19:06:56 +02:00
iris
bddeb5478e fix(#1286): pretty-print system events in the agent terminal
The catch-all renderer was dumping raw JSON (in loud orange) for
claude's system events like api_retry — producing noise like:
  ! {"attempt":1,"error":"unknown","subtype":"api_retry",...}

Replace the single 'init' filter with a full system-event block:
- api_retry → muted note: '⚠ api retry 1/10 · unknown · 502ms'
- api_error → amber note:  '✗ api error · <message>'
- other subtypes (context_window_exceeded, etc.) → '⚙ <subtype>'

The loud orange sys catch-all is now reserved for truly unrecognised
top-level event types, not routine operational noise.
2026-06-04 18:54:33 +02:00
iris
0894030754 fix(#1285): handle restart queue kind in dashboard container row
The pending-label derivation had no case for op.kind === 'restart', so
it fell through to the rebuild/rebuild-queued else branch — showing a
misleading 'rebuild pending' badge when the operator issued a restart.

Add explicit 'restarting' / 'restart queued' cases alongside the
existing meta_update and destroy cases.

Also add restart / startup_sweep / perm_change to QUEUE_KIND_GLYPH so
they render with a meaningful glyph (↺ /  / 🔑) instead of '?' in
the rebuild-queue panel.
2026-06-04 18:54:28 +02:00
lexis
13fe5ef9d3 docs(turn-loop.md): document get_loose_ends agent parameter scoping
Add clarification for the agent parameter: omit to list own threads,
pass agent name for direct children (always accessible), or query_agent_state
capability for non-children. Note that hive-wide '*' query unavailable
on agent socket.
2026-06-04 18:38:19 +02:00
damocles
b5503cde2c fix(logs): add ?id= deep-link to logs viewer, fix queue entry link target 2026-06-04 17:39:31 +02:00
damocles
5f05caee31 fix(queue): rename queued_ids to active_ids, document circular-dep caveat 2026-06-04 17:39:31 +02:00
damocles
7118c5efdd feat(queue): link rebuild queue entries to build log rows for live streaming 2026-06-04 17:39:31 +02:00
damocles
d31a723daf feat(#550): add depends_on to queue entries for explicit dep tracking 2026-06-04 17:39:31 +02:00
atlas
1fa398e99e fix: use correct tuwunel admin room command prefix for reset-password 2026-06-04 16:43:53 +02:00
atlas
85de83bd49 fix: use correct tuwunel admin room command prefix for make-user-admin
tuwunel requires '!admin users <cmd>' — bare 'make-user-admin @user:server'
is not recognised. Update command string and doc comments.
2026-06-04 16:35:59 +02:00
atlas
0f505d5c95 fix: increase admin-room poll timeout from 5s to 15s
tuwunel can take longer than 5 seconds to process admin-room commands
during startup when the homeserver is under load. Bump both the poll
count (5→15) and the timeout message strings to match.
2026-06-04 16:28:47 +02:00
atlas
e06d3e3a8b refactor: extract admin_room_send_and_poll helper — deduplicate poll loop 2026-06-04 16:28:47 +02:00
atlas
2252eac650 fix: replace Synapse admin API in promote_user_to_admin with admin-room command 2026-06-04 16:26:59 +02:00
atlas
74253a9f71 fix: increase admin-room poll timeout from 5s to 15s
tuwunel can take longer than 5 seconds to process admin-room commands
during startup when the homeserver is under load. Bump poll count 5→15
and update timeout message strings to match.
2026-06-04 16:26:04 +02:00
atlas
0a3a956d28 fix: add /var/lib/hyperhive to hive-priv ReadWritePaths
WriteAgentForgeToken and WriteAgentMatrixToken write agent state files
under /var/lib/hyperhive/agents/<name>/state/. The hive-priv service
has ProtectSystem=strict which makes all of /var read-only by default,
but /var/lib/hyperhive was missing from ReadWritePaths, causing EROFS
on every token write.

Closes #1274.
2026-06-04 15:35:46 +02:00
damocles
c2d023ba86 fix(#1269): add requires+after hive-c0re.socket to service unit 2026-06-04 15:16:39 +02:00
atlas
a56badd004 fix: remove Synapse admin API path — use admin-room reset exclusively (tuwunel only) 2026-06-04 15:16:22 +02:00
atlas
97a78cc5d6 fix: address argus review on matrix admin-room reset — ascii lowercase, tighter markers, unit tests 2026-06-04 15:16:22 +02:00
atlas
b2913bc656 fix: admin-room fallback for matrix password reset when Synapse API absent
tuwunel 1.6.x does not implement the Synapse admin REST API.
reset_user_password() now falls back to the Matrix admin room
(#admins:<server>) when PUT /_synapse/admin/v2/users returns 404:

1. Discover admin room ID via #admins:<server> alias
2. Get current messages end-token (pagination anchor)
3. Send 'reset-password @<localpart>:<server>' as @hive admin user
4. Poll for bot response up to 5 x 1s; extract password from message
5. Persist the new password and return it

The function signature changes from Result<()> to Result<String> so the
caller can use the effective password (which may be server-generated on
the admin-room path) for subsequent login calls.

Closes #1267.
2026-06-04 15:16:22 +02:00
atlas
7e229889a6 fix: address argus review on ensure_claude_dir — narrow to EPERM, fix doc comment 2026-06-04 15:03:12 +02:00
atlas
e553e40577 fix: soft-fail chmod in ensure_claude_dir when dir is agent-owned
hive-agent-user-migrate chowns the bind-mounted claude dir to the agent
user on every container boot. After that, hive-core (a different user)
cannot chmod it — set_permissions fails with EPERM, which was propagated
as an error and caused the rebuild to fail entirely.

Fix: make the chmod best-effort. Newly created dirs (owned by hive-core)
get the 0755 mode set immediately; after the agent-migration chown the
mode is preserved so claude_has_session works correctly. Subsequent calls
that hit the EPERM path just log at DEBUG and continue.
2026-06-04 14:48:08 +02:00
damocles
911dc5227b fix(#1263): repeat security headers in locations with their own add_header 2026-06-04 14:35:18 +02:00
damocles
681a86bce9 fix: address argus review on #1260 — drop stale HIVE_ROLE and mcp::Flavor refs from CLAUDE.md 2026-06-04 14:31:44 +02:00
damocles
41eb3f806c refactor: remove hyperhive.role option — there is only one role: agent 2026-06-04 14:31:44 +02:00
damocles
d35408a18d docs: remove stale role field from AgentMeta in conventions.md 2026-06-04 14:30:27 +02:00
atlas
7022cd3826 fix: split WriteAgentStateFile into WriteAgentForgeToken + WriteAgentMatrixToken
Addresses mara's review: each credential type gets its own PrivRequest
variant, making the exact priv surface visible in the wire protocol.
No runtime filename dispatch — the operation name is the gate.

- WriteAgentForgeToken { agent_name, token } → state/forge-token
- WriteAgentMatrixToken { agent_name, token } → state/matrix-token
- priv_client: two typed fns (write_agent_forge_token, write_agent_matrix_token)
- forge.rs: split mint_and_persist_token into mint_and_persist_agent_token
  (priv) + mint_and_persist_core_token (direct write); drop dead token_path fn
- matrix.rs: call write_agent_matrix_token directly
2026-06-04 14:30:01 +02:00
atlas
89092caba4 fix: restrict WriteAgentStateFile to explicit filename allowlist
Addresses mara's security review: replace validate_state_filename (which
accepted any non-traversal filename) with a tight allowlist containing
only the two known credential filenames: forge-token and matrix-token.

Also addresses argus review feedback:
- drop issue tag from priv_proto.rs doc comment
- add comment explaining the path-detection heuristic in forge.rs
- add note about create_dir_all uid=0 edge case in write_agent_state_file
2026-06-04 14:30:01 +02:00
atlas
eb51362d50 fix: route forge/matrix token writes through hive-priv
hive-c0re runs as the unprivileged hive-core user (privsep from #702)
and cannot write to agent-owned state directories. forge-token and
matrix-token writes were failing with EACCES on every startup sweep.

Add WriteAgentStateFile to PrivRequest: hive-priv (root) writes the
file 0600 and chowns it to the agent user so the agent can read it.

- hive-sh4re: add AGENT_STATE_ROOT constant + WriteAgentStateFile variant
- hive-priv: validate agent name + filename (no traversal), write via root
- priv_client: add write_agent_state_file helper
- forge: mint_and_persist_token routes agent paths through priv
- matrix: ensure_user_for routes matrix-token through priv

Closes #1257
2026-06-04 14:30:01 +02:00
damocles
be8d8e48bf docs(bash): document wait_seconds param on run + status 2026-06-04 13:51:39 +02:00
damocles
60628fbb13 docs(tools/bash): fix incorrect execution tool group claims 2026-06-04 13:51:39 +02:00
damocles
9c1c4f62fb fix: add approval kind to cancel_loose_end docs; drop stale role comment in mcp.rs; fix system.md approval-cancel description 2026-06-04 13:51:39 +02:00
damocles
78a00d1258 docs(#1251): split MCP tool docs into docs/tools/ sub-mds by feature 2026-06-04 13:51:39 +02:00
atlas
5ec1306c7b feat(gateway): assert hsts.enable requires TLS
Addresses mara's review: add a NixOS assertion that rejects
hsts.enable = true when no TLS mode is configured. HSTS over
plain HTTP is silently ignored by browsers; the assertion turns
a silent misconfiguration into a build-time error.
2026-06-04 13:50:22 +02:00
atlas
5264ef7d5e feat(gateway): add gateway.hsts.enable option, disabled by default
HSTS was unconditionally tied to hasTls. This is risky: enabling it on a
deployment that later loses TLS locks browsers out until max-age expires.

Add three options under services.hyperhive.gateway.hsts:
  enable          — bool, default false
  maxAge          — seconds, default 31536000 (1 year)
  includeSubDomains — bool, default true

HSTS header is now only emitted when hsts.enable = true.
2026-06-04 13:50:22 +02:00
atlas
18941848dc feat(gateway): add security headers to all vhosts
X-Frame-Options, X-Content-Type-Options, Referrer-Policy at server
scope on _, forge, and matrix vhosts. HSTS added when TLS is active.

nginx inheritance rule: locations with their own add_header (CORS API
endpoints like /.well-known/matrix/client, /_matrix/) are unaffected —
they already carry the headers they need. HTML-serving and proxy
locations pick the security headers up automatically.
2026-06-04 13:50:22 +02:00
damocles
5fdc3ffdbf fix: remove issue ref from comment, use forge::FORGE_HTTP in knowledge.rs 2026-06-04 12:31:25 +02:00
atlas
bc0087830f fix: chmod claude dir 0755 so hive-core can list session files
claude_has_session() calls read_dir() on the agent's claude/ dir to
detect a valid session. The dir was 0700 (create-only path), so
hive-core (a different unix user) got EACCES → read_dir returns Err
→ has_session returns false → every agent shows 'needs login' in the
dashboard even when working.

Fix: ensure_claude_dir now always sets 0755. The credential files
inside (e.g. .credentials.json) are 0600, so the secrets stay private
regardless of the containing directory's mode. Existing 0700 dirs are
corrected on the next spawn/rebuild cycle that calls ensure_claude_dir.
2026-06-04 12:15:32 +02:00
damocles
cccb055245 docs: remove stale conflict marker from turn-loop.md 2026-06-04 12:15:03 +02:00
damocles
e58ead4329 fix: address argus+mara review on #1243 — stale docs, MANAGER_DEFAULT refs, ruth migration 2026-06-04 12:15:03 +02:00
damocles
f56b272a23 remove Role::Manager + ManagerSurface + Flavor::Manager — there is only one role: agent 2026-06-04 12:15:03 +02:00
damocles
186ee430b5 docs: comment std::process::exit(0) intent in M_UNKNOWN_TOKEN recovery 2026-06-04 12:12:20 +02:00