//! Wire types for the unix socket protocol between `hive-bash-daemon` //! and `hive-bash-mcp`. One JSON request line in, one JSON response line //! out per connection. Connections are short-lived (per tool call). use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- // Task state (shared between runner and protocol) // --------------------------------------------------------------------------- // `TaskFile` + `TaskStatus` are the bash-task on-disk schema. They live in // `hive-sh4re` (the shared wire-types crate) so the agent web UI in // `hive-ag3nt` can deserialize the same canonical type when reading the // tasks dir for its running-tasks panel — no parallel copy to drift. Both // are re-exported here so existing `crate::protocol::{TaskFile, TaskStatus}` // imports across this crate keep compiling unchanged. pub use hive_sh4re::{TaskFile, TaskStatus}; // --------------------------------------------------------------------------- // Request / response // --------------------------------------------------------------------------- /// Requests the MCP bridge sends to the daemon. #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum DaemonRequest { /// Liveness probe — fast round-trip that doesn't touch any subprocess. Ping, /// Submit a new bash task. Returns the task ID on success. /// `wait_seconds`: optional inline poll (capped at 30s); when the /// task finishes within the window the response carries the full /// status payload. When it doesn't, the response carries just the /// task ID so the caller can check back with `BashStatus`. BashRun { cmd: String, #[serde(default)] timeout_secs: Option, /// Inline wait cap: 30s. Pass `None` or `0` to get the /// task-started-id response immediately. #[serde(default)] wait_seconds: Option, /// Optional caller-chosen task name, used as the task id (so it /// flows into the wake `from`, status lookups, and the loose-ends /// summary). Must be filesystem-safe. Reusable once any prior task /// of the same name has finished; rejected while one is still /// running. `None` falls back to the auto-generated id. #[serde(default)] name: Option, }, /// Query the current status of a task. Returns the full `TaskFile` /// (formatted as text by the bridge). `wait_seconds`: optional /// inline poll (capped at 30s) — daemon returns as soon as the /// task reaches a terminal state or the window expires. BashStatus { id: String, #[serde(default)] wait_seconds: Option, }, /// Return all tasks currently in `Pending` or `Running` state. /// Used by the harness `get_loose_ends` to surface active /// background work. ActiveTasks, /// Kill a running (or still-pending) task. `force = false` sends /// `SIGINT` to the task's process group (graceful — the process can /// clean up); `force = true` sends `SIGKILL` (immediate). Signalling /// the whole process group reaps `sh -c` plus any children it spawned, /// so a runaway grandchild (e.g. `cargo`/`nix`) is actually stopped. BashKill { id: String, #[serde(default)] force: bool, }, } /// Response shape from the daemon. `Ok` carries a JSON payload; `Error` /// carries a human-readable message. #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum DaemonResponse { Ok { payload: serde_json::Value }, Error { message: String }, } impl DaemonResponse { /// Build an Ok response from any serialisable value. pub fn ok(payload: &T) -> Self { let payload = serde_json::to_value(payload) .unwrap_or_else(|e| serde_json::json!({ "serialise_error": e.to_string() })); Self::Ok { payload } } /// Build an Error response from any `Display` value. pub fn error(msg: impl std::fmt::Display) -> Self { Self::Error { message: msg.to_string(), } } }