diff --git a/CHANGELOG.md b/CHANGELOG.md index 939ba9c6708..08098e7b425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ Docs: https://docs.openclaw.ai - fix: harden backend message action gateway routing [AI]. (#76374) Thanks @pgondhi987. - Gate QQBot streaming command auth [AI]. (#76375) Thanks @pgondhi987. - Plugins/release: make the published npm runtime verifier reject blank `openclaw.runtimeExtensions` entries instead of treating them as absent and passing via inferred outputs. Thanks @vincentkoc. +- Doctor/plugins: remove stale managed install records for bundled plugins even when the bundled plugin is not explicitly configured, so doctor cleanup cannot leave orphaned install metadata behind. Thanks @vincentkoc. - Web fetch: scope provider fallback cache entries by the selected fetch provider so config reloads cannot reuse another provider's cached fallback payload. Thanks @vincentkoc. - Web search: honor late-bound `tools.web.search.enabled: false` during tool execution so config reloads cannot leave an already-created `web_search` tool runnable. Thanks @vincentkoc. - Plugins/packages: reject inferred built runtime entries that exist but fail package-boundary checks instead of falling back to TypeScript source for installed packages. Thanks @vincentkoc. diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts index d102649ff55..9cdd56e9ef9 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts @@ -743,6 +743,52 @@ describe("repairMissingConfiguredPluginInstalls", () => { }); }); + it("removes stale bundled install records even when the plugin is not configured", async () => { + const records = { + "google-meet": { + source: "npm", + spec: "@openclaw/google-meet", + resolvedName: "@openclaw/google-meet", + installPath: "/missing/google-meet", + }, + }; + mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue(records); + mocks.loadPluginMetadataSnapshot.mockReturnValue({ + plugins: [], + diagnostics: [], + }); + mocks.loadInstalledPluginIndex.mockReturnValue({ + plugins: [ + { + pluginId: "google-meet", + origin: "bundled", + packageName: "@openclaw/google-meet", + }, + ], + diagnostics: [], + installRecords: {}, + }); + + const { repairMissingConfiguredPluginInstalls } = + await import("./missing-configured-plugin-install.js"); + const result = await repairMissingConfiguredPluginInstalls({ + cfg: {}, + env: {}, + }); + + expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled(); + expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( + {}, + { + env: {}, + }, + ); + expect(result).toEqual({ + changes: ['Removed stale managed install record for bundled plugin "google-meet".'], + warnings: [], + }); + }); + it.each([ [ "npm", diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index 7c285e14fb7..8ed0ee3d90c 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -646,11 +646,7 @@ async function repairMissingPluginInstalls(params: { for (const [pluginId, record] of Object.entries(records)) { const bundled = bundledPluginsById.get(pluginId); - if ( - !bundled || - !params.pluginIds.has(pluginId) || - !recordMatchesBundledPackage(record, bundled) - ) { + if (!bundled || !recordMatchesBundledPackage(record, bundled)) { continue; } if (nextRecords === records) {