diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7aacabc9e..6db0b4d1ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ Docs: https://docs.openclaw.ai - Providers/Amazon Bedrock Mantle: refresh IAM-backed bearer tokens at runtime instead of baking discovery-time tokens into provider config, so long-lived Mantle sessions keep working after the initial token ages out. Thanks @wirjo. - Config/includes: write through single-file top-level includes for isolated OpenClaw-owned mutations, so `plugins install` and `plugins update` update an included `plugins.json5` file instead of flattening modular `$include` configs. Fixes #41050 and #66048. - Config/reload: plan gateway reloads from source-authored config instead of runtime-materialized snapshots, so plugin update writes no longer trigger false restarts from derived provider/plugin config paths. Fixes #68732. -- Plugins/update: skip npm plugin reinstall/config rewrites when the installed version and recorded artifact identity already match the registry target, and let bare npm package names resolve back to tracked install records. Fixes #46955 and #67957. +- Plugins/update: skip npm plugin reinstall/config rewrites when the installed version and recorded artifact identity already match the registry target, let bare npm package names resolve back to tracked install records, and point already-installed `plugins install` attempts at `plugins update` / `--force` instead of a hook-pack fallback. Fixes #46955, #67957, and #68073. - Agents/MCP: keep `mcp.servers` and bundle MCP tools available in Pi embedded `coding` and `messaging` sessions while preserving `minimal` profile and `tools.deny: ["bundle-mcp"]` opt-out behavior. Fixes #68875 and #68818. diff --git a/docs/cli/plugins.md b/docs/cli/plugins.md index 7c164e5781b..f315fa274ce 100644 --- a/docs/cli/plugins.md +++ b/docs/cli/plugins.md @@ -76,6 +76,8 @@ bundled-plugin recovery path for plugins that explicitly opt into `--force` reuses the existing install target and overwrites an already-installed plugin or hook pack in place. Use it when you are intentionally reinstalling the same id from a new local path, archive, ClawHub package, or npm artifact. +For routine upgrades of an already tracked npm plugin, prefer +`openclaw plugins update `. `--pin` applies to npm installs only. It is not supported with `--marketplace`, because marketplace installs persist marketplace source metadata instead of an diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 8280564812c..d20f8202703 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -678,6 +678,29 @@ describe("plugins cli install", () => { ); }); + it("suggests update or --force when npm plugin install target already exists", async () => { + loadConfig.mockReturnValue({} as OpenClawConfig); + mockClawHubPackageNotFound("@example/lossless-claw"); + installPluginFromNpmSpec.mockResolvedValue({ + ok: false, + error: + "plugin already exists: /home/openclaw/.openclaw/extensions/lossless-claw (delete it first)", + }); + installHooksFromNpmSpec.mockResolvedValue({ + ok: false, + error: "package.json missing openclaw.hooks", + }); + + await expect( + runPluginsCommand(["plugins", "install", "@example/lossless-claw"]), + ).rejects.toThrow("__exit__:1"); + + expect(runtimeErrors.at(-1)).toContain( + "Use `openclaw plugins update ` to upgrade the tracked plugin, or rerun install with `--force` to replace it.", + ); + expect(runtimeErrors.at(-1)).not.toContain("Also not a valid hook pack"); + }); + it("passes the install logger to the --link dry-run probe", async () => { const localPluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-link-plugin-")); const cfg = { diff --git a/src/cli/plugins-command-helpers.ts b/src/cli/plugins-command-helpers.ts index 99e9ed30e65..55e7bfc4a11 100644 --- a/src/cli/plugins-command-helpers.ts +++ b/src/cli/plugins-command-helpers.ts @@ -103,6 +103,9 @@ export function formatPluginInstallWithHookFallbackError( pluginError: string, hookError: string, ): string { + if (/plugin already exists: .+ \(delete it first\)/.test(pluginError)) { + return `${pluginError}\nUse \`openclaw plugins update \` to upgrade the tracked plugin, or rerun install with \`--force\` to replace it.`; + } return `${pluginError}\nAlso not a valid hook pack: ${hookError}`; }