From 2b822f6ed0d204317b8cd3ddcacfa8d3be1662e0 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 04:55:06 -0700 Subject: [PATCH] fix(plugins): preserve default enablement for relocation --- src/cli/plugins-location-bridges.ts | 1 + src/plugins/externalized-bundled-plugins.ts | 2 ++ src/plugins/update.test.ts | 40 +++++++++++++++++++++ src/plugins/update.ts | 1 + 4 files changed, 44 insertions(+) diff --git a/src/cli/plugins-location-bridges.ts b/src/cli/plugins-location-bridges.ts index 9b569c1e8d2..1aad295e7a2 100644 --- a/src/cli/plugins-location-bridges.ts +++ b/src/cli/plugins-location-bridges.ts @@ -19,6 +19,7 @@ function buildBridgeFromPersistedBundledRecord( bundledPluginId: record.pluginId, pluginId: record.pluginId, npmSpec, + ...(record.enabledByDefault === true ? { enabledByDefault: true } : {}), channelIds: record.contributions.channels, }; } diff --git a/src/plugins/externalized-bundled-plugins.ts b/src/plugins/externalized-bundled-plugins.ts index a52fc6f3fb6..009fcb81572 100644 --- a/src/plugins/externalized-bundled-plugins.ts +++ b/src/plugins/externalized-bundled-plugins.ts @@ -7,6 +7,8 @@ export type ExternalizedBundledPluginBridge = { npmSpec: string; /** Bundled directory name, when it differs from bundledPluginId. */ bundledDirName?: string; + /** Previous bundled manifest default enablement from the persisted registry. */ + enabledByDefault?: boolean; /** Legacy ids that should be treated as this plugin during enablement checks. */ legacyPluginIds?: readonly string[]; /** Channel ids that imply this plugin is enabled when configured. */ diff --git a/src/plugins/update.test.ts b/src/plugins/update.test.ts index 4b277150263..e7c00bf502c 100644 --- a/src/plugins/update.test.ts +++ b/src/plugins/update.test.ts @@ -1063,6 +1063,46 @@ describe("syncPluginsForUpdateChannel", () => { }); }); + it("externalizes bundled plugins that were enabled by default", async () => { + resolveBundledPluginSourcesMock.mockReturnValue(new Map()); + installPluginFromNpmSpecMock.mockResolvedValue( + createSuccessfulNpmUpdateResult({ + pluginId: "default-chat", + targetDir: "/tmp/openclaw-plugins/default-chat", + version: "2.0.0", + }), + ); + + const result = await syncPluginsForUpdateChannel({ + channel: "stable", + externalizedBundledPluginBridges: [ + { + bundledPluginId: "default-chat", + enabledByDefault: true, + npmSpec: "@openclaw/default-chat", + channelIds: ["default-chat"], + }, + ], + config: {}, + }); + + expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith( + expect.objectContaining({ + spec: "@openclaw/default-chat", + mode: "update", + expectedPluginId: "default-chat", + }), + ); + expect(result.changed).toBe(true); + expect(result.summary.switchedToNpm).toEqual(["default-chat"]); + expect(result.config.plugins?.installs?.["default-chat"]).toMatchObject({ + source: "npm", + spec: "@openclaw/default-chat", + installPath: "/tmp/openclaw-plugins/default-chat", + version: "2.0.0", + }); + }); + it("does not externalize disabled bundled plugins", async () => { resolveBundledPluginSourcesMock.mockReturnValue(new Map()); diff --git a/src/plugins/update.ts b/src/plugins/update.ts index d8eefbd9af5..458d1fc4b18 100644 --- a/src/plugins/update.ts +++ b/src/plugins/update.ts @@ -325,6 +325,7 @@ function isExternalizedBundledPluginEnabled(params: { origin: "bundled", config: normalized, rootConfig: params.config, + enabledByDefault: params.bridge.enabledByDefault, }).enabled ) { return true;