mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 04:57:09 +02:00
fix(plugins): reset context engine slot on uninstall
This commit is contained in:
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- CLI/update: run package post-update doctor with `--fix` so package updates repair config migrations before restart. Thanks @shakkernerd.
|
||||
- CLI/update: retry failed npm global updates with `--omit=optional` and ignore the superseded first failure when the fallback succeeds. Thanks @shakkernerd.
|
||||
- Plugins/uninstall: migrate and reset `plugins.slots.contextEngine` alongside memory slots when plugin ids change or selected plugins are removed. Thanks @shakkernerd.
|
||||
- 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.
|
||||
- Package: include patched dependency files in the published npm package so downstream installs can resolve `patchedDependencies`. (#69224) Thanks @gucasbrg and @vincentkoc.
|
||||
- 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.
|
||||
|
||||
@@ -594,6 +594,7 @@ export function resetPluginsCliTestState() {
|
||||
allowlist: false,
|
||||
loadPath: false,
|
||||
memorySlot: false,
|
||||
contextEngineSlot: false,
|
||||
directory: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -634,6 +634,11 @@ export function registerPluginsCli(program: Command) {
|
||||
if (cfg.plugins?.slots?.memory === pluginId) {
|
||||
preview.push(`memory slot (will reset to "${defaultSlotIdForKey("memory")}")`);
|
||||
}
|
||||
if (cfg.plugins?.slots?.contextEngine === pluginId) {
|
||||
preview.push(
|
||||
`context engine slot (will reset to "${defaultSlotIdForKey("contextEngine")}")`,
|
||||
);
|
||||
}
|
||||
const channelIds = plugin?.status === "loaded" ? plugin.channelIds : undefined;
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
if (hasInstall && channels) {
|
||||
@@ -723,6 +728,9 @@ export function registerPluginsCli(program: Command) {
|
||||
if (result.actions.memorySlot) {
|
||||
removed.push("memory slot");
|
||||
}
|
||||
if (result.actions.contextEngineSlot) {
|
||||
removed.push("context engine slot");
|
||||
}
|
||||
if (result.actions.channelConfig) {
|
||||
removed.push("channel config");
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ describe("plugins cli uninstall", () => {
|
||||
installPath: ALPHA_INSTALL_PATH,
|
||||
},
|
||||
},
|
||||
slots: {
|
||||
contextEngine: "alpha",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
buildPluginDiagnosticsReport.mockReturnValue({
|
||||
@@ -53,6 +56,7 @@ describe("plugins cli uninstall", () => {
|
||||
expect(writeConfigFile).not.toHaveBeenCalled();
|
||||
expect(refreshPluginRegistry).not.toHaveBeenCalled();
|
||||
expect(runtimeLogs.some((line) => line.includes("Dry run, no changes made."))).toBe(true);
|
||||
expect(runtimeLogs.some((line) => line.includes("context engine slot"))).toBe(true);
|
||||
});
|
||||
|
||||
it("uninstalls with --force and --keep-files without prompting", async () => {
|
||||
@@ -93,6 +97,7 @@ describe("plugins cli uninstall", () => {
|
||||
allowlist: false,
|
||||
loadPath: false,
|
||||
memorySlot: false,
|
||||
contextEngineSlot: false,
|
||||
directory: false,
|
||||
},
|
||||
});
|
||||
@@ -162,6 +167,7 @@ describe("plugins cli uninstall", () => {
|
||||
allowlist: false,
|
||||
loadPath: false,
|
||||
memorySlot: false,
|
||||
contextEngineSlot: false,
|
||||
directory: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -365,6 +365,22 @@ describe("removePluginFromConfig", () => {
|
||||
expect(actions.memorySlot).toBe(expectedChanged);
|
||||
});
|
||||
|
||||
it("clears context engine slot when uninstalling active context engine plugin", () => {
|
||||
const config = createPluginConfig({
|
||||
entries: {
|
||||
"context-plugin": { enabled: true },
|
||||
},
|
||||
slots: {
|
||||
contextEngine: "context-plugin",
|
||||
},
|
||||
});
|
||||
|
||||
const { config: result, actions } = removePluginFromConfig(config, "context-plugin");
|
||||
|
||||
expect(result.plugins?.slots?.contextEngine).toBe("legacy");
|
||||
expect(actions.contextEngineSlot).toBe(true);
|
||||
});
|
||||
|
||||
it("removes plugins object when uninstall leaves only empty slots", () => {
|
||||
const config = createSinglePluginWithEmptySlotsConfig();
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export type UninstallActions = {
|
||||
allowlist: boolean;
|
||||
loadPath: boolean;
|
||||
memorySlot: boolean;
|
||||
contextEngineSlot: boolean;
|
||||
channelConfig: boolean;
|
||||
directory: boolean;
|
||||
};
|
||||
@@ -155,6 +156,7 @@ export function removePluginFromConfig(
|
||||
allowlist: false,
|
||||
loadPath: false,
|
||||
memorySlot: false,
|
||||
contextEngineSlot: false,
|
||||
channelConfig: false,
|
||||
};
|
||||
|
||||
@@ -204,7 +206,7 @@ export function removePluginFromConfig(
|
||||
}
|
||||
}
|
||||
|
||||
// Reset memory slot if this plugin was selected
|
||||
// Reset slots if this plugin was selected.
|
||||
let slots = pluginsConfig.slots;
|
||||
if (slots?.memory === pluginId) {
|
||||
slots = {
|
||||
@@ -213,6 +215,13 @@ export function removePluginFromConfig(
|
||||
};
|
||||
actions.memorySlot = true;
|
||||
}
|
||||
if (slots?.contextEngine === pluginId) {
|
||||
slots = {
|
||||
...slots,
|
||||
contextEngine: defaultSlotIdForKey("contextEngine"),
|
||||
};
|
||||
actions.contextEngineSlot = true;
|
||||
}
|
||||
if (slots && Object.keys(slots).length === 0) {
|
||||
slots = undefined;
|
||||
}
|
||||
|
||||
@@ -876,6 +876,41 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
expect(result.config.plugins?.installs?.["voice-call"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("migrates context engine slot when a plugin id changes during update", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue({
|
||||
ok: true,
|
||||
pluginId: "@openclaw/context-engine",
|
||||
targetDir: "/tmp/openclaw-context-engine",
|
||||
version: "0.0.2",
|
||||
extensions: ["index.ts"],
|
||||
});
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: {
|
||||
plugins: {
|
||||
slots: { contextEngine: "context-engine" },
|
||||
installs: {
|
||||
"context-engine": {
|
||||
source: "npm",
|
||||
spec: "@openclaw/context-engine",
|
||||
installPath: "/tmp/context-engine",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
pluginIds: ["context-engine"],
|
||||
});
|
||||
|
||||
expect(result.config.plugins?.slots?.contextEngine).toBe("@openclaw/context-engine");
|
||||
expect(result.config.plugins?.installs?.["@openclaw/context-engine"]).toMatchObject({
|
||||
source: "npm",
|
||||
spec: "@openclaw/context-engine",
|
||||
installPath: "/tmp/openclaw-context-engine",
|
||||
version: "0.0.2",
|
||||
});
|
||||
expect(result.config.plugins?.installs?.["context-engine"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("checks marketplace installs during dry-run updates", async () => {
|
||||
installPluginFromMarketplaceMock.mockResolvedValue({
|
||||
ok: true,
|
||||
|
||||
@@ -417,13 +417,13 @@ function migratePluginConfigId(cfg: OpenClawConfig, fromId: string, toId: string
|
||||
delete nextEntries[fromId];
|
||||
}
|
||||
|
||||
const nextSlots =
|
||||
slots?.memory === fromId
|
||||
? {
|
||||
...slots,
|
||||
memory: toId,
|
||||
}
|
||||
: slots;
|
||||
const nextSlots = slots
|
||||
? {
|
||||
...slots,
|
||||
...(slots.memory === fromId ? { memory: toId } : {}),
|
||||
...(slots.contextEngine === fromId ? { contextEngine: toId } : {}),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...cfg,
|
||||
|
||||
Reference in New Issue
Block a user