mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 10:16:49 +00:00
451 lines
14 KiB
TypeScript
451 lines
14 KiB
TypeScript
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
|
|
// Plans deterministic Gateway startup plugin activation from prepared registry metadata.
|
|
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
|
|
import {
|
|
listExplicitlyDisabledChannelIdsForConfig,
|
|
type AmbientEnvTriggerPolicy,
|
|
} from "../channels/config-presence.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { resolveEffectivePluginActivationState } from "./config-state.js";
|
|
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
|
import {
|
|
canStartConfiguredChannelPlugin,
|
|
canStartConfiguredGenerationProviderPlugin,
|
|
canStartConfiguredMemoryEmbeddingProviderPlugin,
|
|
canStartConfiguredModelProviderPlugin,
|
|
canStartConfiguredRootPlugin,
|
|
canStartConfiguredSpeechProviderPlugin,
|
|
canStartConfiguredVoiceProviderPlugin,
|
|
canStartConfiguredWebSearchProviderPlugin,
|
|
canStartConfiguredWorkerProviderPlugin,
|
|
canStartExplicitHookPlugin,
|
|
canStartRequiredAgentHarnessPlugin,
|
|
canStartTrustedToolPolicyPlugin,
|
|
} from "./gateway-startup-plugin-activation.js";
|
|
import {
|
|
hasConfiguredStartupChannel,
|
|
listPotentialEnabledChannelIds,
|
|
resolveAuthorizedGatewayStartupDreamingPluginIds,
|
|
resolveContextEngineSlotStartupPluginId,
|
|
resolveMemorySlotStartupPluginId,
|
|
shouldConsiderForGatewayStartup,
|
|
createManifestRegistryLookup,
|
|
findManifestPlugin,
|
|
} from "./gateway-startup-plugin-config.js";
|
|
import type {
|
|
GatewayStartupPluginPlan,
|
|
ManifestRegistryLookup,
|
|
} from "./gateway-startup-plugin-contracts.js";
|
|
import {
|
|
collectConfiguredAgentModelProviderIds,
|
|
collectConfiguredGenerationProviderIds,
|
|
collectConfiguredMemoryEmbeddingProviderIds,
|
|
collectConfiguredVoiceProviderIds,
|
|
collectConfiguredWebSearchProviderIds,
|
|
} from "./gateway-startup-plugin-providers.js";
|
|
import { collectConfiguredSpeechProviderIds } from "./gateway-startup-speech-providers.js";
|
|
import type { PluginManifestRegistry } from "./manifest-registry.js";
|
|
import {
|
|
createPluginRegistryIdNormalizer,
|
|
normalizePluginsConfigWithRegistry,
|
|
} from "./plugin-registry-contributions.js";
|
|
import type { PluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
|
|
import {
|
|
collectConfiguredWorkerProviderIds,
|
|
normalizeWorkerProviderIds,
|
|
} from "./worker-provider-registry.js";
|
|
|
|
export function resolveChannelPluginIdsFromRegistry(params: {
|
|
manifestRegistry: PluginManifestRegistry;
|
|
}): string[] {
|
|
const { manifestRegistry } = params;
|
|
return manifestRegistry.plugins
|
|
.filter((plugin) => plugin.channels.length > 0)
|
|
.map((plugin) => plugin.id);
|
|
}
|
|
|
|
export function resolveConfiguredDeferredChannelPluginIdsFromRegistry(params: {
|
|
config: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
index: PluginRegistrySnapshot;
|
|
manifestRegistry: PluginManifestRegistry;
|
|
ambientEnvTriggers?: AmbientEnvTriggerPolicy;
|
|
}): string[] {
|
|
const configuredChannelIds = new Set(
|
|
listPotentialEnabledChannelIds(params.config, params.env, params.ambientEnvTriggers),
|
|
);
|
|
if (configuredChannelIds.size === 0) {
|
|
return [];
|
|
}
|
|
const pluginsConfig = normalizePluginsConfigWithRegistry(params.config.plugins, params.index, {
|
|
manifestRegistry: params.manifestRegistry,
|
|
});
|
|
const activationSource = {
|
|
plugins: pluginsConfig,
|
|
rootConfig: params.config,
|
|
};
|
|
const manifestLookup = createManifestRegistryLookup(params.manifestRegistry);
|
|
return resolveConfiguredDeferredChannelPluginIdsFromPrepared({
|
|
config: params.config,
|
|
index: params.index,
|
|
configuredChannelIds,
|
|
pluginsConfig,
|
|
activationSource,
|
|
manifestLookup,
|
|
});
|
|
}
|
|
|
|
function resolveConfiguredDeferredChannelPluginIdsFromPrepared(params: {
|
|
config: OpenClawConfig;
|
|
index: PluginRegistrySnapshot;
|
|
configuredChannelIds: ReadonlySet<string>;
|
|
pluginsConfig: ReturnType<typeof normalizePluginsConfigWithRegistry>;
|
|
activationSource: {
|
|
plugins: ReturnType<typeof normalizePluginsConfigWithRegistry>;
|
|
rootConfig?: OpenClawConfig;
|
|
};
|
|
manifestLookup: ManifestRegistryLookup;
|
|
platform?: NodeJS.Platform;
|
|
}): string[] {
|
|
if (params.configuredChannelIds.size === 0) {
|
|
return [];
|
|
}
|
|
return params.index.plugins
|
|
.filter(
|
|
(plugin) =>
|
|
hasConfiguredStartupChannel({
|
|
plugin,
|
|
manifestLookup: params.manifestLookup,
|
|
configuredChannelIds: params.configuredChannelIds,
|
|
}) &&
|
|
plugin.startup.deferConfiguredChannelFullLoadUntilAfterListen &&
|
|
canStartConfiguredChannelPlugin({
|
|
plugin,
|
|
config: params.config,
|
|
pluginsConfig: params.pluginsConfig,
|
|
activationSource: params.activationSource,
|
|
manifestLookup: params.manifestLookup,
|
|
platform: params.platform,
|
|
}),
|
|
)
|
|
.map((plugin) => plugin.pluginId);
|
|
}
|
|
|
|
export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
|
config: OpenClawConfig;
|
|
activationSourceConfig?: OpenClawConfig;
|
|
env: NodeJS.ProcessEnv;
|
|
index: PluginRegistrySnapshot;
|
|
manifestRegistry: PluginManifestRegistry;
|
|
workerProviderIds?: readonly string[];
|
|
platform?: NodeJS.Platform;
|
|
ambientEnvTriggers?: AmbientEnvTriggerPolicy;
|
|
}): GatewayStartupPluginPlan {
|
|
const channelPluginIds = resolveChannelPluginIdsFromRegistry({
|
|
manifestRegistry: params.manifestRegistry,
|
|
});
|
|
const configuredChannelIds = new Set(
|
|
listPotentialEnabledChannelIds(params.config, params.env, params.ambientEnvTriggers),
|
|
);
|
|
const pluginsConfig = normalizePluginsConfigWithRegistry(params.config.plugins, params.index, {
|
|
manifestRegistry: params.manifestRegistry,
|
|
});
|
|
// Startup must classify allowlist exceptions against the raw config snapshot,
|
|
// not the auto-enabled effective snapshot, or configured-only channels can be
|
|
// misclassified as explicit enablement.
|
|
const activationSourceConfig = params.activationSourceConfig ?? params.config;
|
|
const activationSourcePlugins = normalizePluginsConfigWithRegistry(
|
|
activationSourceConfig.plugins,
|
|
params.index,
|
|
{ manifestRegistry: params.manifestRegistry },
|
|
);
|
|
const activationSource = {
|
|
plugins: activationSourcePlugins,
|
|
rootConfig: activationSourceConfig,
|
|
};
|
|
const manifestLookup = createManifestRegistryLookup(params.manifestRegistry);
|
|
const explicitlyDisabledChannelIds = new Set(
|
|
listExplicitlyDisabledChannelIdsForConfig(params.config),
|
|
);
|
|
const configuredDeferredChannelPluginIds: string[] = [];
|
|
const requiredAgentHarnessRuntimes = new Set(
|
|
collectConfiguredAgentHarnessRuntimes(activationSourceConfig),
|
|
);
|
|
const configuredSpeechProviderIds = collectConfiguredSpeechProviderIds(activationSourceConfig);
|
|
const configuredWebSearchProviderIds =
|
|
collectConfiguredWebSearchProviderIds(activationSourceConfig);
|
|
const configuredModelProviderIds = collectConfiguredAgentModelProviderIds(
|
|
activationSourceConfig,
|
|
params.manifestRegistry,
|
|
);
|
|
const configuredGenerationProviderIds =
|
|
collectConfiguredGenerationProviderIds(activationSourceConfig);
|
|
const configuredVoiceProviderIds = collectConfiguredVoiceProviderIds(activationSourceConfig);
|
|
const configuredMemoryEmbeddingProviderIds =
|
|
collectConfiguredMemoryEmbeddingProviderIds(activationSourceConfig);
|
|
const configuredWorkerProviderIds = new Set([
|
|
...collectConfiguredWorkerProviderIds(activationSourceConfig),
|
|
...normalizeWorkerProviderIds(params.workerProviderIds ?? []),
|
|
]);
|
|
const normalizePluginId = createPluginRegistryIdNormalizer(params.index, {
|
|
manifestRegistry: params.manifestRegistry,
|
|
});
|
|
const memorySlotStartupPluginId = resolveMemorySlotStartupPluginId({
|
|
activationSourceConfig,
|
|
activationSourcePlugins,
|
|
normalizePluginId,
|
|
});
|
|
const startupDreamingPluginIds = resolveAuthorizedGatewayStartupDreamingPluginIds({
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
activationSourcePlugins,
|
|
selectedMemoryPluginId: memorySlotStartupPluginId,
|
|
index: params.index,
|
|
platform: params.platform,
|
|
});
|
|
const contextEngineSlotStartupPluginId = resolveContextEngineSlotStartupPluginId({
|
|
activationSourceConfig,
|
|
activationSourcePlugins,
|
|
normalizePluginId,
|
|
});
|
|
const pluginIds: string[] = [];
|
|
for (const plugin of params.index.plugins) {
|
|
const manifest = findManifestPlugin(manifestLookup, plugin.pluginId);
|
|
const hasEnabledManifestChannel =
|
|
manifest?.channels?.some((channelId) => {
|
|
const normalizedChannelId = normalizeOptionalLowercaseString(channelId);
|
|
return normalizedChannelId ? !explicitlyDisabledChannelIds.has(normalizedChannelId) : false;
|
|
}) ?? false;
|
|
// Non-bundled plugin that explicitly declares channels and is enabled
|
|
// in plugins.entries must be treated as a configured startup channel
|
|
// even when the channel itself is not listed in config.channels.
|
|
// Published install flows configure channels via plugins.entries, and
|
|
// the channel config may only have {enabled: true} which does not
|
|
// produce a `configuredChannelIds` entry.
|
|
const hasExplicitlyEnabledNonBundledChannel =
|
|
plugin.origin !== "bundled" &&
|
|
hasEnabledManifestChannel &&
|
|
pluginsConfig.entries[plugin.pluginId]?.enabled === true &&
|
|
!pluginsConfig.deny.includes(plugin.pluginId);
|
|
if (
|
|
hasConfiguredStartupChannel({
|
|
plugin,
|
|
manifestLookup,
|
|
configuredChannelIds,
|
|
}) ||
|
|
hasExplicitlyEnabledNonBundledChannel
|
|
) {
|
|
const canStartConfiguredChannel = canStartConfiguredChannelPlugin({
|
|
plugin,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
manifestLookup,
|
|
platform: params.platform,
|
|
});
|
|
if (canStartConfiguredChannel) {
|
|
pluginIds.push(plugin.pluginId);
|
|
if (plugin.startup.deferConfiguredChannelFullLoadUntilAfterListen) {
|
|
configuredDeferredChannelPluginIds.push(plugin.pluginId);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (
|
|
canStartRequiredAgentHarnessPlugin({
|
|
plugin,
|
|
pluginsConfig,
|
|
activationSource,
|
|
config: params.config,
|
|
requiredAgentHarnessRuntimes,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredRootPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredWorkerProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredWorkerProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredSpeechProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredSpeechProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredWebSearchProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredWebSearchProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredModelProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredModelProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredGenerationProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredGenerationProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredVoiceProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredVoiceProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartConfiguredMemoryEmbeddingProviderPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
configuredMemoryEmbeddingProviderIds,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartExplicitHookPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
activationSourcePlugins,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
canStartTrustedToolPolicyPlugin({
|
|
plugin,
|
|
manifest,
|
|
config: params.config,
|
|
pluginsConfig,
|
|
activationSource,
|
|
platform: params.platform,
|
|
})
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
if (
|
|
!shouldConsiderForGatewayStartup({
|
|
plugin,
|
|
manifest,
|
|
startupDreamingPluginIds,
|
|
memorySlotStartupPluginId,
|
|
contextEngineSlotStartupPluginId,
|
|
})
|
|
) {
|
|
continue;
|
|
}
|
|
if (startupDreamingPluginIds.has(plugin.pluginId)) {
|
|
pluginIds.push(plugin.pluginId);
|
|
continue;
|
|
}
|
|
const isSourceExternalPlugin =
|
|
plugin.origin === "bundled" && plugin.packageBuild?.bundledDist === false;
|
|
// Source checkout discovery still uses the bundled root, but source-only
|
|
// packages are externally owned and must keep the external explicit-startup policy.
|
|
const startupPolicyOrigin = isSourceExternalPlugin ? "workspace" : plugin.origin;
|
|
const activationState = resolveEffectivePluginActivationState({
|
|
id: plugin.pluginId,
|
|
origin: startupPolicyOrigin,
|
|
config: pluginsConfig,
|
|
rootConfig: params.config,
|
|
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin, params.platform),
|
|
activationSource,
|
|
});
|
|
if (!activationState.enabled) {
|
|
continue;
|
|
}
|
|
if (
|
|
startupPolicyOrigin !== "bundled"
|
|
? activationState.explicitlyEnabled
|
|
: activationState.source === "explicit" || activationState.source === "default"
|
|
) {
|
|
pluginIds.push(plugin.pluginId);
|
|
}
|
|
}
|
|
return {
|
|
channelPluginIds,
|
|
configuredDeferredChannelPluginIds,
|
|
pluginIds,
|
|
};
|
|
}
|