From bd42f350978b8202f5f7b11855912820387ea3de Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 03:21:37 +0100 Subject: [PATCH] fix(ui): show configured thinking defaults --- CHANGELOG.md | 1 + ui/src/ui/chat-model.test-helpers.ts | 3 ++ ui/src/ui/views/chat.test.ts | 41 ++++++++++++++++++++++++++++ ui/src/ui/views/sessions.test.ts | 26 ++++++++++++++++++ ui/src/ui/views/sessions.ts | 3 +- 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c91c10fa6d..123944b43a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai - Providers/Ollama: route local web search through Ollama's signed `/api/experimental/web_search` daemon proxy, use hosted `/api/web_search` directly for `ollama.com`, and keep `OLLAMA_API_KEY` scoped to cloud fallback auth. Fixes #69132. Thanks @yoon1012 and @hyspacex. - Memory/doctor: treat Ollama memory embeddings as key-optional so `openclaw doctor` no longer warns about a missing API key when the gateway reports embeddings are ready. Fixes #46584. Thanks @fengly78. - Agents/Ollama: apply provider-owned replay turn normalization to native Ollama chat so Cloud models no longer reject non-alternating replay history in agent/Gateway runs. Fixes #71697. Thanks @ismael-81. +- Control UI/Ollama: show the resolved configured thinking default in chat and session thinking dropdowns so inherited `adaptive`/per-model thinking config no longer appears as `Default (off)` or a generic inherit value. Fixes #72407. Thanks @NotecAG. - Agents/Ollama: validate explicit `--thinking max` against catalog-discovered Ollama reasoning metadata so local agent runs accept the same native thinking levels shown in the model catalog. Fixes #71584. Thanks @g0st1n. - CLI/models: include explicitly configured provider models in `openclaw models list --provider ` without requiring the full catalog path, so configured Ollama models are visible. Fixes #65207. Thanks @drzeast-png. - Docker/QA: add observability coverage to the normal Docker aggregate so QA-lab OTEL and Prometheus diagnostics run inside Docker. Thanks @vincentkoc. diff --git a/ui/src/ui/chat-model.test-helpers.ts b/ui/src/ui/chat-model.test-helpers.ts index 0c5f78c77d3..fb86798a989 100644 --- a/ui/src/ui/chat-model.test-helpers.ts +++ b/ui/src/ui/chat-model.test-helpers.ts @@ -63,6 +63,7 @@ export function createSessionsListResult( defaultsThinkingLevels?: SessionsListResult["defaults"]["thinkingLevels"]; defaultsThinkingOptions?: string[]; defaultsThinkingDefault?: string; + thinkingDefault?: string; omitSessionFromList?: boolean; } = {}, ): SessionsListResult { @@ -74,6 +75,7 @@ export function createSessionsListResult( defaultsThinkingLevels, defaultsThinkingOptions, defaultsThinkingDefault, + thinkingDefault, omitSessionFromList = false, } = params; @@ -95,6 +97,7 @@ export function createSessionsListResult( createMainSessionRow({ ...(modelProvider ? { modelProvider } : {}), ...(model ? { model } : {}), + ...(thinkingDefault ? { thinkingDefault } : {}), }), ], }; diff --git a/ui/src/ui/views/chat.test.ts b/ui/src/ui/views/chat.test.ts index 3a62433f1cd..5c82c0aa85e 100644 --- a/ui/src/ui/views/chat.test.ts +++ b/ui/src/ui/views/chat.test.ts @@ -180,6 +180,8 @@ function createChatHeaderState( model?: string | null; modelProvider?: string | null; models?: ModelCatalogEntry[]; + defaultsThinkingDefault?: string; + thinkingDefault?: string; omitSessionFromList?: boolean; } = {}, ): { state: AppViewState; request: ReturnType } { @@ -218,6 +220,8 @@ function createChatHeaderState( return createSessionsListResult({ model: currentModel, modelProvider: currentModelProvider, + defaultsThinkingDefault: overrides.defaultsThinkingDefault, + thinkingDefault: overrides.thinkingDefault, omitSessionFromList, }); } @@ -240,6 +244,8 @@ function createChatHeaderState( sessionsResult: createSessionsListResult({ model: currentModel, modelProvider: currentModelProvider, + defaultsThinkingDefault: overrides.defaultsThinkingDefault, + thinkingDefault: overrides.thinkingDefault, omitSessionFromList, }), chatModelOverrides: {}, @@ -704,4 +710,39 @@ describe("chat session controls", () => { ?.textContent?.trim(), ).toBe("maximum"); }); + + it("labels chat thinking default from the active session row", () => { + const { state } = createChatHeaderState({ + model: "gemma4:hermes-e4b", + modelProvider: "ollama", + thinkingDefault: "adaptive", + }); + const container = document.createElement("div"); + render(renderChatSessionSelect(state), container); + + const thinkingSelect = container.querySelector( + 'select[data-chat-thinking-select="true"]', + ); + + expect(thinkingSelect?.value).toBe(""); + expect(thinkingSelect?.options[0]?.textContent?.trim()).toBe("Default (adaptive)"); + expect(thinkingSelect?.title).toBe("Default (adaptive)"); + }); + + it("labels chat thinking default from session defaults when the row is absent", () => { + const { state } = createChatHeaderState({ + defaultsThinkingDefault: "adaptive", + omitSessionFromList: true, + }); + const container = document.createElement("div"); + render(renderChatSessionSelect(state), container); + + const thinkingSelect = container.querySelector( + 'select[data-chat-thinking-select="true"]', + ); + + expect(thinkingSelect?.value).toBe(""); + expect(thinkingSelect?.options[0]?.textContent?.trim()).toBe("Default (adaptive)"); + expect(thinkingSelect?.title).toBe("Default (adaptive)"); + }); }); diff --git a/ui/src/ui/views/sessions.test.ts b/ui/src/ui/views/sessions.test.ts index 74b9e495228..760facc4a09 100644 --- a/ui/src/ui/views/sessions.test.ts +++ b/ui/src/ui/views/sessions.test.ts @@ -109,6 +109,32 @@ describe("sessions view", () => { expect(onPatch).toHaveBeenCalledWith("agent:main:main", { thinkingLevel: "max" }); }); + it("labels inherited thinking with the resolved session default", async () => { + const container = document.createElement("div"); + render( + renderSessions( + buildProps( + buildResult({ + key: "agent:main:main", + kind: "direct", + updatedAt: Date.now(), + thinkingDefault: "adaptive", + thinkingLevels: [ + { id: "off", label: "off" }, + { id: "adaptive", label: "adaptive" }, + ], + }), + ), + ), + container, + ); + await Promise.resolve(); + + const thinking = container.querySelector("tbody select") as HTMLSelectElement | null; + expect(thinking?.value).toBe(""); + expect(thinking?.options[0]?.textContent?.trim()).toBe("Default (adaptive)"); + }); + it("keeps legacy binary thinking labels patching canonical ids", async () => { const container = document.createElement("div"); const onPatch = vi.fn(); diff --git a/ui/src/ui/views/sessions.ts b/ui/src/ui/views/sessions.ts index 2e8d50dec96..e5a475370b6 100644 --- a/ui/src/ui/views/sessions.ts +++ b/ui/src/ui/views/sessions.ts @@ -87,6 +87,7 @@ function normalizeThinkingOptionValue(raw: string): string { function resolveThinkLevelOptions( row: GatewaySessionRow, ): readonly { value: string; label: string }[] { + const defaultLabel = row.thinkingDefault ? `Default (${row.thinkingDefault})` : "inherit"; const options: readonly GatewayThinkingLevelOption[] = row.thinkingLevels?.length ? row.thinkingLevels : (row.thinkingOptions?.length ? row.thinkingOptions : DEFAULT_THINK_LEVELS).map((label) => ({ @@ -94,7 +95,7 @@ function resolveThinkLevelOptions( label, })); return [ - { value: "", label: "inherit" }, + { value: "", label: defaultLabel }, ...options.map((option) => ({ value: normalizeThinkingOptionValue(option.id), label: option.label,