From d9f2d4ac6caafdbab63870a401f4e8aacc2bfa9d Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:21:43 -0400 Subject: [PATCH] Support remote host Windows terminal UI (#4589) Co-authored-by: Orca --- .../src/components/settings/Settings.tsx | 22 ++++- .../settings/TerminalPane.pwsh.test.ts | 24 +++++ .../src/components/settings/TerminalPane.tsx | 12 ++- .../src/components/status-bar/StatusBar.tsx | 21 +++- .../src/components/tab-bar/TabBar.tsx | 10 +- .../TabBar.windows-shell-launch.test.ts | 57 +++++++++++ .../hooks/useSettingsNavigationMetadata.ts | 28 +++++- .../lib/windows-terminal-capabilities.test.ts | 58 +++++++++++ .../src/lib/windows-terminal-capabilities.ts | 97 ++++++++++++++----- 9 files changed, 286 insertions(+), 43 deletions(-) diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index 3b0b839a917..f48e98f01e3 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -55,6 +55,7 @@ import { getWindowsTerminalCapabilityOwnerKey, useWindowsTerminalCapabilities } from '@/lib/windows-terminal-capabilities' +import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' import { getShortcutPlatform } from '@/lib/shortcut-platform' import { keybindingMatchesAction } from '../../../../shared/keybindings' import { @@ -583,19 +584,29 @@ function Settings(): React.JSX.Element { const windowsTerminalCapabilityOwnerKey = getWindowsTerminalCapabilityOwnerKey( settings?.activeRuntimeEnvironmentId ) - // Why: General owns the Orca CLI controls, including WSL skill-location setup. - const windowsTerminalCapabilities = useWindowsTerminalCapabilities( - (isWindows || isWebClient) && + const runtimeTarget = useMemo( + () => getActiveRuntimeTarget(settings), + [settings?.activeRuntimeEnvironmentId] + ) + const hasActiveRuntimeEnvironment = Boolean(settings?.activeRuntimeEnvironmentId?.trim()) + const shouldLoadWindowsTerminalCapabilities = + hasActiveRuntimeEnvironment || + ((isWindows || isWebClient) && (neededSectionIds.has('terminal') || neededSectionIds.has('general') || neededSectionIds.has('accounts') || - neededSectionIds.has('agents')), + neededSectionIds.has('agents'))) + // Why: General owns the Orca CLI controls, including WSL skill-location setup. + const windowsTerminalCapabilities = useWindowsTerminalCapabilities( + shouldLoadWindowsTerminalCapabilities, true, - windowsTerminalCapabilityOwnerKey + windowsTerminalCapabilityOwnerKey, + runtimeTarget ) // Why: WSL can be unsupported on macOS/Linux, or supported-but-unavailable on Windows. // Only the latter should render disabled WSL controls. const wslSupportedPlatform = isWindows || windowsTerminalCapabilities.hostPlatform === 'win32' + const isWindowsTerminalHost = isWindows || windowsTerminalCapabilities.hostPlatform === 'win32' if ([...neededSectionIds].some((id) => !mountedSectionIds.has(id))) { // Why: lazy Settings sections are remembered for the session; record newly @@ -1074,6 +1085,7 @@ function Settings(): React.JSX.Element { wslCapabilitiesLoading={windowsTerminalCapabilities.isLoading} pwshAvailable={windowsTerminalCapabilities.pwshAvailable} gitBashAvailable={windowsTerminalCapabilities.gitBashAvailable} + isWindowsTerminalHost={isWindowsTerminalHost} /> ) : null} diff --git a/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts b/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts index 95d78b096b3..fa444f60ece 100644 --- a/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts +++ b/src/renderer/src/components/settings/TerminalPane.pwsh.test.ts @@ -261,6 +261,30 @@ describe('TerminalPane PowerShell version setting', () => { expect(collectText(element)).toContain('WSL') }) + it('shows Windows shell controls for a remote Windows host on a non-Windows client', () => { + const element = TerminalPane({ + settings: { + terminalScrollbackBytes: 10_000_000, + terminalWindowsShell: 'powershell.exe', + terminalWindowsPowerShellImplementation: 'auto', + terminalWordSeparator: '' + } as never, + updateSettings: () => {}, + scrollbackMode: 'preset', + setScrollbackMode: () => {}, + wslAvailable: true, + wslDistros: ['Ubuntu'], + pwshAvailable: false, + gitBashAvailable: false, + isWindowsTerminalHost: true + }) + + const text = collectText(element) + expect(text).toContain('Default shell for new terminal panes on Windows') + expect(text).toContain('Command Prompt') + expect(text).toContain('WSL') + }) + it('hides WSL as a Windows default shell option when unavailable', () => { const element = TerminalPane({ settings: { diff --git a/src/renderer/src/components/settings/TerminalPane.tsx b/src/renderer/src/components/settings/TerminalPane.tsx index 88ca8e51f47..3248bdd614e 100644 --- a/src/renderer/src/components/settings/TerminalPane.tsx +++ b/src/renderer/src/components/settings/TerminalPane.tsx @@ -53,6 +53,8 @@ type TerminalPaneProps = { pwshAvailable?: boolean /** Whether Git for Windows bash.exe is installed on this machine. */ gitBashAvailable?: boolean + /** Whether the active terminal host is Windows, even if the client is not. */ + isWindowsTerminalHost?: boolean } export function TerminalPane({ @@ -64,10 +66,12 @@ export function TerminalPane({ wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false, pwshAvailable, - gitBashAvailable = false + gitBashAvailable = false, + isWindowsTerminalHost }: TerminalPaneProps): React.JSX.Element { const searchQuery = useAppStore((state) => state.settingsSearchQuery) const isWindows = isWindowsUserAgent() + const showWindowsHostSettings = isWindowsTerminalHost ?? isWindows const isMac = isMacUserAgent() const detectedLayout = useDetectedOptionAsAlt() const detectedLayoutLabel = @@ -90,11 +94,13 @@ export function TerminalPane({ ? [selectedWslDistroName, ...wslDistros] : wslDistros const powerShellImplementation = settings.terminalWindowsPowerShellImplementation ?? 'auto' - const showWindowsPowerShellImplementation = isWindows && windowsShell === 'powershell.exe' + const showWindowsPowerShellImplementation = + showWindowsHostSettings && windowsShell === 'powershell.exe' const showGitBashOption = gitBashAvailable || windowsShell === WINDOWS_GIT_BASH_SHELL const visibleSections = [ - isWindows && matchesSettingsSearch(searchQuery, TERMINAL_WINDOWS_SHELL_SEARCH_ENTRY) ? ( + showWindowsHostSettings && + matchesSettingsSearch(searchQuery, TERMINAL_WINDOWS_SHELL_SEARCH_ENTRY) ? (
s.rateLimits.inactiveClaudeAccounts) const claudeTarget = useAppStore((s) => s.rateLimits.claudeTarget) const settings = useAppStore((s) => s.settings) + const hasActiveRuntimeEnvironment = Boolean(settings?.activeRuntimeEnvironmentId?.trim()) + const runtimeTarget = useMemo( + () => getActiveRuntimeTarget(settings), + [settings?.activeRuntimeEnvironmentId] + ) const windowsTerminalCapabilities = useWindowsTerminalCapabilities( - navigator.userAgent.includes('Windows'), + navigator.userAgent.includes('Windows') || hasActiveRuntimeEnvironment, false, - getWindowsTerminalCapabilityOwnerKey(settings?.activeRuntimeEnvironmentId) + getWindowsTerminalCapabilityOwnerKey(settings?.activeRuntimeEnvironmentId), + runtimeTarget ) const claudeAccountSyncKey = useAppStore((s) => { const settings = s.settings @@ -1092,10 +1099,16 @@ function CodexSwitcherMenu({ const inactiveCodexAccounts = useAppStore((s) => s.rateLimits.inactiveCodexAccounts) const codexTarget = useAppStore((s) => s.rateLimits.codexTarget) const settings = useAppStore((s) => s.settings) + const hasActiveRuntimeEnvironment = Boolean(settings?.activeRuntimeEnvironmentId?.trim()) + const runtimeTarget = useMemo( + () => getActiveRuntimeTarget(settings), + [settings?.activeRuntimeEnvironmentId] + ) const windowsTerminalCapabilities = useWindowsTerminalCapabilities( - navigator.userAgent.includes('Windows'), + navigator.userAgent.includes('Windows') || hasActiveRuntimeEnvironment, false, - getWindowsTerminalCapabilityOwnerKey(settings?.activeRuntimeEnvironmentId) + getWindowsTerminalCapabilityOwnerKey(settings?.activeRuntimeEnvironmentId), + runtimeTarget ) const codexAccountSyncKey = useAppStore((s) => { const settings = s.settings diff --git a/src/renderer/src/components/tab-bar/TabBar.tsx b/src/renderer/src/components/tab-bar/TabBar.tsx index 02a7a40b51e..f157f7ae542 100644 --- a/src/renderer/src/components/tab-bar/TabBar.tsx +++ b/src/renderer/src/components/tab-bar/TabBar.tsx @@ -37,6 +37,7 @@ import { getWindowsTerminalCapabilityOwnerKey, useWindowsTerminalCapabilities } from '@/lib/windows-terminal-capabilities' +import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { type BuiltInWindowsTerminalShell, @@ -248,13 +249,18 @@ function TabBarInner({ const windowsTerminalCapabilityOwnerKey = getWindowsTerminalCapabilityOwnerKey( activeRuntimeEnvironmentId ) + const runtimeTarget = useMemo( + () => getActiveRuntimeTarget({ activeRuntimeEnvironmentId }), + [activeRuntimeEnvironmentId] + ) const shouldProbeWindowsShellCapabilities = - (isWindows || (isWebClient && activeRuntimeEnvironmentId !== null)) && + (isWindows || Boolean(activeRuntimeEnvironmentId?.trim()) || isWebClient) && !worktreeHasRemoteConnection const windowsTerminalCapabilities = useWindowsTerminalCapabilities( shouldProbeWindowsShellCapabilities, false, - windowsTerminalCapabilityOwnerKey + windowsTerminalCapabilityOwnerKey, + runtimeTarget ) // Why: SSH-backed PTYs ignore local Windows shell overrides; showing these // entries there promises PowerShell/CMD/Git Bash but opens the remote shell. diff --git a/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts b/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts index dd43c247315..744493946ed 100644 --- a/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts +++ b/src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts @@ -428,6 +428,63 @@ describe('TabBar PowerShell launch wiring', () => { expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal: WSL')).not.toBeNull() }) + it('uses the active remote host platform to show Windows shell rows in a Mac desktop client', async () => { + vi.stubGlobal('navigator', { userAgent: 'Macintosh' }) + vi.stubGlobal('__ORCA_WEB_CLIENT__', false) + vi.stubGlobal('window', { + api: { + wsl: { + isAvailable: vi.fn().mockResolvedValue(true), + listDistros: vi.fn().mockResolvedValue(['Ubuntu']) + }, + pwsh: { isAvailable: vi.fn().mockResolvedValue(false) }, + gitBash: { isAvailable: vi.fn().mockResolvedValue(false) }, + runtime: { getStatus: vi.fn().mockResolvedValue({ hostPlatform: 'win32' }) } + } + }) + appStoreSnapshot.activeRuntimeEnvironmentId = 'desktop-env-1' + const capabilities = await import('@/lib/windows-terminal-capabilities') + await capabilities.loadWindowsTerminalCapabilities({ + force: true, + ownerKey: 'runtime:desktop-env-1' + }) + + const tabBarModule = await import('./TabBar') + const candidate = tabBarModule.default ?? tabBarModule + const TabBar = + typeof candidate === 'function' + ? candidate + : typeof (candidate as { type?: unknown }).type === 'function' + ? (candidate as { type: (props: Record) => unknown }).type + : null + expect(TabBar).not.toBeNull() + + const element = TabBar!({ + tabs: [], + activeTabId: null, + worktreeId: 'wt-1', + expandedPaneByTabId: {}, + onActivate: () => {}, + onClose: () => {}, + onCloseOthers: () => {}, + onCloseToRight: () => {}, + onNewTerminalTab: () => {}, + onNewTerminalWithShell: () => {}, + onNewBrowserTab: () => {}, + onSetCustomTitle: () => {}, + onSetTabColor: () => {}, + onTogglePaneExpand: () => {} + }) + + expect( + findDropdownMenuItemByText(expandNode(element), 'New Terminal: PowerShell') + ).not.toBeNull() + expect( + findDropdownMenuItemByText(expandNode(element), 'New Terminal: CMD Prompt') + ).not.toBeNull() + expect(findDropdownMenuItemByText(expandNode(element), 'New Terminal: WSL')).not.toBeNull() + }) + it('shows the Git Bash terminal row when shared Windows capabilities find bash.exe', async () => { vi.stubGlobal('window', { api: { diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts index 64261ced9e3..848891284c4 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts @@ -68,6 +68,10 @@ import { STATS_PANE_SEARCH_ENTRIES } from '@/components/stats/stats-search' import { EXPERIMENTAL_PANE_SEARCH_ENTRIES } from '@/components/settings/experimental-search' import { getRepositoryPaneSearchEntries } from '@/components/settings/repository-search' import { cn } from '@/lib/utils' +import { + getCachedWindowsTerminalCapabilities, + getWindowsTerminalCapabilityOwnerKey +} from '@/lib/windows-terminal-capabilities' function OrcaLogoSettingsIcon({ className }: LucideProps) { return createElement('img', { @@ -88,17 +92,19 @@ export function isWebClientLocation(): boolean { export function buildSettingsNavigationMetadata({ isMac, isWindows, + isWindowsTerminalHost = isWindows, isWebClient, repos }: { isMac: boolean isWindows: boolean + isWindowsTerminalHost?: boolean isWebClient: boolean repos: readonly Repo[] }): SettingsNavSection[] { const showDesktopOnlySettings = !isWebClient const terminalPaneSearchEntries = getTerminalPaneSearchEntries({ - isWindows, + isWindows: isWindowsTerminalHost, isMac }) const runtimeEnvironmentsSearchEntry = isWebClient @@ -366,15 +372,31 @@ export function buildSettingsNavigationMetadata({ export function useSettingsNavigationMetadata(): SettingsNavSection[] { const repos = useAppStore((state) => state.repos) + const activeRuntimeEnvironmentId = useAppStore( + (state) => state.settings?.activeRuntimeEnvironmentId + ) const isMac = isMacUserAgent() const isWindows = isWindowsUserAgent() const isWebClient = isWebClientLocation() + const windowsTerminalCapabilityOwnerKey = getWindowsTerminalCapabilityOwnerKey( + activeRuntimeEnvironmentId + ) + const isWindowsTerminalHost = + isWindows || + getCachedWindowsTerminalCapabilities(windowsTerminalCapabilityOwnerKey).hostPlatform === 'win32' // Why: Settings and Cmd+J share this metadata so platform/runtime visibility // and search entries cannot drift. Keep this hook free of Settings pane UI // imports; see docs/reference/cmd-j-settings-actions-plan.md. return useMemo( - () => buildSettingsNavigationMetadata({ isMac, isWindows, isWebClient, repos }), - [isMac, isWindows, isWebClient, repos] + () => + buildSettingsNavigationMetadata({ + isMac, + isWindows, + isWindowsTerminalHost, + isWebClient, + repos + }), + [isMac, isWindows, isWindowsTerminalHost, isWebClient, repos] ) } diff --git a/src/renderer/src/lib/windows-terminal-capabilities.test.ts b/src/renderer/src/lib/windows-terminal-capabilities.test.ts index e98f33b5c4f..256a39e0bd8 100644 --- a/src/renderer/src/lib/windows-terminal-capabilities.test.ts +++ b/src/renderer/src/lib/windows-terminal-capabilities.test.ts @@ -197,6 +197,64 @@ describe('windows terminal capabilities', () => { expect(runtimeGetStatus).toHaveBeenCalledTimes(2) }) + it('loads remote runtime host capabilities through runtime RPC', async () => { + const runtimeEnvironmentCall = vi.fn(async (args: { selector: string; method: string }) => { + const resultByMethod: Record = { + 'status.get': { + hostPlatform: 'win32', + runtimeProtocolVersion: 3, + minCompatibleRuntimeClientVersion: 2 + }, + 'host.wsl.isAvailable': true, + 'host.wsl.listDistros': ['Ubuntu'], + 'host.pwsh.isAvailable': true, + 'host.gitBash.isAvailable': false + } + return { + id: args.method, + ok: true, + result: resultByMethod[args.method] + } + }) + vi.stubGlobal('window', { + api: { + runtimeEnvironments: { + call: runtimeEnvironmentCall + } + } + }) + + await expect( + loadWindowsTerminalCapabilities({ + ownerKey: 'runtime:env-win', + target: { kind: 'environment', environmentId: 'env-win' } + }) + ).resolves.toEqual({ + wslAvailable: true, + wslDistros: ['Ubuntu'], + pwshAvailable: true, + gitBashAvailable: false, + hostPlatform: 'win32', + isLoading: false + }) + + expect(runtimeEnvironmentCall).toHaveBeenCalledWith( + expect.objectContaining({ selector: 'env-win', method: 'host.wsl.isAvailable' }) + ) + expect(runtimeEnvironmentCall).toHaveBeenCalledWith( + expect.objectContaining({ selector: 'env-win', method: 'host.wsl.listDistros' }) + ) + expect(runtimeEnvironmentCall).toHaveBeenCalledWith( + expect.objectContaining({ selector: 'env-win', method: 'host.pwsh.isAvailable' }) + ) + expect(runtimeEnvironmentCall).toHaveBeenCalledWith( + expect.objectContaining({ selector: 'env-win', method: 'host.gitBash.isAvailable' }) + ) + expect(runtimeEnvironmentCall).toHaveBeenCalledWith( + expect.objectContaining({ selector: 'env-win', method: 'status.get' }) + ) + }) + it('prunes expired runtime owner capability caches', async () => { stubTerminalCapabilityApi({ wslAvailable: false, diff --git a/src/renderer/src/lib/windows-terminal-capabilities.ts b/src/renderer/src/lib/windows-terminal-capabilities.ts index 9ea165af4e0..13c138e718e 100644 --- a/src/renderer/src/lib/windows-terminal-capabilities.ts +++ b/src/renderer/src/lib/windows-terminal-capabilities.ts @@ -1,4 +1,6 @@ import { useEffect, useState } from 'react' +import { callRuntimeRpc, type RuntimeClientTarget } from '@/runtime/runtime-rpc-client' +import type { RuntimeStatus } from '../../../shared/runtime-types' export type WindowsTerminalCapabilities = { wslAvailable: boolean @@ -37,16 +39,15 @@ type WindowsTerminalCapabilityHookState = { capabilities: WindowsTerminalCapabilities } +type WindowsTerminalCapabilityLoadTarget = RuntimeClientTarget + export function getWindowsTerminalCapabilityOwnerKey( activeRuntimeEnvironmentId?: string | null ): string { - const isWebClient = (globalThis as { __ORCA_WEB_CLIENT__?: boolean }).__ORCA_WEB_CLIENT__ === true - if (!isWebClient) { - return 'local' - } - // Why: paired web clients can switch hosts; Git Bash/WSL availability is + // Why: remote desktop and paired web clients can switch hosts; Git Bash/WSL availability is // host-owned, so a previous runtime's answer must not bleed into the next. - return `runtime:${activeRuntimeEnvironmentId?.trim() || 'none'}` + const environmentId = activeRuntimeEnvironmentId?.trim() + return environmentId ? `runtime:${environmentId}` : 'local' } function publish( @@ -99,10 +100,12 @@ export function loadWindowsTerminalCapabilities( force?: boolean now?: number ownerKey?: string + target?: WindowsTerminalCapabilityLoadTarget } = {} ): Promise { const now = options.now ?? Date.now() const ownerKey = options.ownerKey ?? 'local' + const target = options.target ?? { kind: 'local' } pruneExpiredCapabilityOwners(now) const cached = cachedCapabilitiesByOwnerKey.get(ownerKey) if (cached && !options.force && now - cached.loadedAt < CAPABILITY_CACHE_TTL_MS) { @@ -117,16 +120,7 @@ export function loadWindowsTerminalCapabilities( // Separate probes can leave one surface showing stale Windows shell choices. const requestId = ++nextCapabilityRequestId latestCapabilityRequestIdByOwnerKey.set(ownerKey, requestId) - const nextPendingCapabilities = Promise.all([ - window.api.wsl.isAvailable().catch(() => false), - window.api.wsl.listDistros().catch(() => []), - window.api.pwsh.isAvailable().catch(() => false), - window.api.gitBash.isAvailable().catch(() => false), - window.api.runtime - .getStatus() - .then((status) => status.hostPlatform ?? null) - .catch(() => null) - ]) + const nextPendingCapabilities = Promise.all(readWindowsTerminalCapabilityPromises(target)) .then(([wslAvailable, wslDistros, pwshAvailable, gitBashAvailable, hostPlatform]) => { const capabilities = { wslAvailable, @@ -157,9 +151,10 @@ export function loadWindowsTerminalCapabilities( } export function refreshWindowsTerminalCapabilities( - ownerKey = 'local' + ownerKey = 'local', + target: WindowsTerminalCapabilityLoadTarget = { kind: 'local' } ): Promise { - return loadWindowsTerminalCapabilities({ force: true, ownerKey }) + return loadWindowsTerminalCapabilities({ force: true, ownerKey, target }) } export function selectWindowsTerminalCapabilitiesForOwner( @@ -178,8 +173,11 @@ export function selectWindowsTerminalCapabilitiesForOwner( export function useWindowsTerminalCapabilities( enabled: boolean, forceRefreshOnMount = false, - ownerKey = 'local' + ownerKey = 'local', + target: WindowsTerminalCapabilityLoadTarget = { kind: 'local' } ): WindowsTerminalCapabilities { + const targetKind = target.kind + const targetEnvironmentId = target.kind === 'environment' ? target.environmentId : null const [state, setState] = useState(() => ({ ownerKey, capabilities: getCachedWindowsTerminalCapabilities(ownerKey) @@ -190,6 +188,10 @@ export function useWindowsTerminalCapabilities( setState({ ownerKey, capabilities: UNAVAILABLE_CAPABILITIES }) return } + const loadTarget: WindowsTerminalCapabilityLoadTarget = + targetKind === 'environment' && targetEnvironmentId + ? { kind: 'environment', environmentId: targetEnvironmentId } + : { kind: 'local' } let cancelled = false const cached = getCachedWindowsTerminalCapabilities(ownerKey) @@ -204,13 +206,15 @@ export function useWindowsTerminalCapabilities( const subscribers = subscribersByOwnerKey.get(ownerKey) ?? new Set() subscribers.add(setCapabilities) subscribersByOwnerKey.set(ownerKey, subscribers) - void loadWindowsTerminalCapabilities({ force: forceRefreshOnMount, ownerKey }).then( - (nextCapabilities) => { - if (!cancelled) { - setState({ ownerKey, capabilities: nextCapabilities }) - } + void loadWindowsTerminalCapabilities({ + force: forceRefreshOnMount, + ownerKey, + target: loadTarget + }).then((nextCapabilities) => { + if (!cancelled) { + setState({ ownerKey, capabilities: nextCapabilities }) } - ) + }) return () => { cancelled = true @@ -220,11 +224,52 @@ export function useWindowsTerminalCapabilities( subscribersByOwnerKey.delete(ownerKey) } } - }, [enabled, forceRefreshOnMount, ownerKey]) + }, [enabled, forceRefreshOnMount, ownerKey, targetKind, targetEnvironmentId]) return selectWindowsTerminalCapabilitiesForOwner(state, enabled, ownerKey) } +function readWindowsTerminalCapabilityPromises( + target: WindowsTerminalCapabilityLoadTarget +): [ + Promise, + Promise, + Promise, + Promise, + Promise +] { + if (target.kind === 'local') { + return [ + window.api.wsl.isAvailable().catch(() => false), + window.api.wsl.listDistros().catch(() => []), + window.api.pwsh.isAvailable().catch(() => false), + window.api.gitBash.isAvailable().catch(() => false), + window.api.runtime + .getStatus() + .then((status) => status.hostPlatform ?? null) + .catch(() => null) + ] + } + + return [ + callRuntimeRpc(target, 'host.wsl.isAvailable', undefined, { timeoutMs: 15_000 }).catch( + () => false + ), + callRuntimeRpc(target, 'host.wsl.listDistros', undefined, { + timeoutMs: 15_000 + }).catch(() => []), + callRuntimeRpc(target, 'host.pwsh.isAvailable', undefined, { + timeoutMs: 15_000 + }).catch(() => false), + callRuntimeRpc(target, 'host.gitBash.isAvailable', undefined, { + timeoutMs: 15_000 + }).catch(() => false), + callRuntimeRpc(target, 'status.get', undefined, { timeoutMs: 15_000 }) + .then((status) => status.hostPlatform ?? null) + .catch(() => null) + ] +} + export function resetWindowsTerminalCapabilitiesForTests(): void { cachedCapabilitiesByOwnerKey.clear() pendingCapabilitiesByOwnerKey.clear()