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
-
-
- profile
-
-
- config repo
-
-
- >
- ) : 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",
- );
-});