Commit graph

2,354 commits

Author SHA1 Message Date
iris
40bd868f65 fix(agent-term): guard edit_schedule targets_add/remove against empty arrays
Empty arrays are truthy in JS so 'if (input.targets_add)' would emit
'+0 tgt' for an explicit []. Add .length guard so zero-element lists
are silently skipped — consistent with fmtArgsGeneric's [N] handling.

The MCP schema defaults both fields to null, making this theoretical,
but the guard is cleaner.
2026-07-04 18:22:05 +02:00
iris
f11357537e feat(agent-term): add formatters for scheduling and remaining request_* tools
Scheduling tools and the two remaining request_* tools fell through to
fmtArgsGeneric. They already had appropriate icons (⏱️ / 📦) but the
arg display was multi-field verbose.

New fmtToolUse cases:
- request_init_config: 'request_init_config* iris'
- request_update_meta_inputs: 'request_update_meta_inputs* [nixpkgs, …]'
  (or 'all' when inputs list is empty)
- list_schedules: 'list_schedules*()' (no args, explicit)
- cancel_schedule: 'cancel_schedule* #42 all' or '#42 [iris, dmatrix]'
- fire_schedule_now: 'fire_schedule_now* #42'
- edit_schedule: 'edit_schedule* #42 · body · interval · +2 tgt'
  (lists which fields are being changed, not their values)
- request_schedule_prompt: 'request_schedule_prompt* → iris at 14:00Z +3600s'

Closes #2201
2026-07-04 18:22:05 +02:00
iris
f4727e30ba docs(web-ui): update reading path to point at terminal-rendering.md
The 'How does the per-agent terminal render tool calls?' reading path
was pointing only at web-ui/agent.md. terminal-rendering.md is the
authoritative reference (added recently with the full row taxonomy
table). Update the pointer to send readers there first, with a
note that web-ui/agent.md has the high-level summary.
2026-07-04 18:06:55 +02:00
iris
bf39e014e3 docs(agent-ui): update per-stream rendering section, cross-ref terminal-rendering.md
The 'system' bullet was stale — it only mentioned the drop list
(init/result/rate_limit_event) and omitted the five specific handlers
added recently: plugin_install (muted note), commands_changed
(expandable details), compact_boundary (compact summary note),
thinking_tokens (in-place counter), and the generic catch-all.

Expand the bullet to cover the full dispatch shape, and add a
cross-reference to docs/terminal-rendering.md which has the
authoritative row taxonomy table and full dispatch walkthrough.
2026-07-04 18:06:55 +02:00
iris
231f2ae2f4 fix(dashboard): correct topology bulk-move endpoint description
The selection bar's M0V3 action uses two different endpoints:
- single agent: POST /api/topology/set-parent (form-encoded)
- multiple agents: POST /api/topology/set-parent-bulk (JSON array)

The previous text said 'POSTs to /api/topology/set-parent ... once
per selected agent' — wrong on both counts for multi-agent moves.
The bulk endpoint lands all moves in one topology.json commit.
2026-07-04 14:34:24 +02:00
iris
42b2f8ec40 fix: correct HIVE_DOCS_DIR path description (nix store path, not /run/ mount) 2026-07-04 14:31:39 +02:00
iris
b41e3276c3 docs(turn-loop): add dedicated section for hyperhive.docs.enable agent.nix option
The option was only mentioned in passing in the system-prompt render
section. Add a proper ### section (following the pattern of Agent icon,
user.passwordlessSudo, etc.) with the nix snippet, default, effect on
$HIVE_DOCS_DIR + system prompt, and a note on the narrow docs flake
input that avoids full container rebuilds on doc edits.
2026-07-04 14:31:39 +02:00
damocles
4842a1ca1d refactor(#1825): remove dead manager_port field from dashboard snapshot 2026-07-04 13:54:42 +02:00
iris
527022c5f2 fix: correct 409 endpoint name — /api/loose-ends not /api/state 2026-07-04 13:52:49 +02:00
iris
8e441ead93 docs(agent-ui): document HTTP error codes for POST endpoints
After the fix in c708f698, POSTs no longer always return 200 — they use
semantic status codes: 400 for bad input, 409 for retryable state
conflicts (turn in flight, hive-c0re busy), 500 only for genuine failures.
Update the preamble to the Per-agent endpoints section to reflect this.
2026-07-04 13:52:49 +02:00
atlas
49aa326a0e chore(#2165): add damocles@pr1ma + lexis@pr1ma mailmap entries 2026-07-04 13:50:16 +02:00
atlas
467d3fa82e fix(#1845): correct ProtectSystem=strict comment (nix called directly via NIX_REMOTE=daemon) 2026-07-04 13:44:42 +02:00
atlas
7e83d906b2 fix(#1845): upgrade hive-c0re ProtectSystem full → strict
hive-c0re never writes outside its StateDirectory + RuntimeDirectory:
  - /etc writes (nixos-containers) go through hive-priv, not c0re
  - /etc/hyperhive/serve.json is read-only at startup (written by the
    NixOS activation script, not by the daemon)
  - nix builds delegate to hive-priv via the privileged Unix socket;
    hive-c0re itself never invokes nix directly

So strict is safe: systemd keeps /var/lib/hyperhive (StateDirectory)
and /run/hyperhive (RuntimeDirectory) writable; everything else is
read-only, shrinking the writable attack surface to exactly what the
service needs.
2026-07-04 13:44:42 +02:00
iris
174876094e docs(observability): document OTEL configuration options
Add docs/observability.md covering all services.hyperhive.otel.*
options: enable, endpoint, protocol, headersCredential,
extraResourceAttributes, debug (new in cb0a66147a), and
metricIntervalMs.

Includes:
- Built-in OTEL_RESOURCE_ATTRIBUTES labels (service.name, agent, hive, swarm)
- Cumulative temporality note (avoids Prometheus DELTA drop)
- Network note for host-side collectors on non-standard ports,
  cross-referencing docs/network.md exposeHostPorts

Also:
- CLAUDE.md: add reading-path entry for the new doc
- docs/network.md: link the OTEL mention to observability.md

Closes no issue — gap found during doc sweep.
2026-07-04 13:17:42 +02:00
iris
2f8e9e2bb4 docs(turn-loop): document docs-pointer system-prompt injection, drop stale CLAUDE.md mention 2026-07-04 13:13:33 +02:00
damocles
c708f698ee fix(#2175): return 409/400 not 500 for turn-in-flight + validation errors 2026-07-04 13:13:20 +02:00
iris
cea2f2faff docs(terminal-rendering): expand fmtToolUse description to cover new cases
The renderer dispatch section only mentioned recv and Bash as fmtToolUse
examples. Update the description to cover the full range: matrix tools,
remind, scheduling, and the fmtArgsGeneric fallback.
2026-07-04 13:12:17 +02:00
iris
66057a97f7 fix(agent-term): split join_room/open_dm cases to fix open_dm always showing ?
join_room and open_dm shared a fall-through case with
(fmtRoom(input.room) || fmtUser(input.user_id) || '?'). fmtRoom()
returns the string '?' when input.room is undefined — truthy — so
the fmtUser() fallback was never reached and open_dm always displayed
'open_dm* ?'.

Split into two separate cases: join_room reads input.room, open_dm
reads input.user_id. Both degrade to '?' via fmtRoom/fmtUser's own
null guard.
2026-07-04 13:12:17 +02:00
iris
44bb64fa1f docs(terminal-rendering): fix stale send_message fmtArgsGeneric example
mcp__matrix__send_message now has a specific fmtToolUse case and no
longer falls through to fmtArgsGeneric. Update the prose to reflect
that common tools have specific cases.
2026-07-04 13:12:17 +02:00
iris
6c56bf76d6 feat(agent-term): matrix-tool icons + formatters, fill remaining fmtToolUse gaps
Add specific icons for matrix tools that had the generic 💬 fallback:
- mcp__matrix__read_room → 📖
- mcp__matrix__mark_read → 👁️
- mcp__matrix__list_rooms / list_room_members / list_invites → 📋
- mcp__bash__kill → 🛑 (was generic 🔧)

Add fmtToolUse cases for high-use tools that fell through to fmtArgsGeneric:
- set_status: 'set_status* "idle"' instead of 'set_status text: "idle"'
- get_loose_ends: 'get_loose_ends* [iris]' or 'get_loose_ends*()'
- get_agent_meta: 'get_agent_meta* iris' or 'get_agent_meta*()'
- cancel_loose_end: 'cancel_loose_end* question #42'
- bash kill: 'kill* abc123 [force]'
- Matrix tools: 'read_room* !abc1234 [50]', 'mark_read* !abc1234',
  'send_message* → !room: "body"', 'send_dm* → @mara: "body"',
  'send_reply*', 'send_reaction*', 'join_room*', 'open_dm*',
  'invite_user*', 'download_file*'

Also extend the short-name shortening to cover mcp__matrix__ prefix
(was only hyperhive + bash), so matrix tool rows show 'read_room*'
instead of 'mcp__matrix__read_room' as the prefix.

Two small helpers added: fmtRoom (truncates !id before ':' for
readability; keeps #alias intact) and fmtUser (@user:server → @user).

Closes #2198. Updates terminal-rendering.md icon list.
2026-07-04 13:12:17 +02:00
atlas
3a4b8d9873 fix(#1748): route all nix invocations through the host daemon (NIX_REMOTE=daemon)
Root contexts (systemd services running as root, PID 1) default to
store=auto which resolves to the LOCAL nix store — bypassing the host
daemon, its remote builders (muede-pc2), and any prebuilt derivation
outputs already in the shared store. This causes spurious full
rebuilds of agent toplevels that the host already built and cached.

Two changes:

harness-base.nix:
- Add systemd.globalEnvironment.NIX_REMOTE = "daemon" — sets
  DefaultEnvironment in systemd.conf so every unit in the container
  inherits NIX_REMOTE=daemon. Non-root contexts already default to
  the daemon socket; this only matters for root services that would
  otherwise use the local store.
- Add NIX_REMOTE = "daemon" to environment.variables so interactive
  shells also have it set (redundant with /etc/profile.d/nix-daemon.sh
  but explicit and profile-agnostic).

hive-c0re.nix (hive-priv service):
- Add NIX_REMOTE = "daemon" to the service environment. hive-priv
  runs as root and invokes nixos-container update + nix prebuild; these
  must route through the host daemon so they see the shared store and
  remote builders, not a private local store.

The sandbox-fallback = true in harness-base.nix is kept as a belt-
and-suspenders fallback but becomes a no-op for the common case once
nix routes through the daemon (the daemon builds on the host where
sandboxing works).
2026-07-04 13:12:02 +02:00
atlas
3e5ddbd2c0 fix(#1845): add ProtectSystem=full to hive-c0re service hardening
ProtectSystem=full makes /usr, /etc, and /boot read-only. Safe for
hive-c0re because it never writes to those paths directly:
- /etc writes (e.g. /etc/nixos-containers) go through hive-priv
- /etc/hyperhive/serve.json reads are read-only
- All runtime writes go to StateDirectory (/var/lib/hyperhive) and
  RuntimeDirectory (/run/hyperhive), which remain writable under full

ProtectSystem=strict (everything read-only) is still deferred — it
requires auditing ReadWritePaths for every nix path c0re touches.
RestrictAddressFamilies deferred pending a watched deploy.

Also tightens the comment block to not mention a numbered issue.
2026-07-04 13:01:47 +02:00
atlas
ef2b3a735c fix: remove tracker tag from source comment (lint) 2026-07-04 13:01:47 +02:00
atlas
b3b1c7b288 fix(#1845): add safe systemd hardening directives to hive-c0re service
hive-c0re runs as the unprivileged hive-core user and delegates all
privileged operations to hive-priv via a Unix socket. Add the subset of
systemd sandboxing directives that are provably safe without a watched
deploy (no ProtectSystem / RestrictAddressFamilies, which require mapping
the full set of filesystem/network paths first):

- NoNewPrivileges: already unprivileged; no setuid/capability escalation
- PrivateTmp: nix subprocesses use StateDirectory temp paths, not /tmp
- ProtectHome: HOME is /var/lib/hyperhive (StateDirectory), not /home/*
- ProtectKernelTunables: no sysctl writes needed
- ProtectKernelLogs: logs via systemd-journal group, not /dev/kmsg
- ProtectControlGroups: cgroup writes go through hive-priv, not c0re
- RestrictNamespaces: namespace creation goes through hive-priv
- LockPersonality: no personality changes needed
- RestrictRealtime: no real-time scheduling

Follows the same pattern as hive-priv (PrivateTmp + ProtectHome already
present there). Deferred: ProtectSystem=strict + ReadWritePaths +
RestrictAddressFamilies — those need a watched deploy per the #1845 plan
since getting the ReadWritePaths wrong silently breaks runtime I/O.
2026-07-04 13:01:47 +02:00
atlas
ef4dd67e9a fix(#1747): stable hive-c0re ExecStart via /etc/hyperhive/serve.json
Previously, serveConfig was a pkgs.writeText store path embedded in
ExecStart. Any repo edit (gateway.nix, frontend, unrelated nix modules)
changes hyperhiveFlakeSource → serveConfig hash → ExecStart → systemd
restarts hive-c0re → StartupSweep → every agent rebuilt unnecessarily.

Fix: write the JSON to /etc/hyperhive/serve.json via environment.etc
(a managed /etc symlink to the store-path content). ExecStart now
references the stable /etc path, changing only when cfg.package (the
hive-c0re binary) changes. Unrelated module edits no longer bounce c0re
or trigger a cascade agent rebuild.

hive-c0re still restarts — and triggers the sweep — when its binary
changes, which is the correct invariant: a new release legitimately
needs to resync all agents with updated harness modules.
2026-07-04 12:52:47 +02:00
iris
94d537b7a9 feat(agent-term): show remind timing and message preview in tool row
`mcp__hyperhive__remind` calls previously rendered as the generic
`fmtArgsGeneric` output — with a multi-field input that showed
`message: "..." · delay_seconds: 300`, burying the message after
a verbose field name.

New format: `remind* +5m "check on PR..."` (or `at HH:MMZ` for
absolute timestamps). The timing renders first so it's scannable
at a glance, followed by the first 60 chars of the message body.
Works for all three input shapes: delay_seconds, at_unix_timestamp,
and file_path-only (shows the path as the preview).
2026-07-04 12:13:44 +02:00
damocles
77a9492a3f refactor(#1825): drop manager_dir/manager_socket_path, use the per-agent fns 2026-07-04 12:11:28 +02:00
damocles
a26bbb15fc feat(#2089): docs pointer via system-prompt line, drop CLAUDE.md mechanism 2026-07-04 12:07:17 +02:00
iris
fc415a72a5 docs: update stale tabs.js→swarm.js/call.js references
After the SW4RM and Y3R C4LL extractions from tabs.js (PRs merged
earlier), several docs still pointed at `assets/tabs.js::fn`. Update
them to the authoritative module:

- dashboard.md: renderContainers, buildAgentTree → swarm.js
                renderApprovals → call.js
                renderPeerHives → swarm.js
- shape.md: generic "tabs.js render function" → "domain module"
- conventions.md: clarify async-form listener lives in tabs.js (dashboard)
                  AND app.js (per-agent UI)
2026-07-04 12:07:14 +02:00
iris
0a9f9a4fa6 feat(agent-term): complete tool-icon and fmtToolUse coverage
Several MCP tools appeared with the generic wrench icon (🔧) making
them hard to identify in the scrollback. fmtToolUse also lacked
specific formatters for some frequently-used tools, falling through
to fmtArgsGeneric.

Icons added:
- ack_until  →   (mark-as-read semantics)
- get_logs   → 📜  (log viewer)
- get_host_journal → 📜  (journal reader)

fmtToolUse cases added:
- ack_until          → "ack_until* ≤N" (message-id bound)
- get_logs           → "get_logs* <agent> [NL]"
- get_host_journal   → "get_host_journal* <container|unit> [/grep/] [NL]"
- restart/start/update → "restart* <name>" etc. (previously used
                          fmtArgsGeneric; now match kill's pattern)

Closes #2188.
2026-07-04 12:07:14 +02:00
iris
1011428cc7 docs(terminal-rendering): document plugin_install, commands_changed, compact_boundary rows
The renderer dispatch section was missing entries for three system
subtypes that now have specific renderers (added in the preceding
commits on this branch). Update the row taxonomy table and the
numbered dispatch description to cover all four system-subtype
handlers (thinking_tokens was already documented; 1b–1e are new).
2026-07-04 11:59:37 +02:00
iris
1fe6783756 feat(agent-term): show compact_boundary details in per-agent terminal
compact_boundary events currently render as the generic "⚙ compact_boundary"
muted note. The event carries useful metadata — pre/post token counts,
duration and trigger — that are invisible to the operator.

With this change the row reads:
  · ⚙ compact · manual · 772k→6k tokens · 101s

Fields rendered (all guarded — missing fields are silently omitted):
- trigger ("manual" or "auto")
- pre_tokens→post_tokens (formatted with k/M suffixes)
- duration_ms (ms or s)

Closes #2187.
2026-07-04 11:59:37 +02:00
iris
44531820d0 feat(agent-term): show details for plugin_install and commands_changed
Currently both system subtypes fall through to the generic muted
note renderer (⚙ <subtype>), giving the operator no insight into
what is happening.

plugin_install:
  Render status explicitly — "loading…" on started, "✓ done" on
  completed — so a slow plugin boot is visible in the scrollback
  instead of two identical cryptic rows.

commands_changed:
  Render the count of available slash commands in the summary row
  and expand to the full list in a collapsible details block. The
  list is most useful right after a fresh plugin_install so the
  operator can see exactly which /commands are now on offer.

Both handlers sit immediately before the generic catch-all in the
system-subtype dispatch in renderStream, preserving the existing
fall-through for other subtypes (context_window_exceeded etc.).

Closes #2183.
2026-07-04 11:59:37 +02:00
damocles
b5acd60cf5 feat(#2170): split docs/ into its own meta-flake input 2026-07-04 11:47:07 +02:00
iris
9b607e2857 refactor(dashboard): extract SW4RM domain from tabs.js into swarm.js
tabs.js shrinks from 1651 to 457 lines. swarm.js is a new 1217-line
module that owns the containers/selection-bar/peer-hives domain:

- Container-state apply handlers (applyContainerStateChanged/Removed)
- Rebuild-queue sync + apply (syncRebuildQueueFromSnapshot, applyRebuildQueueChanged)
- Transient-state sync + apply (syncTransientsFromSnapshot, applyTransientSet/Cleared)
- renderContainersFromState (re-render from cached snapshot)
- Per-agent context menu (buildAgentMenu, agentMenuPost, closeAllAgentMenus)
- Topology tree builder (buildAgentTree, treePrefixDom)
- Container row (containerRowFingerprint, buildContainerLi, renderContainers)
- Selection bar (renderSelectionBar, addMoveActions, validReparentCandidates, addBulkButton)
- Peer hives section (renderPeerHives)
- Status-age ticker (30s setInterval for .status-age[data-set-at] spans)
- CTX_WARN/CTX_CAUTION constants
- Selection event listeners (Esc to clear, click on selection-clear)
- Agent-menu event listeners (click outside to close, Esc to close)

tabs.js (coordinator) retains:
- notifyDeltas + seenApprovals/seenQuestions/seededNotify
- bindAsyncForms
- Cross-domain ticker (approval request-age + reminder/schedule due-at)
- refreshState, pollTimer, operatorIsTyping, snapshotOpenDetails
- MUTATION_HANDLERS dispatch + bindDashboardStream
- activateTab, createTabStrip, initCall, refreshTabCounts, setTabCount

Behaviour-preserving code-move. Build verified.
2026-07-04 11:37:39 +02:00
damocles
6267bf56d0 feat(#2089): additive claude.md pointer to in-container docs 2026-07-04 11:30:33 +02:00
damocles
5d597d92ea fix(#2174): create operators team in agent-configs org too so config-repo branch protection applies 2026-07-03 22:40:38 +02:00
atlas
cb0a66147a fix(#2172): gate CLAUDE_CODE_OTEL_DIAG_STDERR on hyperhive.otel.debug
CLAUDE_CODE_OTEL_DIAG_STDERR was always set when OTEL is enabled, making
OTEL SDK errors (e.g. 404 from a misconfigured collector endpoint) appear
in every agent's stderr unconditionally. Move it behind a new opt-in flag.

Changes:
- nix/modules/hive-c0re.nix: add services.hyperhive.otel.debug (bool,
  default false); wire to HYPERHIVE_OTEL_DEBUG env on hive-c0re unit.
- hive-c0re/src/meta.rs: add debug field to OtelConfig; read
  HYPERHIVE_OTEL_DEBUG; emit hyperhive.otel.debug = true when set.
- nix/templates/harness-base.nix: add hyperhive.otel.debug internal
  option; move CLAUDE_CODE_OTEL_DIAG_STDERR out of otelSettingsEnv into
  a debug-gated lib.optionalAttrs block.

Default behaviour: OTEL exports silently (no stderr noise). Operators
troubleshooting collector connectivity set services.hyperhive.otel.debug
= true to re-enable the diagnostic output.
2026-07-03 22:12:56 +02:00
damocles
2c5d9ed336 wire types: use chrono DateTime<Utc> as the timestamp type throughout 2026-07-03 22:07:23 +02:00
damocles
1e205289c5 wire types: WireTime newtype for timestamps instead of adaptor-annotated i64 2026-07-03 22:07:23 +02:00
damocles
cf1f7288bf make agent_config_pending async — the sync git fork on every sweep starved the runtime under IO load 2026-07-03 21:42:45 +02:00
damocles
3d5957c87b web-ui: deadline-bound all broker-backed fetches so a busy hive-c0re can't hang api/state (closes #2148) 2026-07-03 21:42:45 +02:00
atlas
9496fb060b fix(forge): set mirror_interval so pull-mirrors sync periodically, not on-access
Forgejo's default mirror behaviour syncs on every git access, which
re-introduces external DNS latency at clone time. The hive-ci runner
shares the host netns, so a host-resolver blip turns an otherwise
local clone into a hard failure (data.forgejo.org DNS lookup that
has nothing to do with the repo being cloned).

Fix:
- New MIRROR_INTERVAL const (8h0m0s) used in ensure_mirror_repo.
- On creation: pass interval=MIRROR_INTERVAL in the migrate API body.
- On existing mirror: PATCH mirror_interval on every startup so repos
  seeded before this change converge without manual intervention.

A stale mirror (up to 8 hours old) is fine for CI; a broken clone
from a transient DNS blip is not. The actions/checkout mirror is
refreshed periodically rather than triggered by runner pulls.
2026-07-03 19:32:05 +02:00
damocles
7def514760 clippy: backtick socket_server in doc comment 2026-07-03 18:50:21 +02:00
damocles
0577c9e3fa drop tracker tags from rust doc comments (lint) 2026-07-03 18:50:21 +02:00
damocles
be5b36911a recv: hoist RECV_BATCH_MAX into hive-sh4re, drop magic 5 in wake hint 2026-07-03 18:50:21 +02:00
damocles
78396c8189 recv: cap batch size at 5, default stays 1 (#2150 clarified) 2026-07-03 18:50:21 +02:00
damocles
ee45db6323 Revert "recv mcp tool: default max to a small batch of 5 (wire default stays 1)"
This reverts commit b8d608d4b6a7ed95faffdec8d8b3167ae8f3b39e.
2026-07-03 18:50:21 +02:00
damocles
d46d3c261b recv mcp tool: default max to a small batch of 5 (wire default stays 1) 2026-07-03 18:50:21 +02:00
damocles
f310b1ce5a config mirror: never force-push — keep forge history on rolled-back deploys 2026-07-03 00:54:32 +02:00