



























@@ -219,27 +219,60 @@ function resolvePluginIdForConfiguredWebFetchProvider(
219219});
220220}
221221222-function buildChannelToPluginIdMap(registry: PluginManifestRegistry): Map<string, string> {
223-const map = new Map<string, string>();
222+function normalizeManifestChannelId(channelId: string): string {
223+return normalizeChatChannelId(channelId) ?? channelId;
224+}
225+226+function getManifestChannelPreferOver(
227+plugin: PluginManifestRecord,
228+channelId: string,
229+): readonly string[] {
230+return plugin.channelConfigs?.[channelId]?.preferOver ?? [];
231+}
232+233+function collectPluginIdsForConfiguredChannel(
234+channelId: string,
235+registry: PluginManifestRegistry,
236+): string[] {
237+const normalizedChannelId = normalizeManifestChannelId(channelId);
238+const builtInId = normalizeChatChannelId(normalizedChannelId);
239+const claims: Array<{ plugin: PluginManifestRecord; preferOver: readonly string[] }> = [];
224240for (const record of registry.plugins) {
225-for (const channelId of record.channels ?? []) {
226-if (channelId && !map.has(channelId)) {
227-map.set(channelId, record.id);
228-}
241+if (
242+(record.channels ?? []).some((id) => normalizeManifestChannelId(id) === normalizedChannelId)
243+) {
244+claims.push({
245+plugin: record,
246+preferOver: getManifestChannelPreferOver(record, normalizedChannelId),
247+});
229248}
230249}
231-return map;
232-}
233250234-function resolvePluginIdForChannel(
235-channelId: string,
236-channelToPluginId: ReadonlyMap<string, string>,
237-): string {
238-const builtInId = normalizeChatChannelId(channelId);
251+ if (claims.length === 0) {
252+ return [builtInId ?? normalizedChannelId];
253+}
254+255+const claimIds = new Set(claims.map((claim) => claim.plugin.id));
239256if (builtInId) {
240-return builtInId;
257+claimIds.add(builtInId);
258+}
259+const preferredIds = new Set<string>();
260+for (const claim of claims) {
261+for (const preferredOverId of claim.preferOver) {
262+if (claimIds.has(preferredOverId)) {
263+// Keep both sides as candidates. The preferOver filter later disables
264+// the lower-priority plugin unless the preferred plugin is explicitly
265+// disabled/denied, preserving fallback to bundled channel support.
266+preferredIds.add(claim.plugin.id);
267+preferredIds.add(preferredOverId);
268+}
269+}
270+}
271+272+if (preferredIds.size > 0) {
273+return [...preferredIds].toSorted((left, right) => left.localeCompare(right));
241274}
242-return channelToPluginId.get(channelId) ?? channelId;
275+return [builtInId ?? claims[0]?.plugin.id ?? normalizedChannelId];
243276}
244277245278function collectCandidateChannelIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): string[] {
@@ -389,9 +422,7 @@ function configMayNeedPluginManifestRegistry(cfg: OpenClawConfig, env: NodeJS.Pr
389422if (key === "defaults" || key === "modelByChannel") {
390423continue;
391424}
392-if (!normalizeChatChannelId(key)) {
393-return true;
394-}
425+return true;
395426}
396427return false;
397428}
@@ -459,11 +490,11 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
459490registry: PluginManifestRegistry;
460491}): PluginAutoEnableCandidate[] {
461492const changes: PluginAutoEnableCandidate[] = [];
462-const channelToPluginId = buildChannelToPluginIdMap(params.registry);
463493for (const channelId of collectCandidateChannelIds(params.config, params.env)) {
464-const pluginId = resolvePluginIdForChannel(channelId, channelToPluginId);
465494if (isChannelConfigured(params.config, channelId, params.env)) {
466-changes.push({ pluginId, kind: "channel-configured", channelId });
495+for (const pluginId of collectPluginIdsForConfiguredChannel(channelId, params.registry)) {
496+changes.push({ pluginId, kind: "channel-configured", channelId });
497+}
467498}
468499}
469500@@ -582,6 +613,45 @@ function isPluginDenied(cfg: OpenClawConfig, pluginId: string): boolean {
582613return Array.isArray(deny) && deny.includes(pluginId);
583614}
584615616+function isPluginExplicitlySelected(cfg: OpenClawConfig, pluginId: string): boolean {
617+const allow = cfg.plugins?.allow;
618+if (Array.isArray(allow) && allow.includes(pluginId)) {
619+return true;
620+}
621+return hasMaterialPluginEntryConfig(cfg.plugins?.entries?.[pluginId]);
622+}
623+624+function disableImplicitPreferredOverPlugin(params: {
625+config: OpenClawConfig;
626+originalConfig: OpenClawConfig;
627+pluginId: string;
628+manifestRegistry: PluginManifestRegistry;
629+}): OpenClawConfig {
630+if (isPluginExplicitlySelected(params.originalConfig, params.pluginId)) {
631+return params.config;
632+}
633+if (
634+!normalizeChatChannelId(params.pluginId) &&
635+!isKnownPluginId(params.pluginId, params.manifestRegistry)
636+) {
637+return params.config;
638+}
639+const existingEntry = params.config.plugins?.entries?.[params.pluginId];
640+return {
641+ ...params.config,
642+plugins: {
643+ ...params.config.plugins,
644+entries: {
645+ ...params.config.plugins?.entries,
646+[params.pluginId]: {
647+ ...(existingEntry && typeof existingEntry === "object" ? existingEntry : {}),
648+enabled: false,
649+},
650+},
651+},
652+};
653+}
654+585655function isBuiltInChannelAlreadyEnabled(cfg: OpenClawConfig, channelId: string): boolean {
586656const channels = cfg.channels as Record<string, unknown> | undefined;
587657const channelConfig = channels?.[channelId];
@@ -753,6 +823,12 @@ export function materializePluginAutoEnableCandidatesInternal(params: {
753823 preferOverCache,
754824})
755825) {
826+next = disableImplicitPreferredOverPlugin({
827+config: next,
828+originalConfig: params.config ?? {},
829+pluginId: entry.pluginId,
830+manifestRegistry: params.manifestRegistry,
831+});
756832continue;
757833}
758834此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。