diff --git a/src/plugins/installed-plugin-index-store.test.ts b/src/plugins/installed-plugin-index-store.test.ts index 28d37c3495d..0f55b54e298 100644 --- a/src/plugins/installed-plugin-index-store.test.ts +++ b/src/plugins/installed-plugin-index-store.test.ts @@ -420,4 +420,70 @@ describe("installed plugin index persistence", () => { plugins: [], }); }); + + it("preserves ClawHub ClawPack source facts when refreshing the manifest cache", async () => { + const stateDir = makeTempDir(); + const installPath = path.join(stateDir, "plugins", "clawpack-demo"); + await writePersistedInstalledPluginIndex( + createIndex({ + installRecords: { + "clawpack-demo": { + source: "clawhub", + spec: "clawhub:clawpack-demo@2026.5.1-beta.2", + installPath, + version: "2026.5.1-beta.2", + integrity: "sha256-archive", + resolvedAt: "2026-05-01T00:00:00.000Z", + clawhubUrl: "https://clawhub.ai", + clawhubPackage: "clawpack-demo", + clawhubFamily: "code-plugin", + clawhubChannel: "official", + clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + clawpackSpecVersion: 1, + clawpackManifestSha256: + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + clawpackSize: 4096, + }, + }, + plugins: [], + }), + { stateDir }, + ); + + const index = await refreshPersistedInstalledPluginIndex({ + reason: "manual", + stateDir, + candidates: [], + env: { + OPENCLAW_BUNDLED_PLUGINS_DIR: undefined, + OPENCLAW_VERSION: "2026.4.25", + VITEST: "true", + }, + }); + + const expected = { + installRecords: { + "clawpack-demo": { + source: "clawhub", + spec: "clawhub:clawpack-demo@2026.5.1-beta.2", + installPath, + version: "2026.5.1-beta.2", + integrity: "sha256-archive", + resolvedAt: "2026-05-01T00:00:00.000Z", + clawhubUrl: "https://clawhub.ai", + clawhubPackage: "clawpack-demo", + clawhubFamily: "code-plugin", + clawhubChannel: "official", + clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + clawpackSpecVersion: 1, + clawpackManifestSha256: + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + clawpackSize: 4096, + }, + }, + plugins: [], + }; + expect(index).toMatchObject(expected); + await expect(readPersistedInstalledPluginIndex({ stateDir })).resolves.toMatchObject(expected); + }); }); diff --git a/src/plugins/uninstall.test.ts b/src/plugins/uninstall.test.ts index 546c0ace0bf..9f36de1dfab 100644 --- a/src/plugins/uninstall.test.ts +++ b/src/plugins/uninstall.test.ts @@ -1037,6 +1037,44 @@ describe("uninstallPlugin", () => { await expect(fs.access(installPath)).rejects.toThrow(); }); + it("deletes managed ClawHub install directories", async () => { + const stateDir = path.join(tempDir, "state"); + const extensionsDir = path.join(stateDir, "extensions"); + const installPath = resolvePluginInstallDir("clawpack-demo", extensionsDir); + await fs.mkdir(installPath, { recursive: true }); + await fs.writeFile(path.join(installPath, "index.js"), "// clawhub plugin"); + + const result = await uninstallPlugin({ + config: createPluginConfig({ + entries: createSinglePluginEntries("clawpack-demo"), + installs: { + "clawpack-demo": { + source: "clawhub", + spec: "clawhub:clawpack-demo@2026.5.1-beta.2", + installPath, + clawhubUrl: "https://clawhub.ai", + clawhubPackage: "clawpack-demo", + clawhubFamily: "code-plugin", + clawhubChannel: "official", + clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + clawpackSpecVersion: 1, + clawpackManifestSha256: + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + clawpackSize: 4096, + }, + }, + }), + pluginId: "clawpack-demo", + deleteFiles: true, + extensionsDir, + }); + + expectSuccessfulUninstallActions(result, { + directory: true, + }); + await expect(fs.access(installPath)).rejects.toThrow(); + }); + it("deletes managed git install repos outside the extensions directory", async () => { const stateDir = path.join(tempDir, "state"); const extensionsDir = path.join(stateDir, "extensions"); @@ -1195,6 +1233,34 @@ describe("resolveUninstallDirectoryTarget", () => { ).toBe(installPath); }); + it("uses configured installPath when ClawHub installed it under the managed extensions root", () => { + const stateDir = path.join(os.tmpdir(), "openclaw-uninstall-safe"); + const extensionsDir = path.join(stateDir, "extensions"); + const installPath = resolvePluginInstallDir("clawpack-demo", extensionsDir); + + expect( + resolveUninstallDirectoryTarget({ + pluginId: "clawpack-demo", + hasInstall: true, + installRecord: { + source: "clawhub", + spec: "clawhub:clawpack-demo@2026.5.1-beta.2", + installPath, + clawhubUrl: "https://clawhub.ai", + clawhubPackage: "clawpack-demo", + clawhubFamily: "code-plugin", + clawhubChannel: "official", + clawpackSha256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + clawpackSpecVersion: 1, + clawpackManifestSha256: + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + clawpackSize: 4096, + }, + extensionsDir, + }), + ).toBe(installPath); + }); + it("does not trust git install paths outside the managed git root", () => { const stateDir = path.join(os.tmpdir(), "openclaw-uninstall-safe"); const extensionsDir = path.join(stateDir, "extensions");