mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: remove managed plugin files on uninstall
This commit is contained in:
@@ -121,6 +121,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Image understanding: preserve configured provider-prefixed vision model metadata when callers request the model without the provider prefix, so custom image models keep their `input: ["text", "image"]` capability. Fixes #33185. Thanks @Kobe9312 and @vincentkoc.
|
||||
- Plugins/install: restore the previous plugin index records if a concurrent config write conflict interrupts install, update, or uninstall metadata commits. Thanks @shakkernerd.
|
||||
- Plugins/install: reject native plugin archives that do not include a valid `openclaw.plugin.json`, preventing manifestless archives from writing install records that later show missing-manifest diagnostics. Thanks @shakkernerd.
|
||||
- Plugins/uninstall: remove tracked managed plugin install directories even when the persisted install path differs from the default id-derived target, while still refusing deletes outside the managed extensions root. Thanks @shakkernerd.
|
||||
- Plugins/update: restore previous plugin index records if core update or channel setup hits a concurrent config write conflict after plugin metadata changes. Thanks @shakkernerd.
|
||||
- Plugins/onboarding: defer channel/provider plugin install records until the owning config write commits, keeping setup failures from advancing the plugin index ahead of `openclaw.json`. Thanks @shakkernerd.
|
||||
- Plugins/config: route configure and agent setup writes with pending plugin install records through the plugin index commit helper so provider onboarding metadata is not stripped by plain config writes. Thanks @shakkernerd.
|
||||
|
||||
@@ -261,13 +261,10 @@ openclaw plugins uninstall <id> --keep-files
|
||||
|
||||
`uninstall` removes plugin records from `plugins.entries`, the persisted plugin
|
||||
index, the plugin allowlist, and linked `plugins.load.paths` entries when
|
||||
applicable.
|
||||
applicable. Unless `--keep-files` is set, uninstall also removes the tracked
|
||||
managed install directory when it is inside OpenClaw's plugin extensions root.
|
||||
For active memory plugins, the memory slot resets to `memory-core`.
|
||||
|
||||
By default, uninstall also removes the plugin install directory under the active
|
||||
state-dir plugin root. Use
|
||||
`--keep-files` to keep files on disk.
|
||||
|
||||
`--keep-config` is supported as a deprecated alias for `--keep-files`.
|
||||
|
||||
### Update
|
||||
|
||||
@@ -782,6 +782,25 @@ describe("uninstallPlugin", () => {
|
||||
});
|
||||
await expect(fs.access(outsideDir)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("deletes tracked managed install paths even when they are not the default target", async () => {
|
||||
const extensionsDir = path.join(tempDir, "extensions");
|
||||
const managedDir = path.join(extensionsDir, "archive-installs", "my-plugin");
|
||||
await fs.mkdir(managedDir, { recursive: true });
|
||||
await fs.writeFile(path.join(managedDir, "index.js"), "// plugin");
|
||||
|
||||
const result = await uninstallPlugin({
|
||||
config: createSingleNpmInstallConfig(managedDir),
|
||||
pluginId: "my-plugin",
|
||||
deleteFiles: true,
|
||||
extensionsDir,
|
||||
});
|
||||
|
||||
expectSuccessfulUninstallActions(result, {
|
||||
directory: true,
|
||||
});
|
||||
await expect(fs.access(managedDir)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveUninstallDirectoryTarget", () => {
|
||||
@@ -814,4 +833,22 @@ describe("resolveUninstallDirectoryTarget", () => {
|
||||
|
||||
expect(target).toBe(resolvePluginInstallDir("my-plugin", extensionsDir));
|
||||
});
|
||||
|
||||
it("uses configured installPath when it stays inside the managed extensions dir", () => {
|
||||
const extensionsDir = path.join(os.tmpdir(), "openclaw-uninstall-safe");
|
||||
const installPath = path.join(extensionsDir, "archive-installs", "my-plugin");
|
||||
|
||||
expect(
|
||||
resolveUninstallDirectoryTarget({
|
||||
pluginId: "my-plugin",
|
||||
hasInstall: true,
|
||||
installRecord: {
|
||||
source: "archive",
|
||||
sourcePath: "/tmp/my-plugin.zip",
|
||||
installPath,
|
||||
},
|
||||
extensionsDir,
|
||||
}),
|
||||
).toBe(installPath);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,7 +57,12 @@ export function resolveUninstallDirectoryTarget(params: {
|
||||
return configuredPath;
|
||||
}
|
||||
|
||||
// Never trust configured installPath blindly for recursive deletes.
|
||||
if (params.extensionsDir && isPathInsideOrEqual(params.extensionsDir, configuredPath)) {
|
||||
return configuredPath;
|
||||
}
|
||||
|
||||
// Never trust configured installPath blindly for recursive deletes outside
|
||||
// the managed extensions directory.
|
||||
return defaultPath;
|
||||
}
|
||||
|
||||
@@ -101,6 +106,11 @@ function resolveComparablePath(value: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function isPathInsideOrEqual(parent: string, child: string): boolean {
|
||||
const relative = path.relative(resolveComparablePath(parent), resolveComparablePath(child));
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove plugin references from config (pure config mutation).
|
||||
* Returns a new config with the plugin removed from entries, installs, allow, load.paths, slots,
|
||||
|
||||
Reference in New Issue
Block a user