fix(config): exempt known channel ids from plugins.allow stale warning (#76872)

This commit is contained in:
jack-stormentswe
2026-05-03 20:32:49 +02:00
committed by Peter Steinberger
parent 3e80805d11
commit 1e4c70e330
3 changed files with 48 additions and 0 deletions

View File

@@ -249,6 +249,7 @@ Docs: https://docs.openclaw.ai
- Channels/Telegram: require an observed Telegram send, edit, or fallback before treating a forum-topic final as delivered, so final replies generated in transcript no longer disappear from Telegram topics. Fixes #76554. (#76764) Thanks @bubucilo and @obviyus.
- Plugins/update: keep externalized bundled npm bridge updates on the normal plugin security scanner path instead of granting source-linked official trust without artifact provenance. (#76765) Thanks @Lucenx9.
- Agents/reply context: label replied-to messages as the current user message target in model-visible metadata, so short replies are grounded to their explicit reply target instead of nearby chat history. (#76817) Thanks @obviyus.
- Config/validation: skip the `plugin not found` warning for `plugins.allow` entries that match a known channel id, so packaged installs where the gateway auto-enables a channel id (e.g. `discord`) without a corresponding plugin manifest no longer emit an unfixable doctor warning. Fixes #76872. Thanks @jack-stormentswe.
## 2026.5.2

View File

@@ -467,6 +467,25 @@ describe("config plugin validation", () => {
expect(res.warnings).not.toContainEqual(expect.objectContaining({ path: "channels.telegarm" }));
});
it("does not warn when plugins.allow contains a known channel id without a plugin manifest (#76872)", async () => {
const res = validateInSuite({
agents: { list: [{ id: "pi" }] },
channels: {
discord: { token: "xxx" },
},
plugins: {
allow: ["discord"],
},
});
expect(res.ok).toBe(true);
expect(res.warnings ?? []).not.toContainEqual({
path: "plugins.allow",
message:
"plugin not found: discord (stale config entry ignored; remove it from plugins config)",
});
});
it("uses persisted installed-plugin records as stale channel evidence", async () => {
const installedPluginIndexPath = path.join(suiteHome, ".openclaw", "plugins", "installs.json");
await mkdirSafe(path.dirname(installedPluginIndexPath));

View File

@@ -794,6 +794,7 @@ function validateConfigObjectWithPluginsBase(
type RegistryInfo = {
registry: PluginManifestRegistry;
knownIds?: Set<string>;
knownChannelIds?: Set<string>;
overriddenPluginIds?: Set<string>;
normalizedPlugins?: ReturnType<typeof normalizePluginsConfig>;
channelSchemas?: Map<
@@ -908,6 +909,29 @@ function validateConfigObjectWithPluginsBase(
return info.knownIds;
};
const ensureKnownChannelIds = (): Set<string> => {
const info = ensureRegistry();
if (!info.knownChannelIds) {
const ids = new Set<string>();
for (const channelId of CHANNEL_IDS) {
const normalized = normalizePluginId(channelId);
if (normalized) {
ids.add(normalized);
}
}
for (const plugin of info.registry.plugins) {
for (const channelId of plugin.channels) {
const normalized = normalizePluginId(channelId);
if (normalized) {
ids.add(normalized);
}
}
}
info.knownChannelIds = ids;
}
return info.knownChannelIds;
};
const ensureOverriddenPluginIds = (): Set<string> => {
const info = ensureRegistry();
if (!info.overriddenPluginIds) {
@@ -1522,11 +1546,15 @@ function validateConfigObjectWithPluginsBase(
}
const allow = pluginsConfig?.allow ?? [];
const knownChannelIds = ensureKnownChannelIds();
for (const pluginId of allow) {
if (typeof pluginId !== "string" || !pluginId.trim()) {
continue;
}
if (!knownIds.has(pluginId)) {
if (knownChannelIds.has(normalizePluginId(pluginId))) {
continue;
}
const commandAlias = resolveManifestCommandAliasOwnerInRegistry({
command: pluginId,
registry,