diff --git a/docs/2-handbook/agents/web-page.md b/docs/2-handbook/agents/web-page.md index 0fd2805c..bf37ec21 100644 --- a/docs/2-handbook/agents/web-page.md +++ b/docs/2-handbook/agents/web-page.md @@ -81,14 +81,11 @@ at a given viewport width. Two columns: - **Links badge** (`πŸ”—`): opens a popover listing `↑ dashboard` (back-link to the host dashboard, `${dashboardBase}`) followed by this agent's `agent_links()`-sourced navigation (stats, - screen when VNC is enabled, forge profile + config-repo mirror when - the agent has a forge account, any `services.hyperhive.agent.dashboardLinks` + screen when VNC is enabled, any `services.hyperhive.agent.dashboardLinks` extras) β€” all as real `` elements, not a `Dropdown`-style command list, so ctrl/middle-click and "copy link address" keep working. Each `AgentLink.kind` resolves differently: `container` β†’ - same-origin path; `forge` β†’ `state.forge_public_url + url`, and - hive-agent drops the link entirely when that's unset (never guessed from - `:3000`); `external` β†’ already absolute. Same source + same-origin path; `external` β†’ already absolute. Same source (`GET /api/state`'s `links` field) also feeds `DashboardState.links` for the dashboard card's icon strip β€” `agent_links()` in hive-agent is the single source of truth for diff --git a/docs/2-handbook/hives/README.md b/docs/2-handbook/hives/README.md index e1ca2d04..7082ad2c 100644 --- a/docs/2-handbook/hives/README.md +++ b/docs/2-handbook/hives/README.md @@ -45,8 +45,8 @@ covers it. **Check on an agent.** 4G3NTS shows every container as a row: name, whether it's running, what it's doing (a live status pill such as `rebuilding…` or -`starting…` while something's in flight), and quick links (stats, screen, -forge profile). Select the name to open its terminal and watch it work. +`starting…` while something's in flight), and quick links (stats, screen). +Select the name to open its terminal and watch it work. **Review a config change.** Agent config changes (new packages, env vars, MCP servers) are pull requests on the agent's `agent-configs` repo. Review and diff --git a/docs/2-handbook/hives/dashboard.md b/docs/2-handbook/hives/dashboard.md index ef6a5143..1ec23330 100644 --- a/docs/2-handbook/hives/dashboard.md +++ b/docs/2-handbook/hives/dashboard.md @@ -197,9 +197,8 @@ fetch entirely. **icon-only nav strip** plus live agent-owned state, all populated async from a single `GET /api/dashboard-state` call to the agent's own hive-agent. The response (`DashboardState`) carries: `links` (nav -strip entries β€” `πŸ“Š stats`, `πŸ–₯ screen` when GUI is enabled, `⬑ forge -profile`, `↳ agent-configs mirror`, plus any agent-declared -`dashboardLinks` extras), `status_text` / `status_set_at` (agent +strip entries β€” `πŸ“Š stats`, `πŸ–₯ screen` when GUI is enabled, plus any +agent-declared `dashboardLinks` extras), `status_text` / `status_set_at` (agent self-reported status β€” the `(set N ago)` chip carries a `data-set-at` stamp and ticks every 30s to stay fresh across the long-lived keyed row cache), `rate_limited`, `ctx_tokens` / `context_window_tokens` @@ -216,11 +215,9 @@ per-agent harness β€” TCP or unix-domain depending on the agent's `docs/4-implementation/networking/gateway.md::Per-agent unix-socket upstream`). Gateway-off is the flag-absent fallback (a dashboard served outside a hyperhive deploy): base URL is -`http://:` (direct TCP). Forge -links resolve against `state.forge_public_url` (sourced from -`services.hyperhive.swarm.forge.publicUrl`) and disappear entirely when -that's unset β€” never guessed from `:3000`. External links are -already absolute. The same base URL drives the primary agent-name +`http://:` (direct TCP). External links are +already absolute. The row has no forge links: an agent's forge profile +and config repo are on the forge itself. The same base URL drives the primary agent-name link + favicon fetch, so the whole row routes through the gateway as a unit. diff --git a/docs/4-implementation/networking/gateway.md b/docs/4-implementation/networking/gateway.md index 833730a3..0a153759 100644 --- a/docs/4-implementation/networking/gateway.md +++ b/docs/4-implementation/networking/gateway.md @@ -431,9 +431,7 @@ when `StateSnapshot.gateway_enabled` is true, and direct every hive, so the direct shape is a fallback for the variable being unset. Three render sites follow it: the agent-name link, the favicon fetch (`/icon`), and the nav-strip `container`-kind links from -`DashboardState.links` (`GET /api/dashboard-state`). Forge links come from -`swarm.forge.publicUrl`; with it unset the dashboard hides them rather than -guess. See `docs/2-handbook/hives/dashboard.md::Container row` for the frontend side. +`DashboardState.links` (`GET /api/dashboard-state`). See `docs/2-handbook/hives/dashboard.md::Container row` for the frontend side. ### Dialing another vhost by name (`verifiedProxyTo`) diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index 61266b49..50e6a907 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -141,7 +141,7 @@ export function Root() { count={todos.length} onClick={() => setOpenPanel("todos")} /> - {/* Needs a loaded snapshot for its data (links/forge_public_url, + {/* Needs a loaded snapshot for its data (links, * dashboard_port) β€” omitted until the first /api/state resolves, * same as the old page's populateOverflowMenu (now deleted β€” * dashboard-link moved here, rebuild removed outright, see @@ -149,7 +149,6 @@ export function Root() { {state ? ( ) : null} diff --git a/frontend/packages/agent/src/agent.css b/frontend/packages/agent/src/agent.css index 3094417e..6e471724 100644 --- a/frontend/packages/agent/src/agent.css +++ b/frontend/packages/agent/src/agent.css @@ -196,7 +196,7 @@ h3 { object-fit: cover; } -/* Meta-nav links (stats / screen / forge / dashboard / extras) β€” +/* Meta-nav links (stats / screen / dashboard / extras) β€” no underline; hover lights with cyan glow + subtle background tint. Reads as a row of soft tabs rather than default-styled inline anchors. */ diff --git a/frontend/packages/agent/src/components/MetaNav.tsx b/frontend/packages/agent/src/components/MetaNav.tsx index 585d5acd..23b8cf0c 100644 --- a/frontend/packages/agent/src/components/MetaNav.tsx +++ b/frontend/packages/agent/src/components/MetaNav.tsx @@ -2,7 +2,7 @@ // (`@hive/shared/icons.js`'s `LinkIcon` β€” an emoji glyph can't match a // neighbouring icon's size/weight on any platform, see that file's // comment) in the pills cluster that opens a popover listing -// stats/screen/forge/config + any `services.hyperhive.agent.dashboardLinks` extras, +// stats/screen + any `services.hyperhive.agent.dashboardLinks` extras, // sourced from the backend's `agent_links()` (the single source of // truth β€” same list also feeds the dashboard card's icon strip) β€” plus // a `🧭 dashboard` back-link. @@ -28,17 +28,12 @@ import "./MetaNav.css"; export interface MetaNavProps { links: AgentLink[]; - forgePublicUrl: string | null; /** Absolute base URL of the host dashboard (`resolveDashboardBase`) * β€” the `🧭 dashboard` item links to `${dashboardBase}`. */ dashboardBase: string; } -export function MetaNav({ - links, - forgePublicUrl, - dashboardBase, -}: MetaNavProps) { +export function MetaNav({ links, dashboardBase }: MetaNavProps) { const [open, setOpen] = useState(false); const rootRef = useRef(null); @@ -64,11 +59,7 @@ export function MetaNav({ }; }, [open]); - // Same kind -> URL resolution as app.js's old refreshState meta-links - // loop: `forge` needs `forgePublicUrl` set or the link is dropped - // entirely (never guessed from `:3000`); `external` is already - // absolute; `container` is a same-origin path. - const visible = links.filter((lnk) => lnk.kind !== "forge" || forgePublicUrl); + // `external` is already absolute; `container` is a same-origin path. // No early-return-on-empty: the dashboard link below is always // present, so the trigger always has at least one item. @@ -93,24 +84,20 @@ export function MetaNav({ > 🧭 dashboard - {visible.map((lnk) => { - const href = - lnk.kind === "forge" ? `${forgePublicUrl}${lnk.url}` : lnk.url; - return ( - setOpen(false)} - > - {lnk.icon ? : null} - {lnk.label} - - ); - })} + {links.map((lnk) => ( + setOpen(false)} + > + {lnk.icon ? : null} + {lnk.label} + + ))} ) : null} diff --git a/frontend/packages/agent/src/types.ts b/frontend/packages/agent/src/types.ts index e5487757..390fee76 100644 --- a/frontend/packages/agent/src/types.ts +++ b/frontend/packages/agent/src/types.ts @@ -28,11 +28,10 @@ export interface AgentState { paused: boolean; inbox: InboxRow[]; links: AgentLink[]; - forge_public_url: string | null; } // Mirrors `hive_agent::web_ui::state::AgentLink` β€” one meta-nav entry -// in the agent page header (stats/screen/forge/config + any +// in the agent page header (stats/screen + any // `services.hyperhive.agent.dashboardLinks` extras). `agent_links()` on the backend // is the single source of truth for what links an agent exposes; see // docs/2-handbook/agents/web-page.md::Header for the kind β†’ URL resolution table. @@ -40,7 +39,7 @@ export interface AgentLink { url: string; icon: string; label: string; - kind: "container" | "forge" | "external"; + kind: "container" | "external"; } export interface TokenUsage { diff --git a/frontend/packages/dashboard/src/agents.js b/frontend/packages/dashboard/src/agents.js index 33da89a9..9344ba65 100644 --- a/frontend/packages/dashboard/src/agents.js +++ b/frontend/packages/dashboard/src/agents.js @@ -270,8 +270,7 @@ function containerRowFingerprint( // changed (keyed cache), skipping the build + async dashboard-state // fetch for stable rows. function buildContainerLi(c, opts) { - const { pending, opRunning, selected, url, containerBase, forgeBase, s } = - opts; + const { pending, opRunning, selected, url, containerBase, s } = opts; // A single `pending-running` class covers any active transient // badge state (see the pending-badge derivation comment in // renderContainers). @@ -342,8 +341,7 @@ function buildContainerLi(c, opts) { // `GET /api/dashboard-state` (via gateway when enabled, direct // TCP otherwise). The agent is the single source of truth for its // link list: stats / screen (GUI agents only β€” c0re's disk-based - // fallback cannot detect this) / forge profile / agent-configs / - // extras. DOM-built β€” link strings come from the agent's process + // fallback cannot detect this) / extras. DOM-built β€” link strings come from the agent's process // and must never reach the HTML parser. const navStrip = el("span", { class: "nav-strip" }); head.append(navStrip); @@ -363,15 +361,13 @@ function buildContainerLi(c, opts) { // -- nav strip ----------------------------------------------- if (Array.isArray(ds.links)) { for (const lnk of ds.links) { - // No public forge URL to link to β€” hide rather than build - // a broken/guessed href (see forgeBase above). - if (lnk.kind === "forge" && !forgeBase) continue; + // An agent not yet rebuilt still sends forge links, which no + // page links to any more. + if (lnk.kind === "forge") continue; const href = - lnk.kind === "forge" - ? forgeBase + (lnk.url || "") - : lnk.kind === "external" - ? lnk.url || "" - : /* container */ containerBase + "/" + (lnk.url || ""); + lnk.kind === "external" + ? lnk.url || "" + : /* container */ containerBase + "/" + (lnk.url || ""); const a = el("a", { class: "nav-link", href, @@ -634,12 +630,6 @@ export function renderContainers(s) { // loopback port while the socket marker is absent). See // `docs/2-handbook/hives/dashboard.md::Container row` + `docs/4-implementation/networking/gateway.md::Vhost map`. const gatewayLinks = !!(s && s.gateway_enabled); - // Forge public URL: state.forge_public_url (set by the NixOS module - // from services.hyperhive.swarm.forge.publicUrl) or null β€” never guessed - // from ":3000". `null` propagates down to - // buildContainerLi/buildAgentMenu, which already hide forge links - // rather than emit a possibly-wrong one. - const forgeBase = (s && s.forge_public_url) || null; const ul = existingUl ?? el("ul", { class: "containers" }); const rows = sortedContainerRows(containers); @@ -691,7 +681,6 @@ export function renderContainers(s) { selected, url, containerBase, - forgeBase, s, }); containerRowCache.set(c.name, { el: li, fingerprint: fp }); diff --git a/hive-agent/src/web_ui/state.rs b/hive-agent/src/web_ui/state.rs index e9daa204..7713d448 100644 --- a/hive-agent/src/web_ui/state.rs +++ b/hive-agent/src/web_ui/state.rs @@ -47,10 +47,7 @@ pub(super) async fn api_state(State(state): State) -> axum::Json, - /// Public URL of the forge (e.g. - /// `"https://forge.pr1ma.darkest.space"`). Sourced from - /// `HIVE_FORGE_PUBLIC_URL` (set from - /// `services.hyperhive.swarm.forge.publicUrl`); `None` when unset. The frontend uses this to build - /// forge nav-strip links, and **hides** the forge link entirely - /// when absent rather than guessing `:3000`. - forge_public_url: Option, /// Human name of this hive instance (e.g. `"pr1ma"`). Sourced /// from `HYPERHIVE_HIVE_NAME`; `None` when unset. The frontend /// uses this for the page `` and header label so browser @@ -190,7 +180,7 @@ pub(super) struct StateSnapshot { /// of truth for what links an agent exposes. #[derive(Serialize)] struct AgentLink { - /// `kind = Container | Forge` β†’ path; `kind = External` β†’ full URL. + /// `kind = Container` β†’ path; `kind = External` β†’ full URL. /// The frontend prepends the right base before rendering. url: String, icon: String, @@ -209,10 +199,6 @@ enum AgentLinkKind { /// `/screen`). Agent page: same-origin path. Dashboard: /// `http://<host>:<container.port><url>`. Container, - /// `url` is a path on the local Forgejo (`/<label>`, - /// `/agent-configs/<label>`). Both surfaces: - /// `http://<host>:3000<url>`. - Forge, /// `url` is already a fully-qualified absolute URL β€” use as-is. /// Agent-declared `services.hyperhive.agent.dashboardLinks` extras arrive here. External, @@ -272,7 +258,7 @@ fn read_own_status() -> (Option<String>, Option<i64>) { } /// Build the navigation link list for the agent page header. URLs -/// are paths (relative) for `Container`/`Forge` targets and absolute +/// are paths (relative) for `Container` targets and absolute /// for `External`; the frontend resolves each against its `kind` /// against the right base so the backend never has to guess the /// operator's browser host. See @@ -282,16 +268,9 @@ fn read_own_status() -> (Option<String>, Option<i64>) { /// Icons are real emoji, not unicode arrow/geometric glyphs β€” mara: /// operator-declared `dashboardLinks` extras are already free-choice /// emoji, so the built-ins should read as the same family rather than -/// mixing in plain text glyphs (which also don't reliably render as a -/// hexagon/arrow across fonts to begin with). `config` went through two -/// rounds on this: gear (`βš™`, with and without a variation selector) -/// still rendered broken in mara's browser either way β€” same -/// font-unreliable codepoint `icons.tsx` already documents avoiding. -/// Landed on the wrench (`πŸ”§`) instead: a supplementary-plane pictograph -/// with `Emoji_Presentation=Yes`, same category as every other icon -/// here (`πŸ“Š`/`πŸ–₯`/`πŸ”¨`/`🧭`), so it doesn't need a variation selector to -/// render as color emoji in the first place. -fn agent_links(label: &str, gui_enabled: bool) -> Vec<AgentLink> { +/// mixing in plain text glyphs (which also don't reliably render across +/// fonts to begin with). +fn agent_links(gui_enabled: bool) -> Vec<AgentLink> { let mut links = Vec::new(); links.push(AgentLink { @@ -310,26 +289,6 @@ fn agent_links(label: &str, gui_enabled: bool) -> Vec<AgentLink> { }); } - // Either copy of the forge token: the one fetched from the swarm secret - // store (`HIVE_FORGE_TOKEN_FILE`), or the state-dir file the hive used - // to write. - let fetched_token = std::env::var_os("HIVE_FORGE_TOKEN_FILE") - .is_some_and(|p| std::path::Path::new(&p).is_file()); - if fetched_token || crate::paths::state_dir().join("forge-token").is_file() { - links.push(AgentLink { - url: format!("/{label}"), - icon: "πŸ”¨".to_owned(), - label: "forge".to_owned(), - kind: AgentLinkKind::Forge, - }); - links.push(AgentLink { - url: format!("/agent-configs/{label}"), - icon: "πŸ”§".to_owned(), - label: "config".to_owned(), - kind: AgentLinkKind::Forge, - }); - } - // Agent-declared extras (`services.hyperhive.agent.dashboardLinks` β†’ the // `hive-dashboard-links` NixOS oneshot writes them to // `{state_dir}/hyperhive-dashboard-links.json`). Shape on disk diff --git a/hive-c0re/src/dashboard/state_snapshot.rs b/hive-c0re/src/dashboard/state_snapshot.rs index d8a825d0..5fe0e52e 100644 --- a/hive-c0re/src/dashboard/state_snapshot.rs +++ b/hive-c0re/src/dashboard/state_snapshot.rs @@ -66,15 +66,6 @@ pub(super) struct StateSnapshot { /// `http://<hostname>:<port>/` TCP links) is retained as a defensive /// fallback for the env being unset. See `docs/4-implementation/networking/gateway.md::Vhost map`. gateway_enabled: bool, - /// Public URL of the forge (e.g. - /// `"https://forge.pr1ma.darkest.space"`). Sourced from the - /// `HIVE_FORGE_PUBLIC_URL` env var, which the c0re NixOS module - /// sets from `services.hyperhive.swarm.forge.publicUrl` (defaults to - /// the gateway vhost URL). `None` when absent β€” the frontend **hides** - /// forge links rather than guessing `http://<hostname>:3000`, - /// which is only right by accident on deployments that aren't - /// plain localhost. - forge_public_url: Option<String>, /// Browser URL of the swarm UI (the swarm apex domain). Sourced from /// `HIVE_SWARM_PUBLIC_URL`, which the c0re NixOS module sets from /// `services.hyperhive.swarm.domain`. `None` when the domain is unset β€” @@ -197,9 +188,6 @@ pub(super) async fn api_state( let s = v.to_string_lossy().to_ascii_lowercase(); matches!(s.as_str(), "1" | "true" | "yes") }), - forge_public_url: std::env::var("HIVE_FORGE_PUBLIC_URL") - .ok() - .filter(|s| !s.is_empty()), swarm_public_url: std::env::var("HIVE_SWARM_PUBLIC_URL") .ok() .filter(|s| !s.is_empty()), diff --git a/hive-meta-flake/src/lib.rs b/hive-meta-flake/src/lib.rs index 2f9ec8e4..1d0aac1d 100644 --- a/hive-meta-flake/src/lib.rs +++ b/hive-meta-flake/src/lib.rs @@ -69,7 +69,6 @@ pub const FORWARDED_VARS: &[&str] = &[ // The metrics store's origin, for `swarm-metrics`, for the same reason. "HIVE_AGENT_METRICS_URL", "HIVE_FORGE_URL", - "HIVE_FORGE_PUBLIC_URL", "HIVE_MATRIX_URL", "HYPERHIVE_SWARM_NAME", ]; diff --git a/hive-meta-flake/testdata/meta-flake-swarm.nix.in b/hive-meta-flake/testdata/meta-flake-swarm.nix.in index ab8865a0..c7df8a2f 100644 --- a/hive-meta-flake/testdata/meta-flake-swarm.nix.in +++ b/hive-meta-flake/testdata/meta-flake-swarm.nix.in @@ -33,7 +33,6 @@ HIVE_AGENT_LOGS_QUERY_URL = "@HIVE_AGENT_LOGS_QUERY_URL@"; HIVE_AGENT_METRICS_URL = "@HIVE_AGENT_METRICS_URL@"; HIVE_FORGE_URL = "@HIVE_FORGE_URL@"; - HIVE_FORGE_PUBLIC_URL = "@HIVE_FORGE_PUBLIC_URL@"; HIVE_MATRIX_URL = "@HIVE_MATRIX_URL@"; }; in diff --git a/nix/host-modules/hive-forge/service.nix b/nix/host-modules/hive-forge/service.nix index 98a90ebe..7c083bcc 100644 --- a/nix/host-modules/hive-forge/service.nix +++ b/nix/host-modules/hive-forge/service.nix @@ -78,17 +78,11 @@ in defaultText = lib.literalExpression ''"https://''${domain}"''; example = "https://forge.example.com"; description = '' - Browser-facing forge URL the dashboard uses to build clickable - forge links (the per-agent-row forge links) β€” - sourced into every agent container + hive-c0re as - `HIVE_FORGE_PUBLIC_URL`. + Browser-facing forge URL, sourced into hive-c0re as + `HIVE_FORGE_PUBLIC_URL` and published with the hive's URLs. Defaults to `https://''${cfg.domain}`, the gateway vhost. When - `null`, the dashboard **hides** forge links rather than - guessing one β€” see `docs/2-handbook/hives/dashboard.md::Container row` for - the rationale (a link built from the operator's own browser - hostname + a container port is only an accident away from - wrong on any deployment that isn't plain localhost). + `null`, the hive publishes no forge URL rather than guessing one. ''; }; diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index c87ad023..20b0c406 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -267,9 +267,6 @@ let vars = { HIVE_FORGE_URL = "http://${forgeCfg.domain}"; } - // lib.optionalAttrs (forgeCfg.publicUrl != null) { - HIVE_FORGE_PUBLIC_URL = forgeCfg.publicUrl; - } // lib.optionalAttrs (matrixCfg.gatewayHost != null) { HIVE_MATRIX_URL = "http://${matrixCfg.gatewayHost}"; } diff --git a/nix/vm-tests/full-setup.nix b/nix/vm-tests/full-setup.nix index a8bf4353..85bfacf1 100644 --- a/nix/vm-tests/full-setup.nix +++ b/nix/vm-tests/full-setup.nix @@ -49,7 +49,6 @@ let "HIVE_AGENT_BAO_SERVICES_PKI_MOUNT" "HIVE_AGENT_LOGS_QUERY_URL" "HIVE_AGENT_METRICS_URL" - "HIVE_FORGE_PUBLIC_URL" "HIVE_FORGE_URL" "HIVE_MATRIX_URL" ];