hive-sh4re: split manager-socket constants + HelperEvent into their own topic module
This commit is contained in:
parent
02bbff1e34
commit
138f6b6c10
24 changed files with 268 additions and 244 deletions
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
pub mod approvals;
|
||||
pub mod assets;
|
||||
pub mod bash_task;
|
||||
pub mod manager;
|
||||
pub mod paths;
|
||||
pub mod wire_time;
|
||||
|
||||
|
|
@ -289,134 +290,6 @@ pub struct MatrixIdentity {
|
|||
pub homeserver: String,
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Manager socket — /run/hyperhive/manager/mcp.sock on the host, bind-mounted
|
||||
// into the manager container at /run/hive/mcp.sock.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// 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: "<parent>", ...)` 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 = "<parent>";
|
||||
|
||||
/// Reserved magic recipient — `send(to: "<children>", ...)` 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 = "<children>";
|
||||
|
||||
/// 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: approvals::ApprovalStatus,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
note: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
sha: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tag: Option<String>,
|
||||
},
|
||||
/// 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<String>,
|
||||
},
|
||||
/// 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<String>,
|
||||
#[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<String>,
|
||||
/// 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<u64>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// Named group of MCP tools an agent may be granted. The harness reads
|
||||
/// `HIVE_TOOL_GROUPS` from the environment (a comma-separated list of
|
||||
/// `snake_case` group names written by the meta renderer from per-agent
|
||||
|
|
|
|||
133
hive-sh4re/src/manager.rs
Normal file
133
hive-sh4re/src/manager.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//! 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: "<parent>", ...)` 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 = "<parent>";
|
||||
|
||||
/// Reserved magic recipient — `send(to: "<children>", ...)` 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 = "<children>";
|
||||
|
||||
/// 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<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
sha: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tag: Option<String>,
|
||||
},
|
||||
/// 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<String>,
|
||||
},
|
||||
/// 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<String>,
|
||||
#[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<String>,
|
||||
/// 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<u64>,
|
||||
/// 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<String>,
|
||||
}
|
||||
Loading…
Reference in a new issue