diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index 9800402fe..ada2ef6e6 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -73,10 +73,8 @@ and config PR. An opened card has four sections, each folded except **overview**, and each reads nothing until you open it: -- **overview**: the agent's turn state, context and cost, its hive, links to its - profile and its config repo on the swarm's forge (built from the forge entry - of `GET /api/links`; the row stays hidden when the swarm lists none), and its - open config PR. +- **overview**: the agent's turn state, context and cost, its hive, and its open + config PR. - **settings**: inference, instructions, resource caps, permissions and allowed domains, each with its own editor (described below). - **accounts**: the agent's linked accounts, and the menu that links another. diff --git a/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.css b/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.css index 0fb4be5d7..289b1d5bf 100644 --- a/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.css +++ b/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.css @@ -12,11 +12,6 @@ .ui-agent-detail-fields dd { margin: 0; } -.ui-agent-detail-links { - display: flex; - flex-wrap: wrap; - gap: 0.4em 1em; -} .ui-agent-detail-actions { display: flex; align-items: center; diff --git a/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.tsx b/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.tsx index ad8ac91b8..bae373eff 100644 --- a/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.tsx +++ b/frontend/packages/swarm-ui/src/agent/detail/AgentDetail.tsx @@ -22,8 +22,6 @@ import { AllowedDomains } from "./AllowedDomains.js"; import { AgentStateRow } from "./AgentStateRow.js"; import { AgentTermPreview } from "../terminal/AgentTermPreview.js"; import { canTurnIntoCustom, FRESHNESS, type AgentRow } from "../AgentTypes.js"; -import { agentForgeLinks } from "./forge-links.js"; -import { useForgeBase } from "./useForgeBase.js"; import { LinkAccountMenu } from "./LinkAccountMenu.js"; import { LinkedAccounts } from "./LinkedAccounts.js"; import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js"; @@ -100,8 +98,6 @@ function OverviewRows({ row: AgentRow; actions: AgentActions; }) { - const forge = useForgeBase(); - const forgeLinks = forge ? agentForgeLinks(forge, row.name) : null; return ( <>
state
@@ -110,19 +106,6 @@ function OverviewRows({
hive
{row.hive ?? "—"}
- {forgeLinks ? ( - <> -
forge
- - - ) : null}
config PR
{row.config_pr ? ( diff --git a/frontend/packages/swarm-ui/src/agent/detail/forge-links.ts b/frontend/packages/swarm-ui/src/agent/detail/forge-links.ts deleted file mode 100644 index 511c1d4a3..000000000 --- a/frontend/packages/swarm-ui/src/agent/detail/forge-links.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Where an agent lives on the swarm's forge, without a DOM: its profile and its -// config repo. The agent's forge login is its name, and its config repo is -// `agent-configs/`. The forge's address is the swarm's "Forge" entry in -// `GET /api/links`, the same list the header's links menu shows. -import type { MenuLink } from "@hive/shared/links-menu.js"; - -export type AgentForgeLinks = { profile: string; configRepo: string }; - -/** The forge's base URL from the swarm's service links, or null without one. */ -export function forgeBase(links: MenuLink[]): string | null { - const forge = links.find((l) => l.label.trim().toLowerCase() === "forge"); - return forge ? forge.url.replace(/\/+$/, "") : null; -} - -/** The agent's profile and config repo on the forge at `base`. */ -export function agentForgeLinks(base: string, agent: string): AgentForgeLinks { - const name = encodeURIComponent(agent); - return { - profile: `${base}/${name}`, - configRepo: `${base}/agent-configs/${name}`, - }; -} diff --git a/frontend/packages/swarm-ui/src/agent/detail/useForgeBase.ts b/frontend/packages/swarm-ui/src/agent/detail/useForgeBase.ts deleted file mode 100644 index 13b3ebb26..000000000 --- a/frontend/packages/swarm-ui/src/agent/detail/useForgeBase.ts +++ /dev/null @@ -1,40 +0,0 @@ -// The swarm forge's base URL, from `GET /api/links`. Fetched once and shared by -// every agent card, so opening many cards sends one request. A failed fetch -// is not kept, so the next card retries; the links just stay hidden meanwhile. -import { useEffect, useState } from "preact/hooks"; -import type { MenuLink } from "@hive/shared/links-menu.js"; -import { forgeBase } from "./forge-links.js"; - -let pending: Promise | null = null; - -function loadForgeBase(): Promise { - if (!pending) { - pending = fetch("/api/links") - .then((r) => - r.ok - ? (r.json() as Promise) - : Promise.reject(new Error(`http ${r.status}`)), - ) - .then(forgeBase) - .catch(() => { - pending = null; - return null; - }); - } - return pending; -} - -/** The forge's base URL, or null until known (or when the swarm lists none). */ -export function useForgeBase(): string | null { - const [base, setBase] = useState(null); - useEffect(() => { - let live = true; - void loadForgeBase().then((b) => { - if (live) setBase(b); - }); - return () => { - live = false; - }; - }, []); - return base; -} diff --git a/frontend/packages/swarm-ui/test/forge-links.test.mjs b/frontend/packages/swarm-ui/test/forge-links.test.mjs deleted file mode 100644 index 94389ad66..000000000 --- a/frontend/packages/swarm-ui/test/forge-links.test.mjs +++ /dev/null @@ -1,41 +0,0 @@ -// Where an agent lives on the swarm's forge: its profile and config repo, from -// the forge entry of the swarm's service links. Run by `npm test` with Node's -// type stripping. -import assert from "node:assert/strict"; -import { test } from "node:test"; -import { agentForgeLinks, forgeBase } from "../src/agent/detail/forge-links.ts"; - -const link = (label, url) => ({ label, icon: "x", url }); - -test("the forge base is the forge entry's URL, any case, without a trailing slash", () => { - assert.equal( - forgeBase([ - link("Grafana", "https://grafana.example/"), - link("Forge", "https://forge.example/"), - ]), - "https://forge.example", - ); - assert.equal( - forgeBase([link("forge", "https://forge.example")]), - "https://forge.example", - ); -}); - -test("no forge entry gives no base", () => { - assert.equal(forgeBase([]), null); - assert.equal(forgeBase([link("Grafana", "https://grafana.example/")]), null); -}); - -test("an agent's profile and config repo sit under the forge base", () => { - assert.deepEqual(agentForgeLinks("https://forge.example", "iris"), { - profile: "https://forge.example/iris", - configRepo: "https://forge.example/agent-configs/iris", - }); -}); - -test("the agent name is encoded", () => { - assert.equal( - agentForgeLinks("https://forge.example", "a b").profile, - "https://forge.example/a%20b", - ); -});