Closes the #3110 split — lib.rs is now just the crate doc comment and the pub mod list. journal.rs's new doc comment fixes a pre-existing bug: the old JournalPriority doc text in lib.rs was actually half Capability's doc (a leftover from an earlier reorder that moved the code but not the comment above it).
210 lines
9 KiB
Rust
210 lines
9 KiB
Rust
//! Per-agent socket — `/run/hyperhive/agents/<name>/mcp.sock` on the
|
|
//! host, bind-mounted into the container at `/run/hive/mcp.sock`. The
|
|
//! inbox/messaging wire shapes: message envelopes, the loose-ends
|
|
//! response types, and the shared wake-prompt/`recv`-result hint
|
|
//! constants + builder.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use hive_types::Ident;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Server-side hard cap on `Recv.max` (see the `Recv` request). Bounds
|
|
/// the size of a single round-trip so a confused caller can't drain the
|
|
/// entire inbox in one go and blow past wire-buffer sizes; everything
|
|
/// above the cap silently clamps. 5 keeps individual turns small — a big
|
|
/// backlog is drained over several recv calls instead of one giant pop.
|
|
/// Lives here so both the enforcing side (hive-c0re's `socket_server`) and
|
|
/// the hinting side (hive-agent's wake prompt + tool docs) reference one
|
|
/// constant instead of a scattered magic value.
|
|
pub const RECV_BATCH_MAX: u32 = 5;
|
|
|
|
/// Banner prepended to a wake prompt / `recv` result when the message was
|
|
/// redelivered after a harness restart (the turn that first drove it never
|
|
/// acked). Shared between the harness serve loop (wake prompt) and the MCP
|
|
/// server (`recv` tool result) so both surfaces phrase it identically.
|
|
pub const REDELIVERY_HINT: &str = "[redelivered after harness restart — may already be handled]\n";
|
|
|
|
/// Banner prepended to a wake prompt when the previous turn was cut off by
|
|
/// an explicit operator `/cancel` (SIGINT) rather than ending normally. Set
|
|
/// once, read-and-cleared by the next turn's wake-prompt build — see
|
|
/// `hive-agent`'s `post_cancel_turn` (sets it) and `handle_turn` (clears
|
|
/// it). Lives here for the same reason as `REDELIVERY_HINT`: a single
|
|
/// phrasing, not duplicated between call sites.
|
|
pub const INTERRUPTED_HINT: &str = "[your previous turn was interrupted by the operator (/cancel) \
|
|
before it finished — check for new messages before resuming prior work]\n";
|
|
|
|
/// Shared "(N more message(s) pending …)" advisory appended after both the
|
|
/// wake prompt body and the `recv` tool result whenever the inbox still has
|
|
/// queued messages once the current message/batch is popped. Returns an empty
|
|
/// string when `remaining == 0`. The leading `\n\n` separates it from the
|
|
/// preceding body/message block, and the suggested `max` is clamped to the
|
|
/// server-side recv cap so the hint never asks for more than one round-trip
|
|
/// can deliver. One builder so the wake prompt (harness serve loop) and the
|
|
/// in-turn recv result (MCP server) stay identical.
|
|
#[must_use]
|
|
pub fn pending_hint(remaining: u64) -> String {
|
|
if remaining == 0 {
|
|
return String::new();
|
|
}
|
|
let batch = remaining.min(u64::from(RECV_BATCH_MAX));
|
|
format!(
|
|
"\n\n({remaining} more message(s) pending in your inbox — call `mcp__hyperhive__recv` \
|
|
with `max: {batch}` to drain the next batch before acting. If the \
|
|
backlog is stale/already handled, `ack_until(up_to: <highest [msg #N] seen>)` \
|
|
clears everything up to that id in one call instead.)"
|
|
)
|
|
}
|
|
|
|
/// A logical message between agents.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Message {
|
|
pub from: Ident,
|
|
pub to: String,
|
|
pub body: String,
|
|
/// Optional broker row-id of the message this is a reply to.
|
|
/// Stored in the DB and echoed back on `Recv` so the dashboard can
|
|
/// render conversation threads. `None` for messages that start a
|
|
/// new thread. Ignored if the referenced id is unknown or out of
|
|
/// retention — purely advisory.
|
|
pub in_reply_to: Option<i64>,
|
|
}
|
|
|
|
/// One row of a broker inbox query — what the dashboard renders in
|
|
/// its operator-inbox section and what a per-agent web UI returns
|
|
/// from a `Recent` request. Lives in `hive_sh4re` so it can travel
|
|
/// over both the dashboard's `/api/state` and the agent socket
|
|
/// without an internal-to-wire conversion.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InboxRow {
|
|
pub id: i64,
|
|
pub from: String,
|
|
pub body: String,
|
|
pub at: i64,
|
|
/// Row-id of the message this is a reply to, if any.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub in_reply_to: Option<i64>,
|
|
}
|
|
|
|
/// One delivered message in a `Recv` response.
|
|
/// See `docs/conventions.md::Broker delivery + ack cycle` for the
|
|
/// full delivery/ack/requeue story.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DeliveredMessage {
|
|
pub from: String,
|
|
pub body: String,
|
|
/// Broker row id, tracked by the harness for `AckTurn`. Opaque to
|
|
/// claude. `default` for wire backwards-compat.
|
|
#[serde(default)]
|
|
pub id: i64,
|
|
/// `true` if this row was resurfaced by `RequeueInflight` (previously
|
|
/// popped, never acked). Formatter prepends a "may already be handled"
|
|
/// hint when set.
|
|
#[serde(default)]
|
|
pub redelivered: bool,
|
|
/// Row-id of the message this is a reply to, if any.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub in_reply_to: Option<i64>,
|
|
}
|
|
|
|
/// Reminder timing: either relative (wait N seconds) or absolute (at unix
|
|
/// timestamp).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "timing_type", rename_all = "snake_case")]
|
|
pub enum ReminderTiming {
|
|
/// Remind after this many seconds from now.
|
|
InSeconds { seconds: u64 },
|
|
/// Remind at this unix timestamp (seconds since epoch).
|
|
At { unix_timestamp: i64 },
|
|
}
|
|
|
|
/// One row in the response to `GetLooseEnds`. Tagged enum so new
|
|
/// thread kinds can land without breaking existing handlers.
|
|
/// Per-flavour scoping + per-variant fields + clock-anomaly
|
|
/// saturation behaviour live in
|
|
/// `docs/conventions.md::Loose-ends wire shape`.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
pub enum LooseEnd {
|
|
/// A pending approval row.
|
|
Approval {
|
|
id: i64,
|
|
agent: String,
|
|
commit_ref: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
description: Option<String>,
|
|
age_seconds: u64,
|
|
},
|
|
/// An unanswered question row.
|
|
Question {
|
|
id: i64,
|
|
asker: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
target: Option<String>,
|
|
question: String,
|
|
age_seconds: u64,
|
|
},
|
|
/// A scheduled but un-delivered reminder row.
|
|
Reminder {
|
|
id: i64,
|
|
owner: String,
|
|
message: String,
|
|
due_at: DateTime<Utc>,
|
|
age_seconds: u64,
|
|
},
|
|
/// Undelivered inbox messages waiting to be `recv`'d by this agent.
|
|
/// Not cancellable — drain them with `recv`. Surfaced so an agent
|
|
/// doing a between-turns `get_loose_ends` sweep sees it still owes
|
|
/// itself a `recv` without having to poll the inbox separately. Only
|
|
/// emitted when `count > 0`.
|
|
PendingMessages {
|
|
/// Number of undelivered messages queued for this agent.
|
|
count: u64,
|
|
},
|
|
/// Unread matrix notifications in one or more rooms. Not cancellable —
|
|
/// use `mark_read` via the matrix MCP to clear. Injected by the
|
|
/// in-container harness (not hive-c0re) because the matrix daemon
|
|
/// runs inside the agent container.
|
|
UnreadMatrix {
|
|
/// Number of rooms with at least one unread notification.
|
|
rooms: u32,
|
|
/// Per-room summary: one line per room with truncated last-message
|
|
/// body when count is 1, or just the unread count otherwise. Empty
|
|
/// when the daemon returned no per-room detail.
|
|
#[serde(default)]
|
|
summary: String,
|
|
},
|
|
/// A dynamic, subsystem-pushed todo (loose-ends v2). Produced by an
|
|
/// in-container subsystem via `UpsertTodo` — matrix/bash/forge are the
|
|
/// *built-in* producers that ship today, but `subsystem` is a plain
|
|
/// string, not a closed set: any user-configured MCP server declared
|
|
/// in an agent's `agent.nix` can dial the in-agent socket and push its
|
|
/// own todos the same way. Cleared by that subsystem (`ClearTodo`) or
|
|
/// by the agent itself (`MarkTodoDone`, by `id`).
|
|
Todo {
|
|
id: i64,
|
|
/// Producing subsystem marker (`"matrix"`, `"forge"`, `"bash"`, …
|
|
/// — built-in producers; a user-configured MCP server can push
|
|
/// its own arbitrary marker here too, nothing enforces the set).
|
|
subsystem: String,
|
|
/// Optional subsystem-specific key (matrix room id, bash task id).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
subsystem_key: Option<String>,
|
|
summary: String,
|
|
/// Optional free-text provenance (room name / task label).
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
source: Option<String>,
|
|
age_seconds: u64,
|
|
},
|
|
}
|
|
|
|
/// Kind discriminator for `CancelLooseEnd`. Per-kind store +
|
|
/// authorisation rules live in
|
|
/// `docs/conventions.md::Loose-ends wire shape`.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum CancelLooseEndKind {
|
|
Question,
|
|
Reminder,
|
|
/// Withdraw a pending approval (manager surface only).
|
|
Approval,
|
|
}
|