diff --git a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx index a6c70284..28faff3f 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/AgentsPage.tsx @@ -35,6 +35,7 @@ import { AgentTermPreview } from "./AgentTermPreview.js"; import { SubagentTerms } from "./SubagentTerms.js"; import { FRESHNESS, type AgentRow } from "./AgentTypes.js"; import { LinkedAccounts } from "./LinkedAccounts.js"; +import { LinkAccountMenu } from "./LinkAccountMenu.js"; import { Button } from "../../ui/button/Button.js"; import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js"; import { Dialog } from "../../ui/dialog/Dialog.js"; @@ -602,53 +603,14 @@ export function AgentsPage() {
- } - value="link matrix account" - onClick={ - detailTarget.hive - ? () => setMatrixTarget(detailTarget) - : undefined - } - disabled={!detailTarget.hive} - title={ - detailTarget.hive - ? `link a matrix account to ${detailTarget.name}` - : "no hive on record for this agent — nothing to link against" - } - /> - } - value="link forge account" - onClick={ - detailTarget.hive - ? () => setForgeTarget(detailTarget) - : undefined - } - disabled={!detailTarget.hive} - title={ - detailTarget.hive - ? `link an external forge account to ${detailTarget.name}` - : "no hive on record for this agent — nothing to link against" - } - /> - } - value="link github account" - onClick={ - detailTarget.hive - ? () => setGithubTarget(detailTarget) - : undefined - } - disabled={!detailTarget.hive} - title={ - detailTarget.hive - ? `link a github account to ${detailTarget.name}` - : "no hive on record for this agent — nothing to link against" - } + { + if (kind === "matrix") setMatrixTarget(detailTarget); + else if (kind === "forge") setForgeTarget(detailTarget); + else setGithubTarget(detailTarget); + }} />
{/* The agent's live terminal and its input box. */} diff --git a/frontend/packages/swarm-ui/src/pages/agents/LinkAccountMenu.tsx b/frontend/packages/swarm-ui/src/pages/agents/LinkAccountMenu.tsx new file mode 100644 index 00000000..0af8bc65 --- /dev/null +++ b/frontend/packages/swarm-ui/src/pages/agents/LinkAccountMenu.tsx @@ -0,0 +1,60 @@ +// — one "link account" badge that opens a `Dropdown` with +// the three kinds of account an agent can be linked to, in place of one +// badge per kind: three side-by-side badges were wider than the detail +// panel and spilled out of it. Presentational only, same split as +// `WantedMenu`: the caller owns what each selection opens. +import { useRef, useState } from "preact/hooks"; +import { Badge } from "@hive/shared/badge.js"; +import { Dropdown, type DropdownOption } from "@hive/shared/dropdown.js"; +import { LinkIcon } from "@hive/shared/icons.js"; + +export type LinkKind = "matrix" | "forge" | "github"; + +const OPTIONS: DropdownOption[] = [ + { value: "matrix", label: "matrix account" }, + { value: "forge", label: "forge account" }, + { value: "github", label: "github account" }, +]; + +export function LinkAccountMenu({ + agent, + hasHive, + onSelect, +}: { + agent: string; + /** Linking writes against the agent's hive, so without one there is + * nothing to link against and the menu stays shut. */ + hasHive: boolean; + onSelect: (kind: LinkKind) => void; +}) { + const [open, setOpen] = useState(false); + const anchorRef = useRef(null); + return ( +
+ } + value="link account" + onClick={hasHive ? () => setOpen((o) => !o) : undefined} + expanded={open} + disabled={!hasHive} + title={ + hasHive + ? `link a matrix, forge or github account to ${agent}` + : "no hive on record for this agent — nothing to link against" + } + /> + { + setOpen(false); + onSelect(value as LinkKind); + }} + onClose={() => setOpen(false)} + anchorRef={anchorRef} + /> +
+ ); +} diff --git a/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.css b/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.css index 3d2247a8..ec8a7a81 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.css +++ b/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.css @@ -11,7 +11,6 @@ gap: 0.5em; } -.ui-subagent-terms-heading, -.ui-subagent-terms-empty { +.ui-subagent-terms-heading { color: var(--muted); } diff --git a/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.tsx b/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.tsx index 7c1bfd67..a1a78cbf 100644 --- a/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.tsx +++ b/frontend/packages/swarm-ui/src/pages/agents/SubagentTerms.tsx @@ -37,16 +37,15 @@ export function SubagentTerms({ agentName }: { agentName: string }) { refresh().catch((e: unknown) => setError({ detail: String(e) })); }); + // An agent with no subagents shows nothing here — no heading, no + // "none" line — and so does the first fetch while it is in flight. A + // failed fetch still shows, so the error is not swallowed. + if (!error && (names === null || names.length === 0)) return null; + return (
subagents - {names === null && !error && } - {names?.length === 0 && ( - - none with output on record - - )} {names?.map((name) => (