From 07a9c459a969d542b6cdba953773310812d20535 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 16 Oct 2026 18:39:17 +0800 Subject: [PATCH] fix(openclaw): keep the User-Agent switch in sync with JSON header edits The Send User-Agent switch was only derived from settingsConfig.headers on mount and on preset reset. Editing headers directly in the Configuration JSON editor left it stale in both directions: adding a UA in JSON kept the switch off (and clicking it then replaced the custom value with the default UA), removing it kept the switch on. Add syncOpenclawUserAgentFromConfig to useOpenclawFormState and call it from the OpenClaw JSON editor's onChange. It only reads valid JSON objects, sets the switch to the case-insensitive presence of a User-Agent header, never rewrites the JSON, and keeps the last state while the draft is invalid. Refs #8256 --- .../providers/forms/ProviderForm.tsx | 7 +++- .../forms/hooks/useOpenclawFormState.ts | 16 +++++++++ tests/hooks/useOpenclawFormState.test.tsx | 34 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index b1ee5832f..1fb54e5a6 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -2945,7 +2945,12 @@ function ProviderFormFull({ form.setValue("settingsConfig", config)} + onChange={(config) => { + form.setValue("settingsConfig", config); + if (appId === "openclaw") { + openclawForm.syncOpenclawUserAgentFromConfig(config); + } + }} placeholder={ appId === "hermes" ? `{ diff --git a/src/components/providers/forms/hooks/useOpenclawFormState.ts b/src/components/providers/forms/hooks/useOpenclawFormState.ts index 32709b565..9d1dbee2f 100644 --- a/src/components/providers/forms/hooks/useOpenclawFormState.ts +++ b/src/components/providers/forms/hooks/useOpenclawFormState.ts @@ -31,6 +31,7 @@ export interface OpenclawFormState { handleOpenclawApiChange: (api: string) => void; handleOpenclawModelsChange: (models: OpenClawModel[]) => void; handleOpenclawUserAgentChange: (enabled: boolean) => void; + syncOpenclawUserAgentFromConfig: (config: string) => void; resetOpenclawState: (config?: OpenClawProviderConfig) => void; } @@ -184,6 +185,20 @@ export function useOpenclawFormState({ [updateOpenclawConfig], ); + // The JSON editor writes settingsConfig directly; keep the switch in step + // with header edits made there. Invalid drafts keep the last known state. + const syncOpenclawUserAgentFromConfig = useCallback((config: string) => { + try { + const parsed = JSON.parse(config); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return; + } + setOpenclawUserAgent(hasUserAgentHeader(parsed.headers)); + } catch { + // ignore incomplete JSON while typing + } + }, []); + const resetOpenclawState = useCallback((config?: OpenClawProviderConfig) => { setOpenclawProviderKey(""); setOpenclawBaseUrl(config?.baseUrl || ""); @@ -208,6 +223,7 @@ export function useOpenclawFormState({ handleOpenclawApiChange, handleOpenclawModelsChange, handleOpenclawUserAgentChange, + syncOpenclawUserAgentFromConfig, resetOpenclawState, }; } diff --git a/tests/hooks/useOpenclawFormState.test.tsx b/tests/hooks/useOpenclawFormState.test.tsx index 463bc58ef..4a629cb05 100644 --- a/tests/hooks/useOpenclawFormState.test.tsx +++ b/tests/hooks/useOpenclawFormState.test.tsx @@ -171,3 +171,37 @@ it("reset tolerates null headers from JSON without rewriting settings", () => { expect(test.read()).toEqual(test.original); expect(test.onSettingsConfigChange).not.toHaveBeenCalled(); }); + +describe("useOpenclawFormState JSON editor sync", () => { + it("turns the switch on when a User-Agent is added in JSON", () => { + const test = mount(customHeaders); + expect(test.result.current.openclawUserAgent).toBe(false); + act(() => + test.result.current.syncOpenclawUserAgentFromConfig( + JSON.stringify({ + ...base, + headers: { ...customHeaders, "user-agent": "edited-agent" }, + }), + ), + ); + expect(test.result.current.openclawUserAgent).toBe(true); + expect(test.onSettingsConfigChange).not.toHaveBeenCalled(); + }); + it("turns the switch off when the User-Agent is removed in JSON", () => { + const test = mount({ ...customHeaders, "User-Agent": "fixture-agent" }); + expect(test.result.current.openclawUserAgent).toBe(true); + act(() => + test.result.current.syncOpenclawUserAgentFromConfig( + JSON.stringify({ ...base, headers: customHeaders }), + ), + ); + expect(test.result.current.openclawUserAgent).toBe(false); + }); + it("keeps the last state while the JSON draft is invalid", () => { + const test = mount({ "User-Agent": "fixture-agent" }); + act(() => + test.result.current.syncOpenclawUserAgentFromConfig('{"headers": {'), + ); + expect(test.result.current.openclawUserAgent).toBe(true); + }); +});