From c35ed548bf0c6bad052d822d7607889e100efd08 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 09:11:49 +0100 Subject: [PATCH] docs(plugins): clarify duplicate override diagnostics --- CHANGELOG.md | 1 + docs/plugins/manifest.md | 18 ++++++++++++++++++ src/plugins/manifest-registry.ts | 5 ++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c508637892c..545f60c0e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Gateway/diagnostics: include a bounded redacted startup error message in stability bundles, so crash-loop reports identify the failing plugin or contract without exposing secrets. Refs #75797. Thanks @ymebosma. - Control UI/Talk: allow the OpenAI Realtime WebRTC offer endpoint through the Control UI CSP, configure browser sessions with explicit VAD/transcription input settings, and surface OpenAI realtime error/lifecycle events instead of leaving Talk stuck as live with no diagnostic. Fixes #73427. +- Plugins: clarify config-selected duplicate plugin override diagnostics and document manifest schema updates for bundled-plugin forks. Fixes #8582. Thanks @sachah. - Providers/OpenAI: resolve `keychain::` `OPENAI_API_KEY` refs before creating OpenAI Realtime browser sessions or voice bridges, with a bounded cached Keychain lookup. Fixes #72120. Thanks @ctbritt. - Discord/gateway: reconnect when the gateway socket closes while waiting for the shared IDENTIFY concurrency window, instead of silently skipping IDENTIFY and leaving the bot online but unresponsive. Fixes #74617. Thanks @zeeskdr-ai. - Voice Call: add `sessionScope: "per-call"` for fresh per-call agent memory while preserving the default per-phone caller history. Fixes #45280. Thanks @pondcountry. diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index 7c4d46ffeed..42865febcc0 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -1271,12 +1271,30 @@ Implications: - A forked or stale copy of a bundled plugin sitting in the workspace will not shadow the bundled build. - To actually override a bundled plugin with a local one, pin it via `plugins.entries.` so it wins by precedence rather than relying on workspace discovery. - Duplicate drops are logged so Doctor and startup diagnostics can point at the discarded copy. +- Config-selected duplicate overrides are worded as explicit overrides in diagnostics, but still warn so stale forks and accidental shadows stay visible. ## JSON Schema requirements - **Every plugin must ship a JSON Schema**, even if it accepts no config. - An empty schema is acceptable (for example, `{ "type": "object", "additionalProperties": false }`). - Schemas are validated at config read/write time, not at runtime. +- When extending or forking a bundled plugin with new config keys, update that plugin's `openclaw.plugin.json` `configSchema` at the same time. Bundled plugin schemas are strict, so adding `plugins.entries..config.myNewKey` in user config without adding `myNewKey` to `configSchema.properties` will be rejected before the plugin runtime loads. + +Example schema extension: + +```json +{ + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "myNewKey": { + "type": "string" + } + } + } +} +``` ## Validation behavior diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index e43a3a8a4dd..9f69c9507af 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -753,7 +753,10 @@ export function loadPluginManifestRegistry( level: "warn", pluginId: manifest.id, source: overriddenCandidate.source, - message: `duplicate plugin id detected; ${overriddenCandidate.origin} plugin will be overridden by ${winnerCandidate.origin} plugin (${winnerCandidate.source})`, + message: + winnerCandidate.origin === "config" + ? `duplicate plugin id resolved by explicit config-selected plugin; ${overriddenCandidate.origin} plugin will be overridden by config plugin (${winnerCandidate.source})` + : `duplicate plugin id detected; ${overriddenCandidate.origin} plugin will be overridden by ${winnerCandidate.origin} plugin (${winnerCandidate.source})`, }); continue; }