Watch
0
0
Fork
You've already forked hyperhive
0

drop hive and agent response fields the frontend never reads

This commit is contained in:
atlas 2026-11-13 03:25:41 +01:00 • committed by mara
commit a437f9ccd5

View file

@ -7,7 +7,6 @@
use std::path::Path;
use chrono::{DateTime, Utc};
use serde::Serialize;
use crate::coordinator::Coordinator;
@ -43,24 +42,6 @@ pub struct ContainerView {
#[serde(default)]
pub failed: bool,
pub needs_login: bool,
/// The Claude model the agent's harness is currently using, read from
/// `state/hyperhive-harness.json["active_model"]`. `None` when the
/// agent has never started a turn or the field is absent. Only
/// meaningful when `running` is true; the dashboard skips the badge
/// for stopped containers.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub active_model: Option<String>,
/// The agent's current free-text status (`set_status`). `None` when
/// unset, or when the container isn't running — see
/// [`read_agent_status_live`] for why a stopped agent's on-disk
/// status is treated as stale rather than surfaced.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_text: Option<String>,
/// When `status_text` was last set, RFC 3339 UTC on the wire (see
/// `hive_sh4re::wire_time`). `None` exactly when `status_text` is
/// `None`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_set_at: Option<DateTime<Utc>>,
/// The agent's turn loop is parked: the container is up and still
/// serving its web UI / MCP daemons, but it drives no turns and its
/// inbox messages queue unacked until it resumes. Sourced from the
@ -95,9 +76,6 @@ impl From<ContainerView> for hive_sh4re::container::AgentStatusRow {
needs_login: v.needs_login,
pending_reminders: 0,
paused: v.paused,
active_model: v.active_model,
status_text: v.status_text,
status_set_at: v.status_set_at,
}
}
}
@ -173,25 +151,6 @@ pub async fn build_all() -> anyhow::Result<Vec<ContainerView>> {
let needs_login = running
&& ((!is_api_key && !claude_has_session(&Coordinator::agent_claude_dir(&logical)))
|| needs_login_sentinel);
// Read the active model from the harness state file. Only surfaced
// when the container is running — stale model info from a stopped
// agent is misleading (the model may change on next boot).
let active_model = if running {
read_active_model(&logical)
} else {
None
};
// Same running-gate as `active_model` just above, and the same
// rule `read_agent_status_live` applies for its other callers —
// duplicated inline rather than calling that helper because it
// would re-derive `running` with its own `lifecycle::is_running`
// shell-out when this loop already has it for free.
let (status_text, status_set_at) = if running {
let (text, set_at) = read_agent_status(&logical);
(text, set_at.map(hive_sh4re::wire_time::from_secs))
} else {
(None, None)
};
let paused = Coordinator::is_paused(&logical);
out.push(ContainerView {
port: lifecycle::agent_web_port(logical.as_str()),
@ -200,9 +159,6 @@ pub async fn build_all() -> anyhow::Result<Vec<ContainerView>> {
container: c.clone(),
name: logical.into_string(),
needs_login,
active_model,
status_text,
status_set_at,
paused,
});
}
@ -329,21 +285,6 @@ fn status_if_running(
(text, set_at, true)
}
/// Read the active Claude model from `hyperhive-harness.json` (the
/// consolidated harness state file in the agent's state dir). Written by
/// the harness on startup and on every `apply_model` / `emit_status` call,
/// so it always reflects the model the agent runs. Returns `None` when the
/// field is absent or the harness has not yet started a turn.
fn read_active_model(name: &hive_types::Ident) -> Option<String> {
let path = Coordinator::agent_notes_dir(name).join("hyperhive-harness.json");
let raw = std::fs::read_to_string(path).ok()?;
let v: serde_json::Value = serde_json::from_str(&raw).ok()?;
v.get("active_model")
.and_then(serde_json::Value::as_str)
.filter(|s| !s.is_empty())
.map(str::to_owned)
}
/// Host-side hive + swarm display names, read from the c0re service's
/// own process env. The hyperhive NixOS module sets these from
/// `services.hyperhive.deploy.hive-controller.hiveName` + `services.hyperhive.swarm.name`
@ -366,6 +307,34 @@ pub fn hive_swarm_names() -> (Option<String>, Option<String>) {
#[cfg(test)]
mod tests {
/// The serialised row has exactly the keys the dashboard reads; `container`
/// is internal and skipped.
#[test]
fn the_container_view_serialises_exactly_its_read_fields() {
use super::ContainerView;
let view = ContainerView {
name: "alice".to_owned(),
container: "h-alice".to_owned(),
port: 7000,
running: true,
failed: false,
needs_login: false,
paused: false,
};
let value = serde_json::to_value(&view).unwrap();
let mut keys: Vec<&str> = value
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
keys.sort_unstable();
assert_eq!(
keys,
["failed", "name", "needs_login", "paused", "port", "running"]
);
}
/// The row says nothing of an update being pending or of a deployed commit:
/// the hive applies a changed declaration on its own, so there is no state
/// for an operator to act on.
@ -379,9 +348,6 @@ mod tests {
running: true,
failed: false,
needs_login: false,
active_model: None,
status_text: None,
status_set_at: None,
paused: false,
};
let value = serde_json::to_value(&view).unwrap();
@ -403,7 +369,6 @@ mod tests {
#[test]
fn agent_status_row_carries_every_field_the_row_has_and_drops_the_rest() {
use super::ContainerView;
use chrono::{TimeZone, Utc};
use hive_sh4re::container::AgentStatusRow;
let view = ContainerView {
@ -413,9 +378,6 @@ mod tests {
running: true,
failed: false,
needs_login: false,
active_model: Some("claude-opus".to_owned()),
status_text: Some("shipping".to_owned()),
status_set_at: Some(Utc.timestamp_opt(1_700_000_000, 0).unwrap()),
paused: true,
};
@ -425,12 +387,6 @@ mod tests {
assert!(!row.failed);
assert!(!row.needs_login);
assert!(row.paused);
assert_eq!(row.active_model.as_deref(), Some("claude-opus"));
assert_eq!(row.status_text.as_deref(), Some("shipping"));
assert_eq!(
row.status_set_at,
Some(Utc.timestamp_opt(1_700_000_000, 0).unwrap())
);
// No source for reminders on this side (see the `From` impl's doc
// comment) — always the stub, never left uninitialised.
assert_eq!(row.pending_reminders, 0);