mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 12:11:20 +00:00
fix: allow disabled plugin config writes (#63296) (thanks @fuller-stack-dev)
* fix(config): ignore synthesized disabled plugin config on write * test(config): keep write-prepare regression generic * test(config): cover explicit disabled plugin config preservation * fix(config): skip disabled plugin config validation * fix(config): avoid tdz in plugin validation * fix: allow disabled plugin config writes (#63296) (thanks @fuller-stack-dev) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
@@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- WhatsApp/auto-reply: keep inbound reply, media, and composing sends on the current socket across reconnects, wait through reconnect gaps, and retry timeout-only send failures without dropping the active socket ref. (#62892) Thanks @mcaxtr.
|
||||
- Config/plugins: let config writes keep disabled plugin entries without forcing required plugin config schemas or crashing raw plugin validation, so slot switches and similar plugin-state updates persist cleanly. (#63296) Thanks @fuller-stack-dev.
|
||||
|
||||
## 2026.4.9
|
||||
|
||||
|
||||
@@ -25,6 +25,15 @@ vi.mock("../plugins/manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: mockLoadPluginManifestRegistry,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/doctor-contract-registry.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/doctor-contract-registry.js")>();
|
||||
return {
|
||||
...actual,
|
||||
listPluginDoctorLegacyConfigRules: () => [],
|
||||
applyPluginDoctorCompatibilityMigrations: () => ({ next: null, changes: [] }),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./backup-rotation.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./backup-rotation.js")>();
|
||||
return {
|
||||
@@ -180,4 +189,53 @@ describe("config io write", () => {
|
||||
expect(overwriteLogs).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("writes disabled plugin entries without requiring plugin config", async () => {
|
||||
mockLoadPluginManifestRegistry.mockReturnValue({
|
||||
diagnostics: [],
|
||||
plugins: [
|
||||
{
|
||||
id: "required-plugin",
|
||||
origin: "bundled",
|
||||
channels: [],
|
||||
providers: [],
|
||||
kind: ["tool"],
|
||||
configSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: { type: "string" },
|
||||
},
|
||||
required: ["token"],
|
||||
additionalProperties: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
} satisfies PluginManifestRegistry);
|
||||
|
||||
await withSuiteHome(async (home) => {
|
||||
const io = createConfigIO({
|
||||
env: { VITEST: "true" } as NodeJS.ProcessEnv,
|
||||
homedir: () => home,
|
||||
logger: silentLogger,
|
||||
});
|
||||
|
||||
await expect(
|
||||
io.writeConfigFile({
|
||||
agents: { list: [{ id: "main", default: true }] },
|
||||
plugins: {
|
||||
entries: {
|
||||
"required-plugin": {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual({ persistedHash: expect.any(String) });
|
||||
});
|
||||
|
||||
mockLoadPluginManifestRegistry.mockReturnValue({
|
||||
diagnostics: [],
|
||||
plugins: [],
|
||||
} satisfies PluginManifestRegistry);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -970,9 +970,6 @@ function validateConfigObjectWithPluginsBase(
|
||||
const entry = normalizedPlugins.entries[pluginId];
|
||||
const entryExists = entry !== undefined;
|
||||
const entryHasConfig = Boolean(entry?.config);
|
||||
const shouldReplacePluginConfig = opts.applyDefaults
|
||||
? entryExists || entryHasConfig
|
||||
: entryHasConfig;
|
||||
|
||||
const activationState = resolveEffectivePluginActivationState({
|
||||
id: pluginId,
|
||||
@@ -999,7 +996,8 @@ function validateConfigObjectWithPluginsBase(
|
||||
}
|
||||
}
|
||||
|
||||
const shouldValidate = enabled || entryExists || entryHasConfig;
|
||||
const shouldReplacePluginConfig = entryHasConfig || (opts.applyDefaults && enabled);
|
||||
const shouldValidate = enabled || entryHasConfig;
|
||||
if (shouldValidate) {
|
||||
if (record.configSchema) {
|
||||
const res = validateJsonSchemaValue({
|
||||
|
||||
Reference in New Issue
Block a user