diff --git a/.gitignore b/.gitignore index a4afea36..d3f13a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -/target -/result -/result-* -/.tmp -/.claude/settings.local.json +.directory +result +secrets diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 97526282..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,243 +0,0 @@ -# hyperhive — claude entry point - -Hey claude. This is your starting page. The detailed docs live in -[`docs/`](docs/) and are written for humans + you both — read them -when you need depth on a subsystem. This file is the index. - -- High-level project intro: **[README.md](README.md)**. -- Open work + backlog: the **[forge issue - tracker](http://localhost:3000/hyperhive/hyperhive/issues)**. -- Operator/agent trust-boundary design: - **[docs/boundary.md](docs/boundary.md)** (`area:ops` issues - for the deployment/gateway/privsep work). -- Credential isolation + sandbox threat model: - **[docs/security.md](docs/security.md)**. - -## File map - -``` -hive-c0re/ host daemon + CLI (one binary, subcommand-dispatched) - src/main.rs clap setup; serve / spawn / kill / rebuild / list / - pending / approve / deny / destroy [--purge] / - request-spawn / set-parent (--parent / --root); - periodic vacuum tasks - src/server.rs host admin socket (HostRequest → dispatch) - src/client.rs admin-socket client - src/manager_server.rs manager-privileged socket (ManagerRequest) - src/agent_server.rs per-sub-agent socket listener (long-poll Recv) - src/broker.rs sqlite Message store + intra-process broadcast - channel (`MessageEvent`) for `recv_blocking_batch` + - the dashboard forwarder; hourly vacuum of acked>30d - src/dashboard_events.rs unified wire-facing event channel feeding - `/dashboard/stream`. Carries broker `Sent` / - `Delivered` (mirrored by the forwarder task - in main.rs) + mutation events - (`ApprovalAdded` / `ApprovalResolved`, - `QuestionAdded` / `QuestionResolved`, - `TransientSet` / `TransientCleared`, - `RebuildQueueChanged`). Each frame carries a - monotonic per-process `seq` clients use to - dedupe against snapshot reads. - src/approvals.rs sqlite Approval queue + kinds - src/operator_questions.rs sqlite question queue backing `ask` / - `answer` (both operator + agent-to-agent) - src/questions.rs shared dispatch for `Ask` / `Answer` — - used by both agent + manager surfaces - src/reminder_scheduler.rs 5s poll loop: drains due reminders, - resolves file_path container→host, persists - payload + delivers pointer string - src/scheduled_prompts.rs sqlite layer for scheduled prompts — - schema, CRUD helpers (`cancel_all`, - `cancel_targets`, `reap_cancelled`), catch-up - clamp on resume (#444) - src/scheduled_prompts_worker.rs 5s poll loop: fires due rows, - fans out one Message per active target, - re-arms recurring rows, deletes fired one-shots - src/events_vacuum.rs host-side hourly sweep of every agent's - /state/hyperhive-events.sqlite - src/crash_watch.rs poll every 10s; fire HelperEvent::ContainerCrash - when a previously-running container disappears - without an operator-initiated transient (or a - RECENT_TRANSIENT_GRACE tombstone within the - last 30s, closes #425) - src/container_view.rs ContainerView struct + build_all helper; - shared between dashboard.rs (cold-load via - /api/state) and coordinator.rs's - rescan_containers_and_emit - src/coordinator.rs shared state (broker/approvals/operator_questions/ - transient/sockets) + tombstone enumeration + - kick_agent + notify_agent (helper-event push) + - last_containers cache + rescan_and_emit diff helper - src/loose_ends.rs loose-ends aggregator (pending approvals + - unanswered questions + pending reminders) — - for_agent (filtered) and hive_wide (manager - surface). Backs AgentRequest::GetLooseEnds + - ManagerRequest::GetLooseEnds (the - get_loose_ends MCP tool). - src/rebuild_queue.rs global serialised queue for long-running ops - (rebuild / meta_update / spawn / destroy). - Single background worker drains FIFO; dedup - collapses re-enqueued still-queued entries. - `QueueEntry` carries id, agent, kind, state, - source, parent_id (cascade grouping), timing, - reason, error, step (current phase label while - Running; cleared on finish). Emits - `RebuildQueueChanged` snapshots on every - mutation. - src/actions.rs approve/deny/destroy (transient-aware) - src/auto_update.rs startup rebuild scan + ensure_manager + - meta::lock_update_hyperhive bump - src/lifecycle.rs `nixos-container` shellouts; per-agent applied - + proposed git repo seeding; tag plumbing - src/meta.rs single hive-c0re-owned flake at /var/lib/ - hyperhive/meta/ — sync_agents, two-phase - prepare/finalize/abort, lock_update_* - src/migrate.rs startup auto-migration from pre-meta layout - (idempotent, marker-guarded phase 4) - src/topology.rs agent parent/child storage at - /var/lib/hyperhive/meta/topology.json — sole - source of truth for who's the parent of whom - (single source the dashboard, render_flake, - and the eventual cap-enforcement plumbing all - read). Reconciled by `meta::sync_agents`; - operator/manager edits land via the - eventual write API (#361 follow-ups). - src/forge.rs optional Forgejo wiring: per-agent users + - tokens, the `agent-configs` org (`push_config`), - and meta read access; mirrors each applied repo - into `agent-configs/` (core-only); agents are - read-only collaborators on `core/meta` - src/dashboard.rs axum HTTP: /api/state JSON + actions - + journald viewer + bind-with-retry (SO_REUSEADDR) - + deployed_sha chip per container + - /dashboard/{stream,history} subscribing to the - unified DashboardEvent channel. Static assets - (HTML/CSS/JS/favicon) served by - tower_http::ServeDir from $HIVE_STATIC_DIR - (= `${frontend}/dashboard` per the c0re module). - -frontend/ npm workspaces (esbuild → static dist). Built - hermetically by `nix/frontend.nix` - (`packages.${system}.frontend`). - packages/shared/ @hive/shared: terminal pane + Catppuccin palette - + base typography (was hive-fr0nt). ES module - exporting { create, linkify }; pure JS, no IIFE - globals; consumed by dashboard + agent. - packages/dashboard/ @hive/dashboard SPA: src/{index.html, tabs.js, - flow.html, flow.js, common.js, dashboard.css} + - build.mjs → dist/{index.html, flow.html, - static/{tabs.js, flow.js, dashboard.css, - stream-worker.js}}. - packages/agent/ @hive/agent default per-container UI: src/ - {index, stats, screen}.html + {app, stats}.js - + agent.css → dist/{*.html, static/*}. - -hive-ag3nt/ in-container harness crate; produces TWO binaries - src/lib.rs re-exports + DEFAULT_SOCKET, DEFAULT_WEB_PORT - src/client.rs generic JSON-line request/response over unix socket - src/web_ui.rs per-container axum HTTP page (incl /api/cancel, - /api/compact, /api/model, /events/history, - /screen, /screen/ws) - src/turn_stats.rs per-turn analytics sink (one sqlite row per - turn at /state/hyperhive-turn-stats.sqlite); - schema + best-effort writer - src/stats.rs read-side aggregations over turn-stats.sqlite - backing the /stats page (bucketed Snapshot: - turns / duration / tokens / model mix) - src/events.rs LiveEvent + broadcast Bus + sqlite-backed history - (/state/hyperhive-events.sqlite) + TurnState + - model selection (persisted at /state/hyperhive-model) - src/turn.rs claude --print + stream-json pump; --compact retry; - proactive compaction + auto session-reset - src/mcp.rs embedded MCP server (rmcp): AgentServer + ManagerServer - src/login.rs probe /root/.claude/ for a valid session - src/login_session.rs drives `claude auth login` over stdio pipes - src/bin/hive-ag3nt.rs sub-agent main (Serve + Mcp subcommands) - src/bin/hive-m1nd.rs manager main (Serve + Mcp subcommands) - Static UI assets served by ServeDir from - $HIVE_STATIC_DIR (= hyperhive.frontend - .mergedDist — default agent dist + per-agent - extraFiles, set per the harness-base module). - prompts/ static role/tools/settings for claude (include_str!): - agent.md — sub-agent system prompt - manager.md — manager system prompt - claude-settings.json — --settings JSON - -hive-forge/ Forgejo CLI wrapper (`hive-forge` binary) - src/main.rs clap dispatch over the verbs/ - src/client.rs blocking reqwest client (Forgejo REST API) - src/body.rs body input resolution (--body / --body-file / piped stdin) - src/verbs/.rs one module per verb (view, issue, pr, comment, - comments, comment-show, comment-edit, issue-create, - issue-edit, pr-create, pr-reviews, assign, - close, labels, milestone, branches, - tree-sha, diff, subscription, attach-issue, - attach-comment, lint). Replaces the 600-line - hive-forge-tools.nix bash script (closes #280). - -hive-sh4re/ wire types (HostRequest/Response, AgentRequest/Response, - ManagerRequest/Response, Message, Approval, HelperEvent) - -nix/ - modules/hive-c0re.nix systemd service + firewall + git wiring; - `contextWindowTokens` attrset (per-model, - injected as env vars into all containers); - imports hive-forge.nix - modules/hive-forge.nix optional in-container Forgejo - (`hyperhive.forge.enable`, default on); - Catppuccin Mocha theme via tmpfiles C+ copy - templates/harness-base.nix shared scaffolding for sub-agents + manager; - `hyperhive.model` option (HIVE_DEFAULT_MODEL) - templates/agent-base.nix sub-agent nixosConfiguration - templates/manager.nix manager nixosConfiguration - templates/weston-vnc.nix optional `hyperhive.gui.enable` - — weston + VNC backend systemd unit; writes - /etc/hyperhive/gui.json (vnc_port + auth) for - the harness WebSocket relay (/screen/ws) - forge-theme/theme-catppuccin-vibec0re.css Catppuccin Mocha forge theme - -docs/ - conventions.md naming, identity=socket, async forms, commit style - gotchas.md NixOS / nspawn quirks and lessons learned - web-ui.md dashboard + per-agent page layouts and endpoints - turn-loop.md claude invocation, wake prompt, MCP tool surface - approvals.md approval flow, manager policy, helper events - persistence.md sqlite dbs, retention, state dir layout - terminal-rendering.md per-agent terminal row taxonomy (as built) - boundary.md operator/agent trust model rationale - agent-hierarchy.md tree-shape topology design + manager-privilege audit (#361) - damocles-migration.md future migration plan for damocles → hyperhive -``` - -## Reading paths - -Pick the doc that matches your task. None depend on the others — -read them à la carte. - -- **"What does the dashboard look like?"** → - [`docs/web-ui.md`](docs/web-ui.md). -- **"How does the per-agent terminal classify + colour - events?"** → [`docs/terminal-rendering.md`](docs/terminal-rendering.md). -- **"How does claude get its prompt and what tools does it have?"** → - [`docs/turn-loop.md`](docs/turn-loop.md). -- **"How do config changes flow from manager to operator to - container?"** → [`docs/approvals.md`](docs/approvals.md). -- **"What state survives destroy / purge / restart?"** → - [`docs/persistence.md`](docs/persistence.md). -- **"Naming, commit style, wire protocol, the `data-async` - pattern."** → [`docs/conventions.md`](docs/conventions.md). -- **"Why does the nspawn flag look like that?"** → - [`docs/gotchas.md`](docs/gotchas.md). - -## Conventions & process - -The docs below own the details — this section just points at them. - -- **Commit style, naming, identity, reconcile verb:** → - [`docs/conventions.md`](docs/conventions.md). -- **NixOS / nspawn quirks** (bind mounts, conf flags, etc.): → - [`docs/gotchas.md`](docs/gotchas.md). -- **Turn loop, sentinels (rate-limit, auth-failed), context - window:** → [`docs/turn-loop.md`](docs/turn-loop.md). -- **Two-step spawn, approval flow, flake.lock validation:** → - [`docs/approvals.md`](docs/approvals.md). diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index e156349b..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,2375 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "base64", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - -[[package]] -name = "clap" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "data-encoding" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hive-ag3nt" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "clap", - "futures-util", - "hive-sh4re", - "reqwest", - "rmcp", - "rusqlite", - "schemars", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower-http", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "hive-c0re" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "base64", - "clap", - "hive-sh4re", - "libc", - "reqwest", - "rusqlite", - "serde", - "serde_json", - "tempfile", - "tokio", - "tokio-stream", - "tower-http", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "hive-forge" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "reqwest", - "serde", - "serde_json", -] - -[[package]] -name = "hive-sh4re" -version = "0.1.0" -dependencies = [ - "serde", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libsqlite3-sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "pastey" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rmcp" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" -dependencies = [ - "async-trait", - "chrono", - "futures", - "pastey", - "pin-project-lite", - "rmcp-macros", - "schemars", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "rmcp-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "serde_json", - "syn", -] - -[[package]] -name = "rusqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "chrono", - "dyn-clone", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "http-range-header", - "httpdate", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand", - "sha1", - "thiserror", -] - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b990949f..00000000 --- a/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[workspace] -resolver = "3" -members = ["hive-ag3nt", "hive-c0re", "hive-forge", "hive-sh4re"] - -[workspace.package] -edition = "2024" -version = "0.1.0" - -[workspace.lints.clippy] -pedantic = { level = "warn", priority = -1 } -# Tolerated stylistic pedantic lints (noisy, not actionable). -missing_errors_doc = "allow" -missing_panics_doc = "allow" -module_name_repetitions = "allow" -must_use_candidate = "allow" - -[workspace.dependencies] -anyhow = "1" -axum = { version = "0.8", features = ["ws"] } -base64 = "0.22" -clap = { version = "4", features = ["derive"] } -hive-sh4re = { path = "hive-sh4re" } -tower-http = { version = "0.6", features = ["fs"] } -rmcp = { version = "1.7", default-features = false, features = [ - "server", - "macros", - "transport-io", -] } -rusqlite = { version = "0.37", features = ["bundled"] } -schemars = "1.0" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -similar = "2" -tokio = { version = "1", features = [ - "fs", - "io-util", - "macros", - "net", - "process", - "rt-multi-thread", - "signal", - "sync", - "time", -] } -tokio-stream = { version = "0.1", features = ["sync"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md index a2f48896..3ad5e880 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,46 @@ -# hyperhive +# nixos-configuration -> a swarm of claude-code agents, each in its own nspawn cage, gossiping -> over unix sockets. config changes flow as git commits, the operator -> approves them in a browser, every deploy is a tag. cyberpunk-themed -> dashboard included. 💜⚡ +Personal NixOS configuration for all machines. Devices are declared in `devices.nix`, per-device configs live in `nixosConfigurations//`, and shared modules in `nixosModules/`. -Claude code is great in one window, *exponentielle* across many — but -only if you can keep the agents from stepping on each other, give them -durable identity, and stop them from eating production. hyperhive is -the substrate. +## Distributed builds -- identity = unix socket -- communication = sqlite-backed broker (`send` / `recv` / `ask` / - `answer` / `remind`) -- config = git (manager proposes, operator approves, deploys land as - tagged commits) -- blast radius = container +Machines are configured to act as build servers / binary caches for each other in devices.nix. -``` -host (NixOS, runs hive-c0re.service) -│ -├── operator -│ ├── browser → :7000 hive-c0re dashboard -│ ├── browser → :8000 / :8100-8999 per-agent web UIs -│ └── CLI → /run/hyperhive/host.sock admin protocol -│ -├── hive-c0re (Rust daemon: lifecycle / broker / approvals / -│ auto-update / dashboard / sockets) -│ -└── nixos-containers - ├── hm1nd manager agent (privileged MCP surface) - └── h- sub-agent (vanilla MCP surface + per-agent extras) -``` +### Onboarding a device as a build client -Depth lives in [`docs/`](docs/) — pick the one matching your task: +1. Generate a key pair on the device: -| reading path | doc | -| --- | --- | -| dashboard layout + endpoints | [`docs/web-ui.md`](docs/web-ui.md) | -| claude turn loop + MCP tools | [`docs/turn-loop.md`](docs/turn-loop.md) | -| config-edit + approval state machine | [`docs/approvals.md`](docs/approvals.md) | -| what survives destroy / purge / restart | [`docs/persistence.md`](docs/persistence.md) | -| naming, wire protocol, commit style | [`docs/conventions.md`](docs/conventions.md) | -| NixOS / nspawn gotchas | [`docs/gotchas.md`](docs/gotchas.md) | + ```sh + sudo ssh-keygen -t ed25519 -f /etc/nix/distributed-build-key -N "" -C "$(hostname)-nix-builds" && sudo cat /etc/nix/distributed-build-key.pub + ``` -## Host config +2. Add the public key to the device entry in `devices.nix`: -Minimal `flake.nix` for a host that runs hive-c0re: + ```nix + distributedBuilds.clientPublicKey = "ssh-ed25519 AAAA... -nix-builds"; + ``` -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; - hyperhive.url = "git+https://git.berlin.ccc.de/vinzenz/hyperhive"; - }; +3. Rebuild all build machines so they pick up the new authorized key. - outputs = { nixpkgs, hyperhive, ... }: { - nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - hyperhive.nixosModules.default # hive-c0re + hive-forge in one import - ({ ... }: { - services.hive-c0re.enable = true; - # services.hive-c0re.operatorPronouns = "they/them"; # default: "she/her" +### Adding a build server - # ... rest of your host config - system.stateVersion = "25.11"; - }) - ]; - }; - }; -} -``` +1. Add to its entry in `devices.nix`: -hive-c0re opens its admin socket + dashboard, auto-creates the -manager container, and auto-rebuilds any container whose hyperhive -rev goes stale. `claude-code` is unfree — hyperhive scopes the -whitelist to itself, nothing for the operator to set. + ```nix + distributedBuilds.isBuilder = true; + distributedBuilds.hostPublicKey = "ssh-ed25519 AAAA..."; # from: ssh-keyscan -t ed25519 "$(hostname)" + ``` -Optional: set `services.hive-c0re.preBuildAgentTemplates = true;` -to pre-fetch the per-container system closures into your host's -/nix/store as part of `nixos-rebuild`. First-agent-spawn then -completes in seconds instead of minutes (no nixpkgs/claude-code -fetch on the critical path), at the cost of a few GB extra in your -system closure. Off by default (the toplevels are pinned to -`x86_64-linux`, so non-x86 hosts would otherwise force a cross-build). -Alternatively warm the store manually: -`nix build github:vinzenz/hyperhive#agent-base-toplevel`. +2. Generate a store signing key on the builder: -## Agent configuration + ```sh + sudo nix key generate-secret --key-name "$(hostname)" | sudo tee /etc/nix/signing-key.sec | sudo nix key convert-secret-to-public + ``` -Per-agent settings live in each agent's `agent.nix` and are synced to -the container as environment variables. Common options: +3. Add the printed public key to `devices.nix`: -- **`hyperhive.model`** — Claude model for this agent (default: `"haiku"`). - Sets `HIVE_DEFAULT_MODEL` in the container; the harness applies it at - boot and it takes priority over any persisted runtime override. The - operator can still switch the model at runtime via the per-agent web UI, - but that choice is reset by any rebuild that changes this option. -- **`hyperhive.allowedRecipients`** — List of agent names this agent can - message (via `send`). If unset, all agents are allowed. Useful to - restrict an agent to talking only to the manager. -- **`hyperhive.forge.url`** — Base URL of the hyperhive-managed Forgejo - (default: `"http://localhost:3000"`). Used to configure the agent's - tea login at boot; no-op if `/state/forge-token` is missing. -- **`hyperhive.forge.keepSubscriptions`** — Boolean. If `true`, the agent's - forge repo subscriptions are never auto-cleaned during rebuild; useful - for agents that want to watch specific repos. Rendered as - `HIVE_FORGE_KEEP_SUBSCRIPTIONS`. -- **`hyperhive.forge.skipNotifyReasons`** — List of forge notification - `reason` values to suppress (e.g. `[ "subscribed" "participating" ]`). - Notifications matching these reasons are silently dropped; all others - including direct mentions and reviews are delivered. Empty list (default) - delivers all notifications. Rendered as `HIVE_FORGE_NOTIFY_SKIP_REASONS` - (comma-separated). -- **`hyperhive.frontend.dist`** — Override the default frontend package - (`pkgs.hyperhive-frontend`, built by `nix/frontend.nix`). Set to a custom - derivation to ship a fully custom per-agent SPA. The JSON contract - (`/api/state`, `/events/stream`, action endpoints) is the source of truth - for any replacement. -- **`hyperhive.frontend.extraFiles`** — Attrset of extra files/directories - to layer on top of the default agent dist. Each entry has a `source` (nix - path) and an optional `target` (URL prefix in the static tree, defaults to - the attribute name). Example: `{ bitburner.source = ./bitburner-dist; }` - serves that dist at `/bitburner/`. Pure additions only — overwriting an - existing default file is a hard eval-time error; use `frontend.dist` to - replace the whole dist. Paths with leading `/` or `..` segments are - rejected at eval time. + ```nix + distributedBuilds.storeSigningPublicKey = ":"; + ``` -See `nix/templates/harness-base.nix` for the full list of options and -their descriptions. - -## Build / deploy - -```sh -nix develop -c cargo check -nix flake check # rust + nix + toml fmt + clippy - -# deploy from a host config that imports hyperhive.nixosModules.hive-c0re -nix flake update --update-input hyperhive -sudo nixos-rebuild switch --flake .# -``` +4. Rebuild all machines so they trust the new signing key. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 74613421..00000000 --- a/TODO.md +++ /dev/null @@ -1,6 +0,0 @@ -# Hyperhive TODOs - -The backlog moved to the forge issue tracker: - - -Operator/agent trust-boundary design rationale: [`docs/boundary.md`](docs/boundary.md). diff --git a/branding/agent-configs.svg b/branding/agent-configs.svg deleted file mode 100644 index d9e4d358..00000000 --- a/branding/agent-configs.svg +++ /dev/null @@ -1,116 +0,0 @@ - - HyperHive · agent-configs - HyperHive agent-configs org icon — stacked config files, amber on dark, same frame as the main hyperhive mark - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { } - - - - - - - - - - - - - - - - diff --git a/branding/hyperhive.png b/branding/hyperhive.png deleted file mode 100644 index 731d2533..00000000 Binary files a/branding/hyperhive.png and /dev/null differ diff --git a/branding/hyperhive.svg b/branding/hyperhive.svg deleted file mode 100644 index 81d931c1..00000000 --- a/branding/hyperhive.svg +++ /dev/null @@ -1,97 +0,0 @@ - - HyperHive - HyperHive icon — hexagonal hive, amber on dark - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/devices.nix b/devices.nix new file mode 100644 index 00000000..26a274d9 --- /dev/null +++ b/devices.nix @@ -0,0 +1,80 @@ +{ self }: +let + nixos-raspberrypi = self.inputs.nixos-raspberrypi; +in +{ + # keep-sorted start block=yes + aur0ra = { + system = "aarch64-linux"; + nixosSystem = nixos-raspberrypi.lib.nixosSystem; + }; + aur0ra-installer = { + # build with nix build .\#nixosConfigurations.aur0ra-installer.config.system.build.sdImage + system = "aarch64-linux"; + nixosSystem = nixos-raspberrypi.lib.nixosInstaller; + }; + damocles = { + system = "x86_64-linux"; + distributedBuilds.maxJobs = 0; + }; + damocles-lab = { + system = "x86_64-linux"; + distributedBuilds.maxJobs = 0; + }; + epimetheus = { + system = "aarch64-linux"; + }; + forgejo-runner-1 = { + system = "aarch64-linux"; + publicFqdn = "forgejo-runner-1.dev.zerforschen.plus"; + distributedBuilds = { + isBuilder = true; + speedFactor = 1; + clientPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0NLgg0sFobBWz/bjYs9WkrMvlcvJC5F6+3jQ/b+AnD forgejo-runner-1-nix-builds"; + hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIANGC89GiT5xCsFICwrharrbV3q7acWHqk6ZwOUXbtGT"; + storeSigningPublicKey = "forgejo-runner-1:ln1FVLL8G5+IveQuBi/Kn3SaqFZ1gaiQrE3yPlMhCMA="; + }; + }; + hetzner-vpn2 = { + system = "aarch64-linux"; + }; + hyperforge = { + system = "aarch64-linux"; + }; + muede-lpt2 = { + system = "x86_64-linux"; + isDesktop = true; + home-manager-users = { + inherit (self.homeConfigurations) muede; + }; + distributedBuilds = { + isBuilder = true; + speedFactor = 2; + hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGKoZ68wwyVRmPB0SkvpJUyUMDWeFbC5Je9zukyEOh7"; + clientPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAbojdhb3PfazSRmudvo381Y+zUFVLMa7AbWbfK/Zp2 muede-lpt2-nix-builds"; + storeSigningPublicKey = "muede-lpt2:3csut7FW6oZK/ztRLBRC80LSBfFE3qzl+aIYgOixB6U="; + }; + }; + muede-pc2 = { + system = "x86_64-linux"; + isDesktop = true; + home-manager-users = { + inherit (self.homeConfigurations) muede; + }; + distributedBuilds = { + isBuilder = true; + speedFactor = 4; + hostPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKEQQS5XNoj62Oj85xQfIuLORwoBRwfqjvfBHHsiI+RH"; + clientPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHmnyhP6L+kGHV15cb/d31AQr50wSEaQhkUBwy2+OEKk muede-pc2-nix-builds"; + storeSigningPublicKey = "muede-pc2:fqQO0E0y65MjUWlQnrgWt5ZsmQKlKCv4jls3CmUXDEQ="; + }; + }; + ronja-pc = { + system = "x86_64-linux"; + isDesktop = true; + home-manager-users = { + inherit (self.homeConfigurations) ronja; + }; + }; + # keep-sorted end +} diff --git a/docs/agent-hierarchy.md b/docs/agent-hierarchy.md deleted file mode 100644 index dc3d3b7c..00000000 --- a/docs/agent-hierarchy.md +++ /dev/null @@ -1,204 +0,0 @@ -# Agent hierarchy & privileges - -Design + audit doc for milestone #6 (the -[issue](http://localhost:3000/hyperhive/hyperhive/issues/361) tree). -The implementation lands in pieces; this doc tracks what's done, what's -planned, and what currently special-cases the manager. - -## Current state (as of this PR) - -Topology lives in the hive-c0re-owned **meta repo**, alongside -`flake.nix`, at `/var/lib/hyperhive/meta/topology.json`: - -```json -{ - "manager": null, - "alice": "manager", - "bob": "alice" -} -``` - -`null` = root-level agent. Today only the manager qualifies by default. -Other agents land under `"manager"` on first sync. Re-parenting is -operator-driven (#486 / #487): - -- CLI: `hive-c0re set-parent --parent ` (or `--root` to - promote). Exactly one of `--parent` / `--root` is required. -- Dashboard: `POST /api/topology/set-parent` (form fields `child`, - optional `new_parent` — absent / empty ⇒ promote to root). -- Wire: `HostRequest::SetParent { child, new_parent: Option }`. - -All three converge on `topology::set_parent`, which delegates the -validation rules to a pure `apply_set_parent` helper. Refuses: -- reparenting the manager (structurally root), -- unknown `child` / `new_parent` (typo guard), -- self-parenting, -- cycles (32-hop ancestor walk, mirroring `is_descendant_of`). - -Idempotent no-op fast path skips the disk write when the parent is -already what's requested. After a successful write the surfaces call -`Coordinator::rescan_containers_and_emit` so connected dashboard -viewers see the tree repaint without polling -(`ContainerView.parent` is sourced from `topology.json`). - -**Today's caveat (#361 follow-up):** the move is purely a JSON edit. -Only the top-level manager (`hm1nd`) gets `/var/lib/hyperhive/agents` -bind-mounted at `/agents` in its container, so sub-agents don't yet -see their would-be children's state. Once sub-manager bind mounts -land alongside #361 enforcement, `set_parent` grows a companion -umount-old / mount-new / restart-cascade step (tracked via the -cross-ref comment on #361). - -### Why meta, not per-agent `agent.nix` - -An agent shouldn't be able to claim a parent without that parent's -consent, and operator-driven re-parenting shouldn't require touching -the moved agent's config. Topology IS a system-level concern; meta is -where system-level facts live. - -### Flow - -1. **Read**: `topology::read()` parses `topology.json` into a - `BTreeMap>`. Missing / unparsable file → - empty map → every agent treated as root (safe degradation for - fresh installs that haven't run `meta::sync_agents` yet). -2. **Reconcile**: `meta::sync_agents` calls `topology::reconcile` - alongside its `flake.nix` regeneration. New agents land at their - default position (manager as parent, manager itself as root); - removed agents drop. Existing entries are preserved as-is so - operator overrides stick across regenerations. -3. **Inject**: `meta::render_flake` looks up each agent's parent and - passes it to `mkAgent`. When non-null, the mkAgent body sets - `HIVE_PARENT = parent` in the agent's systemd service environment - so the harness / claude prompts can see it. -4. **Surface**: `container_view::build_all` reads `topology.json` and - populates `ContainerView.parent: Option` on every rescan. - The dashboard renders the field as a tree (#363 follow-up). - -## Target topology semantics - -Once enforcement lands the rules collapse into: - -| operation | who can do it | -|---|---| -| `kill` / `start` / `restart` / `update` (any descendant) | any ancestor | -| `request_init_config` (spawn a new child) | any agent, child added under self | -| `request_apply_commit` (any descendant's config) | any ancestor | -| `get_logs` (any descendant) | any ancestor | -| moderate questions / reminders (cancel any open thread of a descendant) | any ancestor | -| `send` / `recv` routing | parent ↔ same-parent siblings ↔ self ↔ descendants; explicit allow-list for anyone else | -| `request_update_meta_inputs` (bump meta lock) | root agents only (today: just `manager`) | - -"Ancestor" walks `ContainerView.parent` chains; cycles are guarded by a -visited-set at dispatch time (a malformed topology.json can't lock the -dispatcher into a loop). - -## Current manager special-casings — the audit - -What currently makes the manager different from every other agent, and -which axis the post-milestone version reads each special-case along: - -### A — naming + bootstrap - -- `MANAGER_AGENT = "manager"` (broker recipient name) and - `MANAGER_NAME = "hm1nd"` (container name). ~28 grep hits across - `hive-c0re/src/`. **Just a name** — the rename plan is `manager` → - `root`, executed via the one-shot migration script in - `migrate.rs` (idempotent, marker-guarded). -- `auto_update::ensure_manager` runs at hive-c0re boot and spawns - `hm1nd` if missing. Becomes "ensure the root agent exists" once any - agent can be at the root. **Topology**: root has no parent, so - hive-c0re itself owns its lifecycle (no parent to delegate to). - -### B — wire-protocol privileges - -The `ManagerRequest::*` variants in `hive-sh4re/src/lib.rs` are -operations the manager flavour socket can make that sub-agent sockets -can't: - -| variant | semantic | post-milestone | -|---|---|---| -| `RequestInitConfig` | seed an agent's proposed config repo | **topology** — descendants only | -| `RequestApplyCommit` | submit a commit sha for operator approval | **topology** — descendants only | -| `Kill` / `Start` / `Restart` / `Update` | container lifecycle on an existing agent | **topology** — descendants only | -| `RequestUpdateMetaInputs` | bump meta `flake.lock` | **per-agent cap** (root-only today; a future "let coder bump its own input" might grant it) | -| `GetLogs` | journalctl scrape of a sub-agent | **topology** — descendants only | -| `Wake` | inject a `from: ` message into self's inbox | **not really privileged** — the wire surface exists because daemon co-processes (e.g. `forge_notify`) need it. Sub-agents have the same via their own socket. | - -### C — storage / mounts (`hive-c0re::lifecycle`) - -The manager container's nspawn bind set: - -- `HOST_AGENTS_ROOT (/var/lib/hyperhive/agents) → /agents` RW — so the - manager can edit any agent's proposed config repo -- `HOST_APPLIED_ROOT (/var/lib/hyperhive/applied) → /applied` RO — so - the manager can diff against what's deployed -- `HOST_META_ROOT (/var/lib/hyperhive/meta) → /meta` RO — so the - manager can read the system-wide deploy log - -Tree-shape version: -- Each agent gets RW to `/agents//` for every descendant in - its subtree. The root agent (today: manager) gets RW to the full - forest as a special case of "the root has every other agent as a - descendant". -- RO `/meta` access if the agent holds a "meta read" cap. -- `request_update_meta_inputs` is the only path that actually writes - `flake.lock`, gated by the cap; everyone else stays RO. - -### D — drop legacy `/state` for manager - -`lifecycle.rs::notes_mount` currently ternary's `/state` for the -manager and `/agents//state` for everyone else (because the -manager pre-dates the per-agent state-dir layout). Milestone bullet: -unify on `/agents//state` for everyone. One-time `mv` of -`/var/lib/hyperhive/manager/state` → `/var/lib/hyperhive/agents/manager/state` -in `migrate.rs` (idempotent, marker-guarded). - -### E — prompt + tools - -- `prompts/manager.md` vs `prompts/agent.md` — two separate system - prompts. **Per-agent cap list** of what the agent can do, rendered - into a single parametrised prompt at boot. -- `mcp.rs::Flavor::{Agent, Manager}` controls which MCP tools claude - sees. Already structured this way internally — the per-flavour - allow-list becomes a per-cap-set lookup. - -### F — drive-by checks across c0re - -(`grep -n MANAGER_AGENT` produced ~28 hits) - -- `loose_ends.rs`: manager sees hive-wide loose-ends, sub-agents only - their own. **Topology** — every agent sees its own + its - descendants'. -- `operator_questions.rs` + `broker.rs`: "manager can cancel any - question" override on the owner check. **Topology** — agents can - moderate threads of their descendants. (per mara's - https://localhost:3000/hyperhive/hyperhive/issues/361#issuecomment-3344) -- `reminder_scheduler.rs`: same override pattern for reminder cancel. - **Topology** — descendants only. -- `actions.rs`: `destroy` refuses to act on `MANAGER_NAME` (no - foot-shooting). **Topology** — agents can destroy descendants but - never themselves or ancestors. -- `crash_watch.rs`: skips `ContainerCrash` for the manager (it - auto-restarts via systemd). **Topology** — the root container has - different recovery semantics, every other agent falls into the same - watch loop. - -### G — sub-agents inside the same container - -Future work mentioned in #361: when enabled for an agent, it can spawn -temporary "sub-agents" that run inside its own container. Lighter than -a full nspawn agent. Open questions, not yet wired: - -- Inherit caps from parent, or take an explicit narrower set? -- Survive container restart, or always ephemeral? -- Inbox: separate from parent, or shared? -- Filesystem: share parent's `/state` RW, or a sub-dir? -- Identity: distinct broker recipient name, or address the parent? - -## Cross-references - -- Milestone: [#361 "Agent privileges and sub-agents"](http://localhost:3000/hyperhive/hyperhive/issues/361) -- Dashboard render: [#363 "show agent topology in container list"](http://localhost:3000/hyperhive/hyperhive/issues/363) -- Audit table source: [comment 3335 on #361](http://localhost:3000/hyperhive/hyperhive/issues/361#issuecomment-3335) -- Operator/agent trust boundary (orthogonal axis): [`boundary.md`](boundary.md) diff --git a/docs/approvals.md b/docs/approvals.md deleted file mode 100644 index 5158e8ba..00000000 --- a/docs/approvals.md +++ /dev/null @@ -1,471 +0,0 @@ -# Approvals + manager + helper events - -The approval queue is hyperhive's pivot: nothing that changes the -shape of an agent (its config, whether it exists) happens without an -operator click. The manager (`hm1nd`) is the policy gate in front of -that queue; helper events are how it stays informed about what -happens after a decision lands. - -## End-to-end approval flow - -1. Manager edits files under `/agents//config/` (any tracked - path, but `agent.nix` is the contract entry point) and commits - with its own git identity. -2. Manager submits the commit sha via `request_apply_commit(agent, - commit_ref)`. `commit_ref` must be a commit **sha** (7-40 hex - chars, short or full) — a branch or tag name is rejected so the - approval pins an immutable commit. -3. **hive-c0re immediately fetches that commit from the proposed - repo into the applied repo and tags it `proposal/`.** It - resolves the sha locally against the proposed repo, fetches all - of proposed's heads into applied's object db, then tags the - resolved commit — `git fetch :` can't fetch - by a bare sha (the left side of a refspec is a remote *ref - name*), so the resolution happens on hive-c0re's side. The - approval row stores both the manager-supplied sha and the - canonical hive-c0re-vouched sha. From here on the proposed - repo is irrelevant for this approval — the manager can amend, - force-push, or `rm -rf` the proposed repo and the queued - approval still points at an immutable git object inside - applied. -3a. **Flake validation (ApplyCommit only):** after the proposal tag - is planted, hive-c0re reads `proposal/:flake.lock` and - runs two checks (closes #317). If either check fails, no - pending approval is created for the operator — the row is - marked failed and surfaces on the dashboard with the - validation message: - - **Stale lock** — materialises the commit in a temp worktree, - runs `nix flake lock` (no `--update-input` flags, so it only - fills missing entries), and rejects if the committed - `flake.lock` differs from the result. Triggered when the - manager added or removed `inputs` in `flake.nix` without - re-running `nix flake lock`. Fix: run `nix flake lock` in - the config repo, commit, and re-submit. - - **Duplicate inputs** — groups lock nodes by their canonical - `original` field; rejects if two or more nodes share the same - source. This usually means an input is missing - `inputs..inputs.nixpkgs.follows = "nixpkgs"`. Fix: add - the `follows` directive, re-lock, and re-submit. - Both checks only flag *new* violations — agents whose lock - already carried duplicates before this check was added are - unaffected until a coordinated config-change pass via manager. -4. Operator sees the proposal as a card on the dashboard — a - full multi-file diff, toggleable between three bases (vs the - running tree / vs the last approved proposal / vs the - previous queued proposal) — and clicks ◆ APPR0VE (or - `hive-c0re approve ` on the CLI). -5. hive-c0re moves the working tree to `proposal/` and runs - the build under a sequence of tags (see below). On success, - `applied/main` fast-forwards to the proposal commit. On - failure, main stays put and the working tree resets back to - the previous deployed commit. -6. `HelperEvent::ApprovalResolved` (and `Rebuilt` for the - ApplyCommit kind) land in the manager's inbox, carrying both - the canonical sha and the terminal tag. - -### Withdrawing a pending approval - -The manager can call `cancel_loose_end(kind: "approval", id)` to -withdraw an approval that hasn't been acted on yet (closes #250). -The row transitions to `ApprovalStatus::Cancelled` (distinct from -`Denied`/`Failed`), the dashboard pulls the card out of the -pending pane, and `ApprovalResolved { status: "cancelled" }` fires -on the manager + dashboard channels. Approvals that have already -been approved/denied/failed return an error — the resolution is -final once the operator (or a lifecycle failure) acted on the row. - -Sub-agent surface refuses the `approval` kind with a clear error: -sub-agents don't submit approvals, so they have nothing of their -own to withdraw. Manager-only. - -`InitConfig` approvals are the first step in a two-step spawn -flow. On approve, hive-c0re seeds the proposed config repo with -a default `agent.nix` template and sends the manager -`HelperEvent::ConfigReady { agent }`. The manager then reviews, -edits, and commits the template before calling `request_apply_commit` -to proceed to an `ApplyCommit` approval. The first `ApplyCommit` -creates the container; subsequent ones rebuild it with new config. -This gives the manager (and operator) an explicit review gate on the -initial configuration before any container is created. - -## Meta flake - -The hive-c0re-owned repo at `/var/lib/hyperhive/meta/` -declares one flake input per agent (`agent-.url = -"git+file:///var/lib/hyperhive/applied/"`) and one -`nixosConfigurations.` output per agent. Each output wraps -`inputs.agent-.nixosModules.default` with the identity + -`HIVE_PORT` / `HIVE_LABEL` / `HIVE_DASHBOARD_PORT` injection -module that `setup_applied` used to generate inline. -Containers run against `--flake /var/lib/hyperhive/meta#`. - -Per-deploy lock flow (two-phase, owned by -`actions::run_apply_commit` → `meta::{prepare,finalize,abort} -_deploy`): - -1. `meta::prepare_deploy(name)` runs - `nix flake lock --update-input agent-` without - committing. Working tree of meta now points the input at - `applied//main` (which `run_apply_commit` already - fast-forwarded to `proposal/`). -2. `lifecycle::rebuild_no_meta` runs - `nixos-container update --flake meta#`. Nix - evaluates against the staged lock. -3. On success — `meta::finalize_deploy(name, sha, "deployed/ - ")` stages `flake.lock` and commits with - `deploy deployed/ `. Meta's git log gains - one entry per successful deploy. -4. On failure — `meta::abort_deploy()` runs - `git restore flake.lock` so the meta history shows only - successes; the failure stays as an annotated `failed/` - tag in `applied/`. - -Single-phase variants exist for paths without -rollback semantics: `meta::lock_update_for_rebuild(name)` for -the manual `↻ R3BU1LD` button (commits if the lock changed) -and `meta::lock_update_hyperhive()` for the -auto-update flake-rev bump (one shot before per-agent -rebuilds, commits if the lock changed). - -`meta::sync_agents(hyperhive_flake, dashboard_port, &agents)` -is the idempotent reconciler called by `spawn`, `destroy`, -`rebuild`, and the startup migration. Renders `flake.nix` -from the agent list; if it differs from disk, runs -`nix flake lock` + commits as `regenerate meta flake` (or -`seed meta from N agent(s)` on the very first call). - -The manager has `/meta` RO-bound inside its container: -`git -C /meta log --oneline` is the swarm-wide deploy log, -`cat /meta/flake.lock | jq '.nodes["agent-"].locked'` -resolves which sha each agent is pinned at right now. -Dashboard surfaces the same info as a `deployed:` chip -per container row. - -## Two repos per agent - -``` -/var/lib/hyperhive/agents//config/ proposed — manager RW -└── # any files the manager - # wants in the commit. - # agent.nix is the - # convention entry - # point; flake.nix is - # tracked boilerplate - # (manager doesn't edit - # it). - -/var/lib/hyperhive/applied// applied — core-only -├── .git/ # tag-rich history -├── flake.nix # tracked, fixed -│ # boilerplate exporting -│ # nixosModules.default -├── agent.nix # working tree of main -└── # also tracked - -/var/lib/hyperhive/meta/ swarm-wide flake — core -├── .git/ # one commit per successful -│ # deploy -├── flake.nix # generated from agent set -└── flake.lock # pins each agent's sha -``` - -Why two physical repos: the manager's `/agents//config/` is -RW — a buggy or hostile agent can `git clean -fdx` its own -proposed tree. The applied repo is never bind-mounted (except -the read-only `.git` exposure described below) so a destructive -move inside the container cannot reach it. - -The container's `--flake` ref is `/var/lib/hyperhive/meta#` -(see "Meta flake" above). The agent's own `applied//flake.nix` -is a fixed boilerplate that exports `nixosModules.default = -import ./agent.nix`; the meta flake imports that module and -wraps it with identity + `HIVE_PORT` / `HIVE_LABEL` / -`HIVE_DASHBOARD_PORT`. - -### Tag state machine - -Every approval id walks through a fixed set of tags on the -underlying commit inside the applied repo: - -| Tag | When | Annotated? | -|---|---|---| -| `proposal/` | request_apply_commit, after fetch | no | -| `approved/` | operator approve | no | -| `building/` | rebuild started | no | -| `deployed/` | rebuild succeeded — `main` ff's here | no | -| `failed/` | rebuild failed | yes (body = error) | -| `denied/` | operator deny | yes (body = operator note) | - -`applied/main` is always the latest `deployed/*`. `denied/` and -`failed/` are terminal; the manager submits a new commit + new -approval id to retry. Because tags are first-class git objects, -rejected and failed trees stay browsable forever — `git log ---tags` in the applied repo is the audit trail. - -### Dispatch via `rebuild_queue` (#441) - -Long-running approval work — `ApplyCommit`, `UpdateMetaInputs`, -`Spawn` — no longer runs inline inside `actions::approve`. Instead -the approval handler enqueues a `QueueEntry` into the global -`rebuild_queue`: - -| `ApprovalKind` | `QueueKind` queued | `QueueSource` | -|---|---|---| -| `ApplyCommit` | `Rebuild` | `Approval` | -| `UpdateMetaInputs` | `MetaUpdate` | `Approval` | -| `Spawn` | `Spawn` | `Approval` | -| `InitConfig` | — runs inline (sub-second git seed) | — | -| `SchedulePrompt` | — runs inline (single sqlite insert) | — | - -Each queue entry carries the originating `approval_id` so the -worker can re-fetch the approval row when it dispatches, run the -kind-specific pipeline (`run_approval_apply_commit` / -`run_approval_update_meta_inputs` / `run_approval_spawn`), and -fire the matching `HelperEvent::*` on completion via -`finish_approval`. - -Two visible consequences: - -- **Operator dashboard**: after clicking APPR0VE the work-in-progress - shows up on the *rebuild queue* card (`POST /api/state.rebuild_queue` - + live `rebuild_queue_changed` events), not on the approvals panel - (which already moved the row to "approved"). A long meta-update - cascade renders as a parent entry with one child per per-agent - rebuild — see `docs/web-ui.md` for the layout. -- **Cancellation**: the dashboard's *× cancel* button on a `Queued` - entry calls `POST /api/rebuild-queue/{id}/cancel`, which flips the - entry to `Cancelled` before the worker dispatches it. Returns - `{"cancelled": true}` on success, `{"cancelled": false}` if the - entry already left `Queued` (running / done / failed) — terminal - states can't be retroactively rewritten. - -`QueueSource::Approval` carries the `approval_id` so a tail-end -build failure surfaces back as a failed approval row, not just a -silent queue entry. `QueueSource::Manual` (dashboard ↻ R3BU1LD) -and `QueueSource::AutoUpdate` (boot-time sweep) use the same -queue but skip the approval row plumbing. - -### Forge mirror - -When the bundled `hive-forge` container is running — on by -default, `hyperhive.forge.enable` — hive-c0re mirrors every -agent's applied repo into a private `agent-configs` Forgejo -org. `forge::push_config()` pushes `applied/main` plus -every tag to `agent-configs/` after each ref mutation: -the spawn that seeds `deployed/0`, every `request_apply_commit` -(which plants `proposal/`), every approve / deny, and a -sweep at startup. Pushes are best-effort — a missing or stopped -forge never blocks a deploy. - -The org is private and agents are not members, so only the -`core` user (a Forgejo site admin) can read it: an agent can't -reach another agent's config — or even its own — through the -forge. The tokenised push URL is passed inline to `git push`, -never written into `applied//.git/config`; that repo is -RO-bind-mounted into the manager, and a stored token would leak -core's admin credential to an agent. - -The dashboard deep-links into this org — a `config repo` link -per container row and a `commit on forge` link per approval -card. See `docs/web-ui.md`. - -### Manager view of applied + meta - -The manager container gets three host-side bind mounts via -`set_nspawn_flags`: - -- `/var/lib/hyperhive/agents/` → `/agents/` (RW) — proposed - repos. Manager edits + commits per-agent config here. -- `/var/lib/hyperhive/applied/` → `/applied/` (RO) — every - agent's authoritative applied repo, including `.git`. -- `/var/lib/hyperhive/meta/` → `/meta/` (RO) — the swarm-wide - deploy flake. - -Each proposed repo (`/agents//config/`) is pre-configured -with `applied` as a git remote pointing at -`/applied//.git`. Useful incantations from inside the -manager: - -```sh -git -C /agents//config fetch applied -git -C /agents//config log applied/main --oneline -git -C /agents//config show applied/refs/tags/deployed/ -git -C /agents//config show applied/refs/tags/failed/ # body = build error -git -C /agents//config show applied/refs/tags/denied/ # body = operator note -git -C /agents//config rebase applied/main # base in-flight work on what's deployed - -git -C /meta log --oneline # swarm-wide deploy history -cat /meta/flake.lock | jq '.nodes | with_entries(select(.key | startswith("agent-")))' -``` - -The RO binds block push at the kernel level, so the manager -can only fetch / read — git plumbing inside the container -cannot corrupt either authoritative repo. - -## Migration from the pre-tag / pre-meta schemes - -Both overhauls (tag-driven flow + meta flake) ship in-place -migrations that run on every hive-c0re startup. Idempotent; -each phase is a no-op once already applied. Behaviour: - -- Tag-driven phase: assumes the operator ran the one-shot - `git tag deployed/0 main` script (see commit history / - earlier docs revisions) once per agent. Tagging is - non-destructive: it doesn't touch live containers, state - dirs, or claude creds. -- Meta-flake phase: rewrites each `applied//flake.nix` to - the module-only boilerplate, wires the `applied` remote in - each proposed repo, bootstraps the meta repo from the - current agent list, and `nixos-container update`s every - container at `meta#`. The expensive last step is - guarded by `/var/lib/hyperhive/.meta-migration-done` so - it only runs once across hive-c0re restarts. Set - `HIVE_SKIP_META_MIGRATION=1` on the service to defer. - -No state loss in either migration. claude creds, /state/ -notes, the events DB, proposed history, and applied history -all survive. The manager keeps its session; sub-agents stay -logged in. - -## Manager (`hm1nd`) is hive-c0re-managed - -The manager container runs through the **same lifecycle as -sub-agents**. On `hive-c0re serve` startup, if `hm1nd` is missing, -hive-c0re creates it. The manager's flake lives at -`/var/lib/hyperhive/applied/hm1nd/`; its proposed config at -`/var/lib/hyperhive/agents/hm1nd/config/`. Manager can edit its own -`agent.nix` (visible inside the container at `/agents/hm1nd/config/`) -and submit `request_apply_commit("hm1nd", )` for operator -approval. - -Differences from sub-agents: - -- `flake.nix` extends `hyperhive.nixosConfigurations.manager` - (vs `agent-base`). -- Container name is `hm1nd` (no `h-` prefix). -- Fixed web UI port (`MANAGER_PORT = 8000`). -- `set_nspawn_flags` adds two extra binds: `/var/lib/hyperhive/agents` - → `/agents` (RW) so the manager can edit per-agent proposed repos, - and `/var/lib/hyperhive/applied` → `/applied` (RO) so the manager - can `git fetch` deployed/failed/denied tags from any agent's - authoritative applied repo (see "Manager view of applied" below). -- First-deploy spawn bypasses the approval queue (manager is - required infrastructure). -- Per-agent socket lives at `/run/hyperhive/manager/`, owned by - `manager_server::start`. - -**Migration note** (for older hosts): drop any `containers.hm1nd = -{ ... }` block from your host NixOS config. hyperhive creates and -updates the manager itself. - -## Manager policy - -From `hive-ag3nt/prompts/manager.md`: the manager does NOT -rubber-stamp sub-agent config requests. It verifies (role match, -package legitimacy, cheaper alternative, blast radius) before -committing and calling `request_apply_commit`. - -For ambiguous cases or anything that needs human signal, the -manager calls `ask(question, options?, multi?, ttl_seconds?, to?)` — -queues the question and returns the id immediately. When `to` is -omitted (or `"operator"`) the question shows up on the dashboard; -when `to` is a sub-agent's name, the recipient receives a -`HelperEvent::QuestionAsked` and answers via their own `answer` -tool. Either way the answer arrives back as -`HelperEvent::QuestionAnswered { id, question, answer, answerer }` -in the asker's inbox. Storage is `hive-c0re::operator_questions` -(sqlite) — same table, with a nullable `target` column -(NULL = operator). Dispatch goes through -`hive-c0re/src/questions.rs::{handle_ask, handle_answer}` so both -the agent + manager surfaces stay aligned. The answer flow is: - -``` -POST /answer-question/{id} agent: Answer { id, answer } - → OperatorQuestions::answer(_, _, "operator") → questions::handle_answer - → notify_agent(asker, QuestionAnswered { → OperatorQuestions::answer(_, _, agent) - answerer: "operator", ... }) → notify_agent(asker, QuestionAnswered { - answerer: agent, ... }) -``` - -Two more paths resolve a pending question with a sentinel answer: - -- `POST /cancel-question/{id}` (✗ CANC3L button on the dashboard) - resolves with `[cancelled]`. The manager sees a terminal state - and can fall back. -- `ttl_seconds` deadline: a tokio watchdog spawned at submit time - fires `answer(id, "[expired]")` once the ttl runs out. Already- - resolved races no-op. The dashboard surfaces a `⏳ MM:SS` chip - on each pending question with a deadline. - -## Helper events to the manager - -`Coordinator::notify_manager(&HelperEvent)` enqueues an inbox -message from sender `system` with the event JSON in the body. The -manager harness no longer short-circuits these — they drive a -regular claude turn so the manager can react. Variants -(`hive_sh4re::HelperEvent`): - -- `ApprovalResolved { id, agent, commit_ref, status, note }` — - fired by `actions::approve` + `actions::deny` whenever an - approval transitions to its terminal state. -- `Spawned { agent, ok, note }` — `actions::approve` (first-time - ApplyCommit-kind) + admin `HostRequest::Spawn` (deprecated). -- `Rebuilt { agent, ok, note }` — `auto_update::rebuild_agent` - (covers startup scan + manual `/rebuild` from dashboard) + - `actions::approve` (ApplyCommit). -- `Killed { agent }` — admin `HostRequest::Kill` + dashboard - `/kill` + manager `Kill` MCP tool. -- `Destroyed { agent }` — `actions::destroy`. -- `ContainerCrash { agent, note }` — `crash_watch`: a previously- - running container went away with no operator-initiated transient - state (Stopping / Restarting / Destroying / Rebuilding) AND no - such transient was cleared in the last 30s (`RECENT_TRANSIENT_GRACE` - tombstone, three `POLL_INTERVAL`s — closes the race where a - lifecycle op finishes between two crash-watch polls and the - container shows briefly as "stopped without transient" before the - next start, #425). Manager can `start` it again or escalate. -- `NeedsLogin { agent }` — sub-agent has no claude session yet. - Manager can't act directly (interactive OAuth); typically flags - the operator. -- `LoggedIn { agent }` — sub-agent just completed login. Manager - often greets the agent on this event. -- `ConfigReady { agent }` — a new agent's proposed config repo was - just seeded (post-`InitConfig` approval). The manager can now - edit `/agents//config/agent.nix`, commit the changes, - and submit `request_apply_commit` with the commit sha to create - the container (first ApplyCommit also triggers spawn bookkeeping). -- `NeedsUpdate { agent }` — sub-agent's recorded flake rev is - stale. Manager calls `update(name)` to rebuild — idempotent, - no approval required. -- `QuestionAnswered { id, question, answer, answerer }` — - dashboard `/answer-question/{id}` (answerer = `"operator"`), - peer `Answer` request (answerer = agent name), or ttl watchdog - expiry (answerer = `"ttl-watchdog"`, answer = `"[expired]"`). -- `QuestionAsked { id, asker, question, options, multi }` — - fired when an agent calls `Ask { to: Some(), ... }`. - The recipient responds via `Answer { id, answer }` and the - asker sees the matching `QuestionAnswered`. - -To add a new event: new `HelperEvent` variant + call sites + update -`prompts/manager.md` so the manager knows the new shape. - -## Auto-update on startup - -`hive-c0re serve` runs `auto_update::run` in a background task right -after opening the coordinator. It enumerates managed containers and -rebuilds any whose recorded hyperhive rev differs from the current -one — sub-agents and manager go through the same `lifecycle::rebuild` -path. - -"Rev" = canonical filesystem path of `cfg.hyperhiveFlake`. Marker -file: `/var/lib/hyperhive/applied/..hyperhive-rev`. If the -flake input has no canonical path (e.g. a `github:` URL), -auto-update is a no-op — rebuild manually. - -The dashboard surfaces pending updates per agent: a clickable -"needs update ↻" badge appears whenever the marker differs from -current rev. The badge POSTs `/rebuild/`, calling the same -`auto_update::rebuild_agent` path so manual triggers and the -startup scan can't drift. When at least one container is stale, a -top-level `↻ UPD4TE 4LL` button appears that loops over every -stale container. diff --git a/docs/boundary.md b/docs/boundary.md deleted file mode 100644 index 95a07ebc..00000000 --- a/docs/boundary.md +++ /dev/null @@ -1,59 +0,0 @@ -# The operator/agent boundary - -Design rationale for hyperhive's two-principal trust model. The -*implementation* work — container network isolation, the unifying -gateway, core-daemon privsep — is tracked as `area:ops` issues on -the forge. - -Today "the operator surface" and "the agent surface" are a -*convention*, not a boundary — nothing stops a container from -curling the core daemon on `localhost:`, or another agent's -web UI. Network isolation, the gateway, and privsep together turn -that convention into an enforced boundary. - -## Two principals, two paths - -- **Operator** — reaches every UI (the dashboard + every - per-agent page) through the gateway, on one origin. - Operator-authority actions (approve / deny, answer-as-operator, - lifecycle POSTs) are served by the core daemon and only - reachable via the gateway. -- **Agent** — speaks only for itself, only over its per-agent - unix socket. The socket's identity *is* the agent (see - `docs/conventions.md`, "identity = socket"). An agent must not - be able to reach the core daemon's HTTP surface, another - agent's socket, or another agent's web UI. - -## Design rule - -**Operator-authority actions never get a per-agent-socket entry -point.** They live on the core backend. - -Worked example — answering an operator-targeted question is a -`POST /answer-question/{id}` on the core dashboard, *never* an -`AgentRequest` variant. If it were a per-agent-socket request, an -agent could `curl` its own socket and spoof an operator answer. -The per-agent web UI POSTs cross-origin to the core for these -(see the inline-answer feature — the loose-ends section on each -agent page). - -## Why network isolation is the load-bearing step - -Containers currently share the host network namespace, so a -container can reach `localhost:`, the dashboard, and -every other agent's web port. Until that changes, the -operator/agent split is on the honour system — every boundary -claim above is aspirational. Network isolation is what makes the -boundary *real*; the gateway and privsep are ergonomics and -defence-in-depth layered on top. - -Suggested sequencing of the `area:ops` issues: - -1. **Gateway** first — pure ergonomics win, unblocks same-origin - (lets the cross-origin CORS shim on `/answer-question/{id}` go - away), no behavioural risk. -2. **Network isolation** next — the step that makes the boundary - real. Everything before it is honour-system. -3. **Privsep** last — defence in depth on the core process - itself; valuable independent of the other two, but the - biggest refactor. diff --git a/docs/conventions.md b/docs/conventions.md deleted file mode 100644 index f61a56ae..00000000 --- a/docs/conventions.md +++ /dev/null @@ -1,79 +0,0 @@ -# Conventions - -Code-style and process expectations across the workspace. Most of these -exist because something already went wrong without them. - -## Naming - -- Containers are length-bounded by `nixos-container` (≤ 11 chars). -- Sub-agents are `h-` with `` ≤ 9 chars. -- The manager is `hm1nd` (no `h-` prefix, fixed name). -- `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap. -- Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE` - (8100..8999); manager fixed at 8000; dashboard `cfg.dashboardPort` - (default 7000). - -## Identity = socket - -There are no auth tokens on the per-agent unix sockets. The socket -*path* identifies the principal; perms come from "who has the -bind-mount." A sub-agent only sees its own `/run/hive/mcp.sock`; the -manager has access to its privileged socket; hive-c0re owns the host -admin socket. - -## Wire protocol - -JSON line-delimited over unix sockets in both directions (host admin -/ manager / agent). SSE streams (`/dashboard/stream` on hive-c0re, -`/events/stream` on the per-agent web UIs) are `text/event-stream`; -each frame carries a `seq` field for the snapshot-dedupe dance -(see `docs/web-ui.md`). Request/response types live in `hive-sh4re` -— change them in one place. The dashboard event vocabulary lives -in `hive-c0re::dashboard_events::DashboardEvent`. - -## Async forms - -Dashboard + per-agent mutating forms carry `data-async`; a delegated -`submit` listener in `assets/tabs.js` intercepts, shows a spinner, -POSTs `application/x-www-form-urlencoded` (axum's `Form` extractor -rejects multipart), calls `refreshState()` on success. New mutating -forms should add `data-async` and optionally `data-confirm` (for a -JS-side `confirm()` prompt) or `data-prompt="…"` (for a -`window.prompt()` whose answer goes into a hidden input named by -`data-prompt-field`, default `note`). - -`refreshState` defers automatically when `document.activeElement` -sits inside a managed section so the operator's typing isn't lost; -collapsible `
` survive the re-render -via `snapshotOpenDetails` / `restoreOpenDetails`. - -## `rebuild` is the reconcile verb - -`lifecycle::rebuild` idempotently rewrites -`/etc/nixos-containers/.conf` (`PRIVATE_NETWORK=0`, clears -`HOST_ADDRESS` / `LOCAL_ADDRESS`, sets `EXTRA_NSPAWN_FLAGS`), -regenerates `applied//flake.nix`, writes the systemd limits -drop-in, then `nixos-container update` + stop + start. - -Anything that changes per-container state on the host should be -re-applied here so a manual `↻ R3BU1LD` from the dashboard is -sufficient to recover. - -## Actions are factored - -`approve` / `deny` / `destroy` (and the lifecycle helper) live in -`actions.rs` / `dashboard.rs`. The admin socket and the dashboard -POST handlers both call into them so the two surfaces never drift. - -## Commit messages - -Short, lowercase, no `Co-Authored-By` trailer. Imperative mood, no -period. Body explains *why* if non-obvious; otherwise the subject -alone is fine. Wrap at ~72 cols. - -## Commit before test - -Stage and commit when work *looks* ready, then run validation -(`cargo check`, `nix flake check`, real deploy). Failures get a -follow-up commit rather than an amend. The commit history is the -work log; rewriting it loses signal. diff --git a/docs/damocles-migration.md b/docs/damocles-migration.md deleted file mode 100644 index 3dd649d7..00000000 --- a/docs/damocles-migration.md +++ /dev/null @@ -1,73 +0,0 @@ -# Migrating damocles onto hyperhive - -The plan calls out damocles → hyperhive as a future migration. This doc lays -out the options + recommended path. Not yet executed. - -## Current state (separate from hyperhive) - -`damocles` is a declarative nixos-container on `muede-lpt2`: - -- Declared in `nixosConfigurations/muede-lpt2/containers.nix` -- Built from `nixosConfigurations/damocles/` (claude-container.nix + extras) -- Bind-mounts (RO unless noted): - - `/etc/nix/distributed-build-key` - - `/persist/damocles-ssh` - - `/persist/damocles-lab` (RW — persistent work dir) -- `privateNetwork = false` -- Has its own systemd-services override (`TimeoutStopSec = 60s`, `RestartSec = 5s`) -- Hosts the user's primary day-job Claude Code session, not part of the swarm - -## Options - -### A. Sub-agent under hive-c0re -Make damocles a `hive-agent-damocles` (or whatever short name fits the 9-char cap). -hive-c0re owns its lifecycle; its config flake is the manager-editable -`/var/lib/hyperhive/agents/damocles/config/`. - -Pros: uniform — message broker, dashboard, approval flow apply to damocles too. -Cons: a lot of damocles-specific state (bind-mounts, ssh keys, build keys) has to -be modeled as per-agent config. Today's `agent.nix` schema doesn't support -declaring bind-mounts; would need to extend. And the user's day-job session -becoming subject to hive-c0re lifecycle (restarts on rebuild) is invasive. - -### B. Peer container (broker-integrated, lifecycle-independent) -Keep damocles declarative; have its harness install `hive-ag3nt` and connect to -the broker via a bind-mounted socket. damocles can send/recv messages with -other sub-agents but is not managed by hive-c0re. - -Pros: low blast radius. damocles keeps its bind-mounts + its own restart policy. -Manager can route messages to it (it's just another inbox key on the broker). -Cons: two lifecycle mechanisms coexist forever; "damocles" doesn't appear in the -dashboard's container list (it filters `hive-` and `hm1nd`). - -### C. Don't migrate -damocles stays out of hyperhive. The two systems coexist; the user's day-job -Claude and the swarm are deliberately separate. - -Pros: zero work; aligns with the actual usage pattern (day-job vs. experiment). -Cons: no message routing between damocles and the swarm. - -## Recommended - -**C for now, B once cross-pollination is wanted.** Hyperhive's invariants -(11-char container names, manager-driven lifecycle, sealed `applied/` config) -fit poorly with damocles's role as the user's working Claude. Wait until there's -a concrete reason to wire them together (e.g. "I want to ask hm1nd from inside -damocles") and then do B — extend the broker socket bind into damocles and -install `hive-ag3nt` there. No need to subsume damocles under hive-c0re. - -If/when option B is taken: - -1. Add `${hyperhive.packages.${system}.default}/bin/hive-ag3nt` (or just - `pkgs.hyperhive`) to `nixosConfigurations/damocles/claude-container.nix`. -2. Bind-mount `/run/hyperhive/agents/damocles/` into damocles at `/run/hive/`. - On the host, this is just another dir hive-c0re needs to know about — - maybe expose a "peer agents" registration mechanism in the broker. -3. Run `hive-ag3nt serve` as a systemd unit inside damocles (separate from - the user's interactive claude session — broker peer, not turn-loop). -4. Optionally: add a `hive-c0re register-peer damocles` admin verb so the - container appears in `list()` and the dashboard. (Or just hard-list it.) - -A is on the table only if the user's workflow shifts toward "the swarm IS the -day-job environment" — at which point damocles dissolves into a `hive-agent-*` -naturally. diff --git a/docs/gotchas.md b/docs/gotchas.md deleted file mode 100644 index b85376d1..00000000 --- a/docs/gotchas.md +++ /dev/null @@ -1,149 +0,0 @@ -# Gotchas - -NixOS + nspawn quirks and lessons we hit the hard way. If something -here looks unmotivated in the code, there's usually a story underneath. - -## `nixos-container` doesn't expose `--bind` on the CLI - -The CLI doesn't accept `--bind`. Path is via `EXTRA_NSPAWN_FLAGS` in -`/etc/nixos-containers/.conf` — the start script -(`/nix/store/.../container_-start`) expands it unquoted into the -`systemd-nspawn` invocation. `lifecycle::set_nspawn_flags()` rewrites -this line. - -## `/run/systemd/nspawn/*.nspawn` overrides are ignored - -`nixos-container`'s start script builds the nspawn command line -directly. Dropping a `.nspawn` file under `/run/systemd/nspawn/` -looks like the obvious extension point and does nothing. Use -`EXTRA_NSPAWN_FLAGS` (above). - -## `boot.isNspawnContainer = true` - -Not `boot.isContainer = true`. Renamed in nixos-25.11+. - -## `nixos-container create` auto-assigns `HOST_ADDRESS` / `LOCAL_ADDRESS` - -…in the `.conf`. The start script's `if HOST_ADDRESS set → ---network-veth` branch then forces a private netns — silently fatal -for our web UIs (the bind is invisible from the host). We -force-clear `HOST_ADDRESS` / `LOCAL_ADDRESS` / `HOST_ADDRESS6` / -`LOCAL_ADDRESS6` / `HOST_BRIDGE` and set `PRIVATE_NETWORK=0`. - -## systemd service PATH ≠ host PATH - -The hive-c0re service sets `path = [ pkgs.git "/run/current-system/sw" ]`. -In-container harness services do the same so anything an agent adds -to its own `agent.nix` (`environment.systemPackages`) is visible to -claude's Bash tool without editing the service definition. -`environment.HYPERHIVE_GIT` bakes git's absolute path in (read by -`lifecycle::git_command()`) for the host. - -## `RuntimeDirectoryPreserve = "yes"` - -…keeps `/run/hyperhive/` (and the per-agent sub-dirs) across -hive-c0re restarts. Without it, every restart wipes bind sources and -existing containers can't be started. - -## `register_agent` is idempotent - -Drops any prior socket task before rebinding. Required so a -hive-c0re restart followed by `rebuild alice` recreates the agent's -socket without needing a clean reinstall. - -## `claude-code` is unfree - -The flake pins it to **nixpkgs-unstable** via -`overlays.claude-unstable` (stable lags too far). The overlay sets -`config.allowUnfreePredicate` on its unstable import to whitelist -`claude-code` specifically — scoped, only this one package. -`harness-base.nix` does the same at the container level because -each per-agent `nixosConfiguration` evaluates its own nixpkgs -instance and the operator's host-level `allowUnfree` does **not** -propagate in. Operators don't need to set anything on their side. - -## Claude credentials are per-agent - -`/var/lib/hyperhive/agents//claude/` bind-mounts to -`/root/.claude` (RW). Sharing one dir across agents is NOT viable — -OAuth refresh tokens rotate, so any sibling refresh invalidates all -the others. Login flow runs from the per-agent web UI; creds persist -across `destroy`/recreate (`--purge` wipes them). - -## Persistent notes dir per agent - -`/var/lib/hyperhive/agents//state/` bind-mounts to `/state` -(RW). System prompts tell agents to keep durable knowledge here -(`/state/notes.md`, anything else under `/state/`). The harness also -writes its events log here (`/state/hyperhive-events.sqlite`). -Survives `destroy`/recreate alongside the claude dir. - -## Web UI ports collide on hash - -Sub-agent web UI ports are deterministic FNV-1a of the agent name -modulo 900 (range 8100..8999). With ~30 agents the birthday-paradox -collision rate gets meaningful; at 2–3 agents you can still get -unlucky. Operator resolves a collision by renaming the offending -agent (different hash → different port) and rebuilding. No state -file, no probing, no port-allocation drift — the value is -reproducible from just the name. Manager is fixed at 8000; -dashboard at `cfg.dashboardPort` (default 7000). - -## Restart races on TCP bind - -Both the dashboard and per-agent web UI use `tokio::net::TcpSocket` -with `SO_REUSEADDR` plus a retry-on-`AddrInUse` loop (12 tries, -exponential backoff capped at 2s, ~22s total). REUSEADDR handles -the `TIME_WAIT` case from a clean previous exit; retry covers the -genuine "previous process is still alive during a systemd restart -overlap" case. REUSEADDR does **not** allow two simultaneous -`LISTEN` sockets on the same port (that would be `SO_REUSEPORT`, -which we don't use) — exclusivity is preserved. - -## Orphan approvals - -If state dirs are wiped out from under a pending approval (test -scripts, manual `rm -rf`), the dashboard's next render marks them -`failed` with note `"agent state dir missing"` so they fall out of -`pending`. They stay in sqlite for audit. - -## Nix store `cp -r` preserves read-only bits - -Copying a nix store path with `cp -r src/. $out/` inside a -`pkgs.runCommand` derivation preserves the read-only permissions of -store files. Any subsequent write into the copied tree (adding new -files in subdirectories) fails with `EPERM`. Fix: pass -`--no-preserve=mode,ownership` so the output tree is writable. - -## `hive-forge`: prefer over raw curl pipelines - -Every agent container has `hive-forge` in PATH (installed via -`harness-base.nix`; lives in `/hive-forge` as a proper Rust binary -since #280). Use it instead of ad-hoc curl pipelines: - -```bash -hive-forge view 42 # title + body + comments -hive-forge comments 42 # list all comments (human-readable) -hive-forge --json comments 42 # same as above, JSON array (global flag, closes #421) -hive-forge comment 42 --body "..." # post comment (inline body) -hive-forge comment 42 --body-file - < --help` prints the full signature for any verb. -Credentials come from `$HYPERHIVE_STATE_DIR/forge-token`; default -repo from `$HIVE_FORGE_REPO`, overridden per-invocation by the -global `-r/--repo` flag. diff --git a/docs/persistence.md b/docs/persistence.md deleted file mode 100644 index 8acd29f3..00000000 --- a/docs/persistence.md +++ /dev/null @@ -1,167 +0,0 @@ -# Persistence + retention - -Where state lives, what survives what, and how it's bounded. - -## Two sqlite databases - -### `/var/lib/hyperhive/broker.sqlite` (host) - -Three tables, all in one file: - -- `messages` — every inter-agent / operator-bound message. - `sender / recipient / body / sent_at / delivered_at / acked_at / - in_reply_to`. `in_reply_to` links a reply to its parent row id; - the dashboard and per-agent inbox render these as threaded rows. -- `approvals` — the queue. `agent / kind (apply_commit | spawn) / - commit_ref / requested_at / status / resolved_at / note`. -- `operator_questions` — `ask` / `answer` queue (despite the - file name, stores both operator-targeted + agent-to-agent - questions since the `ask` rename). - `asker / question / options_json / multi / asked_at / - deadline_at (ttl) / answered_at / answer / target`. `target IS - NULL` = operator path (dashboard); `target = ''` = peer - Q&A (`HelperEvent::QuestionAsked` pushed into target's inbox, - answered via `Answer` request). Migrated via `ALTER TABLE ADD - COLUMN` against `pragma_table_info`. - -Retention: - -- `Broker::vacuum_delivered` runs hourly via a tokio task in - `hive-c0re::main`. Drops delivered rows older than 30 days. - Undelivered rows are always kept (still in flight). -- Approvals and questions are kept indefinitely — both are - audit trails. `actions::destroy` and answered questions stay - visible to anything that queries by id. - -### `/state/hyperhive-events.sqlite` (per agent) - -Lives inside each container's bind-mounted `/state/` dir (host -path: `/var/lib/hyperhive/agents//state/hyperhive-events.sqlite`). -One table: - -- `events(id, ts, kind, payload_json)` — every `LiveEvent` the - harness emits during turn loop execution. - -The harness writes; the host vacuums. `hive-c0re::events_vacuum` -runs hourly and sweeps every existing agent state dir, deleting -rows older than 7 days. Age-only — no row cap — so a chatty turn -doesn't lose history sooner than a quiet one; disk pressure on a -sustained burst is the cheaper problem to have. Centralising -retention on the host means a misbehaving harness can't disable -its own vacuum and agents don't need any cleanup wiring of their -own. - -Path overridable via `HYPERHIVE_EVENTS_DB` (for dev / no-`/state` -setups). On open failure the `Bus` falls back to no-store mode -rather than crashing the harness — events still broadcast over SSE, -just nothing persisted. - -### `/state/hyperhive-turn-stats.sqlite` (per agent) - -Per-turn analytics sink. One row per claude turn captures -identity (`model`, `wake_from`, `result_kind`), timing -(`started_at`, `ended_at`, `duration_ms`), cost (input / output / -cache_read / cache_creation token counts), behaviour -(`tool_call_count` + `tool_call_breakdown_json`), and post-turn -snapshot metrics (`open_threads_count`, -`open_reminders_count` — fetched via the same socket the harness -already uses for `GetOpenThreads` + `CountPendingReminders`). -Bin-loop helpers `build_row` + `record` land each row at -`turn_end`; writes are best-effort, a sqlite hiccup logs + lets -the turn loop continue. - -No host-side vacuum yet — tracked as forge issue -[#10](http://localhost:3000/hyperhive/hyperhive/issues/10) -(target retention ~90 days, age-only sweep like events_vacuum). - -### `/state/hyperhive-rate-limited` (per agent) - -Sentinel file written by `Bus::emit_status("rate_limited")` when the -harness detects a 429 / rate-limit response from the Claude API, and -removed when the retry sleep expires (any subsequent status emit -clears it). The file's presence is checked by hive-c0re's -`container_view::is_rate_limited` on each `build_all` sweep (~10s) to -populate `ContainerView.rate_limited` for the dashboard. Survives a -harness restart (the Bus reads it back at boot and restores the flag), -so the badge remains accurate if hive-c0re restarts while the harness -is mid-sleep. - -### `/state/hyperhive-model` (per agent) - -Single-line text file holding the claude model name currently -selected for this agent (default `haiku` when absent). Written by -`Bus::set_model` whenever the operator flips it via `/model -` in the web terminal. Read once at harness boot in -`Bus::new`. Path overridable via `HYPERHIVE_MODEL_FILE`. -Survives destroy/recreate, gone on `--purge`. - -## State dirs (per agent) - -Under `/var/lib/hyperhive/agents//`: - -- `config/` — the proposed nix repo (manager-editable). Bind-mounted - **read-only** to `/agents//config` inside the sub-agent's own - container so the agent can inspect what defines it and request - precise changes from the manager; RW into the manager via the - `/agents` tree bind. -- `claude/` — claude OAuth credentials, bind-mounted RW to - `/root/.claude` inside the container. -- `state/` — durable notes, the events.sqlite db, and the - turn-stats sqlite db. Bind-mounted to `/agents//state` - inside the container (the manager still uses the legacy - `/state` mount point — same host path either way). - -Under `/var/lib/hyperhive/applied//` — the hive-c0re-only -applied repo. Tracks `flake.nix` (module-only boilerplate; never -edited after first spawn) + `agent.nix` (the actual config; the -manager's edits land here via the approval flow) + any other -files the manager committed. `.git/` carries the proposal / -approved / building / deployed / failed / denied tag history. - -Under `/var/lib/hyperhive/meta/` — the swarm-wide deploy flake. -Single repo for the whole host; `flake.nix` declares one input -per agent + one `nixosConfigurations.` output per agent; -`flake.lock` is the canonical "what's deployed where." The git -log is the deploy audit trail (one commit per successful -deploy or hyperhive bump). Manager has this RO-mounted at -`/meta/`. - -Marker file `/var/lib/hyperhive/.meta-migration-done` is -written by the startup migration after every container has -been repointed at `meta#`. Removing it forces a re-run on -next hive-c0re start (idempotent — only the actual repoint -step would re-fire). - -## Destroy vs purge - -- `DESTR0Y` (default) — stops + removes the nspawn container, - drops the systemd drop-in, fails any pending approvals. State - dirs stay put; the agent appears in the dashboard's K3PT ST4T3 - section as a tombstone with `⊕ R3V1V3` and `PURG3` actions. - `R3V1V3` queues a Spawn approval that reuses the kept state on - approve (no re-login). -- `PURG3` (opt-in via the dashboard button or - `hive-c0re destroy --purge `) — DESTR0Y plus wipes - `/var/lib/hyperhive/{agents,applied}//`. Config history, - claude creds, /state/ notes, and the events db are all gone. - No undo. - -The manager is non-destroyable from both paths (declarative -container; would fight with the host's NixOS config). - -## Run-time dirs - -`/run/hyperhive/` is tmpfs-backed (systemd `RuntimeDirectory=`) but -preserved across hive-c0re restarts via `RuntimeDirectoryPreserve=yes`. -Without that, every restart wipes bind sources and existing -containers can't be started. - -- `/run/hyperhive/host.sock` — admin socket (host-side CLI). -- `/run/hyperhive/manager/mcp.sock` — manager-privileged socket. -- `/run/hyperhive/agents//mcp.sock` — per-sub-agent socket - (bind-mounted into the container as `/run/hive/mcp.sock`). - -On startup, `Coordinator::register_agent` drops any prior socket -task before rebinding — idempotent so a hive-c0re restart followed -by `rebuild alice` recreates the agent's socket without a clean -reinstall. diff --git a/docs/security.md b/docs/security.md deleted file mode 100644 index 32c6951c..00000000 --- a/docs/security.md +++ /dev/null @@ -1,33 +0,0 @@ -# Security model - -## Nix builds and credential isolation (issue #240) - -### Background - -Agent containers bind-mount the host's `nix-daemon` socket. The host daemon may -have `sandbox-fallback = false` (strict NixOS defaults), which causes `nix build` -inside nspawn containers to fail — containers lack kernel user namespaces, so nix -cannot set up its build sandbox. `harness-base.nix` sets `sandbox-fallback = true` -so that builds fall back to unsandboxed execution rather than failing outright. - -### Threat model - -Unsandboxed nix builds run as `nixbld` users (non-root, typically UIDs 30001-30010). -Without sandbox isolation, a build derivation's builder script has read access to -any file in the container that the nixbld user can read. - -**What is NOT exposed**: - -- `/root/.claude/` — mode `0700`, owned by root. nixbld users cannot read it. -- `/state/forge-token` — written at mode `0600` by `hive-c0re/src/forge.rs`. - nixbld users cannot read it. - -**Policy**: all credential files written to agent state directories MUST be mode -`0600` or stricter. Do not create world-readable secret files in agent state dirs. - -### Long-term fix - -The proper fix is to enable user namespaces inside nspawn containers -(`--private-users=inherit` in `EXTRA_NSPAWN_FLAGS`) so nix can set up its real -sandbox and `sandbox-fallback` becomes a true last resort. This requires verifying -bind-mount compatibility with user namespace UID mapping and is tracked as a TODO. diff --git a/docs/terminal-rendering.md b/docs/terminal-rendering.md deleted file mode 100644 index fce8d21a..00000000 --- a/docs/terminal-rendering.md +++ /dev/null @@ -1,114 +0,0 @@ -# Per-agent terminal: row taxonomy (as built) - -Snapshot of how the per-agent web UI's live pane renders each -event kind today. Source of truth lives in -`frontend/packages/agent/src/app.js` (`renderStream`, `fmtToolUse`, -`renderRichToolUse`, `renderToolResult`, `renderTaskEvent`, -`mdNode`, `detailsOpenMd`, `fmtArgsGeneric`) + -`frontend/packages/shared/src/terminal.css` (the shared -`.live .` styling) + the `marked` npm package (markdown). - -## Layout contract - -Every row — flat `
` and expandable -`
` alike — shares one prefix column. -The mechanism is `padding-left + negative text-indent` on -`.live .row`: the row's first character (the prefix glyph) -gets pulled back into the column at ~0.5em, and wrapped -continuation lines hang under the body, not under the glyph. - -`
` summaries inherit those metrics. The disclosure -marker (`▸` / `▾`) is supplied by CSS `summary::before` so it -lands in the same column as flat-row glyphs. To make that -work the JS-side summary text **does not** include a -directional `→` / `←` — the row's colour (cyan = outbound, -muted = inbound) carries the direction, and the prefix -column never has to fit two glyphs side-by-side. - -Child blocks inside a row (the `.md` markdown wrapper, an -inner `
`) get `text-indent: 0` so their content -lays out from the body column instead of inheriting the -parent's negative pull. - -## Row taxonomy - -| CSS class | Prefix glyph | Color | Triggered by | Source | -|---|---|---|---|---| -| `.turn-start` | `◆ TURN ← ` | amber, left rule | `LiveEvent::TurnStart` | harness wake | -| `.turn-body` | (child div under turn-start) | fg | same | the wake-prompt body | -| `.turn-end-ok` | `✓ turn ok` | green, left rule | `LiveEvent::TurnEnd { ok: true }` | harness | -| `.turn-end-fail` | `✗ turn fail — note` | red, left rule | `LiveEvent::TurnEnd { ok: false }` | harness | -| `.text` | (no prefix; markdown body) | fg | claude `assistant.content[].text` | stream-json | -| `.thinking` | `· thinking …` | muted, italic | claude `assistant.content[].thinking` | stream-json | -| `.tool-use` (flat) | `→ Name args…` | cyan | tool_use w/o rich renderer | stream-json | -| `.tool-use` `
` | `Write/Edit · +N` (no `→`) | cyan, body is +/- diff | `renderRichToolUse` Write/Edit | stream-json | -| `.tool-use` `
` | `send → to · NL`, `ask → to`, `answer #id` | cyan, body is markdown | rich renderer for send / ask / answer | stream-json | -| `.tool-result` (flat) | `← ` | muted | short `tool_result` (≤120c, non-recv) | stream-json | -| `.tool-result-block` `
` | `Nl · headline` | muted, body is text | long generic `tool_result` | stream-json | -| `.tool-result-block` `
` | `recv ← ` | muted, body is markdown | `tool_result` correlated to a prior `recv` tool_use via id | stream-json | -| `.tool-use` | `⌁ task started · [type]` | cyan | claude Task-tool subagent start | `renderTaskEvent` | -| `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task ✓/✗/◌ · · → ` | green / red / muted | claude Task-tool result | `renderTaskEvent` | -| `.note` | `· ` | muted | harness chatter | `LiveEvent::Note` | -| `.note.stderr` | `! stderr: ` | amber/orange | stderr lines off claude | `LiveEvent::Note` (`text` starts `stderr:`) | -| `.note.op` | `· operator: ` | mauve italic | operator-initiated notes (/cancel, /compact, /model, new-session) | `LiveEvent::Note` (`text` starts `operator:`) | -| `.sys` | `! {json…}` | amber/orange | catch-all for stream shapes `renderStream` didn't classify | catch-all | -| Banner shimmer | mauve | turn in flight (ref-counted) | — | `setBannerActive` | - -## Renderer dispatch - -`renderStream(v, api)` walks each stream-json line: - -1. Drops `system/init`, `rate_limit_event`, `result` (noise / - handled elsewhere — `result` powers the `cost` badge). -2. `subtype == "task_started" | "task_notification"` → - `renderTaskEvent` (subagent activity gets the `⌁` glyph). -3. `type == "assistant"` → walk `message.content[]`: - - `text` → `.text` row with a markdown body via `mdNode`. - - `thinking` → `.thinking` row. - - `tool_use` → record `id → name` in `toolNameById`, try - `renderRichToolUse` (Write/Edit/send/ask/answer get - custom renderings); on miss fall through to a flat - `.tool-use` row with `fmtToolUse → fmtArgsGeneric`. - `fmtToolUse` surfaces the salient arg per built-in tool — - e.g. `recv` shows `wait s` / `max ` when set (bare - `recv()` otherwise), `Bash` flags `[bg]` for - `run_in_background` commands. -4. `type == "user"` → walk `message.content[]` for - `tool_result`; `renderToolResult` correlates via - `tool_use_id → toolNameById` to default-open `recv` - results with a markdown body, else short = flat / - long = collapsed details. -5. Unrecognised shape → `.sys` row (amber, `!` glyph). - -## Markdown - -`mdNode(text)` wraps `marked.parse(text)` (the `marked` v4.x npm -dep, bundled by esbuild into the page's `app.js`) in a `
`. CSS in `terminal.css` scopes paragraph / code / -list / blockquote / link styling under `.live .row .md` so -the markdown body doesn't bleed into the row's own -text-indent. Falls back to plain text if `marked` didn't -load. Applied to `text` rows and to send / ask / answer / -recv message bodies. - -## Extra-MCP tools - -`fmtArgsGeneric(name, input)` is the fallback when a tool -isn't in the built-in `fmtToolUse` switch: - -- single string field → `name k: "v"` -- single number/bool field → `name k: v` -- multi-field → first 4 pairs trimmed to `k: "v"` / - `k: [N]` / `k: {…}` with a `…+N` overflow - -This keeps `mcp__matrix__send_message` and similar from -dumping raw JSON. - -## Dashboard side (not covered here) - -The main dashboard's message-flow pane is a different -shape: broker messages render as `.msgrow` grid lines (ts / -arrow / from / → / to / body) with their own styling. -`.live .msgrow` explicitly resets `text-indent: 0` so the -per-agent terminal's hanging-indent metrics don't leak into -the flex-grid broker rows. diff --git a/docs/turn-loop.md b/docs/turn-loop.md deleted file mode 100644 index 339137ca..00000000 --- a/docs/turn-loop.md +++ /dev/null @@ -1,423 +0,0 @@ -# Turn loop + MCP - -How the harness wakes up, what it asks claude to do, and what tools -claude has access to in return. - -## The loop - -Each agent harness (`hive-ag3nt serve` or `hive-m1nd serve`) runs: - -1. Long-poll `Recv` on its socket. The host-side broker - (`broker.rs::recv_blocking_batch`) returns immediately if there's - a pending message, otherwise waits up to 30 s for a broker `Sent` - event for this recipient. -2. Pop one message. Peek the remaining inbox depth with `Status`. -3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE - bus. -4. Spawn claude (one process per turn) and pipe the wake prompt - over stdin. -5. Stream stdout (JSON lines) into the bus as - `LiveEvent::Stream(value)`. Pump stderr as `Note`. -6. Wait for claude to exit. Compaction is two-pronged — *reactive* - on `Prompt is too long` and *proactive* on a context watermark - (see [Compaction](#compaction) below). **Rate-limit detection**: - on stderr the harness does a raw-line match for `429` / - `rate_limit` markers; on stdout it only fires on parsed - `{"type":"error"}` JSON events (avoiding false positives when - agents discuss `rate_limit_error` in conversation text). On - detection the harness sets the `rate_limited` sentinel - (`Bus::emit_status("rate_limited")`), sleeps - `HIVE_RATE_LIMIT_SLEEP_SECS` (default 300), then retries. - The dashboard and per-agent page show a `⊘ rate limited` badge - while the harness is parked. **Auth-failed detection** (closes - #419): both stdout and stderr pumps also match - `AUTH_FAIL_MARKERS` (`"authentication_failed"`, `401`, etc.). - On match the harness writes `{state_dir}/hyperhive-needs-login`, - emits `needs_login_idle` status, requeues the inflight message - (so it replays after re-auth), and parks in `wait_for_login` — - the same path used at boot. The operator re-authenticates via - the per-agent web UI login flow; on success the sentinel is - cleared and the queued message drives the next turn normally. -7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid - tight loops on transient failures. - -## The claude invocation - -``` -claude --print --verbose --output-format stream-json --model \ - --continue --settings /run/hive/claude-settings.json \ - --system-prompt-file /run/hive/claude-system-prompt.md \ - --mcp-config /run/hive/claude-mcp-config.json --strict-mcp-config \ - --tools --allowedTools -# wake prompt piped over stdin -``` - -`` is read from `Bus::model()` on each turn. The initial -default is set by `hyperhive.model` in the agent's `agent.nix` -(NixOS option; propagates via `HIVE_DEFAULT_MODEL` env var; falls -back to `"haiku"` if unset). The operator can flip it at runtime -with `/model ` in the web terminal — the next turn picks it -up. The choice is persisted to `/state/hyperhive-model` so it -survives restart; override path: `HYPERHIVE_MODEL_FILE` env var -for tests. - -Context-window size is looked up per-model via -`events::context_window_tokens(model)`. Resolution order (first -match wins): - -1. `HIVE_CONTEXT_WINDOW_TOKENS_` env var, where `KEY` - (lowercased) is a substring of the active model name. Injected - by the meta flake from `services.hive-c0re.contextWindowTokens` - (host-level NixOS option, defaults: haiku=200k, sonnet=1M, - opus=1M). Override these for all agents at once without a - per-agent config change. -2. `HIVE_CONTEXT_WINDOW_TOKENS` — single global override for any - model (useful in dev / test). -3. Hard fallback: `200_000` (conservative; only reached outside - NixOS where the env vars aren't set). - -The effective window drives watermarks and is exposed at runtime -via `/api/state.context_window_tokens` so the UI can show a -percentage-of-window ctx badge. - -`--continue` keeps a persistent session per agent (claude stores -sessions in `~/.claude/projects/`, which is bind-mounted -persistently). Auto-compact and auto-memory are disabled via -`--settings` because hyperhive owns compaction — see -[Compaction](#compaction) below. -A one-shot `--continue` suppression is available via -`POST /api/new-session` (or `/new-session` slash command in the -per-agent terminal) — `Bus::take_skip_continue()` flips an -`AtomicBool` once per turn, the next claude invocation drops -`--continue`, every subsequent turn resumes normal behaviour. - -### Compaction - -claude's own in-session auto-compact is off (`--settings`); hyperhive -owns it explicitly in `turn::drive_turn`. There are two triggers: - -- **Reactive** — claude-code prints `Prompt is too long` (the - `PROMPT_TOO_LONG_MARKER`). The session is *already* past the context - window, so no turn can run on it — `drive_turn` runs `/compact` - straight away and retries the same wake-up prompt once. No - notes-checkpoint turn is possible here: the detail is gone. -- **Proactive** — a turn finishes cleanly but the last inference's - context size (`Bus::last_ctx_usage().context_tokens()`) is at or - above a watermark. While the session is still healthy, `drive_turn` - injects one synthetic *notes-checkpoint* turn (`CHECKPOINT_PROMPT` - — "context is filling up, flush durable state into `/state` now") - and *then* runs `/compact`. This gives the agent a chance to - persist in-flight task state, decisions, and file paths before the - conversation detail collapses into a summary. - -The compact watermark defaults to **75% of `context_window_tokens(model)`** -(dynamically derived — 150k for haiku, 750k for sonnet/opus). Override -with `HIVE_COMPACT_WATERMARK_TOKENS` (absolute token count); set to `0` -to disable proactive compaction entirely (the reactive path always -applies). The proactive path is best-effort — a failed checkpoint turn -or `/compact` is surfaced as a `Note` but never fails the turn that -already succeeded. The operator can also force a compaction any time -via `/api/compact`. - -- **Auto session-reset** — a third path that fires when both - conditions hold: context is ≥ a watermark (`HIVE_AUTO_RESET_WATERMARK_TOKENS`, - default **50% of `context_window_tokens(model)`**) AND the time since - the last turn exceeds the assumed prompt-cache TTL - (`HIVE_CACHE_TTL_SECS`, default `3600`). - Claude's prompt cache lives ~5 minutes; if the cache is already - cold, resuming with `--continue` pays the full re-upload cost of - the current context with no benefit over starting fresh. So: - `drive_turn` injects one `AUTO_RESET_CHECKPOINT_PROMPT` notes turn - ("flush state to files, cache is cold") then arms - `Bus::take_skip_continue()` for the real turn — the next turn runs - without `--continue`, starting a fresh session. Unlike proactive - compaction the session is dropped entirely, not compacted. Set - `HIVE_AUTO_RESET_WATERMARK_TOKENS=0` to disable. - -The child runs with `cwd = /state` (when the bind exists; falls -back to the parent's cwd in dev), so any relative path in a tool -call (`Read foo.md`, `Bash ls`, `Write notes.md`) lands in the -agent's durable bind-mounted dir. CLAUDE.md auto-load walks -upward from `/state` — drop a per-agent CLAUDE.md there if you -want long-term hints that survive destroy/recreate. - -The wake prompt is intentionally minimal: just the popped message's -`from`/`body`, plus an inline `({unread} more pending — drain via -…)` hint when `unread > 0`. Claude drives any further `recv`/`send` -itself via the embedded MCP server. - -Whenever hive-c0re starts / restarts / rebuilds a container, it -also drops a `system` message into the agent's inbox via -`Coordinator::kick_agent` — a one-line "you were just (re)started, -check /state/ for your notes, --continue session is intact". The -next turn picks it up like any other inbox message. - -### On-boot files - -`hive_ag3nt::turn::write_*` writes three files next to the per-agent -socket at `/run/hive/` once at startup: - -- `claude-mcp-config.json` — re-invokes the running binary as `mcp` - child (so the same binary serves as harness + as claude's MCP - child process). -- `claude-settings.json` — the `--settings` blob (auto-compact and - auto-memory off, effortLevel medium). -- `claude-system-prompt.md` — rendered from - `hive-ag3nt/prompts/{agent,manager}.md` with `{label}` and - `{operator_pronouns}` substituted. Pronouns come from - `HIVE_OPERATOR_PRONOUNS` env (set by the meta flake from - `services.hive-c0re.operatorPronouns`, default `she/her`). - Passed via `--system-prompt-file`. - -The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config, -write_settings, write_system_prompt, run_turn, drive_turn, -emit_turn_end, wait_for_login, compact_session}` so the two binaries -can't drift. - -## MCP surface - -The harness ships an embedded MCP server (rmcp 1.7). Claude launches -it as a stdio child via `--mcp-config`. The hyperhive socket name is -`hyperhive`, so the tools land in claude as `mcp__hyperhive__`. - -### Sub-agent tools - -- `send(to, body, in_reply_to?)` — message a peer (logical agent - name), another agent, or the operator (recipient `operator`, - surfaces in the dashboard inbox). Optional `in_reply_to: i64` - links this message to a prior message id for thread rendering - in the dashboard message flow and the per-agent inbox. -- `recv(wait_seconds?, max?)` — drain inbox messages. Without - `wait_seconds` (or with `0`) returns immediately, a cheap - "anything pending?" peek. Positive value parks the turn up - to that many seconds (cap 180) — incoming messages wake - instantly, otherwise returns empty at the timeout. `max` - (default 1, server-side cap 32) drains up to N popped rows - in one round-trip; `wait_seconds` applies to the *first* - message, then the call drains up to `max` total. -- `ask(question, options?, multi?, ttl_seconds?, to?)` — - surface a structured question. Same shape as the manager's; - recipient defaults to the operator (dashboard) but can be set - to a peer agent name via `to: ""`. Answer routes back - to the asker's own inbox as `HelperEvent::QuestionAnswered` - via `coord.notify_agent`. For peer questions the recipient - sees a `HelperEvent::QuestionAsked` event and replies with - `answer(id, answer)`. -- `answer(id, answer)` — respond to a `question_asked` event - routed to this agent. Authorisation is strict: only the - declared target (or the operator via the dashboard) can - answer. -- `get_loose_ends()` — list everything still pending against - this agent: unanswered questions it asked / was asked, plus - reminders it scheduled. Each row carries an id + kind for - `cancel_loose_end`. -- `cancel_loose_end(kind, id)` — withdraw a `question` - (posts `[cancelled by ]` to unblock the asker), a - `reminder` (hard-delete before fire), or (manager only) an - `approval` (transitions to `Cancelled`; sub-agents refused with a - clear error). Sub-agents may only cancel rows they own. -- `remind(message, due)` — schedule a reminder that lands in - this agent's own inbox at a future time (sender shows as - `reminder`). Large payloads spill to - `/agents//state/reminders/` with the inbox message a - short pointer. Each agent's pending-reminder count is capped - (default 50, override via `HIVE_REMIND_MAX_PENDING_PER_AGENT`); - scheduling a new one fails if the cap is already hit. -- `set_status(text)` — set a free-text status string visible on - the operator dashboard. Persisted to - `{state_dir}/hyperhive-status`; survives harness restarts. Pass - an empty string to clear. -- `get_agent_meta(name?)` — fetch identity + status metadata for - an agent: `{ name, role, hyperhive_rev, running, status_text, - status_set_at }`. Pass `name` to query a peer (e.g. check - whether a sub-agent is idle before sending it work). Omit - `name` to get your own identity stamp — replaces the previous - `whoami` tool. `running` is `true` when the container is up. - When `running` is `false` the host clears `status_text` / - `status_set_at` (they would be stale snapshots from before the - container stopped) before serving the response. Status fields are - also `None` when the target has never called `set_status` or - has cleared it. -- `request_next_turn()` — ask the harness to start another turn - immediately after this one ends, even if the inbox is empty. Use for - multi-turn tasks (long builds, sequential steps) where you want to - continue without waiting for an external message. The next turn starts - with `from: "self"` and `body: "continue"`. No-op if new inbox - messages arrive before this turn ends. No args. - -### Waking the agent from inside the container - -External MCP servers (and any other in-container process) can -inject a wake-up event into the agent's inbox via the per-agent -socket at `/run/hive/mcp.sock`. Two equivalent paths: - -- **Shell out to `hive-ag3nt wake --from