diff --git a/src/plugins/install.ts b/src/plugins/install.ts index 40aeb3c5a63..49ce72dcd07 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -26,6 +26,7 @@ import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js"; import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js"; import * as skillScanner from "../security/skill-scanner.js"; import { CONFIG_DIR, resolveUserPath } from "../utils.js"; +import { loadPluginManifest } from "./manifest.js"; type PluginInstallLogger = { info?: (message: string) => void; @@ -149,7 +150,17 @@ async function installPluginFromPackageDir(params: { } const pkgName = typeof manifest.name === "string" ? manifest.name : ""; - const pluginId = pkgName ? unscopedPackageName(pkgName) : "plugin"; + const npmPluginId = pkgName ? unscopedPackageName(pkgName) : "plugin"; + + // Prefer the canonical `id` from openclaw.plugin.json over the npm package name. + // This avoids a latent key-mismatch bug: if the manifest id (e.g. "memory-cognee") + // differs from the npm package name (e.g. "cognee-openclaw"), the plugin registry + // uses the manifest id as the authoritative key, so the config entry must match it. + const ocManifestResult = loadPluginManifest(params.packageDir); + const manifestPluginId = + ocManifestResult.ok && ocManifestResult.manifest.id ? ocManifestResult.manifest.id : undefined; + + const pluginId = manifestPluginId ?? npmPluginId; const pluginIdError = validatePluginId(pluginId); if (pluginIdError) { return { ok: false, error: pluginIdError }; @@ -161,6 +172,12 @@ async function installPluginFromPackageDir(params: { }; } + if (manifestPluginId && manifestPluginId !== npmPluginId) { + logger.info?.( + `Plugin manifest id "${manifestPluginId}" differs from npm package name "${npmPluginId}"; using manifest id as the config key.`, + ); + } + const packageDir = path.resolve(params.packageDir); const forcedScanEntries: string[] = []; for (const entry of extensions) {