//! Per-container HTTP UI. Phase 6 minimum — a status page on a host port. //! Containers share the host's network namespace (privateNetwork = false), so //! each instance must bind a distinct port. `HIVE_PORT` is set per agent by //! `hive-c0re`'s generated per-agent flake (deterministic from agent name). use std::net::SocketAddr; use anyhow::{Context, Result}; use axum::{Router, extract::State, response::Html, routing::get}; #[derive(Clone)] struct AppState { label: String, } pub async fn serve(label: String, port: u16) -> Result<()> { let state = AppState { label }; let app = Router::new().route("/", get(index)).with_state(state); let addr = SocketAddr::from(([0, 0, 0, 0], port)); let listener = tokio::net::TcpListener::bind(addr) .await .with_context(|| format!("bind web UI on port {port}"))?; tracing::info!(%port, "web UI listening"); axum::serve(listener, app).await?; Ok(()) } async fn index(State(state): State) -> Html { Html(format!( "\n\n\n\n{label} // hyperhive\n{STYLE}\n\n\n
░▒▓█▓▒░  {label}  ░▒▓█▓▒░  hyperhive ag3nt  ░▒▓█▓▒░
\n

◆ {label} ◆

\n
══════════════════════════════════════════════════════════════
\n

▓█▓▒░ harness alive ▓█▓▒░

\n

phase 6a placeholder — turn-loop status / inbox / xterm.js coming in 6b+

\n\n\n", label = state.label, )) } const STYLE: &str = r#" "#;