fix(plugins): centralize explicit plugin scope handling (#65298)

* fix(plugins): centralize explicit plugin scope handling

* fix(plugins): preserve explicit empty web scopes

* fix(plugins): preserve runtime web provider scopes without config

* fix(plugins): preserve web provider runtime filtering

* fix(plugins): preserve scoped web runtime fallback

* fix(plugins): harden plugin scope normalization
This commit is contained in:
Vincent Koc
2026-04-12 16:16:37 +01:00
committed by GitHub
parent 659bcc5e5b
commit 6a189eec0b
19 changed files with 475 additions and 56 deletions

View File

@@ -58,6 +58,12 @@ import {
} from "./memory-state.js";
import { unwrapDefaultModuleExport } from "./module-export.js";
import { isPathInside, safeStatSync } from "./path-safety.js";
import {
createPluginIdScopeSet,
hasExplicitPluginIdScope,
normalizePluginIdScope,
serializePluginIdScope,
} from "./plugin-scope.js";
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
import { resolvePluginCacheInputs } from "./roots.js";
import {
@@ -360,8 +366,7 @@ function buildCacheKey(params: {
},
]),
);
const scopeKey =
params.onlyPluginIds === undefined ? "__unscoped__" : JSON.stringify(params.onlyPluginIds);
const scopeKey = serializePluginIdScope(params.onlyPluginIds);
const setupOnlyKey = params.includeSetupOnlyChannelPlugins === true ? "setup-only" : "runtime";
const startupChannelMode =
params.preferSetupRuntimeForChannelPlugins === true ? "prefer-setup" : "full";
@@ -376,14 +381,6 @@ function buildCacheKey(params: {
})}::${scopeKey}::${setupOnlyKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
}
function normalizeScopedPluginIds(ids?: string[]): string[] | undefined {
if (!ids) {
return undefined;
}
const normalized = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean))).toSorted();
return normalized;
}
function matchesScopedPluginRequest(params: {
onlyPluginIdSet: ReadonlySet<string> | null;
pluginId: string;
@@ -451,7 +448,7 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
options.autoEnabledReasons !== undefined ||
options.workspaceDir !== undefined ||
options.env !== undefined ||
options.onlyPluginIds !== undefined ||
hasExplicitPluginIdScope(options.onlyPluginIds) ||
options.runtimeOptions !== undefined ||
options.pluginSdkResolution !== undefined ||
options.coreGatewayHandlers !== undefined ||
@@ -469,7 +466,7 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
const activationSource = createPluginActivationSource({
config: activationSourceConfig,
});
const onlyPluginIds = normalizeScopedPluginIds(options.onlyPluginIds);
const onlyPluginIds = normalizePluginIdScope(options.onlyPluginIds);
const includeSetupOnlyChannelPlugins = options.includeSetupOnlyChannelPlugins === true;
const preferSetupRuntimeForChannelPlugins = options.preferSetupRuntimeForChannelPlugins === true;
const runtimeSubagentMode = resolveRuntimeSubagentMode(options.runtimeOptions);
@@ -1097,7 +1094,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
} = resolvePluginLoadCacheContext(options);
const logger = options.logger ?? defaultLogger();
const validateOnly = options.mode === "validate";
const onlyPluginIdSet = onlyPluginIds ? new Set(onlyPluginIds) : null;
const onlyPluginIdSet = createPluginIdScopeSet(onlyPluginIds);
const cacheEnabled = options.cache !== false;
if (cacheEnabled) {
const cached = getCachedPluginRegistry(cacheKey);
@@ -1833,7 +1830,7 @@ export async function loadOpenClawPluginCliRegistry(
cache: false,
});
const logger = options.logger ?? defaultLogger();
const onlyPluginIdSet = onlyPluginIds ? new Set(onlyPluginIds) : null;
const onlyPluginIdSet = createPluginIdScopeSet(onlyPluginIds);
const getJiti = createPluginJitiLoader(options);
const { registry, registerCli } = createPluginRegistry({
logger,