diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md index 83c75584..4d43f5d9 100644 --- a/docs/swarm/ui.md +++ b/docs/swarm/ui.md @@ -39,9 +39,7 @@ terminal, and below it the agent's **subagents**: every subagent the swarm queue holds terminal rows for from the last 24 hours. Picking one shows its terminal in the same preview, and **expand** opens it in its own tab, as it does for the agent. The list refreshes every 10 seconds, so a subagent the -agent spawns shows up once it writes output. A subagent that drops out of the -list closes its terminal, and a later subagent with the same name starts -unpicked. +agent spawns shows up once it writes output. On a terminal's own page (`/agents//terminal`, or a subagent's) the header has an **N running subagents** badge. It opens a list of the agent's diff --git a/frontend/packages/swarm-ui/src/agent/terminal/SubagentTerms.tsx b/frontend/packages/swarm-ui/src/agent/terminal/SubagentTerms.tsx index 718a005a..a053d2bf 100644 --- a/frontend/packages/swarm-ui/src/agent/terminal/SubagentTerms.tsx +++ b/frontend/packages/swarm-ui/src/agent/terminal/SubagentTerms.tsx @@ -8,27 +8,18 @@ // agent's own: subagents take no input. // // Mount it keyed by agent name: the refresh timer is armed once per mount. -import { useEffect, useState } from "preact/hooks"; +import { useState } from "preact/hooks"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { Badge } from "@hive/shared/badge.js"; import { AgentTermPreview } from "./AgentTermPreview.js"; -import { STALE_S, stillListed } from "./subagent-list.js"; +import { STALE_S } from "./subagent-list.js"; import { useSubagentList } from "./useSubagentList.js"; import { RelativeTime } from "../../ui/relative-time/RelativeTime.js"; import "./SubagentTerms.css"; export function SubagentTerms({ agentName }: { agentName: string }) { const { subagents, error } = useSubagentList(agentName); - const [picked, setPicked] = useState(null); - // The refresh can drop the picked subagent (its stream is gone). Only a - // name still in the list counts as selected, so the terminal closes with - // its badge and a later subagent of the same name does not reopen it. - const selected = stillListed(picked, subagents); - useEffect(() => { - if (picked !== null && subagents !== null && selected === null) { - setPicked(null); - } - }, [picked, subagents, selected]); + const [selected, setSelected] = useState(null); // 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 @@ -58,7 +49,7 @@ export function SubagentTerms({ agentName }: { agentName: string }) { ? `hide ${name}'s terminal` : `show ${name}'s terminal` } - onClick={() => setPicked(name === selected ? null : name)} + onClick={() => setSelected(name === selected ? null : name)} /> {last_row_unix !== null && ( diff --git a/frontend/packages/swarm-ui/src/agent/terminal/subagent-list.ts b/frontend/packages/swarm-ui/src/agent/terminal/subagent-list.ts index 6904b51c..9de9262c 100644 --- a/frontend/packages/swarm-ui/src/agent/terminal/subagent-list.ts +++ b/frontend/packages/swarm-ui/src/agent/terminal/subagent-list.ts @@ -48,18 +48,3 @@ export function splitRunning( export function runningLabel(count: number): string { return count === 1 ? "1 running subagent" : `${count} running subagents`; } - -/** - * The picked subagent while the list still holds it, else `null`. The list - * refreshes on a timer and can drop a subagent whose stream is gone, so a - * pick outlives its badge unless it is checked against the list. `null` - * list (not fetched yet) holds no pick either. - */ -export function stillListed( - picked: string | null, - subs: Subagent[] | null, -): string | null { - return picked !== null && subs?.some((s) => s.name === picked) - ? picked - : null; -} diff --git a/frontend/packages/swarm-ui/test/subagent-list.test.mjs b/frontend/packages/swarm-ui/test/subagent-list.test.mjs index dcf1f282..79ad2fb6 100644 --- a/frontend/packages/swarm-ui/test/subagent-list.test.mjs +++ b/frontend/packages/swarm-ui/test/subagent-list.test.mjs @@ -9,7 +9,6 @@ import { isRunning, runningLabel, splitRunning, - stillListed, } from "../src/agent/terminal/subagent-list.ts"; const NOW = 1_000_000; @@ -60,12 +59,3 @@ test("the badge counts in words", () => { assert.equal(runningLabel(1), "1 running subagent"); assert.equal(runningLabel(4), "4 running subagents"); }); - -test("a pick holds only while the refreshed list still has it", () => { - const subs = [sub("a", 10), sub("b", 20)]; - assert.equal(stillListed("a", subs), "a"); - assert.equal(stillListed("gone", subs), null); - assert.equal(stillListed("a", []), null); - assert.equal(stillListed("a", null), null); - assert.equal(stillListed(null, subs), null); -});