Skip to content

Commit 5355ef0

Browse files
committed
fix(plugins): dedupe manifest diagnostics
1 parent 9ebdd26 commit 5355ef0

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ Docs: https://docs.openclaw.ai
7474
- Doctor/Telegram: warn when selected Telegram quote replies can suppress `streaming.preview.toolProgress`, and document the `replyToMode` trade-off without changing runtime delivery. Fixes #73487. Thanks @GodsBoy.
7575
- Channels/Discord: send a best-effort native typing cue immediately after an inbound DM is accepted, so slow pre-dispatch turns show Discord liveness before queueing, context assembly, model, or tool work starts. Fixes #76417. Thanks @mlopez14.
7676
- Plugins/install: reject source-only TypeScript package installs and installed plugin packages that are missing compiled runtime output, so broken npm artifacts fail at install/discovery time instead of falling through jiti and surfacing later as unavailable providers. Fixes #76720.
77+
- Plugins/config: deduplicate identical manifest compatibility diagnostics when an explicitly configured plugin overrides another discovered candidate, so external channel plugins do not print the same missing `channelConfigs` warning repeatedly during install and enable. Thanks @vincentkoc.
7778
- Discord/status: honor explicit `messages.statusReactions.enabled: true` in tool-only guild channels so queued ack reactions can progress through thinking/done lifecycle reactions instead of stopping at the initial emoji. Thanks @Marvinthebored.
7879
- Discord/native commands: compare Discord-normalized slash-command descriptions and localized descriptions during reconcile so CJK or multiline command text no longer triggers redundant startup PATCH bursts and rate-limit 429s. Fixes #76587. Thanks @zhengsx.
7980
- Agents/OpenAI: omit Chat Completions `reasoning_effort` for `gpt-5.4-mini` only when function tools are present while preserving tool-free Chat and Responses reasoning support, preventing Telegram-routed fallback runs from hanging after OpenAI rejects tool payloads. Fixes #76176. Thanks @ThisIsAdilah and @chinar-amrutkar.

src/plugins/manifest-registry.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,36 @@ describe("loadPluginManifestRegistry", () => {
400400
expect(warning?.message).toContain(path.join(configDir, "index.ts"));
401401
});
402402

403+
it("deduplicates compatibility diagnostics when a config plugin replaces a global candidate", () => {
404+
const globalDir = makeTempDir();
405+
const configDir = makeTempDir();
406+
const manifest = {
407+
id: "external-chat",
408+
channels: ["external-chat"],
409+
configSchema: { type: "object" },
410+
};
411+
writeManifest(globalDir, manifest);
412+
writeManifest(configDir, manifest);
413+
414+
const registry = loadRegistry([
415+
createPluginCandidate({
416+
idHint: "external-chat",
417+
rootDir: globalDir,
418+
origin: "global",
419+
}),
420+
createPluginCandidate({
421+
idHint: "external-chat",
422+
rootDir: configDir,
423+
origin: "config",
424+
}),
425+
]);
426+
427+
const channelConfigWarnings = registry.diagnostics.filter((diagnostic) =>
428+
diagnostic.message.includes("without channelConfigs metadata"),
429+
);
430+
expect(channelConfigWarnings).toHaveLength(1);
431+
});
432+
403433
it("suppresses duplicate warnings for explicit installed globals overriding bundled plugins", () => {
404434
const bundledDir = makeTempDir();
405435
const globalDir = makeTempDir();

src/plugins/manifest-registry.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,20 @@ function pushManifestCompatibilityDiagnostics(params: {
490490
pushNonBundledChannelConfigDescriptorDiagnostic(params);
491491
}
492492

493+
function dedupePluginDiagnostics(diagnostics: PluginDiagnostic[]): PluginDiagnostic[] {
494+
const seen = new Set<string>();
495+
const deduped: PluginDiagnostic[] = [];
496+
for (const diagnostic of diagnostics) {
497+
const key = JSON.stringify([diagnostic.level, diagnostic.pluginId ?? "", diagnostic.message]);
498+
if (seen.has(key)) {
499+
continue;
500+
}
501+
seen.add(key);
502+
deduped.push(diagnostic);
503+
}
504+
return deduped;
505+
}
506+
493507
function matchesInstalledPluginRecord(params: {
494508
pluginId: string;
495509
candidate: PluginCandidate;
@@ -786,6 +800,6 @@ export function loadPluginManifestRegistry(
786800
pushManifestCompatibilityDiagnostics({ record, diagnostics });
787801
}
788802

789-
const registry = { plugins: records, diagnostics };
803+
const registry = { plugins: records, diagnostics: dedupePluginDiagnostics(diagnostics) };
790804
return registry;
791805
}

0 commit comments

Comments
 (0)