Commit graph hyperhive/hive-c0re
Author SHA1 Message Date
atlas
2ddabdc410 fixup! fix(#2245): drop TCP fallback from gateway nginx upstream 2026-07-04 22:42:56 +02:00
atlas
0456206e52 fix(#2245): drop TCP fallback from gateway nginx upstream
Agents run in private netns (always-on isolation). The TCP fallback
to 127.0.0.1:<port> was unreachable from the gateway's host netns
regardless of whether the per-agent socket marker existed.

Remove the conditional entirely: always use the unix socket path.
If the socket is not yet bound, nginx returns 502 which is already
handled by the error_page 502 503 504 = /__hive_agent_unreachable
directive in every location block.

Also removes the unused lifecycle::agent_web_port call and the
now-misleading '.bound state' mention from the render doc comment.
2026-07-04 22:42:56 +02:00
damocles
9b2d16ae6a fix(#2190): replace MANAGER_AGENT fallbacks with "operator" for attribution
- loose_ends.rs: NULL submitter on legacy approval rows → "operator"
- questions.rs: NULL submitter on cancel_loose_end → "operator"
- server.rs: HostRequest::RequestSpawn submitter → "operator"
- dashboard.rs: web-UI spawn submitter → "operator"
- socket_server.rs: submit_init_config with no declared parent → "operator"
- mcp.rs: drop MANAGER_AGENT exception from check_send_allowed; keep <parent> only
2026-07-04 18:56:16 +02:00
damocles
672e77c849 feat(#2102): skip startup rebuild for stopped/unchanged containers 2026-07-04 18:45:27 +02:00
damocles
4842a1ca1d refactor(#1825): remove dead manager_port field from dashboard snapshot 2026-07-04 13:54:42 +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
b5acd60cf5 feat(#2170): split docs/ into its own meta-flake input 2026-07-04 11:47:07 +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
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
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
f310b1ce5a config mirror: never force-push — keep forge history on rolled-back deploys 2026-07-03 00:54:32 +02:00
damocles
bac2c0a65e hive-c0re: rfc3339 timestamps on dashboard api + sse json 2026-07-02 22:28:30 +02:00
damocles
2f43077e31 rename choom flag --continue to --resume to match the claude flag it maps to (mara review) 2026-07-02 20:51:20 +02:00
damocles
4e7a9b93d9 choom: map --continue <id> to claude --resume — claude's own --continue takes no value (closes #2127) 2026-07-02 20:51:20 +02:00
damocles
c89642872c recv/count/dedupe queries exclude acked rows — a pending row closed by ack_until must never pop (iris review) 2026-07-02 12:55:18 +02:00
damocles
34374fd10a add ack_until: bulk-ack inbox messages by id + surface msg ids in wake prompts and recv (closes #2125) 2026-07-02 12:55:18 +02:00
damocles
b191858366 defer start-after-rebuild to the fast lane so the build lane frees up (closes #2094) 2026-07-01 23:37:13 +02:00
atlas
ae41e39dfb refactor(#2111): remove nixpkgs-unstable, source claude-code from main nixpkgs 2026-07-01 19:32:46 +02:00
damocles
d2a0493092 address mara: drop clippy allow on run_graceful_stop — return () since the body is infallible, wrap the dispatch arm 2026-07-01 19:05:24 +02:00
damocles
fc42f97691 parallelize graceful agent drains, serialize container stops on fast lane; unify shutdown+checkpoint+compact prompt 2026-07-01 19:05:24 +02:00
iris
5264828091 fix: view-queue link + agent inbox shows unread messages only
fix(dashboard): update 'view queue' link to /builds.html

The build queue moved to its own /builds.html page. The queue-summary
'view queue →' link in tabs.js still pointed at /core.html, so clicking
it landed on the wrong page.

fix(broker): filter agent inbox to unread (acked_at IS NULL)

recent_for was returning all messages regardless of ack state, so the
agent inbox showed everything even after 'mark all read'. Now filters
to acked_at IS NULL — mirroring exactly what mark_all_read drains —
so the inbox empties on reload after the operator drains it.
2026-07-01 18:41:43 +02:00
damocles
2248881be8 docs(#2067): trim choom prose in code, link to docs/tools/hivectl.md 2026-06-29 00:50:53 +02:00
damocles
0125fcfae8 feat(#2067): choom --continue passes through to claude --continue (drop --resume translation) 2026-06-29 00:50:53 +02:00
damocles
c5847e25ea feat(#2067): choom takes optional session id to resume, else blank session 2026-06-29 00:50:53 +02:00
damocles
09b83c9d1e feat(#2067): choom uses a fresh named session, drop --fresh flag 2026-06-29 00:50:53 +02:00
damocles
ce959d7700 feat(#2067): pin choom to a dedicated claude session id 2026-06-29 00:50:53 +02:00
atlas
af7ec98542 doc: ensure_mirror_repo docstring — 409 only, not 409/422 (match the fix) 2026-06-29 00:26:41 +02:00
atlas
64e51fe3bf address argus: 422 from migrate is a validation error, not 'exists'
ensure_mirror_repo treated 409|422 as success (copied from ensure_org, where
422 *does* mean 'org exists'). For the migrate endpoint 422 is a validation
error (bad clone_addr/service); the GET-first check is the real idempotency
guard, so 409 stays as a race guard but 422 now falls through to the bail arm
(→ caller warns) instead of silently dropping a misconfigured mirror.
2026-06-29 00:26:41 +02:00
atlas
53df2c9598 rework(#2072): seed mirrors in c0re startup, not a host-side oneshot
Per mara: the mirror seeding belongs in hive-c0re's forge provisioning
sweep, where the core admin token + org-ensure already live — not a parallel
host-side nix oneshot.

- forge.rs: ensure_mirrors() reads HYPERHIVE_FORGE_MIRRORS (JSON list of
  {upstream,dest}), ensures each dest org (reuse ensure_org) + creates the
  pull-mirror via the migrate API (reuse forge_http, serde_json::json! body,
  409/existing = success). Called in ensure_all() right after the SEEDED_ORGS
  loop (token in scope, warn-and-continue like the other ensure_* steps).
- hive-forge.nix: forward effectiveMirrors to c0re via
  systemd.services.hive-c0re.environment.HYPERHIVE_FORGE_MIRRORS; drop the
  forgejo-seed-mirrors.service + its script + the host-side core-token read.
  Keep the forge.mirrors option, DEFAULT_ACTIONS_URL=self (CI-gated), and the
  dest-shape / no-c0re-namespace-collision assertions.

Verified locally: nix parse + treefmt (incl rustfmt) clean; serde/serde_json
patterns mirror dashboard.rs. cargo build runs in CI (no cc in my container).
2026-06-29 00:26:41 +02:00
damocles
72c4bb18c9 refactor(#2077): drop impl-history from broker/dashboard/mcp comments 2026-06-29 00:22:54 +02:00
iris
75e49f7752 fix(permissions): clarify kept_state_names comment in get_stale_permissions
The variable was misleadingly named `tombstones` and commented as
'any name here is a tombstone', but kept_state_names() returns ALL
agents with a state dir on disk — both live containers and soft-deleted
tombstones. The logic was always correct (union with the live roster
filters both), but the comment would mislead future readers.

Rename the variable to `kept` and update comments to accurately
describe the contents.
2026-06-28 23:29:51 +02:00
iris
cffe645197 fix(permissions): collapse known-set assignment to one line (rustfmt) 2026-06-28 23:29:51 +02:00
iris
4df286345a refactor(permissions): move ghost-perm detection server-side
Add GET /api/permissions/stale endpoint that returns agent names with
explicit capability/tool-group JSON entries but no live container AND
no kept-state tombstone. Ghost detection is now entirely server-side —
one authoritative call, no client-side roster cache, no staleness window.

The previous client-side approach in core.js made three parallel API
calls (GET /api/capabilities, GET /api/tool-groups, GET /api/state) and
filtered the result against a module-level `liveContainerNames` Set
populated only on cold load and form submits. Any container lifecycle
event (spawn, destroy) while core.html was open left `liveContainerNames`
stale, risking a false-positive ghost entry for a live container.

Changes:
- permissions.rs: add `get_stale_permissions` handler + `StalePermsResponse`
  struct. Computes live roster (containers_snapshot), tombstone set
  (Coordinator::kept_state_names), explicit perm names (capabilities::read
  + tool_groups::read), then returns the difference sorted.
- dashboard.rs: register GET /api/permissions/stale.
- core.js: replace the three-call client-side logic in
  `fetchAndRenderStalePerms` with a single fetch to /api/permissions/stale.
  Remove `liveContainerNames` state + its syncFromSnapshot population.
2026-06-28 23:29:51 +02:00
damocles
c99fa714d9 feat(#2050): set matrix avatar in the daemon over the live client 2026-06-28 01:38:21 +02:00
iris
c6639fe093 docs: fix stale field doc for ContainerView::active_model
The field was originally backed by harness/hyperhive-model; after the
rework (fab6259d) it reads from state/hyperhive-harness.json. Update
the struct-level doc comment to match.
2026-06-27 22:59:40 +02:00
iris
c580d721fb refactor(dash): fold active_model into hyperhive-harness.json, not a separate file
hive-c0re was reading harness/hyperhive-model directly to surface the
model badge on the dashboard. hyperhive-model is a runtime-override
file (not the resolved priority) and adds to the marker-file count.

Instead: mirror the fully-resolved model into hyperhive-harness.json
(the consolidated state file that already replaced hyperhive-rate-limited
/ hyperhive-needs-login). Written by hive-ag3nt on:
- Bus::new() startup (captures nix config > override > default)
- set_model() runtime change (MCP set-model call)
- emit_status() (keeps model current across rate-limit / auth flips)

hive-c0re reads active_model from hyperhive-harness.json, same dir +
same read path as rate_limited / needs_login. No new files.
2026-06-27 22:59:40 +02:00
iris
4375ab6246 feat(dash): show active model badge on agent cards (closes #2069)
Read the persisted model name from each agent's harness state file
(harness/hyperhive-model) and surface it as a small blue badge on
the container row in the SW4RM tab.

- container_view.rs: add `active_model: Option<String>` to
  ContainerView; populated by new `read_active_model` helper that
  reads harness/hyperhive-model; only set when container is running
  (stale model info from a stopped agent is misleading)
- container_view.rs: add active_model to ContainerView literal in
  host_stats test helper
- tabs.js: render badge-model chip after needs-update, before
  reminders; add active_model to the row fingerprint so re-renders
  fire on model change
- common.css: add .badge-model (blue, 80% opacity — informational)
2026-06-27 22:59:40 +02:00
iris
dbd4b7a15c fix(schedules): use typed error for pause/resume 404 discrimination
Replace brittle msg.contains("not found") string matching in
post_schedule_pause / post_schedule_resume with a typed
ScheduleNotFoundOrCancelled error that handlers downcast on directly.

pause() and resume() now return Err(ScheduleNotFoundOrCancelled(id).into())
instead of bail!("schedule {id} not found or is cancelled"); handlers call
e.downcast_ref::<ScheduleNotFoundOrCancelled>().is_some() for the 404 branch,
making the discrimination stable even if the error message wording changes.
2026-06-27 20:01:16 +02:00
iris
2bfa5bc1a8 feat(schedules): make schedules pausable
Adds pause/resume support for scheduled prompts.

Backend:
- New paused_at_unix column on scheduled_prompts table (added via
  ALTER TABLE migration so existing databases are upgraded on first
  start). The due-rows index is dropped and recreated to also exclude
  paused rows so the worker never fires them while paused.
- Worker's due() query gains AND paused_at_unix IS NULL filter.
- New pause(id) and resume(id) methods on ScheduledPrompts; both are
  idempotent and refuse cancelled rows.
- New POST /api/schedules/{id}/pause and /api/schedules/{id}/resume
  dashboard endpoints (operator-direct, no approval gate). Both emit
  a schedules snapshot on success so the tab updates live.
- WireSchedule gains paused_at_unix: Option<i64> so the frontend can
  render the state without an extra fetch.

Frontend:
- Paused rows render with a distinct row class + muted opacity.
- The next-fire cell shows a yellow pause glyph + tooltip with the
  paused-since timestamp and the would-have-fired time.
- Actions column: pause/resume toggle button (⏸/▶) beside fire/edit/cancel.
  Fire-now is disabled while paused (resume first).
- Sort order: active → paused → cancelled (paused slot keeps schedules
  visible without mixing them into the active top section).
- pauseSchedule() / resumeSchedule() async functions POST to the new
  endpoints and refresh the table on success.
2026-06-27 20:01:16 +02:00
atlas
3fedc102cc refactor(#2051): fold body-return into forge_http instead of a near-copy
Per review: rather than adding forge_http_full (a near-duplicate of
forge_http), change forge_http itself to return (StatusCode, String).
Status-only callers bind (status, _); the branch-protection verify path
uses the body to log the real Forgejo rejection reason. Updates all call
sites accordingly.
2026-06-27 19:52:14 +02:00
atlas
512e9ff09f fix(#2051): verify config-repo branch protection actually applied
apply_config_repo_branch_protection treated 200/409/422 from the
create-branch-protection POST all as success. But a 422 means Forgejo
*rejected* the request and created no rule — so a rejected POST silently
left the agent's config repo unprotected, with nothing logged (a new
agent's config repo was found with no main-branch protection and no
trace of why).

Don't trust the status code:
- On any non-201, GET the single .../branch_protections/main rule and
  only treat it as success if the rule is actually present.
- Otherwise return Err carrying the POST's response body, so the real
  Forgejo rejection reason lands in the host journal. (forge_http
  discarded the body; added forge_http_full that returns it.)

ensure_config_repo runs on every sync_agent sweep (startup + each
rebuild), so a now-Err result is logged and retried next sweep —
self-healing once a real cause is fixed. Net: the failure is loud +
retried instead of silently swallowed.

nix fmt clean.
2026-06-27 19:52:14 +02:00
iris
31433da3aa fix(permissions): propagate I/O errors as 500 from delete_agent_permissions
Both remove_agent() calls now run unconditionally for maximum partial
cleanup, but any I/O error is returned as HTTP 500 instead of silently
200-ing — so the frontend's !resp.ok path fires and the operator sees a
meaningful error rather than the stale row reappearing unchanged.

Also add a clarifying comment on isStale in permissions.js explaining
that containersState is keyed from nixos-container list (which includes
stopped-but-configured containers), so a temporarily-stopped agent is
not treated as stale — only destroyed/renamed agents are absent.
2026-06-27 13:44:11 +02:00
iris
a8fb33e2ee fix(permissions): mark stale agents and allow removing their explicit entries
The P3RM1SS10NS tab showed agents that no longer exist in the live
container roster — e.g. an agent named 'root' that was renamed or
destroyed but still had explicit entries in tool-groups.json and/or
capabilities.json. The roster-union behaviour is intentional for
temporarily-stopped agents, but stale entries from renamed/destroyed
agents are confusing.

Backend (dashboard/permissions.rs):
- New DELETE /api/permissions/{agent} handler that bypasses the live-
  roster guard (intentionally — that's the point). Calls
  tool_groups::remove_agent + capabilities::remove_agent to clear both
  JSON files, then emits live SSE snapshots so the tab updates without
  a page reload. Format-checks the agent name but does not require it to
  be in the containers snapshot.

Frontend (permissions.js):
- renderCapabilities / renderToolGroups now cross-reference agentNames
  against containersState (the live roster, already imported). Agents
  not in the live roster get an isStale flag.
- Stale rows get a '(not running)' label and a '✕ remove' button that
  calls clearStaleAgent() — a new async helper that DELETEs the stale
  entry and re-fetches both perm tables.
- Non-stale agents without explicit assignments still get '(default)'.

CSS (dashboard.css):
- .perm-row-stale (reduced opacity), .perm-stale-label (muted small
  text), .perm-remove-btn (small red-bordered button) + disabled state.
2026-06-27 13:44:11 +02:00
iris
c65201ed7c fix(dashboard): render update_meta_inputs + schedule_prompt approval cards
Both kinds fell through to the spawn branch in renderApprovals, showing
a misleading 'spawn' chip and agent-spawn body text. Mara saw a meta-input
bump render as a spawn card for agent damocles and denied it.

Backend (dashboard.rs):
- Add commit_ref: None to the MergeConfigPr arm (struct was incomplete).
  All arms of ApprovalView now initialise every field.

Frontend (call.js):
- Add isUpdateMeta / isSchedule booleans alongside the existing kind flags.
- Glyph: update_meta_inputs gets ↻, schedule_prompt gets ⏱.
- Kind chip: 'meta-update' / 'schedule' (no kind-spawn class for either).
- Body: update_meta_inputs parses commit_ref as JSON Vec<String> and shows
  'bump flake inputs: foo, bar' or 'bump all flake inputs'; schedule_prompt
  parses SchedulePromptPayload and shows targets + first-fire time + cadence
  + a truncated body excerpt.
- History row: add 'meta-update' and 'schedule' cases (were both 'spawn').
- Import fmtDuration from util.js (needed for schedule cadence display).
2026-06-27 13:03:41 +02:00