refactor: unify AgentRequest/Response + ManagerRequest/Response into Request/Response (#691)
This commit is contained in:
parent
2fd6de7bab
commit
15617cef9a
6 changed files with 197 additions and 534 deletions
|
|
@ -188,14 +188,6 @@ trait Surface {
|
|||
/// system-prompt block + tool registration goes into the spawned
|
||||
/// `claude` process.
|
||||
const FLAVOR: mcp::Flavor;
|
||||
/// `is_manager` flag passed to `forge_notify::run`. Picks which
|
||||
/// wire enum (`AgentRequest::Wake` vs `ManagerRequest::Wake`) the
|
||||
/// poller uses to push notifications into the harness inbox — the
|
||||
/// per-role broker socket rejects the wrong type. Lifting
|
||||
/// `Surface` into the lib crate to make `forge_notify::run`
|
||||
/// generic is deferred to its own issue.
|
||||
const FORGE_IS_MANAGER: bool;
|
||||
|
||||
/// Ack the in-flight turn. Logs warnings on transport/broker
|
||||
/// errors but never propagates — turn loop continues either way.
|
||||
fn ack_turn(socket: &Path) -> impl Future<Output = ()>;
|
||||
|
|
@ -248,7 +240,6 @@ struct AgentSurface;
|
|||
|
||||
impl Surface for AgentSurface {
|
||||
const FLAVOR: mcp::Flavor = mcp::Flavor::Agent;
|
||||
const FORGE_IS_MANAGER: bool = false;
|
||||
|
||||
async fn ack_turn(socket: &Path) {
|
||||
match client::request::<_, AgentResponse>(socket, &AgentRequest::AckTurn).await {
|
||||
|
|
@ -281,7 +272,7 @@ impl Surface for AgentSurface {
|
|||
|
||||
async fn post_turn_counts(socket: &Path) -> (Option<u64>, Option<u64>) {
|
||||
let threads =
|
||||
match client::request::<_, AgentResponse>(socket, &AgentRequest::GetLooseEnds).await {
|
||||
match client::request::<_, AgentResponse>(socket, &AgentRequest::GetLooseEnds { agent: None }).await {
|
||||
Ok(AgentResponse::LooseEnds { loose_ends }) => {
|
||||
u64::try_from(loose_ends.len()).ok()
|
||||
}
|
||||
|
|
@ -289,7 +280,7 @@ impl Surface for AgentSurface {
|
|||
};
|
||||
let reminders = match client::request::<_, AgentResponse>(
|
||||
socket,
|
||||
&AgentRequest::CountPendingReminders,
|
||||
&AgentRequest::CountPendingReminders { agent: None },
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -386,7 +377,6 @@ struct ManagerSurface;
|
|||
|
||||
impl Surface for ManagerSurface {
|
||||
const FLAVOR: mcp::Flavor = mcp::Flavor::Manager;
|
||||
const FORGE_IS_MANAGER: bool = true;
|
||||
|
||||
async fn ack_turn(socket: &Path) {
|
||||
match client::request::<_, ManagerResponse>(socket, &ManagerRequest::AckTurn).await {
|
||||
|
|
@ -560,10 +550,7 @@ async fn serve_main<S: Surface>(socket: &Path, poll_ms: u64) -> Result<()> {
|
|||
for failure in plugins::install_configured(socket).await {
|
||||
S::send_to_parent(socket, failure).await;
|
||||
}
|
||||
tokio::spawn(hive_ag3nt::forge_notify::run(
|
||||
socket.to_path_buf(),
|
||||
S::FORGE_IS_MANAGER,
|
||||
));
|
||||
tokio::spawn(hive_ag3nt::forge_notify::run(socket.to_path_buf()));
|
||||
// Log web_ui::serve's error instead of dropping it. A bare
|
||||
// `tokio::spawn(web_ui::serve(...))` discards the JoinHandle, so
|
||||
// any Err (e.g. EACCES from `bind_unix` when HIVE_WEB_SOCKET points
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ const BODY_TRUNCATE: usize = 500;
|
|||
/// configured. Otherwise loops forever, polling every
|
||||
/// `POLL_INTERVAL_SECS` seconds. Errors are never fatal.
|
||||
///
|
||||
/// `is_manager`: when true, wakes the inbox via `ManagerRequest::Wake`
|
||||
/// instead of `AgentRequest::Wake` (the manager socket rejects the agent
|
||||
/// request type).
|
||||
pub async fn run(socket: PathBuf, is_manager: bool) {
|
||||
pub async fn run(socket: PathBuf) {
|
||||
let forge_url = match std::env::var("HIVE_FORGE_URL") {
|
||||
Ok(u) if !u.is_empty() => u,
|
||||
_ => {
|
||||
|
|
@ -122,7 +119,6 @@ pub async fn run(socket: PathBuf, is_manager: bool) {
|
|||
&forge_url,
|
||||
&token,
|
||||
&socket,
|
||||
is_manager,
|
||||
keep_subscriptions,
|
||||
&mut unsubbed_repos,
|
||||
&own_login,
|
||||
|
|
@ -623,7 +619,6 @@ async fn poll_once(
|
|||
forge_url: &str,
|
||||
token: &str,
|
||||
socket: &Path,
|
||||
is_manager: bool,
|
||||
keep_subscriptions: bool,
|
||||
unsubbed_repos: &mut HashSet<String>,
|
||||
own_login: &str,
|
||||
|
|
@ -690,23 +685,13 @@ async fn poll_once(
|
|||
continue;
|
||||
};
|
||||
|
||||
let delivered = if is_manager {
|
||||
let req = hive_sh4re::ManagerRequest::Wake {
|
||||
from: "forge".to_owned(),
|
||||
body,
|
||||
};
|
||||
crate::client::request::<_, hive_sh4re::ManagerResponse>(socket, &req)
|
||||
.await
|
||||
.map(|_| ())
|
||||
} else {
|
||||
let req = hive_sh4re::AgentRequest::Wake {
|
||||
from: "forge".to_owned(),
|
||||
body,
|
||||
};
|
||||
crate::client::request::<_, hive_sh4re::AgentResponse>(socket, &req)
|
||||
.await
|
||||
.map(|_| ())
|
||||
let req = hive_sh4re::Request::Wake {
|
||||
from: "forge".to_owned(),
|
||||
body,
|
||||
};
|
||||
let delivered = crate::client::request::<_, hive_sh4re::Response>(socket, &req)
|
||||
.await
|
||||
.map(|_| ());
|
||||
match delivered {
|
||||
Ok(()) => {
|
||||
debug!(%id, "forge_notify: delivered");
|
||||
|
|
|
|||
|
|
@ -64,60 +64,23 @@ pub enum SocketReply {
|
|||
},
|
||||
}
|
||||
|
||||
impl From<hive_sh4re::AgentResponse> for SocketReply {
|
||||
fn from(r: hive_sh4re::AgentResponse) -> Self {
|
||||
impl From<hive_sh4re::Response> for SocketReply {
|
||||
fn from(r: hive_sh4re::Response) -> Self {
|
||||
match r {
|
||||
hive_sh4re::AgentResponse::Ok => Self::Ok,
|
||||
hive_sh4re::AgentResponse::Err { message } => Self::Err(message),
|
||||
hive_sh4re::AgentResponse::Messages { messages } => Self::Messages(messages),
|
||||
hive_sh4re::AgentResponse::Status { unread } => Self::Status(unread),
|
||||
hive_sh4re::AgentResponse::Recent { rows } => Self::Recent(rows),
|
||||
hive_sh4re::AgentResponse::QuestionQueued { id } => Self::QuestionQueued(id),
|
||||
hive_sh4re::AgentResponse::LooseEnds { loose_ends } => Self::LooseEnds(loose_ends),
|
||||
hive_sh4re::AgentResponse::PendingRemindersCount { count } => {
|
||||
hive_sh4re::Response::Ok => Self::Ok,
|
||||
hive_sh4re::Response::Err { message } => Self::Err(message),
|
||||
hive_sh4re::Response::Messages { messages } => Self::Messages(messages),
|
||||
hive_sh4re::Response::Status { unread } => Self::Status(unread),
|
||||
hive_sh4re::Response::Recent { rows } => Self::Recent(rows),
|
||||
hive_sh4re::Response::QuestionQueued { id } => Self::QuestionQueued(id),
|
||||
hive_sh4re::Response::LooseEnds { loose_ends } => Self::LooseEnds(loose_ends),
|
||||
hive_sh4re::Response::PendingRemindersCount { count } => {
|
||||
Self::PendingRemindersCount(count)
|
||||
}
|
||||
hive_sh4re::AgentResponse::ReminderRollup(stats) => Self::ReminderRollup(stats),
|
||||
hive_sh4re::AgentResponse::AgentMeta {
|
||||
name,
|
||||
role,
|
||||
running,
|
||||
hyperhive_rev,
|
||||
status_text,
|
||||
status_set_at,
|
||||
hive_name,
|
||||
swarm_name,
|
||||
} => Self::AgentMeta {
|
||||
name,
|
||||
role,
|
||||
running,
|
||||
hyperhive_rev,
|
||||
status_text,
|
||||
status_set_at,
|
||||
hive_name,
|
||||
swarm_name,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hive_sh4re::ManagerResponse> for SocketReply {
|
||||
fn from(r: hive_sh4re::ManagerResponse) -> Self {
|
||||
match r {
|
||||
hive_sh4re::ManagerResponse::Ok => Self::Ok,
|
||||
hive_sh4re::ManagerResponse::Err { message } => Self::Err(message),
|
||||
hive_sh4re::ManagerResponse::Messages { messages } => Self::Messages(messages),
|
||||
hive_sh4re::ManagerResponse::Status { unread } => Self::Status(unread),
|
||||
hive_sh4re::ManagerResponse::QuestionQueued { id } => Self::QuestionQueued(id),
|
||||
hive_sh4re::ManagerResponse::Recent { rows } => Self::Recent(rows),
|
||||
hive_sh4re::ManagerResponse::Logs { content } => Self::Logs(content),
|
||||
hive_sh4re::ManagerResponse::Schedules { schedules } => Self::Schedules(schedules),
|
||||
hive_sh4re::ManagerResponse::LooseEnds { loose_ends } => Self::LooseEnds(loose_ends),
|
||||
hive_sh4re::ManagerResponse::PendingRemindersCount { count } => {
|
||||
Self::PendingRemindersCount(count)
|
||||
}
|
||||
hive_sh4re::ManagerResponse::ReminderRollup(stats) => Self::ReminderRollup(stats),
|
||||
hive_sh4re::ManagerResponse::AgentMeta {
|
||||
hive_sh4re::Response::ReminderRollup(stats) => Self::ReminderRollup(stats),
|
||||
hive_sh4re::Response::Logs { content } => Self::Logs(content),
|
||||
hive_sh4re::Response::Schedules { schedules } => Self::Schedules(schedules),
|
||||
hive_sh4re::Response::AgentMeta {
|
||||
name,
|
||||
role,
|
||||
running,
|
||||
|
|
@ -647,7 +610,7 @@ impl AgentServer {
|
|||
)]
|
||||
async fn get_loose_ends(&self) -> String {
|
||||
run_tool_envelope("get_loose_ends", String::new(), async move {
|
||||
let (resp, retries) = self.dispatch(hive_sh4re::AgentRequest::GetLooseEnds).await;
|
||||
let (resp, retries) = self.dispatch(hive_sh4re::AgentRequest::GetLooseEnds { agent: None }).await;
|
||||
annotate_retries(format_loose_ends(resp), retries)
|
||||
})
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -63,17 +63,7 @@ struct AppState {
|
|||
gui_vnc_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn flavor(&self) -> Flavor {
|
||||
self.files.flavor
|
||||
}
|
||||
}
|
||||
|
||||
/// Which wire protocol the per-agent UI's `/send` handler should speak.
|
||||
/// Sub-agent → `AgentRequest::OperatorMsg`; manager →
|
||||
/// `ManagerRequest::OperatorMsg`. Reuses the MCP-side enum so a
|
||||
/// single value drives both the send protocol and (in
|
||||
/// `post_compact`) the allowed-tools surface claude sees.
|
||||
/// Re-export so callers in `turn.rs` can name the type via `web_ui::Flavor`.
|
||||
pub type Flavor = mcp::Flavor;
|
||||
|
||||
/// Bind the per-container web listener and serve the SPA.
|
||||
|
|
@ -379,7 +369,7 @@ async fn api_stats(
|
|||
// filters its counts to the same time range as the chart data.
|
||||
let window_secs = window.span_secs();
|
||||
let window_secs_u = u64::try_from(window_secs).unwrap_or(0);
|
||||
snapshot.reminder_stats = fetch_reminder_stats(&state.socket, state.flavor(), window_secs_u).await;
|
||||
snapshot.reminder_stats = fetch_reminder_stats(&state.socket, window_secs_u).await;
|
||||
axum::Json(snapshot)
|
||||
}
|
||||
|
||||
|
|
@ -505,39 +495,18 @@ struct SessionView {
|
|||
/// the `mcp__hyperhive__get_loose_ends` tool sees from inside the
|
||||
/// container.
|
||||
async fn api_loose_ends(State(state): State<AppState>) -> Response {
|
||||
let loose_ends: Vec<hive_sh4re::LooseEnd> = match state.flavor() {
|
||||
Flavor::Agent => {
|
||||
match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::AgentRequest::GetLooseEnds,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::AgentResponse::LooseEnds { loose_ends }) => loose_ends,
|
||||
Ok(hive_sh4re::AgentResponse::Err { message }) => {
|
||||
return error_response(&format!("get_loose_ends: {message}"));
|
||||
}
|
||||
Ok(other) => return error_response(&format!("unexpected response: {other:?}")),
|
||||
Err(e) => return error_response(&format!("transport: {e:#}")),
|
||||
}
|
||||
}
|
||||
Flavor::Manager => {
|
||||
match client::request::<_, hive_sh4re::ManagerResponse>(
|
||||
&state.socket,
|
||||
// Manager's own loose ends — the web page is the
|
||||
// manager's page, not a hive-wide console.
|
||||
&hive_sh4re::ManagerRequest::GetLooseEnds { agent: None },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::ManagerResponse::LooseEnds { loose_ends }) => loose_ends,
|
||||
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
|
||||
return error_response(&format!("get_loose_ends: {message}"));
|
||||
}
|
||||
Ok(other) => return error_response(&format!("unexpected response: {other:?}")),
|
||||
Err(e) => return error_response(&format!("transport: {e:#}")),
|
||||
}
|
||||
let loose_ends: Vec<hive_sh4re::LooseEnd> = match client::request::<_, hive_sh4re::Response>(
|
||||
&state.socket,
|
||||
&hive_sh4re::Request::GetLooseEnds { agent: None },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::Response::LooseEnds { loose_ends }) => loose_ends,
|
||||
Ok(hive_sh4re::Response::Err { message }) => {
|
||||
return error_response(&format!("get_loose_ends: {message}"));
|
||||
}
|
||||
Ok(other) => return error_response(&format!("unexpected response: {other:?}")),
|
||||
Err(e) => return error_response(&format!("transport: {e:#}")),
|
||||
};
|
||||
axum::Json(serde_json::json!({ "loose_ends": loose_ends })).into_response()
|
||||
}
|
||||
|
|
@ -567,7 +536,7 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
|
|||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(7000);
|
||||
let inbox = recent_inbox(&state.socket, state.flavor()).await;
|
||||
let inbox = recent_inbox(&state.socket).await;
|
||||
let (turn_state, turn_state_since) = state.bus.state_snapshot();
|
||||
let model = state.bus.model();
|
||||
let context_window_tokens = state
|
||||
|
|
@ -676,67 +645,34 @@ struct ExtraLink {
|
|||
/// Best-effort: pull the last 30 messages addressed to us via the
|
||||
/// per-agent / manager socket. Empty list on any transport / decode
|
||||
/// failure — the inbox section is decorative, not authoritative.
|
||||
async fn recent_inbox(socket: &std::path::Path, flavor: Flavor) -> Vec<hive_sh4re::InboxRow> {
|
||||
async fn recent_inbox(socket: &std::path::Path) -> Vec<hive_sh4re::InboxRow> {
|
||||
const LIMIT: u64 = 30;
|
||||
match flavor {
|
||||
Flavor::Agent => {
|
||||
match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
socket,
|
||||
&hive_sh4re::AgentRequest::Recent { limit: LIMIT },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::AgentResponse::Recent { rows }) => rows,
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
Flavor::Manager => {
|
||||
match client::request::<_, hive_sh4re::ManagerResponse>(
|
||||
socket,
|
||||
&hive_sh4re::ManagerRequest::Recent { limit: LIMIT },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::ManagerResponse::Recent { rows }) => rows,
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
match client::request::<_, hive_sh4re::Response>(
|
||||
socket,
|
||||
&hive_sh4re::Request::Recent { limit: LIMIT },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::Response::Recent { rows }) => rows,
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch reminder activity stats from the broker via the per-agent /
|
||||
/// manager socket. Returns None on any transport / decode failure — the
|
||||
/// stats are decorative, not authoritative.
|
||||
async fn fetch_reminder_stats(socket: &std::path::Path, flavor: Flavor, window_secs: u64) -> Option<hive_sh4re::ReminderStats> {
|
||||
match flavor {
|
||||
Flavor::Agent => {
|
||||
match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
socket,
|
||||
&hive_sh4re::AgentRequest::ReminderRollup {
|
||||
since_secs: window_secs,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::AgentResponse::ReminderRollup(stats)) => Some(stats),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Flavor::Manager => {
|
||||
match client::request::<_, hive_sh4re::ManagerResponse>(
|
||||
socket,
|
||||
&hive_sh4re::ManagerRequest::ReminderRollup {
|
||||
since_secs: window_secs,
|
||||
// Manager's own stats page — its own reminders.
|
||||
agent: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::ManagerResponse::ReminderRollup(stats)) => Some(stats),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
async fn fetch_reminder_stats(socket: &std::path::Path, window_secs: u64) -> Option<hive_sh4re::ReminderStats> {
|
||||
match client::request::<_, hive_sh4re::Response>(
|
||||
socket,
|
||||
&hive_sh4re::Request::ReminderRollup {
|
||||
since_secs: window_secs,
|
||||
agent: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::Response::ReminderRollup(stats)) => Some(stats),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -754,29 +690,16 @@ async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) ->
|
|||
if body.is_empty() {
|
||||
return error_response("send: `body` required");
|
||||
}
|
||||
let result = match state.flavor() {
|
||||
Flavor::Agent => match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::AgentRequest::OperatorMsg { body },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::AgentResponse::Ok) => Ok(()),
|
||||
Ok(hive_sh4re::AgentResponse::Err { message }) => Err(message),
|
||||
Ok(other) => Err(format!("unexpected response: {other:?}")),
|
||||
Err(e) => Err(format!("transport: {e:#}")),
|
||||
},
|
||||
Flavor::Manager => match client::request::<_, hive_sh4re::ManagerResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::ManagerRequest::OperatorMsg { body },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::ManagerResponse::Ok) => Ok(()),
|
||||
Ok(hive_sh4re::ManagerResponse::Err { message }) => Err(message),
|
||||
Ok(other) => Err(format!("unexpected response: {other:?}")),
|
||||
Err(e) => Err(format!("transport: {e:#}")),
|
||||
},
|
||||
let result = match client::request::<_, hive_sh4re::Response>(
|
||||
&state.socket,
|
||||
&hive_sh4re::Request::OperatorMsg { body },
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(hive_sh4re::Response::Ok) => Ok(()),
|
||||
Ok(hive_sh4re::Response::Err { message }) => Err(message),
|
||||
Ok(other) => Err(format!("unexpected response: {other:?}")),
|
||||
Err(e) => Err(format!("transport: {e:#}")),
|
||||
};
|
||||
match result {
|
||||
// 200 instead of 303 → the client doesn't refetch /api/state.
|
||||
|
|
|
|||
|
|
@ -208,13 +208,13 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
|||
timing,
|
||||
file_path,
|
||||
} => handle_remind(coord, agent, message, timing, file_path.as_deref()),
|
||||
AgentRequest::GetLooseEnds => match crate::loose_ends::for_agent(coord, agent) {
|
||||
AgentRequest::GetLooseEnds { .. } => match crate::loose_ends::for_agent(coord, agent) {
|
||||
Ok(loose_ends) => AgentResponse::LooseEnds { loose_ends },
|
||||
Err(e) => AgentResponse::Err {
|
||||
message: format!("{e:#}"),
|
||||
},
|
||||
},
|
||||
AgentRequest::CountPendingReminders => {
|
||||
AgentRequest::CountPendingReminders { .. } => {
|
||||
match coord.broker.count_pending_reminders_for(agent) {
|
||||
Ok(count) => AgentResponse::PendingRemindersCount { count },
|
||||
Err(e) => AgentResponse::Err {
|
||||
|
|
@ -222,7 +222,7 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
|||
},
|
||||
}
|
||||
}
|
||||
AgentRequest::ReminderRollup { since_secs } => {
|
||||
AgentRequest::ReminderRollup { since_secs, .. } => {
|
||||
match coord.broker.reminder_rollup_for(agent, *since_secs) {
|
||||
Ok(stats) => AgentResponse::ReminderRollup(stats),
|
||||
Err(e) => AgentResponse::Err {
|
||||
|
|
@ -310,6 +310,10 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
|
|||
message: format!("{e:#}"),
|
||||
},
|
||||
},
|
||||
// Manager-only variants are not valid on the agent socket.
|
||||
_ => AgentResponse::Err {
|
||||
message: "request not supported on agent socket".to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -295,11 +295,15 @@ pub enum CancelLooseEndKind {
|
|||
Approval,
|
||||
}
|
||||
|
||||
/// Requests on a per-agent socket. The agent's identity is the socket
|
||||
/// it came in on; `Send.from` is filled in by the server, not the client.
|
||||
/// Unified request enum for both agent and manager sockets. The agent's
|
||||
/// identity is the socket it arrived on. Manager-only variants are tagged
|
||||
/// `// privileged` — an agent socket returns `Err` for them server-side.
|
||||
///
|
||||
/// `AgentRequest` and `ManagerRequest` are type aliases for this enum;
|
||||
/// existing callers continue to compile unchanged.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "snake_case")]
|
||||
pub enum AgentRequest {
|
||||
pub enum Request {
|
||||
/// Send a message to another agent.
|
||||
Send {
|
||||
to: String,
|
||||
|
|
@ -367,23 +371,37 @@ pub enum AgentRequest {
|
|||
#[serde(default)]
|
||||
file_path: Option<String>,
|
||||
},
|
||||
/// Loose-ends view: every pending row against THIS agent.
|
||||
/// Per-flavour scoping in
|
||||
/// Loose-ends view. On the agent socket, scoped to the calling agent
|
||||
/// (the `agent` field is ignored — agents can only see their own
|
||||
/// loose ends). On the manager socket, `agent = None` scopes to the
|
||||
/// manager itself, `Some("*")` is hive-wide, `Some("<name>")` is
|
||||
/// that agent's loose ends. See
|
||||
/// `docs/conventions.md::Loose-ends wire shape`.
|
||||
GetLooseEnds,
|
||||
/// Count of this agent's pending (un-delivered) reminders. Used
|
||||
/// by the harness's per-turn stats sink to snapshot "what was
|
||||
/// queued at turn-end time" without paying for a full list.
|
||||
CountPendingReminders,
|
||||
/// Reminder statistics for this agent: counts of scheduled, delivered,
|
||||
/// and pending reminders over a time window. Used by the stats page
|
||||
/// to display reminder activity. `since_secs` filters to reminders
|
||||
/// created in the last N seconds (0 = all reminders).
|
||||
GetLooseEnds {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Count of pending (un-delivered) reminders. On the agent socket
|
||||
/// always scoped to the calling agent. On the manager socket,
|
||||
/// `agent = None` means self, `Some("<name>")` means that agent.
|
||||
/// Used by the harness's per-turn stats sink.
|
||||
CountPendingReminders {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Reminder statistics: counts of scheduled, delivered, and pending
|
||||
/// reminders over a time window. `since_secs` filters to reminders
|
||||
/// created in the last N seconds (0 = all). On the manager socket
|
||||
/// `agent = None` means self, `Some("<name>")` means that agent.
|
||||
ReminderRollup {
|
||||
/// Only count reminders created in the last N seconds from now.
|
||||
/// Pass 0 to include all reminders.
|
||||
#[serde(default)]
|
||||
since_secs: u64,
|
||||
/// Whose reminders to roll up. `None` = the caller's own.
|
||||
/// Manager socket only: `Some("<name>")` = that agent's.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Set a free-text status string visible on the dashboard. Persisted
|
||||
/// to `{state_dir}/hyperhive-status` so it survives harness restarts.
|
||||
|
|
@ -409,12 +427,88 @@ pub enum AgentRequest {
|
|||
/// crashed-mid-turn sessions. See
|
||||
/// `docs/conventions.md::Broker delivery + ack cycle`.
|
||||
RequeueInflight,
|
||||
|
||||
// ---- privileged: manager socket only -----------------------------------
|
||||
|
||||
/// Initialise a brand-new agent's proposed config repo and queue an
|
||||
/// approval for the operator to review. // privileged
|
||||
RequestInitConfig {
|
||||
name: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Stop a sub-agent (graceful). // privileged
|
||||
Kill { name: String },
|
||||
/// Start a previously-stopped sub-agent container. // privileged
|
||||
Start { name: String },
|
||||
/// Restart a sub-agent container (stop + start). // privileged
|
||||
Restart { name: String },
|
||||
/// Rebuild a sub-agent against the current hyperhive flake + agent.nix.
|
||||
/// No approval required. // privileged
|
||||
Update { name: String },
|
||||
/// Submit a config commit for the operator to approve. // privileged
|
||||
RequestApplyCommit {
|
||||
agent: String,
|
||||
commit_ref: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Fetch recent journal lines for a sub-agent container. // privileged
|
||||
GetLogs {
|
||||
agent: String,
|
||||
#[serde(default)]
|
||||
lines: Option<u32>,
|
||||
},
|
||||
/// Queue an approval to run `nix flake update [inputs...]`. // privileged
|
||||
RequestUpdateMetaInputs {
|
||||
#[serde(default)]
|
||||
inputs: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Queue an approval to add a scheduled prompt. // privileged
|
||||
RequestSchedulePrompt(SchedulePromptPayload),
|
||||
/// Cancel a scheduled prompt. // privileged
|
||||
CancelSchedule {
|
||||
id: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets: Option<Vec<String>>,
|
||||
},
|
||||
/// List every schedule in the queue. // privileged
|
||||
ListSchedules,
|
||||
/// Fire a scheduled prompt out of band immediately. // privileged
|
||||
FireScheduleNow { id: i64 },
|
||||
/// Edit an existing schedule's mutable fields. // privileged
|
||||
EditSchedule {
|
||||
id: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<Option<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
interval_seconds: Option<Option<u64>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
next_fire_at_unix: Option<i64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets_add: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets_remove: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Responses on a per-agent socket.
|
||||
/// Backwards-compatible aliases. Both sockets now speak the unified `Request`
|
||||
/// / `Response` wire; the server-side privilege gate rejects manager-only
|
||||
/// variants on agent sockets with `Err { message: "privileged variant..." }`.
|
||||
pub type AgentRequest = Request;
|
||||
pub type ManagerRequest = Request;
|
||||
|
||||
/// Unified response enum for both agent and manager sockets. Manager-only
|
||||
/// variants (`Logs`, `Schedules`) are never returned on agent sockets.
|
||||
///
|
||||
/// `AgentResponse` and `ManagerResponse` are type aliases for this enum.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum AgentResponse {
|
||||
pub enum Response {
|
||||
/// `Send` succeeded.
|
||||
Ok,
|
||||
/// Either `Send` failed or `Recv` errored.
|
||||
|
|
@ -458,8 +552,18 @@ pub enum AgentResponse {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
swarm_name: Option<String>,
|
||||
},
|
||||
/// `GetLogs` result: journal lines for the requested container.
|
||||
/// Returned on the manager socket only.
|
||||
Logs { content: String },
|
||||
/// `ListSchedules` result. Snapshot of every schedule.
|
||||
/// Returned on the manager socket only.
|
||||
Schedules { schedules: Vec<WireSchedule> },
|
||||
}
|
||||
|
||||
/// Backwards-compatible response aliases.
|
||||
pub type AgentResponse = Response;
|
||||
pub type ManagerResponse = Response;
|
||||
|
||||
/// Serde default for the `running` field; keeps wire backwards-compat
|
||||
/// with pre-running-field payloads. See
|
||||
/// `docs/conventions.md::Agent metadata`.
|
||||
|
|
@ -582,240 +686,6 @@ pub enum HelperEvent {
|
|||
},
|
||||
}
|
||||
|
||||
/// Requests on the manager socket. Manager has the agent surface (send/recv)
|
||||
/// plus privileged lifecycle verbs.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "snake_case")]
|
||||
pub enum ManagerRequest {
|
||||
Send {
|
||||
to: String,
|
||||
body: String,
|
||||
/// Optional id of the message being replied to. Mirror of
|
||||
/// `AgentRequest::Send.in_reply_to`; see that doc.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
in_reply_to: Option<i64>,
|
||||
},
|
||||
/// Same shape as `AgentRequest::Recv` — caller-tunable
|
||||
/// `wait_seconds` (capped at 60s server-side, default 30s when
|
||||
/// None) for first-message long-poll, plus `max` (default 1, cap
|
||||
/// 32) to drain up to N popped rows in one round-trip.
|
||||
Recv {
|
||||
#[serde(default)]
|
||||
wait_seconds: Option<u64>,
|
||||
#[serde(default)]
|
||||
max: Option<u32>,
|
||||
},
|
||||
/// Non-mutating: pending message count, used to render a status line
|
||||
/// after each MCP tool call (mirrors `AgentRequest::Status`).
|
||||
Status,
|
||||
/// Operator-injected message TO the manager (from the manager's own web
|
||||
/// UI). Same shape as `AgentRequest::OperatorMsg`.
|
||||
OperatorMsg { body: String },
|
||||
/// Last `limit` messages addressed to the manager, newest-first.
|
||||
/// Non-mutating; mirror of `AgentRequest::Recent`.
|
||||
Recent { limit: u64 },
|
||||
/// Initialise a brand-new agent's proposed config repo and queue an
|
||||
/// approval for the operator to review. On approval hive-c0re seeds
|
||||
/// `/agents/<name>/config/` with the default `agent.nix` template,
|
||||
/// giving the manager RW access so it can customise the config and
|
||||
/// commit changes. After the `ConfigReady` event arrives, edit
|
||||
/// `agent.nix`, commit, and call `request_apply_commit` — which
|
||||
/// creates the container on the first deploy. Fails if a proposed
|
||||
/// repo for this name already exists (use `request_apply_commit` to
|
||||
/// update an existing agent's config).
|
||||
RequestInitConfig {
|
||||
name: String,
|
||||
/// Optional description shown on the dashboard approval card.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Stop a sub-agent (graceful).
|
||||
Kill { name: String },
|
||||
/// Start a previously-stopped sub-agent container.
|
||||
Start { name: String },
|
||||
/// Restart a sub-agent container (stop + start).
|
||||
Restart { name: String },
|
||||
/// Rebuild a sub-agent: re-applies the current hyperhive flake +
|
||||
/// agent.nix, restarts the container. No approval required —
|
||||
/// it's idempotent and the manager owns its own update cadence.
|
||||
Update { name: String },
|
||||
/// Submit a config commit for the user to approve. `commit_ref` must
|
||||
/// be a commit sha (7-40 hex chars, short or full) in the agent's
|
||||
/// proposed config repo — a branch or tag name is rejected so the
|
||||
/// approval pins an immutable commit. On approval the host applies
|
||||
/// the change via `nixos-container update`.
|
||||
RequestApplyCommit {
|
||||
agent: String,
|
||||
commit_ref: String,
|
||||
/// Optional description shown on the dashboard approval card so the
|
||||
/// operator knows what the change does without opening the diff.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Surface a question to either the operator or another agent.
|
||||
/// Manager-flavour mirror of `AgentRequest::Ask` — routing + shape
|
||||
/// docs in `docs/conventions.md::Question routing (Ask / Answer)`.
|
||||
Ask {
|
||||
question: String,
|
||||
#[serde(default)]
|
||||
options: Vec<String>,
|
||||
#[serde(default)]
|
||||
multi: bool,
|
||||
#[serde(default)]
|
||||
ttl_seconds: Option<u64>,
|
||||
#[serde(default)]
|
||||
to: Option<String>,
|
||||
},
|
||||
/// Answer a question previously routed to the manager via
|
||||
/// `HelperEvent::QuestionAsked`. Mirror of `AgentRequest::Answer`;
|
||||
/// see `docs/conventions.md::Question routing (Ask / Answer)`.
|
||||
Answer { id: i64, answer: String },
|
||||
/// Fetch recent journal lines for a sub-agent container. `agent`
|
||||
/// is the logical agent name; hive-c0re resolves it to the
|
||||
/// machine name (`gui` → `h-gui`) and runs `journalctl -M
|
||||
/// <machine> -n <lines> --no-pager`, returning the output as a
|
||||
/// string. Useful for diagnosing MCP registration failures,
|
||||
/// startup crashes, and harness errors.
|
||||
///
|
||||
/// `lines` defaults to 50 when omitted.
|
||||
GetLogs {
|
||||
agent: String,
|
||||
#[serde(default)]
|
||||
lines: Option<u32>,
|
||||
},
|
||||
/// Mirror of `AgentRequest::Remind` on the manager surface — schedule
|
||||
/// a reminder addressed to the manager itself. Same semantics: body
|
||||
/// soft-caps at 4 KiB, oversize bodies auto-persist to
|
||||
/// `/state/reminders/auto-<ts>.md` (the manager container's own state
|
||||
/// mount) and the inbox sees a pointer.
|
||||
Remind {
|
||||
message: String,
|
||||
timing: ReminderTiming,
|
||||
#[serde(default)]
|
||||
file_path: Option<String>,
|
||||
},
|
||||
/// Loose-ends view for the manager surface. The optional `agent`
|
||||
/// field selects scope:
|
||||
/// - `None` — the manager's own loose ends: approvals it
|
||||
/// submitted + questions where it is asker/target + its own
|
||||
/// pending reminders. This is the default.
|
||||
/// - `Some("*")` — hive-wide: EVERY pending approval, unanswered
|
||||
/// question, and pending reminder across the swarm.
|
||||
/// - `Some("<name>")` — that specific agent's loose ends.
|
||||
GetLooseEnds {
|
||||
#[serde(default)]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Count of pending reminders. `agent` selects whose: `None` =
|
||||
/// the manager's own, `Some("<name>")` = that agent's. Mirror of
|
||||
/// `AgentRequest::CountPendingReminders` on the manager surface.
|
||||
CountPendingReminders {
|
||||
#[serde(default)]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Reminder statistics: counts of scheduled, delivered, and pending
|
||||
/// reminders (manager-flavour). Mirror of `AgentRequest::ReminderRollup`.
|
||||
ReminderRollup {
|
||||
/// Only count reminders created in the last N seconds from now.
|
||||
/// Pass 0 to include all reminders.
|
||||
#[serde(default)]
|
||||
since_secs: u64,
|
||||
/// Whose reminders to roll up: `None` = the manager's own,
|
||||
/// `Some("<name>")` = that agent's.
|
||||
#[serde(default)]
|
||||
agent: Option<String>,
|
||||
},
|
||||
/// Mirror of `AgentRequest::SetStatus` on the manager surface.
|
||||
SetStatus { text: String },
|
||||
/// Mirror of `AgentRequest::GetAgentMeta` on the manager surface.
|
||||
/// See `docs/conventions.md::Agent metadata`.
|
||||
GetAgentMeta {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
},
|
||||
/// Cancel an open thread (question or reminder). Manager surface
|
||||
/// can cancel any row (no owner check) — same dispatch as
|
||||
/// `AgentRequest::CancelLooseEnd` but with privileged auth.
|
||||
CancelLooseEnd { kind: CancelLooseEndKind, id: i64 },
|
||||
/// Mirror of `AgentRequest::AckTurn` on the manager surface — fired
|
||||
/// by the manager harness after `TurnOutcome::Ok` to close out
|
||||
/// every message popped during the turn.
|
||||
AckTurn,
|
||||
/// Mirror of `AgentRequest::RequeueInflight` on the manager
|
||||
/// surface — fired exactly once on manager harness boot.
|
||||
RequeueInflight,
|
||||
/// Mirror of `AgentRequest::Wake` on the manager surface. See
|
||||
/// `docs/conventions.md::Wake injection`.
|
||||
Wake { from: String, body: String },
|
||||
/// Queue an approval to run `nix flake update [inputs...]` on the
|
||||
/// meta flake. `inputs` is the list of named inputs to update
|
||||
/// (e.g. `["bitburner-agent", "nixpkgs"]`). Pass an empty list to
|
||||
/// update ALL inputs. On operator approval hive-c0re runs the lock
|
||||
/// update and commits the result. The `UpdateMetaInputs` approval
|
||||
/// resolves with `ApprovalResolved` in the manager inbox.
|
||||
RequestUpdateMetaInputs {
|
||||
#[serde(default)]
|
||||
inputs: Vec<String>,
|
||||
/// Optional description shown on the dashboard approval card.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// Queue an approval to add a scheduled prompt. The requester
|
||||
/// (caller of this request) is recorded as the schedule owner; on
|
||||
/// operator approval hive-c0re inserts the schedule and the worker
|
||||
/// fans the body out at fire time. Even agent-self schedules go
|
||||
/// through approval — the existing `remind` MCP tool is the
|
||||
/// unapproved self-wake path.
|
||||
RequestSchedulePrompt(SchedulePromptPayload),
|
||||
/// Cancel a scheduled prompt. `targets = None` cancels the whole
|
||||
/// schedule; `Some(list)` cancels just those recipients,
|
||||
/// auto-cancelling the parent when no active targets remain.
|
||||
/// Authorization: manager can cancel its own schedules + any
|
||||
/// sub-agent schedules (i.e. owner reachable via topology); the
|
||||
/// operator surface bypasses this check.
|
||||
CancelSchedule {
|
||||
id: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets: Option<Vec<String>>,
|
||||
},
|
||||
/// List every schedule in the queue. Manager-side this is
|
||||
/// unfiltered — the dashboard does the topology-filter for the
|
||||
/// per-agent view.
|
||||
ListSchedules,
|
||||
/// Fire a scheduled prompt out of band. Runs the per-target
|
||||
/// fan-out once immediately without touching
|
||||
/// `next_fire_at_unix` on recurring schedules; one-shots are
|
||||
/// consumed by the manual fire. Authorization mirrors
|
||||
/// `CancelSchedule`: the manager can fire its own schedules and
|
||||
/// any owned by a sub-agent in its subtree per topology.json;
|
||||
/// the operator surface bypasses the check.
|
||||
FireScheduleNow { id: i64 },
|
||||
/// Edit an existing schedule's mutable fields. Partial PATCH
|
||||
/// semantics: `None` / missing JSON key = leave alone,
|
||||
/// `Some(_)` = set. `interval_seconds` and `description` are
|
||||
/// doubly-wrapped so `Some(None)` (set explicit null) can flip
|
||||
/// a recurring schedule back to one-shot / clear the
|
||||
/// description, while plain `None` keeps the current value.
|
||||
/// `targets_add` / `targets_remove` mutate the recipient list
|
||||
/// in the same transaction; re-adding a previously-cancelled
|
||||
/// target drops the tombstone (replace-on-conflict). Refuses
|
||||
/// cancelled rows. Authorization mirrors `CancelSchedule`.
|
||||
EditSchedule {
|
||||
id: i64,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<Option<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
interval_seconds: Option<Option<u64>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
next_fire_at_unix: Option<i64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets_add: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
targets_remove: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Submission payload for `RequestSchedulePrompt`. Lives outside the
|
||||
/// enum so it can also serialize into the approval row's `commit_ref`
|
||||
|
|
@ -882,72 +752,3 @@ pub struct WireScheduleTarget {
|
|||
pub last_result: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum ManagerResponse {
|
||||
Ok,
|
||||
Err {
|
||||
message: String,
|
||||
},
|
||||
/// Mirror of `AgentResponse::Messages` on the manager surface.
|
||||
/// Always-list shape: 0..=max popped rows, FIFO-ordered. Carries
|
||||
/// per-row `id` + `redelivered` so the manager harness drives the
|
||||
/// same ack + requeue-with-hint flow as a sub-agent.
|
||||
Messages {
|
||||
messages: Vec<DeliveredMessage>,
|
||||
},
|
||||
Status {
|
||||
unread: u64,
|
||||
},
|
||||
/// Result of `Ask`: the queued question id. The actual answer
|
||||
/// arrives later as a `HelperEvent::QuestionAnswered` in the
|
||||
/// asker's inbox, so this returns immediately rather than blocking
|
||||
/// the turn.
|
||||
QuestionQueued {
|
||||
id: i64,
|
||||
},
|
||||
/// `Recent` result: mirror of `AgentResponse::Recent`.
|
||||
Recent {
|
||||
rows: Vec<InboxRow>,
|
||||
},
|
||||
/// `GetLogs` result: journal lines for the requested container.
|
||||
Logs {
|
||||
content: String,
|
||||
},
|
||||
/// `ListSchedules` result. Snapshot of every schedule (active +
|
||||
/// cancelled-but-not-yet-reaped); the dashboard does the
|
||||
/// per-agent topology filter on top.
|
||||
Schedules {
|
||||
schedules: Vec<WireSchedule>,
|
||||
},
|
||||
/// `GetLooseEnds` result: hive-wide loose ends (approvals +
|
||||
/// unanswered questions). Same `LooseEnd` variants as the
|
||||
/// agent surface; the manager's view is unfiltered.
|
||||
LooseEnds {
|
||||
loose_ends: Vec<LooseEnd>,
|
||||
},
|
||||
/// `CountPendingReminders` result.
|
||||
PendingRemindersCount {
|
||||
count: u64,
|
||||
},
|
||||
/// `ReminderRollup` result: reminder activity stats for the manager.
|
||||
ReminderRollup(ReminderStats),
|
||||
/// Mirror of `AgentResponse::AgentMeta` on the manager surface.
|
||||
/// See `docs/conventions.md::Agent metadata`.
|
||||
AgentMeta {
|
||||
name: String,
|
||||
role: String,
|
||||
#[serde(default = "default_true")]
|
||||
running: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
hyperhive_rev: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
status_text: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
status_set_at: Option<i64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
hive_name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
swarm_name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue