diff --git a/src/lib/onboard/provider-host-state.test.ts b/src/lib/onboard/provider-host-state.test.ts index e5b067d920c..0c98a5c7891 100644 --- a/src/lib/onboard/provider-host-state.test.ts +++ b/src/lib/onboard/provider-host-state.test.ts @@ -65,18 +65,19 @@ function detectWithDeps( } describe("detectInferenceProviderHostState", () => { - it("suppresses local endpoint probes when route preflight disallows them (#6315)", () => { + it("suppresses local and Windows-host Ollama probes when provider discovery disables them (#6315, #9604)", () => { const runCapture = vi.fn(() => "{}"); const findReachableOllamaHost = vi.fn(() => "127.0.0.1"); + const detectWindowsHostOllama = vi.fn(() => ({ + installed: true, + installedPath: "C:\\Ollama\\ollama.exe", + loopbackOnly: false, + })); const deps = buildDeps({ runCapture, findReachableOllamaHost, isWsl: vi.fn(() => true), - detectWindowsHostOllama: vi.fn(() => ({ - installed: true, - installedPath: "C:\\Ollama\\ollama.exe", - loopbackOnly: false, - })), + detectWindowsHostOllama, }); const state = detectInferenceProviderHostState({ @@ -91,6 +92,7 @@ describe("detectInferenceProviderHostState", () => { }); expect(findReachableOllamaHost).not.toHaveBeenCalled(); + expect(detectWindowsHostOllama).not.toHaveBeenCalled(); expect(state.ollamaRunning).toBe(false); expect(state.vllmRunning).toBe(false); expect(state.windowsOllamaReachable).toBe(false); diff --git a/src/lib/onboard/provider-host-state.ts b/src/lib/onboard/provider-host-state.ts index f0e4b71f0a7..5866a9e6a7d 100644 --- a/src/lib/onboard/provider-host-state.ts +++ b/src/lib/onboard/provider-host-state.ts @@ -253,7 +253,10 @@ export function detectInferenceProviderHostState( const windowsHostOllamaDockerRequirement = deps.getWindowsHostOllamaDockerRequirement( isWsl ? deps.getContainerRuntime() : null, ); - const winOllamaState = deps.detectWindowsHostOllama(); + const winOllamaState = + input.probeOllama === false + ? { installed: false, installedPath: "", loopbackOnly: false } + : deps.detectWindowsHostOllama(); const hasWindowsOllama = winOllamaState.installed; const windowsOllamaReachable = input.probeOllama === false diff --git a/src/lib/onboard/windows-host-ollama.test.ts b/src/lib/onboard/windows-host-ollama.test.ts index 782e5109a26..80c892441e3 100644 --- a/src/lib/onboard/windows-host-ollama.test.ts +++ b/src/lib/onboard/windows-host-ollama.test.ts @@ -3,10 +3,13 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -const runCapture = vi.fn<(cmd: readonly string[]) => string>(() => ""); +const runCapture = vi.fn(() => ""); vi.mock("../runner", () => ({ - runCapture: (cmd: readonly string[]) => runCapture(cmd), + runCapture: ( + cmd: readonly string[], + options?: Parameters[1], + ) => runCapture(cmd, options), })); vi.mock("../platform", () => ({ @@ -52,7 +55,7 @@ describe("detectWindowsHostOllama", () => { expect(runCapture).not.toHaveBeenCalled(); }); - it("returns uninstalled when all Windows Ollama probes miss", () => { + it("returns absent state when Windows-host probes do not respond (#9604)", () => { runCapture.mockImplementation(() => ""); expect(detectWindowsHostOllama({ isWsl: () => true, runCapture })).toEqual({ @@ -60,5 +63,29 @@ describe("detectWindowsHostOllama", () => { installedPath: "", loopbackOnly: false, }); + expect(runCapture).toHaveBeenCalledTimes(3); + expect(runCapture.mock.calls.map(([, options]) => options)).toEqual([ + { ignoreError: true, timeout: 5_000 }, + { ignoreError: true, timeout: 5_000 }, + { ignoreError: true, timeout: 5_000 }, + ]); + }); + + it("continues when the Windows-host port probe does not respond (#9604)", () => { + const installedPath = "C:\\Users\\tester\\AppData\\Local\\Programs\\Ollama\\ollama.exe"; + const outputs = [installedPath, "42", ""]; + runCapture.mockImplementation(() => outputs.shift() ?? ""); + + expect(detectWindowsHostOllama()).toEqual({ + installed: true, + installedPath, + loopbackOnly: false, + }); + expect(runCapture).toHaveBeenCalledTimes(3); + expect(runCapture.mock.calls.map(([, options]) => options)).toEqual([ + { ignoreError: true, timeout: 5_000 }, + { ignoreError: true, timeout: 5_000 }, + { ignoreError: true, timeout: 5_000 }, + ]); }); }); diff --git a/src/lib/onboard/windows-host-ollama.ts b/src/lib/onboard/windows-host-ollama.ts index 247a92bdd9d..6a03d77d1dd 100644 --- a/src/lib/onboard/windows-host-ollama.ts +++ b/src/lib/onboard/windows-host-ollama.ts @@ -20,6 +20,7 @@ export interface WindowsHostOllamaState { } const POWERSHELL = "powershell.exe"; +const WINDOWS_HOST_OLLAMA_PROBE_TIMEOUT_MS = 5_000; const GET_COMMAND_OLLAMA = "Get-Command ollama.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source"; @@ -55,7 +56,12 @@ function resolveDeps( } function powershell(script: string, deps: DetectWindowsHostOllamaDeps): string { - return deps.runCapture([POWERSHELL, "-Command", script], { ignoreError: true }).trim(); + return deps + .runCapture([POWERSHELL, "-Command", script], { + ignoreError: true, + timeout: WINDOWS_HOST_OLLAMA_PROBE_TIMEOUT_MS, + }) + .trim(); } function probeInstalledPath(deps: DetectWindowsHostOllamaDeps): string { @@ -78,9 +84,7 @@ function probeInstalledPath(deps: DetectWindowsHostOllamaDeps): string { function probeLoopbackOnly(deps: DetectWindowsHostOllamaDeps): boolean { const pid = powershell(GET_PROCESS_OLLAMA_ID, deps); if (!pid) return false; - const listenAddrs = deps.runCapture([POWERSHELL, "-Command", GET_NETTCP_OLLAMA_LISTEN], { - ignoreError: true, - }); + const listenAddrs = powershell(GET_NETTCP_OLLAMA_LISTEN, deps); return /127\.0\.0\.1/.test(listenAddrs) && !/0\.0\.0\.0|^::\s*$/m.test(listenAddrs); }