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
This commit is contained in:
Jason committed 2026-10-16 19:29:24 +08:00
1 parent 2e03d8ce06
commit 07a9c459a9
3 files changed
+56 -1

No files matched your search

@@ -2945,7 +2945,12 @@ function ProviderFormFull({
</Label>
<JsonEditor
value={form.getValues("settingsConfig")}
onChange={(config) => form.setValue("settingsConfig", config)}
onChange={(config) => {
form.setValue("settingsConfig", config);
if (appId === "openclaw") {
openclawForm.syncOpenclawUserAgentFromConfig(config);
}
}}
placeholder={
appId === "hermes"
? `{
@@ -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,
};
}
+34
View File
@@ -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);
});
});