deploy the closure the swarm declares, read no forge head, drop the update badge
This commit is contained in:
parent
279428a66f
commit
47c2acb640
1 changed files with 647 additions and 2001 deletions
|
|
@ -42,12 +42,7 @@ pub struct ContainerView {
|
|||
/// up is indistinguishable from one an operator stopped on purpose.
|
||||
#[serde(default)]
|
||||
pub failed: bool,
|
||||
pub needs_update: bool,
|
||||
pub needs_login: bool,
|
||||
/// First 12 chars of the config commit the agent's last successful build
|
||||
/// pinned (`paths::applied_config_marker`).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployed_sha: Option<String>,
|
||||
/// 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
|
||||
|
|
@ -97,9 +92,7 @@ impl From<ContainerView> for hive_sh4re::container::AgentStatusRow {
|
|||
name: v.name,
|
||||
running: v.running,
|
||||
failed: v.failed,
|
||||
needs_update: v.needs_update,
|
||||
needs_login: v.needs_login,
|
||||
deployed_sha: v.deployed_sha,
|
||||
pending_reminders: 0,
|
||||
paused: v.paused,
|
||||
active_model: v.active_model,
|
||||
|
|
@ -153,11 +146,6 @@ pub async fn build_all() -> anyhow::Result<Vec<ContainerView>> {
|
|||
let Ok(logical) = hive_types::Ident::parse(logical) else {
|
||||
continue;
|
||||
};
|
||||
let deployed_full =
|
||||
std::fs::read_to_string(crate::paths::applied_config_marker(logical.as_str())).ok();
|
||||
let needs_update =
|
||||
crate::auto_update::agent_config_pending(logical.as_str(), deployed_full.as_deref());
|
||||
let deployed_sha = deployed_full.as_deref().map(short_rev);
|
||||
// One `systemctl` call for both facts: `unit_state` is the same
|
||||
// shell-out `is_running` makes, minus `--quiet`. Asking twice would
|
||||
// double the per-agent subprocess count on every SSE scan.
|
||||
|
|
@ -211,9 +199,7 @@ pub async fn build_all() -> anyhow::Result<Vec<ContainerView>> {
|
|||
failed,
|
||||
container: c.clone(),
|
||||
name: logical.into_string(),
|
||||
needs_update,
|
||||
needs_login,
|
||||
deployed_sha,
|
||||
active_model,
|
||||
status_text,
|
||||
status_set_at,
|
||||
|
|
@ -378,27 +364,36 @@ pub fn hive_swarm_names() -> (Option<String>, Option<String>) {
|
|||
(read("HYPERHIVE_HIVE_NAME"), read("HYPERHIVE_SWARM_NAME"))
|
||||
}
|
||||
|
||||
/// First 12 characters of a rev. Counts chars, not bytes, so a byte slice at 12
|
||||
/// cannot split a multi-byte char.
|
||||
fn short_rev(rev: &str) -> String {
|
||||
rev.chars().take(12).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::short_rev;
|
||||
|
||||
/// 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.
|
||||
#[test]
|
||||
fn short_rev_truncates_and_keeps_short() {
|
||||
assert_eq!(short_rev("0123456789abcdef"), "0123456789ab");
|
||||
assert_eq!(short_rev("abc123"), "abc123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_rev_survives_multibyte_at_boundary() {
|
||||
// 'é' is 2 bytes and straddles byte index 12.
|
||||
let rev = "0123456789aébcdef";
|
||||
assert_eq!(short_rev(rev), "0123456789aé");
|
||||
fn the_row_carries_no_update_state() {
|
||||
use super::ContainerView;
|
||||
let view = ContainerView {
|
||||
name: "alice".to_owned(),
|
||||
container: "h-alice".to_owned(),
|
||||
port: 7000,
|
||||
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();
|
||||
assert_eq!(value["running"], true, "control: the lookup finds fields");
|
||||
for gone in ["needs_update", "deployed_sha"] {
|
||||
assert!(value.get(gone).is_none(), "{gone} is still serialized");
|
||||
}
|
||||
let row = serde_json::to_value(hive_sh4re::container::AgentStatusRow::from(view)).unwrap();
|
||||
assert_eq!(row["running"], true);
|
||||
for gone in ["needs_update", "deployed_sha"] {
|
||||
assert!(row.get(gone).is_none(), "{gone} is still serialized");
|
||||
}
|
||||
}
|
||||
|
||||
/// The `From` impl both `handle_agent_status`'s poll path and
|
||||
|
|
@ -417,9 +412,7 @@ mod tests {
|
|||
port: 7000,
|
||||
running: true,
|
||||
failed: false,
|
||||
needs_update: true,
|
||||
needs_login: false,
|
||||
deployed_sha: Some("abc123def456".to_owned()),
|
||||
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()),
|
||||
|
|
@ -430,9 +423,7 @@ mod tests {
|
|||
assert_eq!(row.name, "alice");
|
||||
assert!(row.running);
|
||||
assert!(!row.failed);
|
||||
assert!(row.needs_update);
|
||||
assert!(!row.needs_login);
|
||||
assert_eq!(row.deployed_sha.as_deref(), Some("abc123def456"));
|
||||
assert!(row.paused);
|
||||
assert_eq!(row.active_model.as_deref(), Some("claude-opus"));
|
||||
assert_eq!(row.status_text.as_deref(), Some("shipping"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue