summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoropencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>2025-11-19 00:40:47 -0600
committerGitHub <[email protected]>2025-11-19 00:40:47 -0600
commit90044196bf06b630b81182a3eb0673a3032b2f06 (patch)
treeef7d61b9e1026110e62626441f89f4dd3ed16dbd
parent963a926db24ea70f6002cde66d0ff858de06fae2 (diff)
downloadopencode-90044196bf06b630b81182a3eb0673a3032b2f06.tar.gz
opencode-90044196bf06b630b81182a3eb0673a3032b2f06.zip
Added subagents to agents modal, non-selectable (#4460)
Co-authored-by: Aiden Cline <[email protected]>
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx42
-rw-r--r--packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx51
2 files changed, 71 insertions, 22 deletions
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 (
<DialogSelect
@@ -23,8 +41,10 @@ export function DialogAgent() {
current={local.agent.current().name}
options={options()}
onSelect={(option) => {
- 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<T>(props: DialogSelectProps<T>) {
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<T>(props: DialogSelectProps<T>) {
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<T>(props: DialogSelectProps<T>) {
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<T>(props: DialogSelectProps<T>) {
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<T>(props: DialogSelectProps<T>) {
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<T>(props: DialogSelectProps<T>) {
description={option.description !== category ? option.description : undefined}
active={active()}
current={isDeepEqual(option.value, props.current)}
+ disabled={option.disabled}
/>
</box>
)
@@ -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 (
<>
- <Show when={props.current}>
+ <Show when={props.current && !props.disabled}>
<text
flexShrink={0}
fg={props.active ? theme.background : props.current ? theme.primary : theme.text}
@@ -271,10 +293,17 @@ function Option(props: {
</text>
</Show>
+ <Show when={props.disabled}>
+ <text flexShrink={0} fg={theme.textMuted} marginRight={0.5}>
+ ○
+ </text>
+ </Show>
<text
flexGrow={1}
fg={props.active ? theme.background : props.current ? theme.primary : theme.text}
- attributes={props.active ? TextAttributes.BOLD : undefined}
+ attributes={
+ props.active && !props.disabled ? TextAttributes.BOLD : props.disabled ? TextAttributes.DIM : undefined
+ }
overflow="hidden"
wrapMode="none"
>