feat(tui): filter subagents by activity
This commit is contained in:
parent
90100c1365
commit
12a931a220
3 changed files with 63 additions and 22 deletions
|
|
@ -767,19 +767,23 @@ export function RunSubagentSelectBody(props: {
|
||||||
onRows?: (rows: number) => void
|
onRows?: (rows: number) => void
|
||||||
mono?: boolean
|
mono?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
const [active, setActive] = createSignal(true)
|
||||||
const entries = createMemo<SubagentEntry[]>(() =>
|
const entries = createMemo<SubagentEntry[]>(() =>
|
||||||
props.tabs().map((item) => {
|
props
|
||||||
const title = item.description || item.title || item.label
|
.tabs()
|
||||||
return {
|
.filter((item) => (active() ? item.status === "running" : item.status !== "running"))
|
||||||
category: "",
|
.map((item) => {
|
||||||
display: title,
|
const title = item.description || item.title || item.label
|
||||||
description: title === item.label ? undefined : item.label,
|
return {
|
||||||
footer: subagentStatusLabel(item.status),
|
category: "",
|
||||||
keywords: `${item.label} ${item.description} ${item.title ?? ""} ${item.status}`,
|
display: title,
|
||||||
sessionID: item.sessionID,
|
description: title === item.label ? undefined : item.label,
|
||||||
current: props.current() === item.sessionID,
|
footer: subagentStatusLabel(item.status),
|
||||||
}
|
keywords: `${item.label} ${item.description} ${item.title ?? ""} ${item.status}`,
|
||||||
}),
|
sessionID: item.sessionID,
|
||||||
|
current: props.current() === item.sessionID,
|
||||||
|
}
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
const controller = createSearchablePanelController({
|
const controller = createSearchablePanelController({
|
||||||
entries,
|
entries,
|
||||||
|
|
@ -788,6 +792,12 @@ export function RunSubagentSelectBody(props: {
|
||||||
onSelect: (item) => props.onSelect(item.sessionID),
|
onSelect: (item) => props.onSelect(item.sessionID),
|
||||||
isCurrent: (item) => item.current,
|
isCurrent: (item) => item.current,
|
||||||
closeOnFirstUp: true,
|
closeOnFirstUp: true,
|
||||||
|
onKey(event) {
|
||||||
|
if (event.name.toLowerCase() !== "tab") return false
|
||||||
|
event.preventDefault()
|
||||||
|
setActive((value) => !value)
|
||||||
|
return true
|
||||||
|
},
|
||||||
onRows: props.onRows,
|
onRows: props.onRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -801,6 +811,7 @@ export function RunSubagentSelectBody(props: {
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
inputRef={controller.inputRef}
|
inputRef={controller.inputRef}
|
||||||
onQuery={controller.setQuery}
|
onQuery={controller.setQuery}
|
||||||
|
hint={`tab show ${active() ? "inactive" : "active"}`}
|
||||||
mono={props.mono}
|
mono={props.mono}
|
||||||
>
|
>
|
||||||
<RunFooterMenu
|
<RunFooterMenu
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
const shortcuts = Keymap.useShortcuts()
|
const shortcuts = Keymap.useShortcuts()
|
||||||
|
|
||||||
const session = createMemo(() => data.session.get(props.sessionID))
|
const session = createMemo(() => data.session.get(props.sessionID))
|
||||||
|
const [store, setStore] = createStore({ selected: 0, active: true })
|
||||||
|
|
||||||
const entries = createMemo<SubagentEntry[]>(() => {
|
const entries = createMemo<SubagentEntry[]>(() => {
|
||||||
const current = session()
|
const current = session()
|
||||||
|
|
@ -72,10 +73,9 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result.filter((entry) => (store.active ? entry.status === "running" : entry.status !== "running"))
|
||||||
})
|
})
|
||||||
|
|
||||||
const [store, setStore] = createStore({ selected: 0 })
|
|
||||||
let selectedSessionID = ""
|
let selectedSessionID = ""
|
||||||
let wasActive = false
|
let wasActive = false
|
||||||
let scroll: ScrollBoxRenderable | undefined
|
let scroll: ScrollBoxRenderable | undefined
|
||||||
|
|
@ -90,7 +90,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
if (!active) {
|
if (!active) {
|
||||||
if (wasActive) {
|
if (wasActive) {
|
||||||
selectedSessionID = ""
|
selectedSessionID = ""
|
||||||
setStore("selected", 0)
|
setStore({ selected: 0, active: true })
|
||||||
}
|
}
|
||||||
wasActive = false
|
wasActive = false
|
||||||
return
|
return
|
||||||
|
|
@ -140,8 +140,15 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
label: "Subagents",
|
label: "Subagents",
|
||||||
hints: () => {
|
hints: () => {
|
||||||
const entry = selectedEntry()
|
const entry = selectedEntry()
|
||||||
if (!entry || entry.status !== "running") return []
|
return [
|
||||||
return [{ label: "interrupt", shortcut: shortcuts.get("composer.subagent.interrupt") ?? "" }]
|
...(entry?.status === "running"
|
||||||
|
? [{ label: "interrupt", shortcut: shortcuts.get("composer.subagent.interrupt") ?? "" }]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
label: `show ${store.active ? "inactive" : "active"}`,
|
||||||
|
shortcut: shortcuts.get("composer.subagent.toggle-activity") ?? "",
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
const parentID = session()?.parentID
|
const parentID = session()?.parentID
|
||||||
|
|
@ -189,6 +196,16 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
if (entry) navigate({ type: "session", sessionID: entry.sessionID })
|
if (entry) navigate({ type: "session", sessionID: entry.sessionID })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "composer.subagent.toggle-activity",
|
||||||
|
title: "Toggle active subagents",
|
||||||
|
group: "Composer",
|
||||||
|
bind: "ctrl+a",
|
||||||
|
run() {
|
||||||
|
setStore({ selected: 0, active: !store.active })
|
||||||
|
scroll?.scrollTo(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "composer.subagent.interrupt",
|
id: "composer.subagent.interrupt",
|
||||||
title: "Interrupt subagent",
|
title: "Interrupt subagent",
|
||||||
|
|
@ -206,7 +223,10 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
return (
|
return (
|
||||||
<Show when={composer.active("subagents")}>
|
<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.text.subdued}> No subagents</text>}>
|
<Show
|
||||||
|
when={entries().length > 0}
|
||||||
|
fallback={<text fg={theme.text.subdued}> No {store.active ? "active" : "inactive"} subagents</text>}
|
||||||
|
>
|
||||||
<For each={entries()}>
|
<For each={entries()}>
|
||||||
{(entry, index) => {
|
{(entry, index) => {
|
||||||
const active = createMemo(() => index() === selected())
|
const active = createMemo(() => index() === selected())
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,7 @@ test("direct command panel keeps completed subagents available", async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("direct subagent panel renders active subagents", async () => {
|
test("direct subagent panel toggles between active and inactive subagents", async () => {
|
||||||
const [tabs] = createSignal([
|
const [tabs] = createSignal([
|
||||||
subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow" }),
|
subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow" }),
|
||||||
subagent({ sessionID: "s-2", label: "General", description: "Write migration plan", status: "completed" }),
|
subagent({ sessionID: "s-2", label: "General", description: "Write migration plan", status: "completed" }),
|
||||||
|
|
@ -856,12 +856,22 @@ test("direct subagent panel renders active subagents", async () => {
|
||||||
|
|
||||||
expect(frame).toContain("Select subagent")
|
expect(frame).toContain("Select subagent")
|
||||||
expect(frame).toContain("Inspect auth flow")
|
expect(frame).toContain("Inspect auth flow")
|
||||||
expect(frame).toContain("Write migration plan")
|
expect(frame).not.toContain("Write migration plan")
|
||||||
expect(frame).toContain("done")
|
expect(frame).not.toContain("done")
|
||||||
|
expect(frame).toContain("tab show inactive")
|
||||||
expect(frame).not.toContain("┌")
|
expect(frame).not.toContain("┌")
|
||||||
expect(frame).not.toContain("┃")
|
expect(frame).not.toContain("┃")
|
||||||
expectPaletteList(list, 0)
|
expectPaletteList(list, 0)
|
||||||
expect(rows).toBe(8)
|
expect(rows).toBe(7)
|
||||||
|
|
||||||
|
app.mockInput.pressKey("TAB")
|
||||||
|
await app.renderOnce()
|
||||||
|
const inactive = app.captureCharFrame()
|
||||||
|
|
||||||
|
expect(inactive).not.toContain("Inspect auth flow")
|
||||||
|
expect(inactive).toContain("Write migration plan")
|
||||||
|
expect(inactive).toContain("done")
|
||||||
|
expect(inactive).toContain("tab show active")
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue