From 42b9212eb24f25ffd01763eef64b46b690d13488 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 19 Mar 2026 00:42:58 +0000 Subject: [PATCH] fix: preserve interactive Ollama model selection (#49249) (thanks @BruceMacD) --- CHANGELOG.md | 1 + extensions/ollama/index.test.ts | 100 ++++++++++++++++++++++++++++++++ src/wizard/setup.test.ts | 27 +++++++++ src/wizard/setup.ts | 4 +- 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 extensions/ollama/index.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f3edc4dc6c..8421eea4f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- CLI/Ollama onboarding: keep the interactive model picker for explicit `openclaw onboard --auth-choice ollama` runs so setup still selects a default model without reintroducing pre-picker auto-pulls. (#49249) Thanks @BruceMacD. - Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev. - Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev. - Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli. diff --git a/extensions/ollama/index.test.ts b/extensions/ollama/index.test.ts new file mode 100644 index 00000000000..b47ba72efa1 --- /dev/null +++ b/extensions/ollama/index.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it, vi } from "vitest"; +import { createTestPluginApi } from "../../test/helpers/extensions/plugin-api.js"; +import plugin from "./index.js"; + +const promptAndConfigureOllamaMock = vi.hoisted(() => + vi.fn(async () => ({ + config: { + models: { + providers: { + ollama: { + baseUrl: "http://127.0.0.1:11434", + api: "ollama", + models: [], + }, + }, + }, + }, + })), +); +const ensureOllamaModelPulledMock = vi.hoisted(() => vi.fn(async () => {})); + +vi.mock("openclaw/plugin-sdk/ollama-setup", () => ({ + promptAndConfigureOllama: promptAndConfigureOllamaMock, + ensureOllamaModelPulled: ensureOllamaModelPulledMock, + configureOllamaNonInteractive: vi.fn(), + buildOllamaProvider: vi.fn(), +})); + +function registerProvider() { + const registerProviderMock = vi.fn(); + + plugin.register( + createTestPluginApi({ + id: "ollama", + name: "Ollama", + source: "test", + config: {}, + runtime: {} as never, + registerProvider: registerProviderMock, + }), + ); + + expect(registerProviderMock).toHaveBeenCalledTimes(1); + return registerProviderMock.mock.calls[0]?.[0]; +} + +describe("ollama plugin", () => { + it("does not preselect a default model during provider auth setup", async () => { + const provider = registerProvider(); + + const result = await provider.auth[0].run({ + config: {}, + prompter: {} as never, + }); + + expect(promptAndConfigureOllamaMock).toHaveBeenCalledWith({ + cfg: {}, + prompter: {}, + }); + expect(result.configPatch).toEqual({ + models: { + providers: { + ollama: { + baseUrl: "http://127.0.0.1:11434", + api: "ollama", + models: [], + }, + }, + }, + }); + expect(result.defaultModel).toBeUndefined(); + }); + + it("pulls the model the user actually selected", async () => { + const provider = registerProvider(); + const config = { + models: { + providers: { + ollama: { + baseUrl: "http://127.0.0.1:11434", + models: [], + }, + }, + }, + }; + const prompter = {} as never; + + await provider.onModelSelected?.({ + config, + model: "ollama/glm-4.7-flash", + prompter, + }); + + expect(ensureOllamaModelPulledMock).toHaveBeenCalledWith({ + config, + model: "ollama/glm-4.7-flash", + prompter, + }); + }); +}); diff --git a/src/wizard/setup.test.ts b/src/wizard/setup.test.ts index df6ca922338..fa90819632f 100644 --- a/src/wizard/setup.test.ts +++ b/src/wizard/setup.test.ts @@ -410,6 +410,33 @@ describe("runSetupWizard", () => { } }); + it("prompts for a model during explicit interactive Ollama setup", async () => { + promptDefaultModel.mockClear(); + const prompter = buildWizardPrompter({}); + const runtime = createRuntime(); + + await runSetupWizard( + { + acceptRisk: true, + flow: "quickstart", + authChoice: "ollama", + installDaemon: false, + skipSkills: true, + skipSearch: true, + skipHealth: true, + skipUi: true, + }, + runtime, + prompter, + ); + + expect(promptDefaultModel).toHaveBeenCalledWith( + expect.objectContaining({ + allowKeep: false, + }), + ); + }); + it("shows plugin compatibility notices for an existing valid config", async () => { buildPluginCompatibilityNotices.mockReturnValue([ { diff --git a/src/wizard/setup.ts b/src/wizard/setup.ts index 8d1a98883d0..19929c5b07c 100644 --- a/src/wizard/setup.ts +++ b/src/wizard/setup.ts @@ -482,7 +482,9 @@ export async function runSetupWizard( } } - if (authChoiceFromPrompt && authChoice !== "custom-api-key") { + const shouldPromptModelSelection = + authChoice !== "custom-api-key" && (authChoiceFromPrompt || authChoice === "ollama"); + if (shouldPromptModelSelection) { const modelSelection = await promptDefaultModel({ config: nextConfig, prompter,