fix(plugins): serialize lifecycle mutations and preserve setup-required installs (#112763)

* fix(plugins): harden concurrent lifecycle mutations

* fix(update): preserve post-core restart snapshot

* fix(update): retain authoritative plugin records on resume

* fix(update): reconcile plugin records after handoff
This commit is contained in:
Peter Steinberger
2026-07-22 23:25:48 -04:00
committed by GitHub
parent 67ef07863f
commit a12e0f26ee
22 changed files with 1187 additions and 336 deletions
+38 -3
View File
@@ -1766,13 +1766,13 @@ describe("plugins cli install", () => {
expect(clawHubInstallCall().spec).toBe("clawhub:demo");
});
it("does not persist incomplete config entries for config-gated bundled installs", async () => {
it("preserves non-config policy for unconfigured bundled installs", async () => {
const pluginId = "config-required-plugin";
const cfg = {
plugins: {
entries: {
[pluginId]: {
config: {},
hooks: { timeoutMs: 5_000 },
},
},
load: {
@@ -1801,7 +1801,10 @@ describe("plugins cli install", () => {
const writtenConfig = writeConfigFile.mock.calls[
writeConfigFile.mock.calls.length - 1
]?.[0] as OpenClawConfig;
expect(writtenConfig.plugins?.entries?.[pluginId]).toBeUndefined();
expect(writtenConfig.plugins?.entries?.[pluginId]).toEqual({
enabled: false,
hooks: { timeoutMs: 5_000 },
});
expect(writtenConfig.plugins?.load?.paths).toEqual(["/existing/plugin"]);
const record = persistedInstallRecord(pluginId);
expect(record.source).toBe("path");
@@ -1812,6 +1815,38 @@ describe("plugins cli install", () => {
expect(runtimeLogsContain("requires configuration first")).toBe(true);
});
it("rejects invalid authored config for config-gated bundled installs", async () => {
const pluginId = "config-required-plugin";
const cfg = {
plugins: {
entries: {
[pluginId]: {
config: {},
hooks: { timeoutMs: 5_000 },
},
},
},
} as OpenClawConfig;
loadConfig.mockReturnValue(cfg);
findBundledPluginSourceMock.mockReturnValue({
pluginId,
localPath: `/app/dist/extensions/${pluginId}`,
configSchema: {
type: "object",
required: ["token"],
properties: { token: { type: "string" } },
},
requiresConfig: true,
});
await expect(runPluginsCommand(["plugins", "install", pluginId])).rejects.toThrow(
"has invalid configured settings",
);
expect(writeConfigFile).not.toHaveBeenCalled();
expect(enablePluginInConfig).not.toHaveBeenCalled();
});
it("enables config-gated bundled installs when provider-backed config is explicit", async () => {
const pluginId = "config-required-plugin";
const cfg = {