fix(cli): keep channel status checks off plugin runtimes (#69479)

Merged via squash.

Prepared head SHA: 63f6e416a9
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana
2026-04-21 13:53:08 -04:00
committed by GitHub
parent 09c5669299
commit 24db09a19b
85 changed files with 3176 additions and 366 deletions

View File

@@ -134,6 +134,8 @@ export type PluginLoadOptions = {
mode?: "full" | "validate";
onlyPluginIds?: string[];
includeSetupOnlyChannelPlugins?: boolean;
forceSetupOnlyChannelPlugins?: boolean;
requireSetupEntryForSetupOnlyChannelPlugins?: boolean;
/**
* Prefer `setupEntry` for configured channel plugins that explicitly opt in
* via package metadata because their setup entry covers the pre-listen startup surface.
@@ -505,6 +507,8 @@ function buildCacheKey(params: {
env: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
includeSetupOnlyChannelPlugins?: boolean;
forceSetupOnlyChannelPlugins?: boolean;
requireSetupEntryForSetupOnlyChannelPlugins?: boolean;
preferSetupRuntimeForChannelPlugins?: boolean;
loadModules?: boolean;
runtimeSubagentMode?: "default" | "explicit" | "gateway-bindable";
@@ -534,6 +538,12 @@ function buildCacheKey(params: {
);
const scopeKey = serializePluginIdScope(params.onlyPluginIds);
const setupOnlyKey = params.includeSetupOnlyChannelPlugins === true ? "setup-only" : "runtime";
const setupOnlyModeKey =
params.forceSetupOnlyChannelPlugins === true ? "force-setup" : "normal-setup";
const setupOnlyRequirementKey =
params.requireSetupEntryForSetupOnlyChannelPlugins === true
? "require-setup-entry"
: "allow-full-fallback";
const startupChannelMode =
params.preferSetupRuntimeForChannelPlugins === true ? "prefer-setup" : "full";
const moduleLoadMode = params.loadModules === false ? "manifest-only" : "load-modules";
@@ -544,7 +554,7 @@ function buildCacheKey(params: {
installs,
loadPaths,
activationMetadataKey: params.activationMetadataKey ?? "",
})}::${scopeKey}::${setupOnlyKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
})}::${scopeKey}::${setupOnlyKey}::${setupOnlyModeKey}::${setupOnlyRequirementKey}::${startupChannelMode}::${moduleLoadMode}::${runtimeSubagentMode}::${params.pluginSdkResolution ?? "auto"}::${gatewayMethodsKey}`;
}
function matchesScopedPluginRequest(params: {
@@ -619,6 +629,8 @@ function hasExplicitCompatibilityInputs(options: PluginLoadOptions): boolean {
options.pluginSdkResolution !== undefined ||
options.coreGatewayHandlers !== undefined ||
options.includeSetupOnlyChannelPlugins === true ||
options.forceSetupOnlyChannelPlugins === true ||
options.requireSetupEntryForSetupOnlyChannelPlugins === true ||
options.preferSetupRuntimeForChannelPlugins === true ||
options.loadModules === false
);
@@ -634,6 +646,9 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
});
const onlyPluginIds = normalizePluginIdScope(options.onlyPluginIds);
const includeSetupOnlyChannelPlugins = options.includeSetupOnlyChannelPlugins === true;
const forceSetupOnlyChannelPlugins = options.forceSetupOnlyChannelPlugins === true;
const requireSetupEntryForSetupOnlyChannelPlugins =
options.requireSetupEntryForSetupOnlyChannelPlugins === true;
const preferSetupRuntimeForChannelPlugins = options.preferSetupRuntimeForChannelPlugins === true;
const runtimeSubagentMode = resolveRuntimeSubagentMode(options.runtimeOptions);
const coreGatewayMethodNames = Object.keys(options.coreGatewayHandlers ?? {}).toSorted();
@@ -648,6 +663,8 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
env,
onlyPluginIds,
includeSetupOnlyChannelPlugins,
forceSetupOnlyChannelPlugins,
requireSetupEntryForSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins,
loadModules: options.loadModules,
runtimeSubagentMode,
@@ -663,6 +680,8 @@ function resolvePluginLoadCacheContext(options: PluginLoadOptions = {}) {
autoEnabledReasons: options.autoEnabledReasons ?? {},
onlyPluginIds,
includeSetupOnlyChannelPlugins,
forceSetupOnlyChannelPlugins,
requireSetupEntryForSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins,
shouldActivate: options.activate !== false,
shouldLoadModules: options.loadModules !== false,
@@ -980,6 +999,17 @@ function shouldLoadChannelPluginInSetupRuntime(params: {
);
}
function channelPluginIdBelongsToManifest(params: {
channelId: string | undefined;
pluginId: string;
manifestChannels: readonly string[];
}): boolean {
if (!params.channelId) {
return true;
}
return params.channelId === params.pluginId || params.manifestChannels.includes(params.channelId);
}
function createPluginRecord(params: {
id: string;
name?: string;
@@ -1410,6 +1440,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
autoEnabledReasons,
onlyPluginIds,
includeSetupOnlyChannelPlugins,
forceSetupOnlyChannelPlugins,
requireSetupEntryForSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins,
shouldActivate,
shouldLoadModules,
@@ -1740,26 +1772,34 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
}
}
const registrationMode = enableState.enabled
? shouldLoadModules &&
!validateOnly &&
shouldLoadChannelPluginInSetupRuntime({
manifestChannels: manifestRecord.channels,
setupSource: manifestRecord.setupSource,
startupDeferConfiguredChannelFullLoadUntilAfterListen:
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
cfg,
env,
preferSetupRuntimeForChannelPlugins,
})
? "setup-runtime"
: "full"
: includeSetupOnlyChannelPlugins &&
!validateOnly &&
onlyPluginIdSet &&
manifestRecord.channels.length > 0
? "setup-only"
: null;
const scopedSetupOnlyChannelPluginRequested =
includeSetupOnlyChannelPlugins &&
!validateOnly &&
onlyPluginIdSet &&
manifestRecord.channels.length > 0 &&
(!enableState.enabled || forceSetupOnlyChannelPlugins);
const canLoadScopedSetupOnlyChannelPlugin =
scopedSetupOnlyChannelPluginRequested &&
(!requireSetupEntryForSetupOnlyChannelPlugins || Boolean(manifestRecord.setupSource));
const registrationMode = canLoadScopedSetupOnlyChannelPlugin
? "setup-only"
: scopedSetupOnlyChannelPluginRequested && requireSetupEntryForSetupOnlyChannelPlugins
? null
: enableState.enabled
? shouldLoadModules &&
!validateOnly &&
shouldLoadChannelPluginInSetupRuntime({
manifestChannels: manifestRecord.channels,
setupSource: manifestRecord.setupSource,
startupDeferConfiguredChannelFullLoadUntilAfterListen:
manifestRecord.startupDeferConfiguredChannelFullLoadUntilAfterListen,
cfg,
env,
preferSetupRuntimeForChannelPlugins,
})
? "setup-runtime"
: "full"
: null;
if (!registrationMode) {
record.status = "disabled";
@@ -1980,7 +2020,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
continue;
}
if (setupRegistration.plugin) {
if (setupRegistration.plugin.id && setupRegistration.plugin.id !== record.id) {
if (
!channelPluginIdBelongsToManifest({
channelId: setupRegistration.plugin.id,
pluginId: record.id,
manifestChannels: manifestRecord.channels,
})
) {
pushPluginLoadError(
`plugin id mismatch (config uses "${record.id}", setup export uses "${setupRegistration.plugin.id}")`,
);
@@ -2105,7 +2151,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
if (!mergedSetupPlugin) {
continue;
}
if (mergedSetupPlugin.id && mergedSetupPlugin.id !== record.id) {
if (
!channelPluginIdBelongsToManifest({
channelId: mergedSetupPlugin.id,
pluginId: record.id,
manifestChannels: manifestRecord.channels,
})
) {
pushPluginLoadError(
`plugin id mismatch (config uses "${record.id}", setup export uses "${mergedSetupPlugin.id}")`,
);