fix(plugins): reset context engine slot on uninstall · openclaw/openclaw@c6b7444
steipete
·
2026-04-26
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
|
12 | 12 | - Doctor: honor `OPENCLAW_SERVICE_REPAIR_POLICY=external` by reporting gateway service health while skipping service install/start/restart/bootstrap, supervisor rewrites, and legacy service cleanup for externally managed environments. Thanks @shakkernerd. |
13 | 13 | - CLI/update: run package post-update doctor with `--fix` so package updates repair config migrations before restart. Thanks @shakkernerd. |
14 | 14 | - CLI/update: retry failed npm global updates with `--omit=optional` and ignore the superseded first failure when the fallback succeeds. Thanks @shakkernerd. |
| 15 | +- Plugins/uninstall: migrate and reset `plugins.slots.contextEngine` alongside memory slots when plugin ids change or selected plugins are removed. Thanks @shakkernerd. |
15 | 16 | - Agents/Discord: keep raw `Agent failed before reply` runner failures out of Discord group/channel chats and show detailed runner errors in direct chats only when `/verbose` is enabled. Thanks @codex. |
16 | 17 | - Package: include patched dependency files in the published npm package so downstream installs can resolve `patchedDependencies`. (#69224) Thanks @gucasbrg and @vincentkoc. |
17 | 18 | - Plugins/channels: treat malformed bundled channel plugin loaders that return `undefined` as unavailable instead of crashing config and help paths. Fixes #69044. Thanks @frankhli843 and @vincentkoc. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -594,6 +594,7 @@ export function resetPluginsCliTestState() {
|
594 | 594 | allowlist: false, |
595 | 595 | loadPath: false, |
596 | 596 | memorySlot: false, |
| 597 | +contextEngineSlot: false, |
597 | 598 | directory: false, |
598 | 599 | }, |
599 | 600 | }); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -634,6 +634,11 @@ export function registerPluginsCli(program: Command) {
|
634 | 634 | if (cfg.plugins?.slots?.memory === pluginId) { |
635 | 635 | preview.push(`memory slot (will reset to "${defaultSlotIdForKey("memory")}")`); |
636 | 636 | } |
| 637 | +if (cfg.plugins?.slots?.contextEngine === pluginId) { |
| 638 | +preview.push( |
| 639 | +`context engine slot (will reset to "${defaultSlotIdForKey("contextEngine")}")`, |
| 640 | +); |
| 641 | +} |
637 | 642 | const channelIds = plugin?.status === "loaded" ? plugin.channelIds : undefined; |
638 | 643 | const channels = cfg.channels as Record<string, unknown> | undefined; |
639 | 644 | if (hasInstall && channels) { |
@@ -723,6 +728,9 @@ export function registerPluginsCli(program: Command) {
|
723 | 728 | if (result.actions.memorySlot) { |
724 | 729 | removed.push("memory slot"); |
725 | 730 | } |
| 731 | +if (result.actions.contextEngineSlot) { |
| 732 | +removed.push("context engine slot"); |
| 733 | +} |
726 | 734 | if (result.actions.channelConfig) { |
727 | 735 | removed.push("channel config"); |
728 | 736 | } |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -40,6 +40,9 @@ describe("plugins cli uninstall", () => {
|
40 | 40 | installPath: ALPHA_INSTALL_PATH, |
41 | 41 | }, |
42 | 42 | }, |
| 43 | +slots: { |
| 44 | +contextEngine: "alpha", |
| 45 | +}, |
43 | 46 | }, |
44 | 47 | } as OpenClawConfig); |
45 | 48 | buildPluginDiagnosticsReport.mockReturnValue({ |
@@ -53,6 +56,7 @@ describe("plugins cli uninstall", () => {
|
53 | 56 | expect(writeConfigFile).not.toHaveBeenCalled(); |
54 | 57 | expect(refreshPluginRegistry).not.toHaveBeenCalled(); |
55 | 58 | expect(runtimeLogs.some((line) => line.includes("Dry run, no changes made."))).toBe(true); |
| 59 | +expect(runtimeLogs.some((line) => line.includes("context engine slot"))).toBe(true); |
56 | 60 | }); |
57 | 61 | |
58 | 62 | it("uninstalls with --force and --keep-files without prompting", async () => { |
@@ -93,6 +97,7 @@ describe("plugins cli uninstall", () => {
|
93 | 97 | allowlist: false, |
94 | 98 | loadPath: false, |
95 | 99 | memorySlot: false, |
| 100 | +contextEngineSlot: false, |
96 | 101 | directory: false, |
97 | 102 | }, |
98 | 103 | }); |
@@ -162,6 +167,7 @@ describe("plugins cli uninstall", () => {
|
162 | 167 | allowlist: false, |
163 | 168 | loadPath: false, |
164 | 169 | memorySlot: false, |
| 170 | +contextEngineSlot: false, |
165 | 171 | directory: false, |
166 | 172 | }, |
167 | 173 | }); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -365,6 +365,22 @@ describe("removePluginFromConfig", () => {
|
365 | 365 | expect(actions.memorySlot).toBe(expectedChanged); |
366 | 366 | }); |
367 | 367 | |
| 368 | +it("clears context engine slot when uninstalling active context engine plugin", () => { |
| 369 | +const config = createPluginConfig({ |
| 370 | +entries: { |
| 371 | +"context-plugin": { enabled: true }, |
| 372 | +}, |
| 373 | +slots: { |
| 374 | +contextEngine: "context-plugin", |
| 375 | +}, |
| 376 | +}); |
| 377 | + |
| 378 | +const { config: result, actions } = removePluginFromConfig(config, "context-plugin"); |
| 379 | + |
| 380 | +expect(result.plugins?.slots?.contextEngine).toBe("legacy"); |
| 381 | +expect(actions.contextEngineSlot).toBe(true); |
| 382 | +}); |
| 383 | + |
368 | 384 | it("removes plugins object when uninstall leaves only empty slots", () => { |
369 | 385 | const config = createSinglePluginWithEmptySlotsConfig(); |
370 | 386 | |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -13,6 +13,7 @@ export type UninstallActions = {
|
13 | 13 | allowlist: boolean; |
14 | 14 | loadPath: boolean; |
15 | 15 | memorySlot: boolean; |
| 16 | +contextEngineSlot: boolean; |
16 | 17 | channelConfig: boolean; |
17 | 18 | directory: boolean; |
18 | 19 | }; |
@@ -155,6 +156,7 @@ export function removePluginFromConfig(
|
155 | 156 | allowlist: false, |
156 | 157 | loadPath: false, |
157 | 158 | memorySlot: false, |
| 159 | +contextEngineSlot: false, |
158 | 160 | channelConfig: false, |
159 | 161 | }; |
160 | 162 | |
@@ -204,7 +206,7 @@ export function removePluginFromConfig(
|
204 | 206 | } |
205 | 207 | } |
206 | 208 | |
207 | | -// Reset memory slot if this plugin was selected |
| 209 | +// Reset slots if this plugin was selected. |
208 | 210 | let slots = pluginsConfig.slots; |
209 | 211 | if (slots?.memory === pluginId) { |
210 | 212 | slots = { |
@@ -213,6 +215,13 @@ export function removePluginFromConfig(
|
213 | 215 | }; |
214 | 216 | actions.memorySlot = true; |
215 | 217 | } |
| 218 | +if (slots?.contextEngine === pluginId) { |
| 219 | +slots = { |
| 220 | + ...slots, |
| 221 | +contextEngine: defaultSlotIdForKey("contextEngine"), |
| 222 | +}; |
| 223 | +actions.contextEngineSlot = true; |
| 224 | +} |
216 | 225 | if (slots && Object.keys(slots).length === 0) { |
217 | 226 | slots = undefined; |
218 | 227 | } |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -876,6 +876,41 @@ describe("updateNpmInstalledPlugins", () => {
|
876 | 876 | expect(result.config.plugins?.installs?.["voice-call"]).toBeUndefined(); |
877 | 877 | }); |
878 | 878 | |
| 879 | +it("migrates context engine slot when a plugin id changes during update", async () => { |
| 880 | +installPluginFromNpmSpecMock.mockResolvedValue({ |
| 881 | +ok: true, |
| 882 | +pluginId: "@openclaw/context-engine", |
| 883 | +targetDir: "/tmp/openclaw-context-engine", |
| 884 | +version: "0.0.2", |
| 885 | +extensions: ["index.ts"], |
| 886 | +}); |
| 887 | + |
| 888 | +const result = await updateNpmInstalledPlugins({ |
| 889 | +config: { |
| 890 | +plugins: { |
| 891 | +slots: { contextEngine: "context-engine" }, |
| 892 | +installs: { |
| 893 | +"context-engine": { |
| 894 | +source: "npm", |
| 895 | +spec: "@openclaw/context-engine", |
| 896 | +installPath: "/tmp/context-engine", |
| 897 | +}, |
| 898 | +}, |
| 899 | +}, |
| 900 | +} as OpenClawConfig, |
| 901 | +pluginIds: ["context-engine"], |
| 902 | +}); |
| 903 | + |
| 904 | +expect(result.config.plugins?.slots?.contextEngine).toBe("@openclaw/context-engine"); |
| 905 | +expect(result.config.plugins?.installs?.["@openclaw/context-engine"]).toMatchObject({ |
| 906 | +source: "npm", |
| 907 | +spec: "@openclaw/context-engine", |
| 908 | +installPath: "/tmp/openclaw-context-engine", |
| 909 | +version: "0.0.2", |
| 910 | +}); |
| 911 | +expect(result.config.plugins?.installs?.["context-engine"]).toBeUndefined(); |
| 912 | +}); |
| 913 | + |
879 | 914 | it("checks marketplace installs during dry-run updates", async () => { |
880 | 915 | installPluginFromMarketplaceMock.mockResolvedValue({ |
881 | 916 | ok: true, |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -417,13 +417,13 @@ function migratePluginConfigId(cfg: OpenClawConfig, fromId: string, toId: string
|
417 | 417 | delete nextEntries[fromId]; |
418 | 418 | } |
419 | 419 | |
420 | | -const nextSlots = |
421 | | -slots?.memory === fromId |
422 | | -? { |
423 | | - ...slots, |
424 | | - memory: toId, |
425 | | - } |
426 | | - : slots; |
| 420 | +const nextSlots = slots |
| 421 | +? { |
| 422 | + ...slots, |
| 423 | + ...(slots.memory === fromId ? { memory: toId } : {}), |
| 424 | +...(slots.contextEngine === fromId ? { contextEngine: toId } : {}), |
| 425 | +} |
| 426 | +: undefined; |
427 | 427 | |
428 | 428 | return { |
429 | 429 | ...cfg, |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。