import type { OpenClawConfig } from "../config/types.openclaw.js"; import { measureDiagnosticsTimelineSpanSync } from "../infra/diagnostics-timeline.js"; import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js"; import type { InstalledPluginIndex } from "./installed-plugin-index.js"; import { loadPluginManifestRegistryForInstalledIndex, resolveInstalledManifestRegistryIndexFingerprint, } from "./manifest-registry-installed.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; import type { LoadPluginMetadataSnapshotParams, PluginMetadataSnapshot, PluginMetadataSnapshotOwnerMaps, } from "./plugin-metadata-snapshot.types.js"; import { createPluginRegistryIdNormalizer } from "./plugin-registry-id-normalizer.js"; import { loadPluginRegistrySnapshotWithMetadata } from "./plugin-registry-snapshot.js"; export type { LoadPluginMetadataSnapshotParams, PluginMetadataSnapshot, PluginMetadataSnapshotMetrics, PluginMetadataSnapshotOwnerMaps, PluginMetadataSnapshotRegistryDiagnostic, } from "./plugin-metadata-snapshot.types.js"; function indexesMatch( left: InstalledPluginIndex | undefined, right: InstalledPluginIndex | undefined, ): boolean { if (!left || !right) { return true; } return ( resolveInstalledManifestRegistryIndexFingerprint(left) === resolveInstalledManifestRegistryIndexFingerprint(right) ); } export function isPluginMetadataSnapshotCompatible(params: { snapshot: Pick; config: OpenClawConfig; workspaceDir?: string; index?: InstalledPluginIndex; }): boolean { return ( params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) && (params.snapshot.workspaceDir ?? "") === (params.workspaceDir ?? "") && indexesMatch(params.snapshot.index, params.index) ); } function appendOwner(owners: Map, ownedId: string, pluginId: string): void { const existing = owners.get(ownedId); if (existing) { existing.push(pluginId); return; } owners.set(ownedId, [pluginId]); } function freezeOwnerMap(owners: Map): ReadonlyMap { return new Map( [...owners.entries()].map(([ownedId, pluginIds]) => [ownedId, Object.freeze([...pluginIds])]), ); } export function buildPluginMetadataOwnerMaps( plugins: readonly PluginManifestRecord[], ): PluginMetadataSnapshotOwnerMaps { const channels = new Map(); const channelConfigs = new Map(); const providers = new Map(); const modelCatalogProviders = new Map(); const cliBackends = new Map(); const setupProviders = new Map(); const commandAliases = new Map(); const contracts = new Map(); for (const plugin of plugins) { for (const channelId of plugin.channels) { appendOwner(channels, channelId, plugin.id); } for (const channelId of Object.keys(plugin.channelConfigs ?? {})) { appendOwner(channelConfigs, channelId, plugin.id); } for (const providerId of plugin.providers) { appendOwner(providers, providerId, plugin.id); } for (const providerId of Object.keys(plugin.modelCatalog?.providers ?? {})) { appendOwner(modelCatalogProviders, providerId, plugin.id); } for (const providerId of Object.keys(plugin.modelCatalog?.aliases ?? {})) { appendOwner(modelCatalogProviders, providerId, plugin.id); } for (const cliBackendId of plugin.cliBackends) { appendOwner(cliBackends, cliBackendId, plugin.id); } for (const cliBackendId of plugin.setup?.cliBackends ?? []) { appendOwner(cliBackends, cliBackendId, plugin.id); } for (const setupProvider of plugin.setup?.providers ?? []) { appendOwner(setupProviders, setupProvider.id, plugin.id); } for (const commandAlias of plugin.commandAliases ?? []) { appendOwner(commandAliases, commandAlias.name, plugin.id); } for (const [contract, values] of Object.entries(plugin.contracts ?? {})) { if (Array.isArray(values) && values.length > 0) { appendOwner(contracts, contract, plugin.id); } } } return { channels: freezeOwnerMap(channels), channelConfigs: freezeOwnerMap(channelConfigs), providers: freezeOwnerMap(providers), modelCatalogProviders: freezeOwnerMap(modelCatalogProviders), cliBackends: freezeOwnerMap(cliBackends), setupProviders: freezeOwnerMap(setupProviders), commandAliases: freezeOwnerMap(commandAliases), contracts: freezeOwnerMap(contracts), }; } export function loadPluginMetadataSnapshot( params: LoadPluginMetadataSnapshotParams, ): PluginMetadataSnapshot { return measureDiagnosticsTimelineSpanSync( "plugins.metadata.scan", () => loadPluginMetadataSnapshotImpl(params), { phase: "startup", config: params.config, env: params.env, attributes: { hasWorkspaceDir: params.workspaceDir !== undefined, hasInstalledIndex: params.index !== undefined, }, }, ); } function loadPluginMetadataSnapshotImpl( params: LoadPluginMetadataSnapshotParams, ): PluginMetadataSnapshot { const totalStartedAt = performance.now(); const registryStartedAt = performance.now(); const registryResult = loadPluginRegistrySnapshotWithMetadata({ config: params.config, workspaceDir: params.workspaceDir, env: params.env, ...(params.index ? { index: params.index } : {}), }); const registrySnapshotMs = performance.now() - registryStartedAt; const index = registryResult.snapshot; const manifestStartedAt = performance.now(); const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({ index, config: params.config, workspaceDir: params.workspaceDir, env: params.env, includeDisabled: true, }); const manifestRegistryMs = performance.now() - manifestStartedAt; const normalizePluginId = createPluginRegistryIdNormalizer(index, { manifestRegistry }); const byPluginId = new Map(manifestRegistry.plugins.map((plugin) => [plugin.id, plugin])); const ownerMapsStartedAt = performance.now(); const owners = buildPluginMetadataOwnerMaps(manifestRegistry.plugins); const ownerMapsMs = performance.now() - ownerMapsStartedAt; const totalMs = performance.now() - totalStartedAt; return { policyHash: index.policyHash, ...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}), index, registryDiagnostics: registryResult.diagnostics, manifestRegistry, plugins: manifestRegistry.plugins, diagnostics: manifestRegistry.diagnostics, byPluginId, normalizePluginId, owners, metrics: { registrySnapshotMs, manifestRegistryMs, ownerMapsMs, totalMs, indexPluginCount: index.plugins.length, manifestPluginCount: manifestRegistry.plugins.length, }, }; }