mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:50:43 +00: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.
|
- 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: 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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,
|
allowlist: false,
|
||||||
loadPath: false,
|
loadPath: false,
|
||||||
memorySlot: false,
|
memorySlot: false,
|
||||||
|
contextEngineSlot: false,
|
||||||
directory: false,
|
directory: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -634,6 +634,11 @@ export function registerPluginsCli(program: Command) {
|
|||||||
if (cfg.plugins?.slots?.memory === pluginId) {
|
if (cfg.plugins?.slots?.memory === pluginId) {
|
||||||
preview.push(`memory slot (will reset to "${defaultSlotIdForKey("memory")}")`);
|
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 channelIds = plugin?.status === "loaded" ? plugin.channelIds : undefined;
|
||||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||||
if (hasInstall && channels) {
|
if (hasInstall && channels) {
|
||||||
@@ -723,6 +728,9 @@ export function registerPluginsCli(program: Command) {
|
|||||||
if (result.actions.memorySlot) {
|
if (result.actions.memorySlot) {
|
||||||
removed.push("memory slot");
|
removed.push("memory slot");
|
||||||
}
|
}
|
||||||
|
if (result.actions.contextEngineSlot) {
|
||||||
|
removed.push("context engine slot");
|
||||||
|
}
|
||||||
if (result.actions.channelConfig) {
|
if (result.actions.channelConfig) {
|
||||||
removed.push("channel config");
|
removed.push("channel config");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ describe("plugins cli uninstall", () => {
|
|||||||
installPath: ALPHA_INSTALL_PATH,
|
installPath: ALPHA_INSTALL_PATH,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
slots: {
|
||||||
|
contextEngine: "alpha",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as OpenClawConfig);
|
} as OpenClawConfig);
|
||||||
buildPluginDiagnosticsReport.mockReturnValue({
|
buildPluginDiagnosticsReport.mockReturnValue({
|
||||||
@@ -53,6 +56,7 @@ describe("plugins cli uninstall", () => {
|
|||||||
expect(writeConfigFile).not.toHaveBeenCalled();
|
expect(writeConfigFile).not.toHaveBeenCalled();
|
||||||
expect(refreshPluginRegistry).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("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 () => {
|
it("uninstalls with --force and --keep-files without prompting", async () => {
|
||||||
@@ -93,6 +97,7 @@ describe("plugins cli uninstall", () => {
|
|||||||
allowlist: false,
|
allowlist: false,
|
||||||
loadPath: false,
|
loadPath: false,
|
||||||
memorySlot: false,
|
memorySlot: false,
|
||||||
|
contextEngineSlot: false,
|
||||||
directory: false,
|
directory: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -162,6 +167,7 @@ describe("plugins cli uninstall", () => {
|
|||||||
allowlist: false,
|
allowlist: false,
|
||||||
loadPath: false,
|
loadPath: false,
|
||||||
memorySlot: false,
|
memorySlot: false,
|
||||||
|
contextEngineSlot: false,
|
||||||
directory: false,
|
directory: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -365,6 +365,22 @@ describe("removePluginFromConfig", () => {
|
|||||||
expect(actions.memorySlot).toBe(expectedChanged);
|
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", () => {
|
it("removes plugins object when uninstall leaves only empty slots", () => {
|
||||||
const config = createSinglePluginWithEmptySlotsConfig();
|
const config = createSinglePluginWithEmptySlotsConfig();
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type UninstallActions = {
|
|||||||
allowlist: boolean;
|
allowlist: boolean;
|
||||||
loadPath: boolean;
|
loadPath: boolean;
|
||||||
memorySlot: boolean;
|
memorySlot: boolean;
|
||||||
|
contextEngineSlot: boolean;
|
||||||
channelConfig: boolean;
|
channelConfig: boolean;
|
||||||
directory: boolean;
|
directory: boolean;
|
||||||
};
|
};
|
||||||
@@ -155,6 +156,7 @@ export function removePluginFromConfig(
|
|||||||
allowlist: false,
|
allowlist: false,
|
||||||
loadPath: false,
|
loadPath: false,
|
||||||
memorySlot: false,
|
memorySlot: false,
|
||||||
|
contextEngineSlot: false,
|
||||||
channelConfig: 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;
|
let slots = pluginsConfig.slots;
|
||||||
if (slots?.memory === pluginId) {
|
if (slots?.memory === pluginId) {
|
||||||
slots = {
|
slots = {
|
||||||
@@ -213,6 +215,13 @@ export function removePluginFromConfig(
|
|||||||
};
|
};
|
||||||
actions.memorySlot = true;
|
actions.memorySlot = true;
|
||||||
}
|
}
|
||||||
|
if (slots?.contextEngine === pluginId) {
|
||||||
|
slots = {
|
||||||
|
...slots,
|
||||||
|
contextEngine: defaultSlotIdForKey("contextEngine"),
|
||||||
|
};
|
||||||
|
actions.contextEngineSlot = true;
|
||||||
|
}
|
||||||
if (slots && Object.keys(slots).length === 0) {
|
if (slots && Object.keys(slots).length === 0) {
|
||||||
slots = undefined;
|
slots = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -876,6 +876,41 @@ describe("updateNpmInstalledPlugins", () => {
|
|||||||
expect(result.config.plugins?.installs?.["voice-call"]).toBeUndefined();
|
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 () => {
|
it("checks marketplace installs during dry-run updates", async () => {
|
||||||
installPluginFromMarketplaceMock.mockResolvedValue({
|
installPluginFromMarketplaceMock.mockResolvedValue({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -417,13 +417,13 @@ function migratePluginConfigId(cfg: OpenClawConfig, fromId: string, toId: string
|
|||||||
delete nextEntries[fromId];
|
delete nextEntries[fromId];
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSlots =
|
const nextSlots = slots
|
||||||
slots?.memory === fromId
|
? {
|
||||||
? {
|
...slots,
|
||||||
...slots,
|
...(slots.memory === fromId ? { memory: toId } : {}),
|
||||||
memory: toId,
|
...(slots.contextEngine === fromId ? { contextEngine: toId } : {}),
|
||||||
}
|
}
|
||||||
: slots;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cfg,
|
...cfg,
|
||||||
|
|||||||
Reference in New Issue
Block a user