From 57c2c78451dcd8501983fa62f6e9c6dfb1247fd4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 12 Jul 2026 01:46:12 +0000 Subject: [PATCH] fix(tui): improve subagent picker states --- .../routes/session/composer/subagents-tab.tsx | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/packages/tui/src/routes/session/composer/subagents-tab.tsx b/packages/tui/src/routes/session/composer/subagents-tab.tsx index 70dde6dc9575..1d06737a61fa 100644 --- a/packages/tui/src/routes/session/composer/subagents-tab.tsx +++ b/packages/tui/src/routes/session/composer/subagents-tab.tsx @@ -8,12 +8,15 @@ import { useTheme, selectedForeground } from "../../../context/theme" import { Locale } from "../../../util/locale" import { useBindings, useCommandShortcut } from "../../../keymap" import { useComposerTab } from "./index" +import { Spinner } from "../../../component/spinner" +import type { SessionMessageInfo } from "@opencode-ai/client" interface SubagentEntry { sessionID: string agent: string title: string status: string + background: boolean current: boolean } @@ -26,6 +29,7 @@ export function SubagentsTab(props: { sessionID: string }) { const navigate = useRoute().navigate const composer = useComposerTab() const interruptHint = useCommandShortcut("composer.subagent.interrupt") + const backgroundHint = useCommandShortcut("composer.subagent.background") const session = createMemo(() => data.session.get(props.sessionID)) @@ -39,13 +43,18 @@ export function SubagentsTab(props: { sessionID: string }) { const siblings = data.session.list().filter((s) => s.parentID === current.parentID) for (const sibling of siblings) { const agentMatch = sibling.title.match(/@(\w+) subagent/) - const agent = sibling.agent ? Locale.titlecase(sibling.agent) : agentMatch ? Locale.titlecase(agentMatch[1]) : "Subagent" + const agent = sibling.agent + ? Locale.titlecase(sibling.agent) + : agentMatch + ? Locale.titlecase(agentMatch[1]) + : "Subagent" const name = agentMatch ? sibling.title.replace(agentMatch[0], "").trim() || sibling.title : sibling.title result.push({ sessionID: sibling.id, agent, title: name, status: data.session.status(sibling.id), + background: isBackgroundSubagent(data.session.message.list(current.parentID), sibling.id, sibling.title), current: sibling.id === route.sessionID, }) } @@ -53,13 +62,18 @@ export function SubagentsTab(props: { sessionID: string }) { const children = data.session.list().filter((s) => s.parentID === props.sessionID) for (const child of children) { const agentMatch = child.title.match(/@(\w+) subagent/) - const agent = child.agent ? Locale.titlecase(child.agent) : agentMatch ? Locale.titlecase(agentMatch[1]) : "Subagent" + const agent = child.agent + ? Locale.titlecase(child.agent) + : agentMatch + ? Locale.titlecase(agentMatch[1]) + : "Subagent" const name = agentMatch ? child.title.replace(agentMatch[0], "").trim() || child.title : child.title result.push({ sessionID: child.id, agent, title: name, status: data.session.status(child.id), + background: isBackgroundSubagent(data.session.message.list(props.sessionID), child.id, child.title), current: child.id === route.sessionID, }) } @@ -133,7 +147,10 @@ export function SubagentsTab(props: { sessionID: string }) { hints: () => { const entry = selectedEntry() if (!entry || entry.status !== "running") return [] - return [{ label: "interrupt", shortcut: interruptHint() }] + return [ + ...(!entry.background && backgroundHint() ? [{ label: "background", shortcut: backgroundHint() }] : []), + { label: "interrupt", shortcut: interruptHint() }, + ] }, onClose: () => { const parentID = session()?.parentID @@ -176,6 +193,16 @@ export function SubagentsTab(props: { sessionID: string }) { if (entry) navigate({ type: "session", sessionID: entry.sessionID }) }, }, + { + name: "composer.subagent.background", + title: "Background blocking tools", + category: "Session", + run() { + const entry = selectedEntry() + if (!entry || entry.status !== "running" || entry.background) return + void sdk.api.session.background({ sessionID: session()?.parentID ?? props.sessionID }) + }, + }, { name: "composer.subagent.interrupt", title: "Interrupt subagent", @@ -191,25 +218,18 @@ export function SubagentsTab(props: { sessionID: string }) { { key: "up", desc: "Previous subagent", group: "Subagents", cmd: "composer.subagent.up" }, { key: "down", desc: "Next subagent", group: "Subagents", cmd: "composer.subagent.down" }, { key: "return", desc: "Navigate to subagent", group: "Subagents", cmd: "composer.subagent.select" }, + { key: "ctrl+b", desc: "Background subagent", group: "Subagents", cmd: "composer.subagent.background" }, { key: "ctrl+d", desc: "Interrupt subagent", group: "Subagents", cmd: "composer.subagent.interrupt" }, ], })) return ( - (scroll = r)} - > + (scroll = r)}> 0} fallback={ No subagents}> {(entry, index) => { const active = createMemo(() => index() === selected()) - const status = createMemo(() => { - if (entry.status === "running") return "Running" - return "" - }) return ( + + + + + + + {entry.agent}{" "} + - {entry.agent}: {entry.title} + {entry.title} - + - {status()} + background @@ -245,3 +277,16 @@ export function SubagentsTab(props: { sessionID: string }) { ) } + +function isBackgroundSubagent(messages: SessionMessageInfo[], sessionID: string, title: string) { + return messages.some((message) => + message.type === "assistant" + ? message.content.some((part) => { + if (part.type !== "tool" || part.name !== "subagent" || part.state.status === "streaming") return false + if (part.state.input.background === true && part.state.input.description === title) return true + if (part.state.status === "running") return false + return part.state.structured.sessionID === sessionID && part.state.structured.status === "running" + }) + : false, + ) +}