mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(control-ui): use canonical thinkingDefault key in Quick Config (#104523)
* fix(control-ui): use canonical thinkingDefault key in Quick Config The Control UI Quick Config panel was reading and writing the non-canonical key agents.defaults.thinkingLevel, which is rejected by the gateway's strict Zod schema with INVALID_REQUEST errors. Fix: read from and write to agents.defaults.thinkingDefault instead. The schema already defines this key and the runtime already reads it at cfg.agents?.defaults?.thinkingDefault, so this is purely a UI path correction with zero runtime changes. The fast-mode control is left unchanged pending a maintainer decision on its intended scope (per-agent, per-model, or new global default). Refs #104501 * test(control-ui): prove quick thinking persistence * docs(changelog): note Quick Config thinking fix * chore: keep changelog release-owned --------- Co-authored-by: Erick Kinnee <erick@ekinnee.dev> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// Control UI tests cover Quick Config persistence through the mocked Gateway.
|
||||
import { chromium, type Browser } from "playwright";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
canRunPlaywrightChromium,
|
||||
installMockGateway,
|
||||
resolvePlaywrightChromiumExecutablePath,
|
||||
startControlUiE2eServer,
|
||||
type ControlUiE2eServer,
|
||||
type MockGatewayRequest,
|
||||
} from "../test-helpers/control-ui-e2e.ts";
|
||||
|
||||
const chromiumExecutablePath = resolvePlaywrightChromiumExecutablePath(chromium.executablePath());
|
||||
const chromiumAvailable = canRunPlaywrightChromium(chromiumExecutablePath);
|
||||
const allowMissingChromium = process.env.OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM === "1";
|
||||
const describeControlUiE2e = chromiumAvailable || !allowMissingChromium ? describe : describe.skip;
|
||||
|
||||
let browser: Browser;
|
||||
let server: ControlUiE2eServer;
|
||||
|
||||
function configResponse(thinkingDefault: "low" | "high", hash: string) {
|
||||
const config = { agents: { defaults: { model: "openai/gpt-5.5", thinkingDefault } } };
|
||||
return {
|
||||
config,
|
||||
hash,
|
||||
issues: [],
|
||||
raw: JSON.stringify(config),
|
||||
valid: true,
|
||||
};
|
||||
}
|
||||
|
||||
function requestRaw(request: MockGatewayRequest): Record<string, unknown> {
|
||||
const params = request.params;
|
||||
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
||||
throw new Error("Expected config.set params");
|
||||
}
|
||||
return JSON.parse(String((params as Record<string, unknown>).raw)) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
describeControlUiE2e("Control UI Quick Config thinking persistence mocked Gateway E2E", () => {
|
||||
beforeAll(async () => {
|
||||
if (!chromiumAvailable) {
|
||||
throw new Error(
|
||||
`Playwright Chromium is not installed or cannot start at ${chromiumExecutablePath}. Run \`pnpm --dir ui exec playwright install --with-deps chromium\`, or set OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM=1 only when intentionally skipping this lane.`,
|
||||
);
|
||||
}
|
||||
server = await startControlUiE2eServer();
|
||||
browser = await chromium.launch({ executablePath: chromiumExecutablePath });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close();
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
it("reads and writes only agents.defaults.thinkingDefault", async () => {
|
||||
const context = await browser.newContext({
|
||||
locale: "en-US",
|
||||
serviceWorkers: "block",
|
||||
viewport: { height: 900, width: 1280 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const initialConfig = configResponse("low", "hash-1");
|
||||
const savedConfig = configResponse("high", "hash-2");
|
||||
const gateway = await installMockGateway(page, {
|
||||
methodResponses: {
|
||||
"config.get": initialConfig,
|
||||
"config.set": savedConfig,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await page.goto(`${server.baseUrl}config`);
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
const modelCard = page.locator(".qs-card--model");
|
||||
const lowButton = modelCard.getByRole("button", { name: "Low", exact: true });
|
||||
await lowButton.waitFor();
|
||||
expect(await lowButton.getAttribute("class")).toContain("qs-segmented__btn--active");
|
||||
|
||||
await modelCard.getByRole("button", { name: "High", exact: true }).click();
|
||||
await page.getByRole("button", { name: "Save", exact: true }).click();
|
||||
|
||||
const raw = requestRaw(await gateway.waitForRequest("config.set"));
|
||||
expect(raw).toEqual({
|
||||
agents: { defaults: { model: "openai/gpt-5.5", thinkingDefault: "high" } },
|
||||
});
|
||||
expect(JSON.stringify(raw)).not.toContain("thinkingLevel");
|
||||
expect(JSON.stringify(raw)).not.toContain("fastMode");
|
||||
|
||||
const freshPage = await context.newPage();
|
||||
await installMockGateway(freshPage, {
|
||||
methodResponses: { "config.get": savedConfig },
|
||||
});
|
||||
await freshPage.goto(`${server.baseUrl}config`);
|
||||
const highButton = freshPage
|
||||
.locator(".qs-card--model")
|
||||
.getByRole("button", { name: "High", exact: true });
|
||||
await highButton.waitFor();
|
||||
expect(await highButton.getAttribute("class")).toContain("qs-segmented__btn--active");
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -816,7 +816,7 @@ export class ConfigPage extends OpenClawLightDomElement {
|
||||
const agentsDefaults = asConfigRecord(asConfigRecord(configObject.agents)?.defaults);
|
||||
const model = typeof agentsDefaults?.model === "string" ? agentsDefaults.model : "default";
|
||||
const thinkingLevel =
|
||||
typeof agentsDefaults?.thinkingLevel === "string" ? agentsDefaults.thinkingLevel : "off";
|
||||
typeof agentsDefaults?.thinkingDefault === "string" ? agentsDefaults.thinkingDefault : "off";
|
||||
const fastMode = agentsDefaults?.fastMode;
|
||||
const appConfig = this.context.config.current;
|
||||
return renderQuickSettings({
|
||||
@@ -879,7 +879,7 @@ export class ConfigPage extends OpenClawLightDomElement {
|
||||
onSaveConfig: () => void runtimeConfig.save(),
|
||||
onApplyConfig: () => void runtimeConfig.apply(),
|
||||
onThinkingChange: (level) =>
|
||||
runtimeConfig.patchForm(["agents", "defaults", "thinkingLevel"], level),
|
||||
runtimeConfig.patchForm(["agents", "defaults", "thinkingDefault"], level),
|
||||
onFastModeChange: (mode: FastMode) =>
|
||||
runtimeConfig.patchForm(["agents", "defaults", "fastMode"], mode),
|
||||
onChannelConfigure: () => this.navigate("communications"),
|
||||
|
||||
Reference in New Issue
Block a user