diff --git a/src/plugins/setup-registry.ts b/src/plugins/setup-registry.ts index aee2565d335..7a25a060fce 100644 --- a/src/plugins/setup-registry.ts +++ b/src/plugins/setup-registry.ts @@ -63,9 +63,11 @@ const NOOP_LOGGER: PluginLogger = { const jitiLoaders = new Map>(); const setupRegistryCache = new Map(); +const setupProviderCache = new Map(); export function clearPluginSetupRegistryCache(): void { setupRegistryCache.clear(); + setupProviderCache.clear(); } function getJiti(modulePath: string) { @@ -97,16 +99,47 @@ function buildSetupRegistryCacheKey(params: { }); } +function buildSetupProviderCacheKey(params: { + provider: string; + workspaceDir?: string; + env?: NodeJS.ProcessEnv; +}): string { + return JSON.stringify({ + provider: normalizeProviderId(params.provider), + registry: buildSetupRegistryCacheKey(params), + }); +} + function resolveSetupApiPath(rootDir: string): string | null { const orderedExtensions = RUNNING_FROM_BUILT_ARTIFACT ? SETUP_API_EXTENSIONS : ([...SETUP_API_EXTENSIONS.slice(3), ...SETUP_API_EXTENSIONS.slice(0, 3)] as const); - for (const extension of orderedExtensions) { - const candidate = path.join(rootDir, `setup-api${extension}`); - if (fs.existsSync(candidate)) { - return candidate; + + const findSetupApi = (candidateRootDir: string): string | null => { + for (const extension of orderedExtensions) { + const candidate = path.join(candidateRootDir, `setup-api${extension}`); + if (fs.existsSync(candidate)) { + return candidate; + } + } + return null; + }; + + const direct = findSetupApi(rootDir); + if (direct) { + return direct; + } + + const bundledExtensionDir = path.basename(rootDir); + const repoRoot = path.resolve(path.dirname(CURRENT_MODULE_PATH), "..", ".."); + const sourceExtensionRoot = path.join(repoRoot, "extensions", bundledExtensionDir); + if (sourceExtensionRoot !== rootDir) { + const sourceFallback = findSetupApi(sourceExtensionRoot); + if (sourceFallback) { + return sourceFallback; } } + return null; } @@ -252,9 +285,99 @@ export function resolvePluginSetupProvider(params: { workspaceDir?: string; env?: NodeJS.ProcessEnv; }): ProviderPlugin | undefined { - return resolvePluginSetupRegistry(params).providers.find((entry) => - matchesProvider(entry.provider, params.provider), - )?.provider; + const cacheKey = buildSetupProviderCacheKey(params); + if (setupProviderCache.has(cacheKey)) { + return setupProviderCache.get(cacheKey) ?? undefined; + } + + const env = params.env ?? process.env; + const normalizedProvider = normalizeProviderId(params.provider); + const discovery = discoverOpenClawPlugins({ + workspaceDir: params.workspaceDir, + env, + cache: true, + }); + const manifestRegistry = loadPluginManifestRegistry({ + workspaceDir: params.workspaceDir, + env, + cache: true, + candidates: discovery.candidates, + diagnostics: discovery.diagnostics, + }); + const record = manifestRegistry.plugins.find((entry) => + entry.providers.some((providerId) => normalizeProviderId(providerId) === normalizedProvider), + ); + if (!record) { + setupProviderCache.set(cacheKey, null); + return undefined; + } + + const setupSource = record.setupSource ?? resolveSetupApiPath(record.rootDir); + if (!setupSource) { + setupProviderCache.set(cacheKey, null); + return undefined; + } + + let mod: OpenClawPluginModule; + try { + mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule; + } catch { + setupProviderCache.set(cacheKey, null); + return undefined; + } + + const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod); + if (!resolved.register) { + setupProviderCache.set(cacheKey, null); + return undefined; + } + if (resolved.definition?.id && resolved.definition.id !== record.id) { + setupProviderCache.set(cacheKey, null); + return undefined; + } + + let matchedProvider: ProviderPlugin | undefined; + const localProviderKeys = new Set(); + const api = buildPluginApi({ + id: record.id, + name: record.name ?? record.id, + version: record.version, + description: record.description, + source: setupSource, + rootDir: record.rootDir, + registrationMode: "setup-only", + config: {} as OpenClawConfig, + runtime: EMPTY_RUNTIME, + logger: NOOP_LOGGER, + resolvePath: (input) => input, + handlers: { + registerProvider(provider) { + const key = normalizeProviderId(provider.id); + if (localProviderKeys.has(key)) { + return; + } + localProviderKeys.add(key); + if (matchesProvider(provider, normalizedProvider)) { + matchedProvider = provider; + } + }, + registerConfigMigration() {}, + registerAutoEnableProbe() {}, + }, + }); + + try { + const result = resolved.register(api); + if (result && typeof result.then === "function") { + // Keep setup registration sync-only. + } + } catch { + setupProviderCache.set(cacheKey, null); + return undefined; + } + + setupProviderCache.set(cacheKey, matchedProvider ?? null); + return matchedProvider; } export function runPluginSetupConfigMigrations(params: {