diff --git a/CHANGELOG.md b/CHANGELOG.md index 698fda8d607..f243228752b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Docs: https://docs.openclaw.ai - Web UI/config form: support SecretInput string-or-secret-ref unions in map `additionalProperties`, so provider API key fields stay editable instead of being marked unsupported. (#31866) Thanks @ningding97. - Slack/Bolt startup compatibility: remove invalid `message.channels` and `message.groups` event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified `message` handler (`channel_type`). (#32033) Thanks @mahopan. - Telegram: guard duplicate-token checks and gateway startup token normalization when account tokens are missing, preventing `token.trim()` crashes during status/start flows. (#31973) Thanks @ningding97. +- Plugins/install diagnostics: reject legacy plugin package shapes without `openclaw.extensions` and return an explicit upgrade hint with troubleshooting docs for repackaging. (#32055) Thanks @liuxiaopai-ai. - Skills/sherpa-onnx-tts: run the `sherpa-onnx-tts` bin under ESM (replace CommonJS `require` imports) and add regression coverage to prevent `require is not defined in ES module scope` startup crashes. (#31965) Thanks @bmendonca3. - Browser/default profile selection: default `browser.defaultProfile` behavior now prefers `openclaw` (managed standalone CDP) when no explicit default is configured, while still auto-provisioning the `chrome` relay profile for explicit opt-in use. (#32031) Fixes #31907. Thanks @liuxiaopai-ai. - Doctor/local memory provider checks: stop false-positive local-provider warnings when `provider=local` and no explicit `modelPath` is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite. diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index 4b6e93afe3c..c2cb1a4312b 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -40,6 +40,31 @@ If you see: `HTTP 429: rate_limit_error: Extra usage is required for long context requests`, go to [/gateway/troubleshooting#anthropic-429-extra-usage-required-for-long-context](/gateway/troubleshooting#anthropic-429-extra-usage-required-for-long-context). +## Plugin install fails with missing openclaw extensions + +If install fails with `package.json missing openclaw.extensions`, the plugin package +is using an old shape that OpenClaw no longer accepts. + +Fix in the plugin package: + +1. Add `openclaw.extensions` to `package.json`. +2. Point entries at built runtime files (usually `./dist/index.js`). +3. Republish the plugin and run `openclaw plugins install ` again. + +Example: + +```json +{ + "name": "@openclaw/my-plugin", + "version": "1.2.3", + "openclaw": { + "extensions": ["./dist/index.js"] + } +} +``` + +Reference: [/tools/plugin#distribution-npm](/tools/plugin#distribution-npm) + ## Decision tree ```mermaid diff --git a/src/plugins/install.test.ts b/src/plugins/install.test.ts index de1041a2631..31611e739c0 100644 --- a/src/plugins/install.test.ts +++ b/src/plugins/install.test.ts @@ -374,6 +374,40 @@ describe("installPluginFromArchive", () => { expect(result.error).toContain("openclaw.extensions"); }); + it("rejects legacy plugin package shape when openclaw.extensions is missing", async () => { + const { pluginDir, extensionsDir } = setupPluginInstallDirs(); + fs.writeFileSync( + path.join(pluginDir, "package.json"), + JSON.stringify({ + name: "@openclaw/legacy-entry-fallback", + version: "0.0.1", + }), + "utf-8", + ); + fs.writeFileSync( + path.join(pluginDir, "openclaw.plugin.json"), + JSON.stringify({ + id: "legacy-entry-fallback", + configSchema: { type: "object", properties: {} }, + }), + "utf-8", + ); + fs.writeFileSync(path.join(pluginDir, "index.ts"), "export {};\n", "utf-8"); + + const result = await installPluginFromDir({ + dirPath: pluginDir, + extensionsDir, + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("package.json missing openclaw.extensions"); + expect(result.error).toContain("update the plugin package"); + return; + } + expect.unreachable("expected install to fail without openclaw.extensions"); + }); + it("warns when plugin contains dangerous code patterns", async () => { const { pluginDir, extensionsDir } = setupPluginInstallDirs(); diff --git a/src/plugins/install.ts b/src/plugins/install.ts index fbcd4bc2203..59a4d94d0a3 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -44,6 +44,9 @@ type PackageManifest = { dependencies?: Record; } & Partial>; +const MISSING_EXTENSIONS_ERROR = + 'package.json missing openclaw.extensions; update the plugin package to include openclaw.extensions (for example ["./dist/index.js"]). See https://docs.openclaw.ai/help/troubleshooting#plugin-install-fails-with-missing-openclaw-extensions'; + export type InstallPluginResult = | { ok: true; @@ -82,10 +85,10 @@ function validatePluginId(pluginId: string): string | null { return null; } -async function ensureOpenClawExtensions(manifest: PackageManifest) { - const extensions = manifest[MANIFEST_KEY]?.extensions; +function ensureOpenClawExtensions(params: { manifest: PackageManifest }): string[] { + const extensions = params.manifest[MANIFEST_KEY]?.extensions; if (!Array.isArray(extensions)) { - throw new Error("package.json missing openclaw.extensions"); + throw new Error(MISSING_EXTENSIONS_ERROR); } const list = extensions.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean); if (list.length === 0) { @@ -149,7 +152,9 @@ async function installPluginFromPackageDir(params: { let extensions: string[]; try { - extensions = await ensureOpenClawExtensions(manifest); + extensions = ensureOpenClawExtensions({ + manifest, + }); } catch (err) { return { ok: false, error: String(err) }; }