// — one agent's details: its status, wanted state, hive, config // PR, inference settings, resource caps and linked accounts, plus the actions on // them. Two layouts. In an opened card on `/agents` (`inCard`) the card header // already shows status, message and wanted state, so the body is four folded // sections (`sections.ts`): overview, settings, accounts, and the terminal with // its subagents, each built when first opened. In the detail dialog on a terminal // page it is one flat list with those three rows, and no terminal, since the // page is already beside one. // // Takes the agent's *live* row and the shared `AgentActions`, so a state // change or a link made here shows wherever else the same row is drawn. import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { Badge } from "@hive/shared/badge.js"; import { RelativeTime } from "../../ui/relative-time/RelativeTime.js"; import type { AgentActions } from "../roster/AgentActions.js"; import { AgentInference } from "./AgentInference.js"; import { AgentInstructions } from "./AgentInstructions.js"; import { AgentLimits } from "./AgentLimits.js"; import { AgentPermissions } from "./AgentPermissions.js"; import { AllowedDomains } from "./AllowedDomains.js"; import { AgentStateRow } from "./AgentStateRow.js"; import { AgentTermPreview } from "../terminal/AgentTermPreview.js"; import { FRESHNESS, type AgentRow } from "../AgentTypes.js"; import { LinkAccountMenu } from "./LinkAccountMenu.js"; import { LinkedAccounts } from "./LinkedAccounts.js"; import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js"; import { SECTION_TITLE, sectionsFor, startsOpen, type DetailSection, } from "./sections.js"; import { SubagentTerms } from "../terminal/SubagentTerms.js"; import { WantedMenu } from "../roster/WantedMenu.js"; import "./AgentDetail.css"; /** The rows the card header already shows: status, message and wanted. */ function HeaderRows({ row, actions, }: { row: AgentRow; actions: AgentActions; }) { const { tone, label } = FRESHNESS[row.freshness]; const declareError = actions.actionErrors.get(row.name); return ( <>
status
{label} {row.last_seen_unix !== null ? ( <> {" "} () ) : null} } />
message
{row.snapshot?.status_text ?? "—"}
wanted
{/* Full menu (destroy included) — mara: "destroy is already available via wanted state", no separate button needed. */} {/* A failed declaration shows here too: on a terminal page's dialog there is no roster card to carry the "failed" badge. */} {declareError ? ( ) : null}
); } function OverviewRows({ row }: { row: AgentRow }) { return ( <>
state
hive
{row.hive ?? "—"}
config PR
{row.config_pr ? ( #{row.config_pr.pr_number} ) : ( `#${row.config_pr.pr_number}` ) } /> ) : ( "—" )}
); } function SettingsRows({ agent }: { agent: string }) { return ( <>
inference
instructions
resource caps
permissions
allowed domains
); } function AccountsRows({ row, actions, }: { row: AgentRow; actions: AgentActions; }) { return ( <>
accounts
{row.hive ? ( ) : ( "—" )}
); } function LinkAccountAction({ row, actions, }: { row: AgentRow; actions: AgentActions; }) { return (
actions.openLink(row, kind)} />
); } /** The body of one folded section of an opened card. */ function SectionBody({ section, row, actions, }: { section: DetailSection; row: AgentRow; actions: AgentActions; }) { switch (section) { case "overview": return (
); case "settings": return (
); case "accounts": return ( <>
); case "terminal": return ( <> {/* The agent's live terminal and its input box. */} ); } } export function AgentDetail({ row, actions, withTerminal = false, inCard = false, }: { row: AgentRow; actions: AgentActions; /** Add the live terminal and subagents: for the flat layout, below the rows. */ withTerminal?: boolean; /** The folded sections of an opened card, without the rows its header shows. */ inCard?: boolean; }) { if (inCard) { return (
{sectionsFor(withTerminal).map((section) => ( ))}
); } return (
{withTerminal ? ( ) : null}
); }