mirror of https://github.com/openclaw/openclaw.git
fix(doctor): avoid repeat talk normalization changes from key order (#59911)
Merged via squash.
Prepared head SHA: a67bcaa11b
Co-authored-by: ejames-dev <180847219+ejames-dev@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
This commit is contained in:
parent
022618e887
commit
a26b844b88
|
|
@ -102,7 +102,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Synology Chat/security: default low-level HTTPS helper TLS verification to on so helper/API defaults match the shipped safe account default, and only explicit `allowInsecureSsl: true` opts out.
|
||||
- Android/canvas security: require exact normalized A2UI URL matches before forwarding canvas bridge actions, rejecting query mismatches and descendant paths while still allowing fragment-only A2UI navigation.
|
||||
- Cron: send failure notifications through the job's primary delivery channel using the same session context as successful delivery when no explicit `failureDestination` is configured. (#60622) Thanks @artwalker.
|
||||
- Gateway/auth: serialize async shared-secret auth attempts per client so concurrent Tailscale-capable failures cannot overrun the intended auth rate-limit budget. Thanks @Telecaster2147.
|
||||
- Gateway/auth: serialize async shared-secret auth attempts per client so concurrent Tailscale-capable failures cannot overrun the intended auth rate-limit budget. Thanks @Telecaster2147.- Doctor/config: compare normalized `talk` configs by deep structural equality instead of key-order-sensitive serialization so `openclaw doctor --fix` stops repeatedly reporting/applying no-op `talk.provider/providers` normalization. (#59911) Thanks @ejames-dev.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
|
|
|
|||
|
|
@ -2017,4 +2017,54 @@ describe("doctor config flow", () => {
|
|||
expect(cfg.channels.googlechat.dm.allowFrom).toEqual(["*"]);
|
||||
expect(cfg.channels.googlechat.allowFrom).toEqual(["*"]);
|
||||
});
|
||||
|
||||
it("does not report repeat talk provider normalization on consecutive repair runs", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
await fs.mkdir(configDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(configDir, "openclaw.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
talk: {
|
||||
interruptOnSpeech: true,
|
||||
silenceTimeoutMs: 1500,
|
||||
provider: "elevenlabs",
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
apiKey: "secret-key",
|
||||
voiceId: "voice-123",
|
||||
modelId: "eleven_v3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {});
|
||||
try {
|
||||
await loadAndMaybeMigrateDoctorConfig({
|
||||
options: { nonInteractive: true, repair: true },
|
||||
confirm: async () => false,
|
||||
});
|
||||
noteSpy.mockClear();
|
||||
|
||||
await loadAndMaybeMigrateDoctorConfig({
|
||||
options: { nonInteractive: true, repair: true },
|
||||
confirm: async () => false,
|
||||
});
|
||||
const secondRunTalkNormalizationLines = noteSpy.mock.calls
|
||||
.filter((call) => call[1] === "Doctor changes")
|
||||
.map((call) => String(call[0]))
|
||||
.filter((line) => line.includes("Normalized talk.provider/providers shape"));
|
||||
expect(secondRunTalkNormalizationLines).toEqual([]);
|
||||
} finally {
|
||||
noteSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -759,6 +759,28 @@ describe("normalizeCompatibilityConfigValues", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("does not report talk provider normalization for semantically identical key ordering differences", () => {
|
||||
const input = {
|
||||
talk: {
|
||||
interruptOnSpeech: true,
|
||||
silenceTimeoutMs: 1500,
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
apiKey: "secret-key",
|
||||
voiceId: "voice-123",
|
||||
modelId: "eleven_v3",
|
||||
},
|
||||
},
|
||||
provider: "elevenlabs",
|
||||
},
|
||||
};
|
||||
|
||||
const res = normalizeCompatibilityConfigValues(input);
|
||||
|
||||
expect(res.config).toEqual(input);
|
||||
expect(res.changes).toEqual([]);
|
||||
});
|
||||
|
||||
it("migrates tools.message.allowCrossContextSend to canonical crossContext settings", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
tools: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { migrateVoiceCallLegacyConfigInput } from "../../extensions/voice-call/config-api.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { shouldMoveSingleAccountChannelKey } from "../channels/plugins/setup-helpers.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveNormalizedProviderModelMaxTokens } from "../config/defaults.js";
|
||||
|
|
@ -386,7 +387,7 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
|||
return;
|
||||
}
|
||||
|
||||
const sameShape = JSON.stringify(normalizedTalk) === JSON.stringify(rawTalk);
|
||||
const sameShape = isDeepStrictEqual(normalizedTalk, rawTalk);
|
||||
if (sameShape) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue