fix(tui): improve subagent picker states
This commit is contained in:
parent
02e2277057
commit
57c2c78451
1 changed files with 60 additions and 15 deletions
|
|
@ -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 (
|
||||
<Show when={composer.active("subagents")}>
|
||||
<scrollbox
|
||||
scrollbarOptions={{ visible: false }}
|
||||
maxHeight={5}
|
||||
ref={(r: ScrollBoxRenderable) => (scroll = r)}
|
||||
>
|
||||
<scrollbox scrollbarOptions={{ visible: false }} maxHeight={5} ref={(r: ScrollBoxRenderable) => (scroll = r)}>
|
||||
<Show when={entries().length > 0} fallback={<text fg={theme.textMuted}> No subagents</text>}>
|
||||
<For each={entries()}>
|
||||
{(entry, index) => {
|
||||
const active = createMemo(() => index() === selected())
|
||||
const status = createMemo(() => {
|
||||
if (entry.status === "running") return "Running"
|
||||
return ""
|
||||
})
|
||||
return (
|
||||
<box
|
||||
flexDirection="row"
|
||||
|
|
@ -222,18 +242,30 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
navigate({ type: "session", sessionID: entry.sessionID })
|
||||
}}
|
||||
>
|
||||
<box width={2} flexShrink={0}>
|
||||
<Show when={entry.status === "running" && !entry.background}>
|
||||
<Spinner color={active() ? fg : theme.textMuted} />
|
||||
</Show>
|
||||
</box>
|
||||
<box flexGrow={1} minWidth={0} flexDirection="row">
|
||||
<text
|
||||
fg={active() ? fg : theme.textMuted}
|
||||
attributes={active() ? TextAttributes.BOLD : undefined}
|
||||
wrapMode="none"
|
||||
>
|
||||
{entry.agent}{" "}
|
||||
</text>
|
||||
<text
|
||||
fg={active() ? fg : entry.current ? theme.primary : theme.text}
|
||||
attributes={active() ? TextAttributes.BOLD : undefined}
|
||||
wrapMode="none"
|
||||
>
|
||||
{entry.agent}: {entry.title}
|
||||
{entry.title}
|
||||
</text>
|
||||
</box>
|
||||
<Show when={status()}>
|
||||
<Show when={entry.status === "running" && entry.background}>
|
||||
<text fg={active() ? fg : theme.textMuted} wrapMode="none">
|
||||
{status()}
|
||||
background
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
|
|
@ -245,3 +277,16 @@ export function SubagentsTab(props: { sessionID: string }) {
|
|||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue