run: replace subagent tabs with on-demand picker (#28508)

Move subagent navigation into the existing palette: a
"View subagents" command entry, a dedicated picker panel, and a
Down-arrow shortcut from the empty composer.
This commit is contained in:
Simon Klee
2026-05-20 21:07:35 +02:00
committed by GitHub
parent ba803dd89a
commit ed839846d1
7 changed files with 518 additions and 165 deletions
@@ -6,7 +6,7 @@ import { createEffect, createMemo, createSignal, type Accessor } from "solid-js"
import { RunFooterMenu, createFooterMenuState, type RunFooterMenuItem } from "./footer.menu"
import { formatBindings } from "./keymap.shared"
import type { RunFooterTheme } from "./theme"
import type { FooterKeybinds, RunCommand, RunInput, RunProvider } from "./types"
import type { FooterKeybinds, FooterSubagentTab, RunCommand, RunInput, RunProvider } from "./types"
type PanelEntry = RunFooterMenuItem & {
category: string
@@ -15,6 +15,7 @@ type PanelEntry = RunFooterMenuItem & {
type CommandEntry =
| (PanelEntry & { action: "model" })
| (PanelEntry & { action: "subagent" })
| (PanelEntry & { action: "variant.cycle" })
| (PanelEntry & { action: "variant.list" })
| (PanelEntry & { action: "slash"; name: string })
@@ -32,11 +33,19 @@ type VariantEntry = PanelEntry & {
current: boolean
}
type SubagentEntry = PanelEntry & {
sessionID: string
current: boolean
}
type MenuState = ReturnType<typeof createFooterMenuState>
const PANEL_PAD = 2
const PANEL_LIST_ROWS = 10
export const RUN_COMMAND_PANEL_ROWS = PANEL_LIST_ROWS + 6
const PANEL_FRAME_ROWS = 6
export const RUN_COMMAND_PANEL_ROWS = PANEL_LIST_ROWS + PANEL_FRAME_ROWS
const SUBAGENT_LIST_ROWS = 12
export const RUN_SUBAGENT_PANEL_ROWS = SUBAGENT_LIST_ROWS + PANEL_FRAME_ROWS
const PANEL_PAGE = PANEL_LIST_ROWS - 1
const PANEL_BORDER = {
topLeft: "",
@@ -89,6 +98,18 @@ function categoryRank(category: string) {
return 2
}
function subagentStatusLabel(status: FooterSubagentTab["status"]) {
if (status === "completed") {
return "done"
}
if (status === "error") {
return "error"
}
return "running"
}
function handleKey(input: {
event: KeyEvent
menu: MenuState
@@ -273,10 +294,12 @@ function PanelShell(props: {
export function RunCommandMenuBody(props: {
theme: Accessor<RunFooterTheme>
commands: Accessor<RunCommand[] | undefined>
subagents: Accessor<FooterSubagentTab[]>
variants: Accessor<string[]>
keybinds: FooterKeybinds
onClose: () => void
onModel: () => void
onSubagent: () => void
onVariant: () => void
onVariantCycle: () => void
onCommand: (name: string) => void
@@ -293,6 +316,19 @@ export function RunCommandMenuBody(props: {
category: "Suggested",
display: "Switch model",
},
...(props.subagents().length > 0
? [
{
action: "subagent" as const,
category: "Suggested",
display: "View subagents",
footer: `${props.subagents().length} active`,
keywords: props.subagents()
.map((item) => `${item.label} ${item.description} ${item.title ?? ""}`)
.join(" "),
},
]
: []),
{
action: "variant.cycle",
category: "Suggested",
@@ -346,6 +382,11 @@ export function RunCommandMenuBody(props: {
return
}
if (item.action === "subagent") {
props.onSubagent()
return
}
if (item.action === "variant.cycle") {
props.onVariantCycle()
return
@@ -423,6 +464,101 @@ export function RunCommandMenuBody(props: {
)
}
export function RunSubagentSelectBody(props: {
theme: Accessor<RunFooterTheme>
tabs: Accessor<FooterSubagentTab[]>
current: Accessor<string | undefined>
onClose: () => void
onSelect: (sessionID: string) => void
onRows?: (rows: number) => void
}) {
let field: InputRenderable | undefined
const [query, setQuery] = createSignal("")
const entries = createMemo<SubagentEntry[]>(() =>
props.tabs().map((item) => {
const title = item.description || item.title || item.label
return {
category: "",
display: title,
description: title === item.label ? undefined : item.label,
footer: subagentStatusLabel(item.status),
keywords: `${item.label} ${item.description} ${item.title ?? ""} ${item.status}`,
sessionID: item.sessionID,
current: props.current() === item.sessionID,
}
}),
)
const items = createMemo<SubagentEntry[]>(() => match(query(), entries()))
const menu = createFooterMenuState({ count: () => items().length, limit: SUBAGENT_LIST_ROWS })
const select = () => {
const item = items()[menu.selected()]
if (!item) {
return
}
props.onSelect(item.sessionID)
}
createEffect(() => {
query()
menu.reset()
})
createEffect(() => {
if (query().trim()) {
return
}
const index = items().findIndex((item) => item.current)
if (index !== -1) {
menu.reveal(index)
}
})
createEffect(() => {
props.onRows?.(menu.rows() + PANEL_FRAME_ROWS)
})
useKeyboard((event) => {
if (event.defaultPrevented) {
return
}
handleKey({ event, menu, field: () => field, setQuery, select, close: props.onClose })
})
return (
<PanelShell
id="run-direct-footer-subagent-panel"
title="Select subagent"
query={query()}
count={items().length}
total={entries().length}
placeholder="Search"
theme={props.theme}
inputRef={(input) => {
field = input
}}
onQuery={setQuery}
>
<RunFooterMenu
id="run-direct-footer-subagent-list"
theme={props.theme}
items={items}
selected={menu.selected}
offset={menu.offset}
rows={menu.rows}
limit={SUBAGENT_LIST_ROWS}
empty="No active subagents"
border={false}
paddingLeft={PANEL_PAD}
paddingRight={PANEL_PAD}
grouped={false}
/>
</PanelShell>
)
}
export function RunVariantSelectBody(props: {
theme: Accessor<RunFooterTheme>
variants: Accessor<string[]>