Compare commits
3 changed files with 1 additions and 105 deletions
|
|
@ -723,21 +723,6 @@
|
||||||
}
|
}
|
||||||
body.append(drill);
|
body.append(drill);
|
||||||
|
|
||||||
// ── extra agent-declared links ───────────────────────────────
|
|
||||||
if (c.extra_links && c.extra_links.length > 0) {
|
|
||||||
const extras = el('div', { class: 'drill-ins drill-ins-extra' });
|
|
||||||
for (const lnk of c.extra_links) {
|
|
||||||
extras.append(el('a', {
|
|
||||||
class: 'panel-trigger',
|
|
||||||
href: lnk.url,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noopener',
|
|
||||||
title: lnk.url,
|
|
||||||
}, (lnk.icon ? lnk.icon + ' ' : '') + lnk.label + ' ↗'));
|
|
||||||
}
|
|
||||||
body.append(extras);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.append(icon, body);
|
li.append(icon, body);
|
||||||
ul.append(li);
|
ul.append(li);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,11 @@ use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::coordinator::Coordinator;
|
use crate::coordinator::Coordinator;
|
||||||
use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_NAME};
|
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)]
|
#[derive(Serialize, Clone, PartialEq, Eq, Debug)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct ContainerView {
|
pub struct ContainerView {
|
||||||
|
|
@ -61,13 +50,6 @@ pub struct ContainerView {
|
||||||
/// removes when it resumes. Stale by up to one crash-watch cycle.
|
/// removes when it resumes. Stale by up to one crash-watch cycle.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub rate_limited: bool,
|
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
|
/// Build the full container list. Wraps `lifecycle::list()` and
|
||||||
|
|
@ -105,7 +87,6 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let ctx_tokens = read_last_ctx_tokens(&logical);
|
let ctx_tokens = read_last_ctx_tokens(&logical);
|
||||||
let rate_limited = is_rate_limited(&logical);
|
let rate_limited = is_rate_limited(&logical);
|
||||||
let extra_links = read_dashboard_links(&logical);
|
|
||||||
out.push(ContainerView {
|
out.push(ContainerView {
|
||||||
port: lifecycle::agent_web_port(&logical),
|
port: lifecycle::agent_web_port(&logical),
|
||||||
running: lifecycle::is_running(&logical).await,
|
running: lifecycle::is_running(&logical).await,
|
||||||
|
|
@ -118,7 +99,6 @@ pub async fn build_all(coord: &Coordinator) -> Vec<ContainerView> {
|
||||||
pending_reminders,
|
pending_reminders,
|
||||||
ctx_tokens,
|
ctx_tokens,
|
||||||
rate_limited,
|
rate_limited,
|
||||||
extra_links,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -137,18 +117,6 @@ pub fn claude_has_session(dir: &Path) -> bool {
|
||||||
.any(|e| e.file_type().is_ok_and(|t| t.is_file()))
|
.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
|
/// Returns true if the agent's harness is currently parked after an API
|
||||||
/// rate-limit response. Detected via the sentinel file written by
|
/// rate-limit response. Detected via the sentinel file written by
|
||||||
/// `hive_ag3nt::events::Bus::emit_status("rate_limited")`.
|
/// `hive_ag3nt::events::Bus::emit_status("rate_limited")`.
|
||||||
|
|
|
||||||
|
|
@ -173,39 +173,6 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
options.hyperhive.dashboardLinks = lib.mkOption {
|
|
||||||
type = lib.types.listOf (lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
label = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Display label for the link.";
|
|
||||||
};
|
|
||||||
icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "";
|
|
||||||
description = "Optional icon emoji or short glyph.";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Full URL (may include a different port, e.g. http://localhost:9001/stats).";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
default = [ ];
|
|
||||||
example = lib.literalExpression ''
|
|
||||||
[
|
|
||||||
{ label = "Stats"; icon = "📊"; url = "http://localhost:9001/stats"; }
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
description = ''
|
|
||||||
Extra navigation links surfaced on the hive-c0re dashboard card for
|
|
||||||
this agent. Declare any additional web UI pages the agent exposes —
|
|
||||||
stats pages, custom UIs, etc. hive-c0re reads the JSON file this
|
|
||||||
option produces at each container-view snapshot and attaches the
|
|
||||||
links to the agent card without any code changes.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
options.hyperhive.claudeMarketplaces = lib.mkOption {
|
options.hyperhive.claudeMarketplaces = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ "anthropics/claude-plugins-official" ];
|
default = [ "anthropics/claude-plugins-official" ];
|
||||||
|
|
@ -513,30 +480,6 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# Write declared dashboardLinks to the state dir so hive-c0re can read
|
|
||||||
# them without accessing the container's /etc/ from the host.
|
|
||||||
# Runs every boot; idempotent (overwrite). Always exits 0.
|
|
||||||
systemd.services.hive-dashboard-links = lib.mkIf (config.hyperhive.dashboardLinks != [ ]) {
|
|
||||||
description = "write declarative dashboardLinks to agent state dir";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
environment.LINKS_JSON = builtins.toJSON config.hyperhive.dashboardLinks;
|
|
||||||
script = ''
|
|
||||||
# Sub-agents have their state dir bind-mounted at /agents/<name>/state.
|
|
||||||
# Use a glob — exactly one match per container at runtime.
|
|
||||||
STATE_DIR=$(echo /agents/*/state)
|
|
||||||
if [ ! -d "$STATE_DIR" ]; then
|
|
||||||
echo "hive-dashboard-links: no state dir found at /agents/*/state; skipping"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
printf '%s' "$LINKS_JSON" > "$STATE_DIR/hyperhive-dashboard-links.json"
|
|
||||||
echo "hive-dashboard-links: wrote $(printf '%s' "$LINKS_JSON" | wc -c) bytes to $STATE_DIR/hyperhive-dashboard-links.json"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Git is needed by claude's Bash tool (for the agent <-> manager config
|
# Git is needed by claude's Bash tool (for the agent <-> manager config
|
||||||
# request flow) and by hive-c0re's own setup_applied / setup_proposed.
|
# request flow) and by hive-c0re's own setup_applied / setup_proposed.
|
||||||
# The per-agent `applied/<name>/flake.nix` overrides `user.name` and
|
# The per-agent `applied/<name>/flake.nix` overrides `user.name` and
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue