diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx index 65aaeb22b..15e8307f4 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx @@ -1,21 +1,39 @@ import { createMemo } from "solid-js" import { useLocal } from "@tui/context/local" +import { useSync } from "@tui/context/sync" import { DialogSelect } from "@tui/ui/dialog-select" import { useDialog } from "@tui/ui/dialog" +import { useTheme } from "@tui/context/theme" export function DialogAgent() { const local = useLocal() + const sync = useSync() const dialog = useDialog() + const { theme } = useTheme() - const options = createMemo(() => - local.agent.list().map((item) => { - return { - value: item.name, - title: item.name, - description: item.builtIn ? "native" : item.description, - } - }), - ) + const options = createMemo(() => { + const allAgents = sync.data.agent + const primaryAgents = allAgents.filter((x) => x.mode !== "subagent") + const subagents = allAgents.filter((x) => x.mode === "subagent") + + const primaryOptions = primaryAgents.map((item) => ({ + value: item.name, + title: item.name, + description: item.builtIn ? "native" : item.description, + category: "Primary Agents", + })) + + const subagentOptions = subagents.map((item) => ({ + value: item.name, + title: item.name, + description: item.builtIn ? "native" : item.description, + category: "Subagents (non-selectable)", + disabled: true, + bg: theme.backgroundPanel, + })) + + return [...primaryOptions, ...subagentOptions] + }) return ( { - local.agent.set(option.value) - dialog.clear() + if (!option.disabled) { + local.agent.set(option.value) + dialog.clear() + } }} /> ) diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 285c039c1..8f3e71bfd 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -54,10 +54,8 @@ export function DialogSelect(props: DialogSelectProps) { const filtered = createMemo(() => { const needle = store.filter.toLowerCase() - const result = pipe( - props.options, - filter((x) => x.disabled !== true), - (x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)), + const result = pipe(props.options, (x) => + !needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj), ) return result }) @@ -96,6 +94,16 @@ export function DialogSelect(props: DialogSelectProps) { let next = store.selected + direction if (next < 0) next = flat().length - 1 if (next >= flat().length) next = 0 + + // Skip disabled options when flipping through agents + let attempts = 0 + while (flat()[next]?.disabled && attempts < flat().length) { + next = next + direction + if (next < 0) next = flat().length - 1 + if (next >= flat().length) next = 0 + attempts++ + } + moveTo(next) } @@ -126,7 +134,7 @@ export function DialogSelect(props: DialogSelectProps) { if (evt.name === "pagedown") move(10) if (evt.name === "return") { const option = selected() - if (option) { + if (option && !option.disabled) { // evt.preventDefault() if (option.onSelect) option.onSelect(dialog) props.onSelect?.(option) @@ -136,7 +144,7 @@ export function DialogSelect(props: DialogSelectProps) { for (const item of props.keybind ?? []) { if (Keybind.match(item.keybind, keybind.parse(evt))) { const s = selected() - if (s) { + if (s && !s.disabled) { evt.preventDefault() item.onTrigger(s) } @@ -208,15 +216,19 @@ export function DialogSelect(props: DialogSelectProps) { id={JSON.stringify(option.value)} flexDirection="row" onMouseUp={() => { - option.onSelect?.(dialog) - props.onSelect?.(option) + if (!option.disabled) { + option.onSelect?.(dialog) + props.onSelect?.(option) + } }} onMouseOver={() => { const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value)) if (index === -1) return moveTo(index) }} - backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)} + backgroundColor={ + active() && !option.disabled ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0) + } paddingLeft={1} paddingRight={1} gap={1} @@ -227,6 +239,7 @@ export function DialogSelect(props: DialogSelectProps) { description={option.description !== category ? option.description : undefined} active={active()} current={isDeepEqual(option.value, props.current)} + disabled={option.disabled} /> ) @@ -256,13 +269,22 @@ function Option(props: { active?: boolean current?: boolean footer?: JSX.Element | string + disabled?: boolean onMouseOver?: () => void }) { const { theme } = useTheme() + const textColor = props.disabled + ? theme.textMuted + : props.active + ? theme.background + : props.current + ? theme.primary + : theme.text + return ( <> - + + + + ○ + +