import type { OpenClawConfig } from "../config/config.js"; import { withActivatedPluginIds } from "./activation-context.js"; import { buildPluginSnapshotCacheEnvKey, resolvePluginSnapshotCacheTtlMs, shouldUsePluginSnapshotCache, } from "./cache-controls.js"; import { isPluginRegistryLoadInFlight, loadOpenClawPlugins, resolveCompatibleRuntimePluginRegistry, resolveRuntimePluginRegistry, } from "./loader.js"; import type { PluginLoadOptions } from "./loader.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; import type { PluginRegistry } from "./registry.js"; import { getActivePluginRegistryWorkspaceDir } from "./runtime.js"; import { buildPluginRuntimeLoadOptionsFromValues, createPluginRuntimeLoaderLogger, } from "./runtime/load-context.js"; import { buildWebProviderSnapshotCacheKey } from "./web-provider-resolution-shared.js"; type WebProviderSnapshotCacheEntry = { expiresAt: number; providers: TEntry[]; }; export type WebProviderSnapshotCache = WeakMap< OpenClawConfig, WeakMap>> >; export type ResolvePluginWebProvidersParams = { config?: PluginLoadOptions["config"]; workspaceDir?: string; env?: PluginLoadOptions["env"]; bundledAllowlistCompat?: boolean; onlyPluginIds?: readonly string[]; activate?: boolean; cache?: boolean; mode?: "runtime" | "setup"; origin?: PluginManifestRecord["origin"]; }; type ResolveWebProviderRuntimeDeps = { snapshotCache: WebProviderSnapshotCache; resolveBundledResolutionConfig: (params: { config?: PluginLoadOptions["config"]; workspaceDir?: string; env?: PluginLoadOptions["env"]; bundledAllowlistCompat?: boolean; }) => { config: PluginLoadOptions["config"]; activationSourceConfig?: PluginLoadOptions["config"]; autoEnabledReasons: Record; }; resolveCandidatePluginIds: (params: { config?: PluginLoadOptions["config"]; workspaceDir?: string; env?: PluginLoadOptions["env"]; onlyPluginIds?: readonly string[]; origin?: PluginManifestRecord["origin"]; }) => string[] | undefined; mapRegistryProviders: (params: { registry: PluginRegistry; onlyPluginIds?: readonly string[]; }) => TEntry[]; }; export function createWebProviderSnapshotCache(): WebProviderSnapshotCache { return new WeakMap< OpenClawConfig, WeakMap>> >(); } function resolveWebProviderLoadOptions( params: ResolvePluginWebProvidersParams, deps: ResolveWebProviderRuntimeDeps, ) { const env = params.env ?? process.env; const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir(); const { config, activationSourceConfig, autoEnabledReasons } = deps.resolveBundledResolutionConfig({ ...params, workspaceDir, env, }); const onlyPluginIds = deps.resolveCandidatePluginIds({ config, workspaceDir, env, onlyPluginIds: params.onlyPluginIds, origin: params.origin, }); return buildPluginRuntimeLoadOptionsFromValues( { env, config, activationSourceConfig, autoEnabledReasons, workspaceDir, logger: createPluginRuntimeLoaderLogger(), }, { cache: params.cache ?? false, activate: params.activate ?? false, ...(onlyPluginIds ? { onlyPluginIds } : {}), }, ); } export function resolvePluginWebProviders( params: ResolvePluginWebProvidersParams, deps: ResolveWebProviderRuntimeDeps, ): TEntry[] { const env = params.env ?? process.env; const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir(); if (params.mode === "setup") { const pluginIds = deps.resolveCandidatePluginIds({ config: params.config, workspaceDir, env, onlyPluginIds: params.onlyPluginIds, origin: params.origin, }) ?? []; if (pluginIds.length === 0) { return []; } const registry = loadOpenClawPlugins( buildPluginRuntimeLoadOptionsFromValues( { config: withActivatedPluginIds({ config: params.config, pluginIds, }), activationSourceConfig: params.config, autoEnabledReasons: {}, workspaceDir, env, logger: createPluginRuntimeLoaderLogger(), }, { onlyPluginIds: pluginIds, cache: params.cache ?? false, activate: params.activate ?? false, }, ), ); return deps.mapRegistryProviders({ registry, onlyPluginIds: pluginIds }); } const cacheOwnerConfig = params.config; const shouldMemoizeSnapshot = params.activate !== true && params.cache !== true && shouldUsePluginSnapshotCache(env); const cacheKey = buildWebProviderSnapshotCacheKey({ config: cacheOwnerConfig, workspaceDir, bundledAllowlistCompat: params.bundledAllowlistCompat, onlyPluginIds: params.onlyPluginIds, origin: params.origin, envKey: buildPluginSnapshotCacheEnvKey(env), }); if (cacheOwnerConfig && shouldMemoizeSnapshot) { const configCache = deps.snapshotCache.get(cacheOwnerConfig); const envCache = configCache?.get(env); const cached = envCache?.get(cacheKey); if (cached && cached.expiresAt > Date.now()) { return cached.providers; } } const memoizeSnapshot = (providers: TEntry[]) => { if (!cacheOwnerConfig || !shouldMemoizeSnapshot) { return; } const ttlMs = resolvePluginSnapshotCacheTtlMs(env); let configCache = deps.snapshotCache.get(cacheOwnerConfig); if (!configCache) { configCache = new WeakMap< NodeJS.ProcessEnv, Map> >(); deps.snapshotCache.set(cacheOwnerConfig, configCache); } let envCache = configCache.get(env); if (!envCache) { envCache = new Map>(); configCache.set(env, envCache); } envCache.set(cacheKey, { expiresAt: Date.now() + ttlMs, providers, }); }; const loadOptions = resolveWebProviderLoadOptions(params, deps); const compatible = resolveCompatibleRuntimePluginRegistry(loadOptions); if (compatible) { const resolved = deps.mapRegistryProviders({ registry: compatible, onlyPluginIds: params.onlyPluginIds, }); memoizeSnapshot(resolved); return resolved; } if (isPluginRegistryLoadInFlight(loadOptions)) { return []; } const resolved = deps.mapRegistryProviders({ registry: loadOpenClawPlugins(loadOptions), onlyPluginIds: params.onlyPluginIds, }); memoizeSnapshot(resolved); return resolved; } export function resolveRuntimeWebProviders( params: Omit, deps: ResolveWebProviderRuntimeDeps, ): TEntry[] { const runtimeRegistry = resolveRuntimePluginRegistry( params.config === undefined ? undefined : resolveWebProviderLoadOptions(params, deps), ); if (runtimeRegistry) { return deps.mapRegistryProviders({ registry: runtimeRegistry, onlyPluginIds: params.onlyPluginIds, }); } return resolvePluginWebProviders(params, deps); }