From 20eebd2a31caee3d64ef85b67772d0ce6780929d Mon Sep 17 00:00:00 2001 From: Romanok Date: Thu, 5 Mar 2026 04:25:50 +0500 Subject: [PATCH] fix(tui): handle empty agent list without crashing The TUI crashes with `TypeError: undefined is not an object (evaluating 'agents()[0].name')` when the filtered agent list is empty. This happens when plugins reconfigure agent modes so no primary/visible agents remain at the time the LocalProvider initializes. - Use optional chaining on `agents()[0]?.name` for safe initial value - Add reactive effect to sync current agent when the list changes - Remove non-null assertion on `current()`, fall back to first agent - Guard `move()` against empty agent lists - Guard `currentModel` memo against undefined agent - Guard `cycle()` and `cycleFavorite()` against undefined agent - Guard agent-change effect against undefined value Co-Authored-By: Claude Opus 4.6 --- .../src/cli/cmd/tui/context/local.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index d93079f12a42..b5b8a30871bf 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -40,7 +40,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const [agentStore, setAgentStore] = createStore<{ current: string }>({ - current: agents()[0].name, + current: agents()[0]?.name ?? "", }) const { theme } = useTheme() const colors = createMemo(() => [ @@ -52,12 +52,16 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ theme.error, theme.info, ]) + createEffect(() => { + const list = agents() + if (list.length && !list.some((x) => x.name === agentStore.current)) setAgentStore("current", list[0].name) + }) return { list() { return agents() }, current() { - return agents().find((x) => x.name === agentStore.current)! + return agents().find((x) => x.name === agentStore.current) ?? agents()[0] }, set(name: string) { if (!agents().some((x) => x.name === name)) @@ -69,6 +73,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setAgentStore("current", name) }, move(direction: 1 | -1) { + if (!agents().length) return batch(() => { let next = agents().findIndex((x) => x.name === agentStore.current) + direction if (next < 0) next = agents().length - 1 @@ -192,6 +197,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const currentModel = createMemo(() => { const a = agent.current() + if (!a) return undefined return ( getFirstValidModel( () => modelStore.model[a.name], @@ -240,7 +246,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ if (next >= recent.length) next = 0 const val = recent[next] if (!val) return - setModelStore("model", agent.current().name, { ...val }) + const a = agent.current() + if (!a) return + setModelStore("model", a.name, { ...val }) }, cycleFavorite(direction: 1 | -1) { const favorites = modelStore.favorite.filter((item) => isModelValid(item)) @@ -266,7 +274,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } const next = favorites[index] if (!next) return - setModelStore("model", agent.current().name, { ...next }) + const a = agent.current() + if (!a) return + setModelStore("model", a.name, { ...next }) const uniq = uniqueBy([next, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`) if (uniq.length > 10) uniq.pop() setModelStore( @@ -285,7 +295,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ }) return } - setModelStore("model", agent.current().name, model) + setModelStore("model", agent.current()?.name ?? "", model) if (options?.recent) { const uniq = uniqueBy([model, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`) if (uniq.length > 10) uniq.pop() @@ -381,6 +391,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ // Automatically update model when agent changes createEffect(() => { const value = agent.current() + if (!value) return if (value.model) { if (isModelValid(value.model)) model.set({