From 3a81b4b257ba77c7a947b1c948de07fc5c101519 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Thu, 2 Apr 2026 14:02:56 +1000 Subject: [PATCH] refactor: centralize shell selection metadata Keep terminal, prompt, and background shell handling in sync while letting the UI reflect discovered shell capabilities without re-parsing paths. --- .../app/src/components/settings-general.tsx | 48 +++---- packages/app/src/i18n/en.ts | 1 + packages/opencode/src/server/routes/pty.ts | 10 +- packages/opencode/src/session/prompt.ts | 47 +----- packages/opencode/src/shell/shell.ts | 135 ++++++++++++++---- packages/opencode/src/tool/bash.ts | 11 +- packages/opencode/test/shell/shell.test.ts | 9 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 6 +- 8 files changed, 166 insertions(+), 101 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 009dc452ce..fd57921337 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -36,6 +36,12 @@ type ThemeOption = { name: string } +type ShellOption = { + path: string + name: string + acceptable: boolean +} + // To prevent audio from overlapping/playing very quickly when navigating the settings menus, // delay the playback by 100ms during quick selection changes and pause existing sounds. const stopDemoSound = () => { @@ -138,41 +144,33 @@ export const SettingsGeneral: Component = () => { const globalSync = useGlobalSync() const globalSdk = useGlobalSDK() - const [shells] = createResource(() => globalSdk.client.pty.shells().then((res) => res.data || [])) + const [shells] = createResource(() => globalSdk.client.pty.shells().then((res) => res.data || [])) + const auto = { value: "", label: "Auto (Default)" } const shellOptions = createMemo(() => { const list = shells() || [] const current = globalSync.data.config.shell - const getShortName = (p: string) => { - const parts = p.split(/[/\\]/) - let name = parts[parts.length - 1] - const dotIndex = name.lastIndexOf(".") - if (dotIndex > 0) { - name = name.slice(0, dotIndex) - } - return name - } - const nameCounts = new Map() for (const s of list) { - const name = getShortName(s) - nameCounts.set(name, (nameCounts.get(name) || 0) + 1) + nameCounts.set(s.name, (nameCounts.get(s.name) || 0) + 1) } - const options = list.map((s) => { - const name = getShortName(s) - const isDuplicate = (nameCounts.get(name) || 0) > 1 - const label = isDuplicate ? s : name - const value = isDuplicate ? s : name - return { value, label } - }) - - options.unshift({ value: "auto", label: "Auto (Default)" }) + const options = [ + auto, + ...list.map((s) => { + const dup = (nameCounts.get(s.name) || 0) > 1 + const text = dup ? s.path : s.name + const label = s.acceptable ? text : `${text} (${language.t("settings.general.row.shell.terminalOnly")})` + const value = dup ? s.path : s.name + return { value, label } + }), + ] if (current && !options.some((o) => o.value === current)) { options.push({ value: current, label: current }) } + return options }) @@ -256,12 +254,12 @@ export const SettingsGeneral: Component = () => {