fix(ui): show configured thinking defaults

This commit is contained in:
Peter Steinberger
2026-04-27 03:21:37 +01:00
parent 90ad79cbcd
commit bd42f35097
5 changed files with 73 additions and 1 deletions

View File

@@ -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 <id>` 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.

View File

@@ -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 } : {}),
}),
],
};

View File

@@ -180,6 +180,8 @@ function createChatHeaderState(
model?: string | null;
modelProvider?: string | null;
models?: ModelCatalogEntry[];
defaultsThinkingDefault?: string;
thinkingDefault?: string;
omitSessionFromList?: boolean;
} = {},
): { state: AppViewState; request: ReturnType<typeof vi.fn> } {
@@ -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<HTMLSelectElement>(
'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<HTMLSelectElement>(
'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)");
});
});

View File

@@ -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();

View File

@@ -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,