diff --git a/src/main/managed-server/install/orcad-remote-context.ts b/src/main/managed-server/install/orcad-remote-context.ts index b7d3a7dfbd1..ab1f332622f 100644 --- a/src/main/managed-server/install/orcad-remote-context.ts +++ b/src/main/managed-server/install/orcad-remote-context.ts @@ -56,7 +56,11 @@ export async function resolveOrcadRemoteContext> { const host = detectedHost ?? (await detectRemoteHostPlatform(connection, { signal })) if (!host) { - throw new OrcadHostUnsupportedError('Orca’s server doesn’t support this SSH host’s platform.') + throw new OrcadHostUnsupportedError( + isWslOrcadHostTarget(target) + ? 'Orca’s server doesn’t support this WSL distro’s platform. Use an x64 or arm64 Linux distro.' + : 'Orca’s server doesn’t support this SSH host’s platform.' + ) } const remote = { conn: connection, host, signal } const { orcadHome: remoteHome, userDataDir } = await resolveOrcadMachineHome( diff --git a/src/main/managed-server/lifecycle/orcad-runtime-target.ts b/src/main/managed-server/lifecycle/orcad-runtime-target.ts index e58ab279076..347cbf9ac83 100644 --- a/src/main/managed-server/lifecycle/orcad-runtime-target.ts +++ b/src/main/managed-server/lifecycle/orcad-runtime-target.ts @@ -5,6 +5,13 @@ import type { OrcadHostTransport } from '../../ssh/orcad-host-transport' import { pinnedRuntimeTargetForHost } from '../../ssh/pinned-runtime-host-target' import type { RemoteHostPlatform } from '../../ssh/ssh-remote-platform' +/** Lets a caller that knows the host kind add its own fix; the message is the one SSH shows. */ +export class OrcadHostGlibcUnsupportedError extends OrcadHostUnsupportedError { + constructor(target: string, glibc: string) { + super(`No Orca runtime supports ${target} with glibc ${glibc}.`) + } +} + export async function resolveOrcadRuntimeTarget(options: { conn: OrcadHostTransport host: RemoteHostPlatform @@ -15,9 +22,7 @@ export async function resolveOrcadRuntimeTarget(options: { const target = pinnedRuntimeTargetForHost(facts) if (!target) { const glibc = facts.glibc ? `${facts.glibc.major}.${facts.glibc.minor}` : 'unknown' - throw new OrcadHostUnsupportedError( - `No Orca runtime supports ${facts.target} with glibc ${glibc}.` - ) + throw new OrcadHostGlibcUnsupportedError(facts.target, glibc) } return target } diff --git a/src/main/wsl/wsl-distro-unsupported.test.ts b/src/main/wsl/wsl-distro-unsupported.test.ts new file mode 100644 index 00000000000..f33b63e3cbf --- /dev/null +++ b/src/main/wsl/wsl-distro-unsupported.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest' +import { classifyOrcadHostUnavailable } from '../orcad/orcad-host-unavailable' +import { OrcadHostGlibcUnsupportedError } from '../managed-server/lifecycle/orcad-runtime-target' +import { RemoteNodeRuntimeSelfTestError } from '../ssh/orcad-remote-node-runtime-report' +import { + isWslDistroNotFoundFailure, + WslDistroNotRegisteredError, + WslDistroVersion1Error, + wslUnsupportedDistroFixMessage +} from './wsl-distro-unsupported' + +const NIXOS_STUB_OUTPUT = + 'Could not start dynamically linked executable: node\n' + + 'NixOS cannot run dynamically linked executables intended for generic\n' + + 'linux environments out of the box.' + +describe('distros that cannot host Orca', () => { + it('says how to convert a WSL 1 distro', () => { + expect(new WslDistroVersion1Error('Legacy').message).toContain('wsl --set-version Legacy 2') + }) + + it('classifies both refusals as an unsupported host, as the SSH path records them', () => { + expect(classifyOrcadHostUnavailable(new WslDistroVersion1Error('Legacy'))).toBe( + 'unsupported_host' + ) + expect(classifyOrcadHostUnavailable(new WslDistroNotRegisteredError('Gone'))).toBe( + 'unsupported_host' + ) + }) + + it.each([ + ['the English sentence', 'There is no distribution with the supplied name.', true], + [ + 'the error code under a localized sentence', + 'Fehler: Wsl/Service/WSL_E_DISTRO_NOT_FOUND', + true + ], + ['an unrelated failure', 'Command "uname -r" failed (exit 1): timeout', false] + ])('recognizes a missing distro from %s', (_case, message, expected) => { + expect(isWslDistroNotFoundFailure(new Error(message))).toBe(expected) + }) + + it('adds the glibc fix for a distro, leaving the shared refusal SSH shows as it was', () => { + const error = new OrcadHostGlibcUnsupportedError('linux-arm64-glibc', '2.27') + expect(error.name).toBe('OrcadHostUnsupportedError') + expect(error.message).toBe('No Orca runtime supports linux-arm64-glibc with glibc 2.27.') + expect(wslUnsupportedDistroFixMessage(error)).toBe( + 'No Orca runtime supports linux-arm64-glibc with glibc 2.27. Use a Linux release with ' + + 'glibc 2.28 or newer (for example Ubuntu 20.04 or Debian 10 and later).' + ) + }) + + it('tells a NixOS distro to turn on nix-ld, leaving the shared self-test wording as it was', () => { + const error = new RemoteNodeRuntimeSelfTestError(127, NIXOS_STUB_OUTPUT) + expect(error.message).toMatch(/^The pinned Node runtime did not run on the host \(exit 127\)/) + const fix = wslUnsupportedDistroFixMessage(error) + expect(fix).toMatch(/^NixOS can’t run Orca’s Node runtime/) + expect(fix).toContain('programs.nix-ld.enable = true;') + expect(fix).toContain(`(exit 127: ${NIXOS_STUB_OUTPUT})`) + }) + + it('has no fix for other failures', () => { + expect( + wslUnsupportedDistroFixMessage(new RemoteNodeRuntimeSelfTestError(139, 'Segmentation fault')) + ).toBeNull() + expect(wslUnsupportedDistroFixMessage(new Error('bridge refused'))).toBeNull() + }) +}) diff --git a/src/main/wsl/wsl-distro-unsupported.ts b/src/main/wsl/wsl-distro-unsupported.ts new file mode 100644 index 00000000000..b11d819d169 --- /dev/null +++ b/src/main/wsl/wsl-distro-unsupported.ts @@ -0,0 +1,70 @@ +/** + * Distros that can't host Orca's server, each refused with the step that fixes it. There is no + * fallback once the WSL bridge is gone, so a refusal must say what to run, not only what failed. + */ +import { OrcadHostUnsupportedError } from '../orcad/orcad-host-unavailable' +import { OrcadHostGlibcUnsupportedError } from '../managed-server/lifecycle/orcad-runtime-target' +import { RemoteNodeRuntimeSelfTestError } from '../ssh/orcad-remote-node-runtime-report' + +export function wsl1DistroMessage(distro: string): string { + return ( + `${distro} runs WSL 1, which Orca no longer supports. ` + + `Convert it to WSL 2 in a Windows terminal with: wsl --set-version ${distro} 2` + ) +} + +export function wslDistroNotRegisteredMessage(distro: string): string { + return ( + `${distro} is not an installed WSL distro. ` + + 'Run wsl --list --verbose in a Windows terminal to see the installed ones, ' + + 'or reinstall it and try again.' + ) +} + +/** Keeps OrcadHostUnsupportedError's name, so callers that classify by name still see it. */ +export class WslDistroVersion1Error extends OrcadHostUnsupportedError { + constructor(distro: string) { + super(wsl1DistroMessage(distro)) + } +} + +export class WslDistroNotRegisteredError extends OrcadHostUnsupportedError { + constructor(distro: string) { + super(wslDistroNotRegisteredMessage(distro)) + } +} + +// wsl.exe localizes its sentence but not the error code that newer builds print after it. +const DISTRO_NOT_FOUND = /WSL_E_DISTRO_NOT_FOUND|There is no distribution with the supplied name/i + +export function isWslDistroNotFoundFailure(error: unknown): boolean { + return error instanceof Error && DISTRO_NOT_FOUND.test(error.message) +} + +/** Rethrows wsl.exe's missing-distro failure as the refusal that says how to fix it. */ +export function refuseMissingWslDistro(distro: string): (error: unknown) => never { + return (error) => { + throw isWslDistroNotFoundFailure(error) ? new WslDistroNotRegisteredError(distro) : error + } +} + +// NixOS's stub loader refuses generic Linux binaries with this sentence. +const NIXOS_STUB_LOADER = /NixOS cannot run dynamically linked executables/ + +/** The shared server refusal plus the step that fixes it in a distro; null when there is none. */ +export function wslUnsupportedDistroFixMessage(error: unknown): string | null { + if (error instanceof OrcadHostGlibcUnsupportedError) { + return ( + `${error.message} Use a Linux release with glibc 2.28 or newer ` + + '(for example Ubuntu 20.04 or Debian 10 and later).' + ) + } + if (error instanceof RemoteNodeRuntimeSelfTestError && NIXOS_STUB_LOADER.test(error.output)) { + return ( + 'NixOS can’t run Orca’s Node runtime as it is. Turn on nix-ld ' + + '(programs.nix-ld.enable = true; in configuration.nix), run nixos-rebuild switch, ' + + `then reconnect. (exit ${error.exitStatus ?? 'unknown'}: ${error.output})` + ) + } + return null +} diff --git a/src/main/wsl/wsl-host-add-admission.ts b/src/main/wsl/wsl-host-add-admission.ts index 81420eb5379..aa0f17a636e 100644 --- a/src/main/wsl/wsl-host-add-admission.ts +++ b/src/main/wsl/wsl-host-add-admission.ts @@ -2,6 +2,7 @@ import type { WslHostAddResult } from '../../shared/wsl-host-types' import type { WslHostDistroCandidate } from './wsl-host-distro-list' import { wslDistroHolderKey } from './wsl-distro-holder-record' +import { wsl1DistroMessage, wslDistroNotRegisteredMessage } from './wsl-distro-unsupported' export const WSL_HOSTS_DISABLED_MESSAGE = 'WSL hosts are turned off in Settings → Experimental.' export const WSL_HOST_SERVER_FILES_MISSING_MESSAGE = @@ -53,14 +54,14 @@ export function admitWslHostDistro(args: { return { ok: false, kind: 'failed', - message: `${args.requested} is not a registered WSL distro.` + message: wslDistroNotRegisteredMessage(args.requested) } } if (distro.version === 1) { return { ok: false, kind: 'unsupported-wsl1', - message: `${distro.name} runs WSL 1, which isn’t supported as a host. Convert it with: wsl --set-version ${distro.name} 2` + message: wsl1DistroMessage(distro.name) } } if (args.alreadyAdded(distro.name)) { diff --git a/src/main/wsl/wsl-host-first-connect.ts b/src/main/wsl/wsl-host-first-connect.ts index 62e30e2efb8..b3b2afe6d3c 100644 --- a/src/main/wsl/wsl-host-first-connect.ts +++ b/src/main/wsl/wsl-host-first-connect.ts @@ -4,6 +4,8 @@ import type { WslHostAddResult } from '../../shared/wsl-host-types' import { OrcadHostUnsupportedError } from '../orcad/orcad-host-unavailable' import type { WslHostConnector } from './wsl-host-service' import { WslOrcadLaunchDeferredError } from './wsl-orcad-connection-errors' +import { WslDistroVersion1Error, wslUnsupportedDistroFixMessage } from './wsl-distro-unsupported' +import { RemoteNodeRuntimeSelfTestError } from '../ssh/orcad-remote-node-runtime-report' import type { WslOrcadConnection } from './wsl-orcad-connection' type WslHostAddRefusal = Extract @@ -30,8 +32,8 @@ export async function connectForWslHostAdd( } return { ok: false, - kind: error instanceof OrcadHostUnsupportedError ? 'unsupported-wsl1' : 'failed', - message: errorMessage(error) + kind: connectFailureKind(error), + message: wslUnsupportedDistroFixMessage(error) ?? errorMessage(error) } } const refusal = lateRefusal() @@ -41,3 +43,14 @@ export async function connectForWslHostAdd( } return { connection } } + +/** A distro Orca can't run in reads apart from a connect that may work on retry. */ +function connectFailureKind(error: unknown): WslHostAddRefusal['kind'] { + if (error instanceof WslDistroVersion1Error) { + return 'unsupported-wsl1' + } + return error instanceof OrcadHostUnsupportedError || + error instanceof RemoteNodeRuntimeSelfTestError + ? 'unsupported-distro' + : 'failed' +} diff --git a/src/main/wsl/wsl-host-service.test.ts b/src/main/wsl/wsl-host-service.test.ts index 5589c4fe90e..be8a33e587d 100644 --- a/src/main/wsl/wsl-host-service.test.ts +++ b/src/main/wsl/wsl-host-service.test.ts @@ -13,6 +13,8 @@ import { getWslHostDeploymentStorePath } from '../../shared/wsl-host-deployment- import { getPreferredPairingOffer } from '../../shared/runtime-environments' import type { WslHostStatus } from '../../shared/wsl-host-types' import { OrcadHostUnsupportedError } from '../orcad/orcad-host-unavailable' +import { RemoteNodeRuntimeSelfTestError } from '../ssh/orcad-remote-node-runtime-report' +import { wsl1DistroMessage, WslDistroVersion1Error } from './wsl-distro-unsupported' import { WslHostService, type WslHostConnector } from './wsl-host-service' import { wakeRememberedWslHosts } from './wsl-host-early-wake' import { WslOrcadLaunchDeferredError } from './wsl-orcad-connection-errors' @@ -773,10 +775,17 @@ describe('WslHostService', () => { }) it.each([ + ['unsupported-wsl1', () => new WslDistroVersion1Error('Ubuntu'), wsl1DistroMessage('Ubuntu')], [ - 'unsupported-wsl1', - () => new OrcadHostUnsupportedError('WSL 1 cannot run the Orca server.'), - 'WSL 1 cannot run the Orca server.' + 'unsupported-distro', + () => new OrcadHostUnsupportedError('No Orca runtime supports linux-arm64-glibc.'), + 'No Orca runtime supports linux-arm64-glibc.' + ], + [ + 'unsupported-distro', + () => + new RemoteNodeRuntimeSelfTestError(127, 'libstdc++.so.6: cannot open shared object file'), + expect.stringContaining('apk add libstdc++') ], ['failed', () => new Error('bridge refused'), 'bridge refused'] ])( diff --git a/src/main/wsl/wsl-orcad-deploy.test.ts b/src/main/wsl/wsl-orcad-deploy.test.ts index c80d3baee31..de77feb8213 100644 --- a/src/main/wsl/wsl-orcad-deploy.test.ts +++ b/src/main/wsl/wsl-orcad-deploy.test.ts @@ -209,7 +209,25 @@ describe('deployAndLaunchWslOrcad', () => { it('refuses a WSL 1 distro before holding it or touching its files', async () => { await expect(launch('4.4.0-19041-Microsoft')).rejects.toMatchObject({ - name: 'OrcadHostUnsupportedError' + name: 'OrcadHostUnsupportedError', + message: expect.stringContaining('wsl --set-version Ubuntu-24.04 2') + }) + expect(mocks.steps).toEqual(['uname']) + }) + + it('tells an unregistered distro how to find the installed ones, before holding anything', async () => { + mocks.exec.mockImplementation(async () => { + mocks.steps.push('uname') + throw new Error( + 'Command "uname -r" failed (exit 1): There is no distribution with the supplied name.\n' + + 'Error code: Wsl/Service/WSL_E_DISTRO_NOT_FOUND' + ) + }) + await expect( + deployAndLaunchWslOrcad('Gone', { holder: holder(), createTransport: transport }) + ).rejects.toMatchObject({ + name: 'OrcadHostUnsupportedError', + message: expect.stringContaining('wsl --list --verbose') }) expect(mocks.steps).toEqual(['uname']) }) diff --git a/src/main/wsl/wsl-orcad-deploy.ts b/src/main/wsl/wsl-orcad-deploy.ts index 52a1c8bc13a..092fe890263 100644 --- a/src/main/wsl/wsl-orcad-deploy.ts +++ b/src/main/wsl/wsl-orcad-deploy.ts @@ -8,7 +8,6 @@ import { recoverInterruptedOrcadActivation } from '../managed-server/activation/ import { readOrcadActivationRecord } from '../managed-server/activation/orcad-activation-record-store' import { readOrcadActivationTransaction } from '../managed-server/activation/orcad-activation-transaction-store' import { materializeOrcadArtifact } from '../orcad/orcad-artifact-materializer' -import { OrcadHostUnsupportedError } from '../orcad/orcad-host-unavailable' import type { OrcadHostTransport } from '../ssh/orcad-host-transport' import { ORCAD_ACTIVATION_POLICY_REFUSED_CODE } from '../ssh/orcad-installed-activation' import { orcadBoundPort } from '../managed-server/lifecycle/orcad-managed-bound-port' @@ -38,6 +37,7 @@ import { joinRemotePath } from '../ssh/ssh-remote-platform' import { readLocalFullVersion } from '../managed-server/install/remote-versioned-install' import { createWslDistroHolder, type WslDistroHolder } from './wsl-distro-holder' import { wslDistroHolderKey } from './wsl-distro-holder-record' +import { refuseMissingWslDistro, WslDistroVersion1Error } from './wsl-distro-unsupported' import { readWslDistroRegistryIdShared } from './wsl-distro-registry-id' import type { WslOrcadHostTarget } from './wsl-orcad-host-target' import { createWslOrcadHostTransport, type WslOrcadHostTransport } from './wsl-orcad-host-transport' @@ -134,12 +134,9 @@ async function launchOverTransport( const { signal } = options let ownBuildVersion: string | null = null const conn = withOwnBuildLaunchEnv(transport, () => ownBuildVersion) - const kernel = await execCommand(conn, 'uname -r', { signal }) + const kernel = await execCommand(conn, 'uname -r', { signal }).catch(refuseMissingWslDistro(name)) if (isWsl1KernelRelease(kernel)) { - throw new OrcadHostUnsupportedError( - `The WSL 1 distro ${name} isn’t supported by Orca’s server. ` + - `Convert it with: wsl --set-version ${name} 2` - ) + throw new WslDistroVersion1Error(name) } // Acquire a holder before launch or WSL can idle-stop orcad's distro. const holder = options.holder ?? createWslDistroHolder({ userDataPath: userDataPath() }) diff --git a/src/renderer/src/components/settings/WslHostsSection.tsx b/src/renderer/src/components/settings/WslHostsSection.tsx index aa72b94561b..2141b388ab4 100644 --- a/src/renderer/src/components/settings/WslHostsSection.tsx +++ b/src/renderer/src/components/settings/WslHostsSection.tsx @@ -44,6 +44,7 @@ import { } from '../wsl-move/wsl-move-orchestrator-status' import { openWslMoveDialogFor } from '../wsl-move/wsl-move-entry' import { WslMoveUnfinishedNotice } from '../wsl-move/WslMoveUnfinishedNotice' +import { readIpcErrorDetail } from '@/lib/ipc-error' function getWslHostRowStatus( status: WslHostStatus | undefined, @@ -118,7 +119,8 @@ export function WslHostsSection({ await api.connect({ environmentId, ...(intent ? { intent } : {}) }) onChanged() } catch (error) { - toast.error(error instanceof Error ? error.message : String(error)) + // The main process's message is for the user; Electron's wrapper and the class name aren't. + toast.error(readIpcErrorDetail(error) ?? String(error)) } finally { setConnectingId(null) } diff --git a/src/renderer/src/components/sidebar/AddWslHostDialogBody.tsx b/src/renderer/src/components/sidebar/AddWslHostDialogBody.tsx index 623381811f3..fbcea74ada9 100644 --- a/src/renderer/src/components/sidebar/AddWslHostDialogBody.tsx +++ b/src/renderer/src/components/sidebar/AddWslHostDialogBody.tsx @@ -5,6 +5,7 @@ import { useAppStore } from '@/store' import { AddWslHostFormPanel, canAddWslDistro } from './AddWslHostFormPanel' import { useWslDistroList } from './use-wsl-distro-list' import { useWslHostServerFilesMissing } from './use-wsl-hosts-enabled' +import { readIpcErrorDetail } from '@/lib/ipc-error' export function AddWslHostDialogBody({ adding, @@ -64,7 +65,7 @@ export function AddWslHostDialogBody({ ) onAdded() } catch (cause) { - setError(cause instanceof Error ? cause.message : String(cause)) + setError(readIpcErrorDetail(cause) ?? String(cause)) } finally { onAddingChange(false) } diff --git a/src/renderer/src/components/sidebar/AddWslHostFormPanel.test.tsx b/src/renderer/src/components/sidebar/AddWslHostFormPanel.test.tsx index f43ed0242d7..63bc6368b50 100644 --- a/src/renderer/src/components/sidebar/AddWslHostFormPanel.test.tsx +++ b/src/renderer/src/components/sidebar/AddWslHostFormPanel.test.tsx @@ -50,6 +50,11 @@ describe('AddWslHostFormPanel', () => { expect(html).toContain('until you move them to this host') }) + it('spells out the WSL 2 conversion for a WSL 1 distro the list cannot offer', () => { + expect(render()).toContain('wsl --set-version Legacy 2') + expect(render({ distros: [ubuntu] })).not.toContain('wsl --set-version') + }) + it('says this build cannot run WSL hosts and offers nothing to add', () => { const html = render({ selectedDistro: 'Ubuntu', serverFilesMissing: true }) expect(html).toContain('This build of Orca can’t run WSL hosts (server files missing).') diff --git a/src/renderer/src/components/sidebar/AddWslHostFormPanel.tsx b/src/renderer/src/components/sidebar/AddWslHostFormPanel.tsx index fae5c9a9f4d..7908f739bdd 100644 --- a/src/renderer/src/components/sidebar/AddWslHostFormPanel.tsx +++ b/src/renderer/src/components/sidebar/AddWslHostFormPanel.tsx @@ -58,6 +58,8 @@ export function AddWslHostFormPanel({ onCancel: () => void }): React.JSX.Element { const selected = distros.find((distro) => distro.name === selectedDistro) + // A disabled option can't show its refusal, so the conversion command is spelled out here. + const wsl1Distro = distros.find((distro) => distro.version === 1) const stopped = selected?.state === 'stopped' && canAddWslDistro(selected) return ( <> @@ -134,6 +136,15 @@ export function AddWslHostFormPanel({ )}

) : null} + {wsl1Distro ? ( +

+ {translate( + 'auto.components.sidebar.AddWslHostFormPanel.wsl1Hint', + 'WSL 1 distros can’t be hosts. Convert one in a Windows terminal with: wsl --set-version {{value0}} 2', + { value0: wsl1Distro.name } + )} +

+ ) : null} {stopped ? (

{translate( diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 02a8c84660e..6a33c2f2aab 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6573,7 +6573,8 @@ "adding": "Setting up…", "startAndAdd": "Start and add", "add": "Add host", - "refresh": "Refresh" + "refresh": "Refresh", + "wsl1Hint": "WSL 1 distros can’t be hosts. Convert one in a Windows terminal with: wsl --set-version {{value0}} 2" }, "wslHostRowStatus": { "connecting": "Starting", diff --git a/src/renderer/src/lib/ipc-error.test.ts b/src/renderer/src/lib/ipc-error.test.ts index 36751451c00..4d2edd2b52e 100644 --- a/src/renderer/src/lib/ipc-error.test.ts +++ b/src/renderer/src/lib/ipc-error.test.ts @@ -99,6 +99,15 @@ describe('readIpcErrorDetail', () => { ) ).toBe('SSH connection failed: Relay package not found.') }) + + it("leaves only an unsupported distro's fix for the user", () => { + const error = new Error( + "Error invoking remote method 'wslHosts:connect': OrcadHostUnsupportedError: Ubuntu-18.04 has glibc 2.27; Orca's WSL host needs 2.28 or newer. Upgrade the distro, or keep this project on Windows." + ) + expect(readIpcErrorDetail(error)).toBe( + "Ubuntu-18.04 has glibc 2.27; Orca's WSL host needs 2.28 or newer. Upgrade the distro, or keep this project on Windows." + ) + }) }) // Typed main-process class names must not obscure actionable worktree-removal refusal copy (#19334). diff --git a/src/shared/wsl-host-types.ts b/src/shared/wsl-host-types.ts index 277a8360f62..c90da4c3302 100644 --- a/src/shared/wsl-host-types.ts +++ b/src/shared/wsl-host-types.ts @@ -68,6 +68,8 @@ export type WslHostServerStop = export type WslHostAddFailureKind = | 'unsupported-wsl1' + /** Its platform, libc or loader can't run Orca's server (e.g. NixOS, musl without libstdc++). */ + | 'unsupported-distro' | 'distro-stopped' | 'already-added' | 'ssh-managed'