drop hive and agent response fields the frontend never reads
This commit is contained in:
parent
1875181d59
commit
a437f9ccd5
1 changed files with 347 additions and 598 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue