The deny list takes precedence over --allowedTools, so granting the
web_tools group had no effect — both tools were silently blocked.
WebFetch/WebSearch are now gated solely via --tools (builtin_tools_arg):
agents without the web_tools tool group don't get them in --tools so
claude never sees them; agents with the group get them in both --tools
and --allowedTools.
Replaces the earlier capability-based approach (closed#1069) with a
ToolGroup — capabilities are for privileged system access, web egress is
a tool permission.
Add ToolGroup::WebTools to hive-sh4re:
- tools() returns &[] (no MCP tools gated)
- builtin_tools() returns &["WebFetch", "WebSearch"] — new method on ToolGroup
- Present in ALL and as_str() → "web_tools"
In hive-ag3nt/mcp.rs:
- allowed_tools_arg() now iterates group.builtin_tools() to prepend any
group-gated built-ins alongside the base ALLOWED_BUILTIN_TOOLS set
- builtin_tools_arg_for_flavor(flavor) replaces builtin_tools_arg() so
the flavor-correct effective groups are used when building --tools
- builtin_tools_arg() kept as a flavor=Agent convenience alias
- turn.rs updated to call builtin_tools_arg_for_flavor(files.flavor) so
manager sessions also see web tools when web_tools is in their groups
The dashboard T00L GR0UPS table gains a web_tools column automatically
(ToolGroup::ALL drives the columns).
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.
Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.
Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
All crates now pass `cargo clippy --workspace -- -D warnings` cleanly.
Fixes span six crates (hive-sh4re, hive-ag3nt, hive-c0re, hive-forge,
hive-priv, hive-matrix-mcp was already clean):
- doc_markdown: wrap snake_case, type names, constants in backticks
- collapsible_if / collapsible_match: fold nested ifs into let-chains
- duration_suboptimal_units: Duration::from_secs(N) → from_mins/from_hours
- implicit_hasher: allow on HashMap-param fns where generalization is risky
- items_after_statements: hoist use to function tops
- map(f).unwrap_or(x) → map_or(x, f); map(f).unwrap_or_else(g) → map_or_else
- is_ok_and / is_none_or in place of map().unwrap_or(bool)
- needless_continue: {} instead of continue in loop match arms
- match_same_arms: Ok(None) | Err(_) merged
- format_push_str: write!() instead of push_str(&format!())
- while let replaces loop { let Some(..) = x else { break } }
- struct_excessive_bools / dead_code: allow on purpose-built structs
- too_many_lines / too_many_arguments: allow where refactor not worth it
- unused_async: remove async from poll_once in bash_runner
- needless_borrow: fix &repo deref in hive-forge comments verb
- cast_possible_truncation: allow u64→usize in fetch_tail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
turn_stats.rs writes to harness_dir()/hyperhive-turn-stats.sqlite but
stats.rs was reading from state_dir()/hyperhive-turn-stats.sqlite.
These paths diverged when the harness/state split was introduced.
The read side must match the write side.
All functional renames (MANAGER_NAME, MANAGER_AGENT → "root") were done in
earlier commits. This cleans up the stale `hm1nd` strings that remained in
doc comments, test fixture labels, and the manager MCP server instructions
(which told the manager its config lived at /agents/hm1nd/config/agent.nix).
Container link URLs (/screen.html, /stats.html) were root-relative,
which works when the agent is accessed directly at :port/ but resolves
to the gateway root when accessed via /agent/<name>/.
Fix at the source: harness emits relative URLs (screen.html, stats.html)
and the dashboard joins containerBase + '/' + url. The agent page uses
the URL directly and relative resolution is now correct in both paths.
Dashboard links to the forge were hardcoded as http://<hostname>:3000,
which breaks when the operator accesses the dashboard through
hive-gateway (forge is served at forge.<domain> with no port).
- nix/modules/hive-c0re.nix: inject HIVE_FORGE_PUBLIC_URL when
forge.behindGateway = true (e.g. http://forge.pr1ma.darkest.space)
- hive-c0re/src/dashboard.rs: expose forge_url: Option<String> in
StateSnapshot, reading from HIVE_FORGE_PUBLIC_URL
- frontend/packages/dashboard/src/tabs.js: use state.forge_url when
present; fall back to http://<hostname>:3000 for gateway-off /
local-dev deploys
The harness runs as the non-root agent user; the per-agent
/run/hive-agent/<name>/ dir lands at 0755 root:root after
create_dir_all, so bind(2) of web.sock failed with EACCES. The error
was invisible because the web_ui::serve future was tokio::spawn'd with
its JoinHandle dropped — no log, no socket, agent looks unreachable
through the gateway.
docs/web-ui.md (1315 lines) split into three sub-files:
- docs/web-ui/shape.md — shared SPA skeleton, SSE multiplexing,
Worker-death self-heal, terminal pane, listener bind, relative
paths, atomic repaint, side panel
- docs/web-ui/dashboard.md — SW4RM/Y3R/SYST3M/SCH3DUL3S/S3TT1NGS
tabs, container row, topology tree, selection bar, approval card,
dashboard endpoints + event channel
- docs/web-ui/agent.md — header, terminal, composer, inbox, live
view, slash commands, per-agent endpoints, stats page
docs/web-ui.md replaced with a thin index linking all three.
Section anchors in docs (gateway.md, gotchas.md), Rust doc comments
(hive-ag3nt/src/web_ui.rs), and nix/templates/weston-vnc.nix updated
to point at the correct sub-file. README and CLAUDE.md file-map
updated with sub-file links. Inline // comments in frontend source
left unchanged (they reference the index which redirects to the right
sub-file).
Damocles's backend (c41bf1b) landed HYPERHIVE_HIVE_NAME / _SWARM_NAME
env vars and identity.rs accessors. This commit wires them into the two
web surfaces:
hive-c0re/dashboard.rs:
StateSnapshot gains `hive_name` + `swarm_name` (Option<String>),
populated from the env vars the c0re NixOS module injects.
hive-ag3nt/web_ui.rs:
Per-agent StateSnapshot gains the same two fields, populated from
crate::identity::hive_name() / swarm_name().
Dashboard frontend (index.html + tabs.js):
A `<p class="banner-thin" id="hive-identity">` sits above the tab
strip in the chrome. refreshState() populates it with "swarm / hive"
(or just "hive") when the fields are non-null, and updates
document.title to "<swarm>/<hive> // h1ve-c0re". No change to
chrome when both fields are null (backward compat).
Per-agent frontend (index.html + app.js + agent.css):
A hidden `.agent-hive-label` span under the title row shows the
swarm/hive label once setHeader() receives non-null names.
document.title is updated to "<label> // <hive_name>" when hive_name
is set, enabling browser-tab disambiguation when multiple hives are
open in parallel. Styled as a small uppercase purple-dim sub-label.
Last #716 hotspot — scrub-only batch. All substantive prose is
already documented in docs/turn-loop.md::MCP surface (Sub-agent
tools, Manager tools, Tool whitelist, Tool envelope), so the 10
cookies in mcp.rs come out without prose extraction.
- format_agent_meta rustdoc: drop #432 attribution, add docs pointer
for the running=false / status_text-stale rationale
- get_agent_meta hive/swarm comment: drop #710 attribution
- AgentServer + ManagerServer add-tool warnings: drop #511 attribution
twice (kept the lockstep invariant)
- edit_schedule description: drop #474 + #478 cookies in user-facing
tool text
- cancel_loose_end description: drop #250 cookie in user-facing text
- Manager allow-list block: drop the long #444/#235/#467/#472/#474/
#478/#509 attribution roll, keep the lockstep invariant
- check_send_allowed (<parent> branch): drop #692, replace with the
actual semantics ("falls back to operator for root agents")
- prompts/system.md: drop #474 cookie from edit_schedule line
- Cargo.toml [[bin]]: drop #598 pre-/post- split history, add docs
pointer to docs/turn-loop.md::Harness binary shape
cargo check + 56 tests pass.
Closes#716 (last cookie hotspot).
12 → 0 cookies in turn.rs (claude --print pump + stream-json +
compaction + wait_for_login). Cookie scrub only — substantive
prose (auth-failed detection, mtime-snapshot resumption,
DirSnapshot two-axis design, AuthFailed turn outcome) is already
documented in docs/turn-loop.md::The loop and the Turn outcomes
table. Rustdocs trimmed to point at the doc where useful
(AUTH_FAIL_MARKERS, wait_for_login).
- AUTH_FAIL_MARKERS doc: drop #658, #419 attribution
- write_settings inner comment: drop #555 attribution
- write_system_prompt doc: drop #519 attribution
- TurnOutcome::AuthFailed doc: drop #419 attribution
- wait_for_login doc: drop #542 attribution, point at docs
- wait_for_login inner emit_status comment: drop #563 attribution
- Test comments: drop #542 and argus #545 attribution
forge_notify.rs is the biggest remaining #716 hotspot: ~22 attribution
cookies (#110#230#231#253#256#455#518#539#544) plus a thick
module-level docstring + per-function rationale blocks for the
notification pipeline. The substantive prose lives in a new
`docs/forge.md` covering the wider forge integration story:
- Per-agent forge accounts + agent-configs mirror (was implicit
across `hive-c0re/src/forge.rs` rustdocs).
- Notification poller: activation gates, self-notification filtering,
body excerpt + truncation + ATX heading escape pipeline, wrapper
formats (comment / review / new-item / state-change) with shape
table, meta suffix shape, review-request override, reason
drop-list rationale (drop vs allow), auto-unsubscribe on broad
watches.
In-code rustdocs reduced to 1-line semantic summaries + doc
pointers; inline cookie comments scrubbed. Net diff is ~150 lines
removed from the .rs file. All 16 forge_notify unit tests pass.
CLAUDE.md gets a new `docs/forge.md` file-map entry + reading-path
question entry. The existing `src/forge_notify.rs` file-map blurb
keeps its `(#539 / #544)` cookie — consistent with the rest of
CLAUDE.md's lineage attributions.
closes the gate atlas raised on PR #813: without per-agent opt-in
signal, agent-sockets.json listed every sub-agent, and any agent
that hadn't flipped hyperhive.web.useUnixSocket would 502 the
gateway (its harness still binds TCP, no socket at the published
path).
harness side (web_ui::bind_unix):
- after successful bind + chmod, drop a `.bound` marker in the
per-agent dir as a stable 'this agent has a unix socket here'
signal. best-effort: a failed marker write logs at WARN but
doesn't abort serve (the socket still binds fine; gateway just
keeps using TCP for one more poll).
c0re side (agent_sockets):
- new READY_MARKER const + ready_marker_for(name) helper
- build_map filters by ready_marker_for(name).exists() — only agents
whose harness has bound the socket appear in the JSON map
- new build_map_with<F> internal extracts the predicate so tests
pass a controlled is_ready closure (no real fs access)
- new spawn_poll() background task: re-fires agent_sockets::write
every 10s so the JSON catches up to fresh markers without
needing a container-start hook. write() idempotency means
steady-state cost is one stat per agent per tick.
10 tests: 6 prior + new build_map_filters_by_ready_predicate +
ready_marker_path_is_sibling_of_socket. existing tests adjusted to
call build_map_with(_, |_| true) since the default path now hits
the fs.
once this lands + #822 lands, atlas's gateway-side step 3 can drop
its eval-time `pathExists` fallback — c0re only publishes opted-in
agents, so the gateway can trust the JSON unconditionally.
phase 1 of #784 (unix-domain agent web UI for #14 prerequisite).
backwards-compatible: when HIVE_WEB_SOCKET is set + non-empty, bind a
UnixListener at that path instead of the legacy TCP bind on HIVE_PORT.
empty env var treated as unset so a stray HIVE_WEB_SOCKET= doesn't
trap an un-bindable empty path.
bind_unix helper:
- mkdir -p the socket parent (covers first-boot fresh /run/hive-agent/
bind-mount target)
- best-effort unlink of stale socket (clean exit removes it, crash
leaves it; bind(2) refuses to overwrite)
- mode 0o660 so gateway peers in the same unix group can connect (the
bind-mount source dir ACL is the real gate; perms are defence in
depth)
axum 0.8's Listener trait covers tokio::net::UnixListener directly
(no extra feature/dep required).
next phases (separate PRs):
- step 2: c0re bind-mounts /run/hive-agent/ + writes agent-sockets.json
alongside agent-ports.json
- step 3: gateway proxy_pass http://unix:… (atlas)
- step 4: drop TCP bind once gateway no longer needs it
closes#788. drops the per-role HIVE_LABEL fallback const ('hive-ag3nt'
on AgentSurface, 'hm1nd' on ManagerSurface) and replaces both with a
single literal 'hive' at the env-var unwrap site. real deploys set
HIVE_LABEL unconditionally via the meta-flake envelope; the fallback
is unreachable in production and there's no semantic reason for it to
differ per role.
nix-side standalone-eval fallback (HIVE_LABEL = 'hm1nd' in the
manager systemd unit) stays — that's wired so 'nixos-rebuild against
nixosConfigurations.manager' produces a sensibly-labelled container
even without the meta-flake wrapper.
Surface trait now: FLAVOR + FORGE_IS_MANAGER + 7 async wire methods.
next steps per #778 roadmap: #691 → #786 → #789.