//! Manager socket — `/run/hyperhive/manager/mcp.sock` on the host, //! bind-mounted into the manager container at `/run/hive/mcp.sock`. //! Reserved recipient names/senders, the out-of-band `HelperEvent` //! payload hive-c0re pushes into the manager's inbox, and the //! schedule-prompt submission payload. use hive_types::Ident; use serde::{Deserialize, Serialize}; use crate::approvals::ApprovalStatus; /// Logical name the broker uses for the manager. pub const MANAGER_AGENT: &str = "ruth"; /// Logical name the broker uses for the human operator. Messages with /// `to = OPERATOR_RECIPIENT` accumulate in sqlite and surface on the /// dashboard's inbox view — they are never `recv`'d by an agent harness. pub const OPERATOR_RECIPIENT: &str = "operator"; /// Reserved magic recipient — `send(to: "", ...)` is rewritten /// by hive-c0re at delivery time to whoever `topology::parent_of(sender)` /// returns, or to [`OPERATOR_RECIPIENT`] when the sender is a root agent /// (no parent). Lets agents address their parent without hardcoding the /// label, so runtime reparenting requires no agent-side restart. The /// angle brackets are not valid in agent names (validators reject /// `<`/`>`), so this name can never collide with a real recipient. pub const PARENT_RECIPIENT: &str = ""; /// Reserved magic recipient — `send(to: "", ...)` fans out to /// every agent whose direct parent (per `topology.json`) is the sender. /// Lets a sub-manager nudge its subtree without enumerating labels at /// call-time; topology changes propagate for free. The angle brackets /// are structurally safe — agent name validation rejects `<`/`>`. /// Delivers to an empty set (no-op) for leaf agents that have no children. pub const CHILDREN_RECIPIENT: &str = ""; /// Sender hive-c0re uses for events it pushes into the manager's inbox. /// Manager harness recognises this and parses the body as a `HelperEvent`. pub const SYSTEM_SENDER: &str = "system"; /// Parse `s` as a [`Ident`] for use as `Message.from`, falling back to /// [`SYSTEM_SENDER`] on the (should-be-unreachable) case that `s` isn't /// ident-shaped. `Message.from` is always either a fixed sentinel literal /// (`SYSTEM_SENDER`, `OPERATOR_RECIPIENT`, `"scheduled"`, …) or an /// already-registered agent's own name reaching this point through /// hive-c0re's internal dispatch — never arbitrary external input — so /// this is a defensive fallback for a programming-bug case, not a /// validation gate. /// /// # Panics /// /// Never, unless [`SYSTEM_SENDER`] itself stops being ident-shaped (which /// would also be a programming bug, caught by `hive-types`' own tests). #[must_use] pub fn trusted_sender(s: &str) -> Ident { Ident::parse(s) .unwrap_or_else(|_| Ident::parse(SYSTEM_SENDER).expect("SYSTEM_SENDER is a valid Ident")) } /// Out-of-band events the host-side daemon pushes to the manager's inbox. /// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`). /// Per-variant triggers + the optional `sha`/`tag` semantics live in /// `docs/approvals.md::Helper events to the manager`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "event", rename_all = "snake_case")] pub enum HelperEvent { /// An approval transitioned to a terminal state. ApprovalResolved { id: i64, agent: String, commit_ref: String, status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, #[serde(default, skip_serializing_if = "Option::is_none")] tag: Option, }, /// A sub-agent's recorded flake rev is stale relative to hyperhive. NeedsUpdate { agent: String }, /// Container exited without an operator-initiated stop (crash). ContainerCrash { agent: String, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, }, /// A question queued via `Ask` was answered. `id` matches the /// originating `QuestionQueued.id`; `answerer` is `"operator"` / /// a peer agent name / `"ttl-watchdog"` on expiry. QuestionAnswered { id: i64, question: String, answer: String, answerer: String, }, /// A peer (or the manager) asked this agent a question. Recipient /// replies via `Answer { id, answer }`; the answer routes back to /// the asker as `QuestionAnswered`. QuestionAsked { id: i64, asker: String, question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, }, } /// Submission payload for `RequestSchedulePrompt`. Lives outside the /// enum so it can also serialize into the approval row's `commit_ref` /// (the dispatcher re-parses it on approve and inserts the schedule). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SchedulePromptPayload { /// Names of recipient agents. Operator + `root` allowed. pub targets: Vec, /// Message body delivered to each target's inbox at fire time. /// Same size budget as `Send.body` — soft cap at the broker level. pub body: String, /// Absolute unix timestamp (seconds) for the FIRST fire. For /// recurring schedules the worker then re-arms in /// `interval_seconds` steps. pub first_fire_at_unix: i64, /// `None` = one-shot. `Some(n > 0)` = recurring every `n` seconds. #[serde(default, skip_serializing_if = "Option::is_none")] pub interval_seconds: Option, /// Optional description shown on the dashboard approval card AND /// stored on the resulting schedule row for the operator's /// "what is this?" reference later. #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, }