//! 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, /// 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, }, } /// 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>, } 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() } } }