//! Host admin socket wire types (`/run/hyperhive/host.sock`). //! //! The host-control protocol spoken between `hivectl` and the `hive-c0re` //! daemon. Re-homed out of `hive-sh4re` so a standalone `hivectl` can depend //! on just this protocol crate instead of the whole daemon crate. The shared //! payload types it references (`Approval`, `AgentStatusRow`, `jobs::DagView`) //! stay in `hive-sh4re`. use hive_sh4re::{AgentStatusRow, Approval, jobs}; use serde::{Deserialize, Serialize}; /// Requests on the host admin socket. /// /// Wire format: one JSON object per line. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum HostRequest { /// Create and start a sub-agent container directly, bypassing the /// approval queue. Privileged-context only. See /// `docs/approvals.md::Approval kinds (wire shapes)`. Spawn { name: String }, /// Submit a spawn request for the operator to approve. See /// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`). RequestSpawn { name: String }, /// Stop a managed container (graceful). Kill { name: String }, /// Tear down a sub-agent container, optionally purging state. /// See `docs/approvals.md::Destroy semantics`. Destroy { name: String, #[serde(default)] purge: bool, }, /// Stop and start a managed container without rebuilding config. /// For "kick the container" operations that don't touch the flake or /// nspawn flags. Mirrors `lifecycle::restart` (kill + start). Restart { name: String }, /// Stop and restart all managed containers in sequence. Convenience /// wrapper for `hivectl agents restart-all`; iterates the live /// container list and restarts each one. RestartAll, /// Restart containers hive-wide (`hivectl restart`), scoped like /// `Stop`/`Start`. Each targeted agent rides exactly one DAG server-side /// (the `Restart` template, hard stop + reconcile — or, when `graceful` /// is set, a submitted graceful-stop DAG the server itself waits out /// before submitting the start DAG) rather than the old client-side /// stop-then-start composition: a dropped `hivectl` connection mid-way /// used to leave the agent stopped with no automatic follow-up, since /// nothing durable remembered "finish the restart" once the CLI process /// was gone. Infra containers have no lease/DAG and restart /// synchronously (stop then start), same as before. Scope semantics /// match `Stop`/`Start` (all-false = everything). RestartScoped { #[serde(default)] scope: LifecycleScope, #[serde(default)] graceful: bool, }, /// Apply pending config to a managed container. Rebuild { name: String }, /// List managed containers. List, /// List managed agents with their full status + technical state /// (running / needs-login / needs-update / deployed sha / parent / /// pending reminders) — the `hivectl agents list` roster view. /// Reuses the dashboard's per-agent `ContainerView` aggregation. AgentStatus, /// Report this hive's canonical DNS domain /// (`services.hyperhive.domain`) plus the browser-facing home / /// forge / matrix URLs, daemon-sourced so custom forge/matrix /// domains resolve correctly. Each URL is `None` when its subsystem /// is unreachable from a browser (e.g. forge not behind the gateway, /// matrix GUI disabled). Backs `hivectl open` + the federation /// peer-config block (which reads the bare `domain`). Urls, /// Fetch one job-queue DAG (plus its live fan-out children, linked /// via `parent_id`) by id — the polling surface behind `hivectl`'s /// wait/progress loop. Result: [`HostResponse::dags`]. QueueDag { id: u64 }, /// List pending approval requests. Pending, /// Approve a pending request by id; the action runs immediately. Approve { id: i64 }, /// Deny a pending request by id. Deny { id: i64 }, /// Move an agent in the topology tree. `new_parent = None` /// promotes the agent to root, `Some(name)` sets a new parent. /// Validation rules + bind-mount caveat documented in /// `docs/agent-hierarchy.md::Current state`. SetParent { child: String, new_parent: Option, }, /// Stop managed containers hive-wide in one operator action /// (`hivectl stop`): agents plus the selected infra containers. `scope` /// selects which classes; an all-false scope means **everything** (the /// bare `hivectl stop`). `graceful` runs the per-agent quiesce (graceful /// agent stop, issue tracker `graceful agent stop`) instead of a hard /// stop. Agents stop via the lifecycle path; infra containers via the /// host `container@` units. Stop { #[serde(default)] scope: LifecycleScope, #[serde(default)] graceful: bool, }, /// Start managed containers hive-wide — the inverse of `Stop` /// (`hivectl start`). Same `scope` semantics (all-false = everything); /// no graceful flag (start is unconditional). Start { #[serde(default)] scope: LifecycleScope, }, /// Create or refresh a matrix account + access token for `name`. /// The daemon runs the provisioning (it holds the register + admin /// tokens and the matrix creds dir) and returns the operator-facing /// results (persisted-token path for agents, or the freshly-minted /// token + password for non-agent accounts) in /// [`HostResponse::messages`]. `password` is resolved by the client /// (inline flag or stdin) and `None` requests a random throwaway. MatrixCreateUser { name: String, #[serde(default)] password: Option, }, /// Provision (or re-provision) the hive system admin matrix account. /// Daemon-side equivalent of `hivectl matrix sync-admin`. MatrixSyncAdmin, /// Promote a matrix user to homeserver admin via the admin API. /// Uses the daemon's system admin token; `server_name` is discovered /// from the running homeserver. MatrixPromoteUser { name: String }, /// Reset a matrix user's password via the admin API and persist the /// new password to the matrix creds dir so a later token mint can /// re-login. Returns the outcome in [`HostResponse::messages`]. MatrixResetPassword { name: String }, /// Invite a matrix user to the hive Space (default) or a specific /// `room`. Uses the daemon's admin token; idempotent /// (already-member / already-invited is a no-op). MatrixInvite { user: String, #[serde(default)] room: Option, }, } /// Selects which container classes a hive-wide [`HostRequest::Stop`] / /// [`HostRequest::Start`] touches. An all-false scope means **everything** /// (the bare `hivectl stop` / `start`); set individual fields to restrict /// (e.g. only `agents` → just the sub-agent containers). `agents` covers /// every managed sub-agent container; the rest are the named infra /// containers (`hive-ci`, `hive-forge`, `hive-gateway`, `hive-matrix`). // // A flat bag of independent flag toggles — one bool per selectable // container class — is exactly the right shape here: each maps 1:1 to a // `hivectl` `--ci` / `--forge` / `--gateway` / `--matrix` flag, and they're // orthogonal (any subset is valid), so a state machine or two-variant enums // would only obscure the mapping. Hence the `struct_excessive_bools` allow. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct LifecycleScope { /// All sub-agent containers (`--agents`). #[serde(default)] pub agents: bool, /// Specific sub-agents by logical name (`--agent `, repeatable). /// Additive with the rest of the scope; redundant when `agents` is set /// (which already covers every sub-agent). #[serde(default)] pub agent_names: Vec, #[serde(default)] pub ci: bool, #[serde(default)] pub forge: bool, #[serde(default)] pub gateway: bool, #[serde(default)] pub matrix: bool, } impl LifecycleScope { /// True when nothing is explicitly selected — interpreted as "all /// classes" (the bare `hivectl stop` / `start` with no scope flags). pub fn is_everything(&self) -> bool { !(self.agents || self.ci || self.forge || self.gateway || self.matrix) && self.agent_names.is_empty() } } /// This hive's canonical domain plus the browser-facing URLs for its /// web surfaces — the `Urls` request result. Every field is `None` when /// the corresponding surface can't be reached from a browser (domain /// unset, forge not behind the gateway, matrix GUI disabled), so the CLI /// can give a precise hint instead of opening a dead link. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HiveUrls { /// Canonical hive domain (`services.hyperhive.domain`). #[serde(default, skip_serializing_if = "Option::is_none")] pub domain: Option, /// Operator dashboard root (`https:///`). #[serde(default, skip_serializing_if = "Option::is_none")] pub home: Option, /// Forge browser URL (`HIVE_FORGE_PUBLIC_URL`) — only the /// behind-gateway public URL; `None` on direct-port forge deploys. #[serde(default, skip_serializing_if = "Option::is_none")] pub forge: Option, /// Matrix GUI (fluffychat) browser URL — `None` when the matrix GUI /// is disabled. #[serde(default, skip_serializing_if = "Option::is_none")] pub matrix: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct HostResponse { pub ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub agents: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals: Option>, /// `Urls` result — this hive's domain plus the browser-facing /// home / forge / matrix URLs. `None` for every other request kind. #[serde(default, skip_serializing_if = "Option::is_none")] pub urls: Option, /// `AgentStatus` result — one row per managed agent with its /// running/health flags + technical state. `None` for every other /// request kind. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_statuses: Option>, /// Ids of the job-queue DAGs this request submitted (rebuild / /// restart / power ops). Clients poll them via /// [`HostRequest::QueueDag`]; `None` for non-submitting requests. #[serde(default, skip_serializing_if = "Option::is_none")] pub queued_dags: Option>, /// `QueueDag` result — the requested DAG followed by its live /// fan-out children ([`jobs::DagView`]). Empty when the DAG has /// been evicted from the queue's history tail. #[serde(default, skip_serializing_if = "Option::is_none")] pub dags: Option>, /// Free-form operator-facing output lines the client prints verbatim /// (one per line). Carries results a request produced daemon-side that /// have no structured home — e.g. a freshly-minted matrix token, a /// reset password, or an invited room id from the `Matrix*` requests. /// Empty for requests that produce no such output. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub messages: Vec, } impl HostResponse { #[must_use] pub fn success() -> Self { Self { ok: true, ..Self::default() } } #[must_use] pub fn error(message: impl Into) -> Self { Self { ok: false, error: Some(message.into()), ..Self::default() } } #[must_use] pub fn list(agents: Vec) -> Self { Self { ok: true, agents: Some(agents), ..Self::default() } } #[must_use] pub fn pending(approvals: Vec) -> Self { Self { ok: true, approvals: Some(approvals), ..Self::default() } } /// `Urls` result — this hive's domain + browser-facing web URLs. #[must_use] pub fn urls(urls: HiveUrls) -> Self { Self { ok: true, urls: Some(urls), ..Self::default() } } /// `AgentStatus` result — one row per managed agent. #[must_use] pub fn agent_statuses(rows: Vec) -> Self { Self { ok: true, agent_statuses: Some(rows), ..Self::default() } } /// A request that submitted job-queue DAGs — carries their ids for /// the client's wait/progress loop. #[must_use] pub fn queued(ids: Vec) -> Self { Self { ok: true, queued_dags: Some(ids), ..Self::default() } } /// `QueueDag` result — the polled DAG + its live children. #[must_use] pub fn dags(dags: Vec) -> Self { Self { ok: true, dags: Some(dags), ..Self::default() } } /// A success carrying operator-facing output lines the client prints /// verbatim — the result shape for the `Matrix*` provisioning requests. #[must_use] pub fn messages(messages: Vec) -> Self { Self { ok: true, messages, ..Self::default() } } }