harness+dashboard: declarative dashboardLinks option (closes #191)
This commit is contained in:
parent
f510a321df
commit
66c481a07a
3 changed files with 109 additions and 1 deletions
|
|
@ -9,11 +9,22 @@ use std::collections::HashMap;
|
|||
use std::path::Path;
|
||||
|
||||
use rusqlite::Connection;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::coordinator::Coordinator;
|
||||
use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME};
|
||||
|
||||
/// An agent-declared extra navigation link surfaced on the dashboard card.
|
||||
/// Written by the `hive-dashboard-links` NixOS oneshot into
|
||||
/// `{state_dir}/hyperhive-dashboard-links.json` and read by `build_all`.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]
|
||||
pub struct DashboardLink {
|
||||
pub label: String,
|
||||
#[serde(default)]
|
||||
pub icon: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, PartialEq, Eq, Debug)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct ContainerView {
|
||||
|
|
@ -50,6 +61,13 @@ pub struct ContainerView {
|
|||
/// removes when it resumes. Stale by up to one crash-watch cycle.
|
||||
#[serde(default)]
|
||||
pub rate_limited: bool,
|
||||
/// Extra navigation links declared by the agent via
|
||||
/// `hyperhive.dashboardLinks` in `agent.nix`. Written to
|
||||
/// `{state_dir}/hyperhive-dashboard-links.json` by the
|
||||
/// `hive-dashboard-links` oneshot at container boot. Empty when
|
||||
/// the file is absent or the agent declares no links.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub extra_links: Vec<DashboardLink>,
|
||||
}
|
||||
|
||||
/// Build the full container list. Wraps `lifecycle::list()` and
|
||||
|
|
@ -87,6 +105,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
|||
.unwrap_or(0);
|
||||
let ctx_tokens = read_last_ctx_tokens(&logical);
|
||||
let rate_limited = is_rate_limited(&logical);
|
||||
let extra_links = read_dashboard_links(&logical);
|
||||
out.push(ContainerView {
|
||||
port: lifecycle::agent_web_port(&logical),
|
||||
running: lifecycle::is_running(&logical).await,
|
||||
|
|
@ -99,6 +118,7 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
|||
pending_reminders,
|
||||
ctx_tokens,
|
||||
rate_limited,
|
||||
extra_links,
|
||||
});
|
||||
}
|
||||
out
|
||||
|
|
@ -117,6 +137,18 @@ pub fn claude_has_session(dir: &Path) -> bool {
|
|||
.any(|e| e.file_type().is_ok_and(|t| t.is_file()))
|
||||
}
|
||||
|
||||
/// Read agent-declared extra dashboard links from
|
||||
/// `{state_dir}/hyperhive-dashboard-links.json`. Returns an empty vec when
|
||||
/// the file is absent, empty, or unparseable — best-effort, never panics.
|
||||
fn read_dashboard_links(name: &str) -> Vec<DashboardLink> {
|
||||
let path = Coordinator::agent_notes_dir(name).join("hyperhive-dashboard-links.json");
|
||||
let text = match std::fs::read_to_string(&path) {
|
||||
Ok(t) if !t.trim().is_empty() => t,
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
serde_json::from_str::<Vec<DashboardLink>>(&text).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns true if the agent's harness is currently parked after an API
|
||||
/// rate-limit response. Detected via the sentinel file written by
|
||||
/// `hive_ag3nt::events::Bus::emit_status("rate_limited")`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue