fix(app): show selector for custom agents (#37198)

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
This commit is contained in:
Brendan Allan
2026-07-16 08:44:47 +08:00
committed by GitHub
co-authored by opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
parent 888c4cb504
commit 4a760b5743
5 changed files with 46 additions and 9 deletions
@@ -0,0 +1,29 @@
import { describe, expect, test } from "bun:test"
import { hasCustomAgent, resolveAgent } from "./local-agent"
describe("hasCustomAgent", () => {
test("detects explicitly custom agents", () => {
expect(hasCustomAgent([{ native: true }, { native: false }])).toBe(true)
})
test("ignores built-in and unclassified agents", () => {
expect(hasCustomAgent([{ native: true }, {}])).toBe(false)
})
})
describe("resolveAgent", () => {
const agents = [{ name: "plan" }, { name: "build" }, { name: "custom" }]
test("uses the requested available agent", () => {
expect(resolveAgent(agents, "custom")?.name).toBe("custom")
})
test("defaults to build", () => {
expect(resolveAgent(agents)?.name).toBe("build")
expect(resolveAgent(agents, "missing")?.name).toBe("build")
})
test("uses the first agent when build is unavailable", () => {
expect(resolveAgent([{ name: "custom" }], "missing")?.name).toBe("custom")
})
})
+7
View File
@@ -0,0 +1,7 @@
export function hasCustomAgent(items: Array<{ native?: boolean }>) {
return items.some((item) => item.native === false)
}
export function resolveAgent<T extends { name: string }>(items: T[], name?: string) {
return items.find((item) => item.name === name) ?? items.find((item) => item.name === "build") ?? items[0]
}
+7 -4
View File
@@ -4,8 +4,10 @@ import { useParams } from "@solidjs/router"
import { batch, createEffect, createMemo, startTransition } from "solid-js" import { batch, createEffect, createMemo, startTransition } from "solid-js"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { useModels } from "@/context/models" import { useModels } from "@/context/models"
import { useSettings } from "@/context/settings"
import { useProviders } from "@/hooks/use-providers" import { useProviders } from "@/hooks/use-providers"
import { Persist, persisted } from "@/utils/persist" import { Persist, persisted } from "@/utils/persist"
import { hasCustomAgent, resolveAgent } from "./local-agent"
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant" import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
import { useSDK } from "./sdk" import { useSDK } from "./sdk"
import { useSync } from "./sync" import { useSync } from "./sync"
@@ -62,9 +64,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const serverSDK = useServerSDK() const serverSDK = useServerSDK()
const providers = useProviders(() => sdk().directory) const providers = useProviders(() => sdk().directory)
const models = useModels() const models = useModels()
const settings = useSettings()
const id = createMemo(() => params.id || undefined) const id = createMemo(() => params.id || undefined)
const list = createMemo(() => sync().data.agent.filter((item) => item.mode !== "subagent" && !item.hidden)) const list = createMemo(() => sync().data.agent.filter((item) => item.mode !== "subagent" && !item.hidden))
const agentsVisible = createMemo(() => settings.visibility.customAgents() || hasCustomAgent(list()))
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id))) const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
const [saved, setSaved, , savedReady] = persisted( const [saved, setSaved, , savedReady] = persisted(
@@ -107,9 +111,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
} }
const pickAgent = (name: string | undefined) => { const pickAgent = (name: string | undefined) => {
const items = list() return resolveAgent(list(), name)
if (items.length === 0) return
return items.find((item) => item.name === name) ?? items[0]
} }
createEffect(() => { createEffect(() => {
@@ -180,8 +182,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const agent = { const agent = {
list, list,
visible: agentsVisible,
current() { current() {
return pickAgent(scope()?.agent ?? store.current) return pickAgent(agentsVisible() ? (scope()?.agent ?? store.current) : "build")
}, },
set(name: string | undefined) { set(name: string | undefined) {
const item = pickAgent(name) const item = pickAgent(name)
@@ -41,7 +41,7 @@ export function createPromptInputController(input: {
options: local.agent.list().map((agent) => agent.name), options: local.agent.list().map((agent) => agent.name),
current: local.agent.current()?.name ?? "", current: local.agent.current()?.name ?? "",
loading: agentsQuery.isLoading, loading: agentsQuery.isLoading,
visible: settings.visibility.customAgents(), visible: local.agent.visible(),
select: local.agent.set, select: local.agent.set,
}, },
model: { model: {
@@ -1,7 +1,6 @@
import { useCommand, type CommandOption } from "@/context/command" import { useCommand, type CommandOption } from "@/context/command"
import { useLanguage } from "@/context/language" import { useLanguage } from "@/context/language"
import { useLocal, type ModelSelection } from "@/context/local" import { useLocal, type ModelSelection } from "@/context/local"
import { useSettings } from "@/context/settings"
import { useDialog } from "@opencode-ai/ui/context/dialog" import { useDialog } from "@opencode-ai/ui/context/dialog"
import { getCursorPosition, setCursorPosition } from "@/components/prompt-input/editor-dom" import { getCursorPosition, setCursorPosition } from "@/components/prompt-input/editor-dom"
import { useSessionLayout } from "./session-layout" import { useSessionLayout } from "./session-layout"
@@ -19,7 +18,6 @@ export const useComposerCommands = (input: { model?: ModelSelection } = {}) => {
const dialog = useDialog() const dialog = useDialog()
const language = useLanguage() const language = useLanguage()
const local = useLocal() const local = useLocal()
const settings = useSettings()
const { sessionKey } = useSessionLayout() const { sessionKey } = useSessionLayout()
const sessionOwnership = createSessionOwnership(sessionKey) const sessionOwnership = createSessionOwnership(sessionKey)
const model = input.model ?? local.model const model = input.model ?? local.model
@@ -70,7 +68,7 @@ export const useComposerCommands = (input: { model?: ModelSelection } = {}) => {
description: language.t("command.agent.cycle.description"), description: language.t("command.agent.cycle.description"),
keybind: "mod+.", keybind: "mod+.",
slash: "agent", slash: "agent",
disabled: !settings.visibility.customAgents(), disabled: !local.agent.visible(),
onSelect: () => local.agent.move(1), onSelect: () => local.agent.move(1),
}), }),
agentCommand({ agentCommand({
@@ -78,7 +76,7 @@ export const useComposerCommands = (input: { model?: ModelSelection } = {}) => {
title: language.t("command.agent.cycle.reverse"), title: language.t("command.agent.cycle.reverse"),
description: language.t("command.agent.cycle.reverse.description"), description: language.t("command.agent.cycle.reverse.description"),
keybind: "shift+mod+.", keybind: "shift+mod+.",
disabled: !settings.visibility.customAgents(), disabled: !local.agent.visible(),
onSelect: () => local.agent.move(-1), onSelect: () => local.agent.move(-1),
}), }),
]) ])