From 3e15090c7e434afe3ebadceec2c622d1cfe13d99 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 08:18:45 +0100 Subject: [PATCH] refactor: route plugin metadata consumers through snapshots --- src/agents/pi-project-settings-snapshot.ts | 39 +++-------------- src/agents/pi-project-settings.bundle.test.ts | 36 ++++++++++++++++ src/config/plugin-auto-enable.shared.ts | 16 +++---- src/config/runtime-schema.test.ts | 8 +++- src/config/runtime-schema.ts | 11 ++--- .../validation.channel-metadata.test.ts | 11 +++++ src/config/validation.ts | 13 +++--- src/config/zod-schema.providers.ts | 6 +-- src/plugins/provider-auth-choices.test.ts | 20 ++++++++- src/plugins/provider-auth-choices.ts | 10 ++--- ...web-provider-resolution-candidates.test.ts | 17 +++++--- src/plugins/web-provider-resolution-shared.ts | 12 +++--- src/plugins/web-search-credential-presence.ts | 5 +-- src/secrets/channel-env-vars.dynamic.test.ts | 9 ++++ src/secrets/channel-env-vars.ts | 11 +++-- src/secrets/target-registry-data.ts | 42 +++++++++---------- 16 files changed, 156 insertions(+), 110 deletions(-) diff --git a/src/agents/pi-project-settings-snapshot.ts b/src/agents/pi-project-settings-snapshot.ts index 320d7eb99d6..39d3619848b 100644 --- a/src/agents/pi-project-settings-snapshot.ts +++ b/src/agents/pi-project-settings-snapshot.ts @@ -10,8 +10,7 @@ import { normalizePluginsConfigWithResolver, resolveEffectivePluginActivationState, } from "../plugins/config-policy.js"; -import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { isRecord } from "../utils.js"; import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js"; @@ -69,33 +68,6 @@ function loadBundleSettingsFile(params: { } } -function buildRegistryPluginIdAliases( - registry: PluginManifestRegistry, -): Readonly> { - return Object.fromEntries( - registry.plugins - .flatMap((record) => [ - ...(record.providers ?? []) - .filter((providerId) => providerId !== record.id) - .map((providerId) => [providerId, record.id] as const), - ...(record.legacyPluginIds ?? []).map( - (legacyPluginId) => [legacyPluginId, record.id] as const, - ), - ]) - .toSorted(([left], [right]) => left.localeCompare(right)), - ); -} - -function createRegistryPluginIdNormalizer( - registry: PluginManifestRegistry, -): (id: string) => string { - const aliases = buildRegistryPluginIdAliases(registry); - return (id: string) => { - const trimmed = id.trim(); - return aliases[trimmed] ?? trimmed; - }; -} - export function loadEnabledBundlePiSettingsSnapshot(params: { cwd: string; cfg?: OpenClawConfig; @@ -104,18 +76,19 @@ export function loadEnabledBundlePiSettingsSnapshot(params: { if (!workspaceDir) { return {}; } - const registry = loadPluginManifestRegistryForPluginRegistry({ + const metadataSnapshot = loadPluginMetadataSnapshot({ workspaceDir, - config: params.cfg, - includeDisabled: true, + config: params.cfg ?? {}, + env: process.env, }); + const registry = metadataSnapshot.manifestRegistry; if (registry.plugins.length === 0) { return {}; } const normalizedPlugins = normalizePluginsConfigWithResolver( params.cfg?.plugins, - createRegistryPluginIdNormalizer(registry), + metadataSnapshot.normalizePluginId, ); let snapshot: PiSettingsSnapshot = {}; diff --git a/src/agents/pi-project-settings.bundle.test.ts b/src/agents/pi-project-settings.bundle.test.ts index 9dc7a919f70..932ce1b5075 100644 --- a/src/agents/pi-project-settings.bundle.test.ts +++ b/src/agents/pi-project-settings.bundle.test.ts @@ -79,6 +79,42 @@ vi.mock("../plugins/plugin-registry.js", async () => { }; }); +vi.mock("../plugins/plugin-metadata-snapshot.js", async () => { + const fs = await import("node:fs"); + const path = await import("node:path"); + const loadRegistry = (params: { workspaceDir?: string }) => { + const rootDir = path.join( + params.workspaceDir ?? "", + ".openclaw", + "extensions", + "claude-bundle", + ); + if (!fs.existsSync(path.join(rootDir, ".claude-plugin", "plugin.json"))) { + return { plugins: [], diagnostics: [] }; + } + const resolvedRootDir = fs.realpathSync(rootDir); + return { + diagnostics: [], + plugins: [ + { + id: "claude-bundle", + origin: "workspace", + format: "bundle", + bundleFormat: "claude", + settingsFiles: ["settings.json"], + rootDir: resolvedRootDir, + }, + ], + }; + }; + return { + loadPluginMetadataSnapshot: (params: { workspaceDir?: string }) => ({ + manifestRegistry: loadRegistry(params), + normalizePluginId: (id: string) => id.trim(), + }), + }; +}); + vi.mock("./embedded-pi-mcp.js", async () => { const fs = await import("node:fs"); const path = await import("node:path"); diff --git a/src/config/plugin-auto-enable.shared.ts b/src/config/plugin-auto-enable.shared.ts index 81767fd7d54..14bda0de1a7 100644 --- a/src/config/plugin-auto-enable.shared.ts +++ b/src/config/plugin-auto-enable.shared.ts @@ -9,7 +9,7 @@ import { type PluginManifestRecord, type PluginManifestRegistry, } from "../plugins/manifest-registry.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { resolveOwningPluginIdsForModelRef } from "../plugins/providers.js"; import { resolvePluginSetupAutoEnableReasons } from "../plugins/setup-registry.js"; import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; @@ -220,16 +220,13 @@ function resolvePluginsWithOwnedToolConfig( function resolvePluginIdForConfiguredWebFetchProvider( providerId: string | undefined, - env: NodeJS.ProcessEnv, + registry: PluginManifestRegistry, ): string | undefined { const normalizedProviderId = normalizeOptionalLowercaseString(providerId); if (!normalizedProviderId) { return undefined; } - return loadPluginManifestRegistryForPluginRegistry({ - env, - includeDisabled: true, - }).plugins.find( + return registry.plugins.find( (plugin) => plugin.origin === "bundled" && (plugin.contracts?.webFetchProviders ?? []).some( @@ -623,7 +620,7 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: { : undefined; const webFetchPluginId = resolvePluginIdForConfiguredWebFetchProvider( webFetchProvider, - params.env, + params.registry, ); if (webFetchPluginId) { changes.push({ @@ -869,11 +866,10 @@ export function resolvePluginAutoEnableManifestRegistry(params: { return ( params.manifestRegistry ?? (configMayNeedPluginManifestRegistry(params.config, params.env) - ? loadPluginManifestRegistryForPluginRegistry({ + ? loadPluginMetadataSnapshot({ config: params.config, env: params.env, - includeDisabled: true, - }) + }).manifestRegistry : EMPTY_PLUGIN_MANIFEST_REGISTRY) ); } diff --git a/src/config/runtime-schema.test.ts b/src/config/runtime-schema.test.ts index b3f680b00e1..a8a8ea59397 100644 --- a/src/config/runtime-schema.test.ts +++ b/src/config/runtime-schema.test.ts @@ -34,6 +34,12 @@ vi.mock("../plugins/plugin-registry.js", () => ({ mockLoadPluginManifestRegistry(...args), })); +vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: (...args: unknown[]) => ({ + manifestRegistry: mockLoadPluginManifestRegistry(...args), + }), +})); + vi.mock("../plugins/current-plugin-metadata-snapshot.js", () => ({ getCurrentPluginMetadataSnapshot: (...args: unknown[]) => mockGetCurrentPluginMetadataSnapshot(...args), @@ -309,7 +315,7 @@ describe("loadGatewayRuntimeConfigSchema", () => { expect(mockLoadPluginManifestRegistry).toHaveBeenCalledTimes(3); for (const call of mockLoadPluginManifestRegistry.mock.calls) { - expect(call[0]).toMatchObject({ includeDisabled: true }); + expect(call[0]).toHaveProperty("config"); expect(call[0]).not.toHaveProperty("bundledChannelConfigCollector"); } expect(getActivePluginRegistry()).toBe(activeRegistry); diff --git a/src/config/runtime-schema.ts b/src/config/runtime-schema.ts index 77830c05d74..11b38f62047 100644 --- a/src/config/runtime-schema.ts +++ b/src/config/runtime-schema.ts @@ -1,6 +1,6 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { collectChannelSchemaMetadata, collectPluginSchemaMetadata, @@ -15,14 +15,11 @@ function loadManifestRegistry(config: OpenClawConfig, env?: NodeJS.ProcessEnv) { if (currentSnapshot) { return currentSnapshot.manifestRegistry; } - return loadPluginManifestRegistryForPluginRegistry({ + return loadPluginMetadataSnapshot({ config, - // Bundled channel schemas are already generated into the base schema; avoid - // loading plugin config-schema modules on every config.get/config.schema. - env, + env: env ?? process.env, workspaceDir, - includeDisabled: true, - }); + }).manifestRegistry; } export function loadGatewayRuntimeConfigSchema(): ConfigSchemaResponse { diff --git a/src/config/validation.channel-metadata.test.ts b/src/config/validation.channel-metadata.test.ts index 5ba25caf367..1947b781b5f 100644 --- a/src/config/validation.channel-metadata.test.ts +++ b/src/config/validation.channel-metadata.test.ts @@ -120,12 +120,23 @@ vi.mock("../plugins/plugin-registry.js", () => ({ loadPluginManifestRegistryForPluginRegistry: () => mockLoadPluginManifestRegistry(), })); +vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: () => ({ + manifestRegistry: mockLoadPluginManifestRegistry(), + }), +})); + vi.mock("../plugins/doctor-contract-registry.js", () => ({ collectRelevantDoctorPluginIds: () => [], listPluginDoctorLegacyConfigRules: () => [], applyPluginDoctorCompatibilityMigrations: () => ({ next: null, changes: [] }), })); +vi.mock("../secrets/target-registry-data.js", () => ({ + getCoreSecretTargetRegistry: () => [], + getSecretTargetRegistry: () => [], +})); + vi.mock("../channels/plugins/legacy-config.js", () => ({ collectChannelLegacyConfigRules: () => [], })); diff --git a/src/config/validation.ts b/src/config/validation.ts index 9072005909e..47e04eb3cb0 100644 --- a/src/config/validation.ts +++ b/src/config/validation.ts @@ -16,8 +16,10 @@ import { import { loadInstalledPluginIndexInstallRecordsSync } from "../plugins/installed-plugin-index-record-reader.js"; import { resolveManifestCommandAliasOwnerInRegistry } from "../plugins/manifest-command-aliases.js"; import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; -import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { + loadPluginMetadataSnapshot, + type PluginMetadataSnapshot, +} from "../plugins/plugin-metadata-snapshot.js"; import { validateJsonSchemaValue } from "../plugins/schema-validator.js"; import { hasKind } from "../plugins/slots.js"; import { collectLegacySecretRefEnvMarkerCandidates } from "../secrets/legacy-secretref-env-marker.js"; @@ -821,12 +823,11 @@ function validateConfigObjectWithPluginsBase( return registryInfo; } const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)); - const registry = loadPluginManifestRegistryForPluginRegistry({ + const registry = loadPluginMetadataSnapshot({ config, workspaceDir: workspaceDir ?? undefined, - env: opts.env, - includeDisabled: true, - }); + env: opts.env ?? process.env, + }).manifestRegistry; registryInfo = { registry }; return registryInfo; }; diff --git a/src/config/zod-schema.providers.ts b/src/config/zod-schema.providers.ts index 8d7da10a7dc..70dd07350a5 100644 --- a/src/config/zod-schema.providers.ts +++ b/src/config/zod-schema.providers.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { collectBundledChannelConfigs } from "../plugins/bundled-channel-config-metadata.js"; import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; import type { PluginManifest } from "../plugins/manifest.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import type { ChannelsConfig } from "./types.channels.js"; import { ChannelHeartbeatVisibilitySchema } from "./zod-schema.channels.js"; import { ContextVisibilityModeSchema, GroupPolicySchema } from "./zod-schema.core.js"; @@ -87,9 +87,7 @@ function normalizeBundledChannelConfigs( let next: ChannelsConfig | undefined; let registry: PluginManifestRegistry | undefined; for (const channelId of Object.keys(value)) { - registry ??= loadPluginManifestRegistryForPluginRegistry({ - includeDisabled: true, - }); + registry ??= loadPluginMetadataSnapshot({ config: {}, env: process.env }).manifestRegistry; const runtimeSchema = getDirectChannelRuntimeSchema(channelId, registry); if (!runtimeSchema) { continue; diff --git a/src/plugins/provider-auth-choices.test.ts b/src/plugins/provider-auth-choices.test.ts index 7c66aab3a15..c78be16368d 100644 --- a/src/plugins/provider-auth-choices.test.ts +++ b/src/plugins/provider-auth-choices.test.ts @@ -4,6 +4,7 @@ const pluginRegistryMocks = vi.hoisted(() => ({ loadPluginManifestRegistryForInstalledIndex: vi.fn(), loadPluginManifestRegistryForPluginRegistry: vi.fn(), loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })), + loadPluginMetadataSnapshot: vi.fn(), })); vi.mock("./manifest-registry-installed.js", () => ({ @@ -23,6 +24,14 @@ vi.mock("../plugins/plugin-registry.js", () => ({ loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot, })); +vi.mock("./plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot, +})); + +vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot, +})); + vi.resetModules(); const { @@ -53,6 +62,10 @@ function setManifestPlugins(plugins: Array>) { pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry.mockReturnValue({ plugins, }); + pluginRegistryMocks.loadPluginMetadataSnapshot.mockReturnValue({ + plugins, + manifestRegistry: { plugins }, + }); } function expectResolvedProviderAuthChoices(params: { @@ -88,6 +101,11 @@ describe("provider auth choice manifest helpers", () => { }); pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset(); pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] }); + pluginRegistryMocks.loadPluginMetadataSnapshot.mockReset(); + pluginRegistryMocks.loadPluginMetadataSnapshot.mockReturnValue({ + plugins: [], + manifestRegistry: { plugins: [] }, + }); resetProviderAuthAliasMapCacheForTest(); }); @@ -572,7 +590,7 @@ describe("provider auth choice manifest helpers", () => { ]); const resolvedProviderId = resolveProviderIdForAuth("fixture-provider-plan"); - expect(pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry).toHaveBeenCalled(); + expect(pluginRegistryMocks.loadPluginMetadataSnapshot).toHaveBeenCalled(); expect(resolvedProviderId).toBe("fixture-provider"); expect( resolveManifestProviderApiKeyChoice({ diff --git a/src/plugins/provider-auth-choices.ts b/src/plugins/provider-auth-choices.ts index 0a7055d9e37..901c5d33fba 100644 --- a/src/plugins/provider-auth-choices.ts +++ b/src/plugins/provider-auth-choices.ts @@ -3,8 +3,8 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { sanitizeForLog } from "../terminal/ansi.js"; import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; +import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js"; import type { PluginOrigin } from "./plugin-origin.types.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js"; export type ProviderAuthChoiceMetadata = { pluginId: string; @@ -180,12 +180,12 @@ function resolveManifestProviderAuthChoiceCandidates(params?: { env?: NodeJS.ProcessEnv; includeUntrustedWorkspacePlugins?: boolean; }): ProviderAuthChoiceCandidate[] { - const registry = loadPluginManifestRegistryForPluginRegistry({ - config: params?.config, + const metadataSnapshot = loadPluginMetadataSnapshot({ + config: params?.config ?? {}, workspaceDir: params?.workspaceDir, - env: params?.env, - includeDisabled: true, + env: params?.env ?? process.env, }); + const registry = metadataSnapshot.manifestRegistry; const normalizedConfig = normalizePluginsConfig(params?.config?.plugins); return registry.plugins.flatMap((plugin) => { if ( diff --git a/src/plugins/web-provider-resolution-candidates.test.ts b/src/plugins/web-provider-resolution-candidates.test.ts index 20ee5604902..a9ad1fb9e63 100644 --- a/src/plugins/web-provider-resolution-candidates.test.ts +++ b/src/plugins/web-provider-resolution-candidates.test.ts @@ -3,6 +3,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ loadPluginRegistrySnapshot: vi.fn(), loadPluginManifestRegistryForInstalledIndex: vi.fn(), + loadPluginMetadataSnapshot: vi.fn(), })); vi.mock("./plugin-registry.js", () => ({ @@ -19,6 +20,10 @@ vi.mock("./manifest-registry-installed.js", () => ({ mocks.loadPluginManifestRegistryForInstalledIndex(...args), })); +vi.mock("./plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: (...args: unknown[]) => mocks.loadPluginMetadataSnapshot(...args), +})); + let resolveManifestDeclaredWebProviderCandidatePluginIds: typeof import("./web-provider-resolution-shared.js").resolveManifestDeclaredWebProviderCandidatePluginIds; describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { @@ -52,6 +57,10 @@ describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { ], diagnostics: [], }); + mocks.loadPluginMetadataSnapshot.mockReset(); + mocks.loadPluginMetadataSnapshot.mockImplementation((...args: unknown[]) => ({ + plugins: mocks.loadPluginManifestRegistryForInstalledIndex(...args).plugins, + })); }); it("treats explicit empty plugin scopes as scoped-empty", () => { @@ -73,11 +82,7 @@ describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { onlyPluginIds: ["missing-plugin"], }), ).toEqual([]); - expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledWith( - expect.objectContaining({ - pluginIds: ["missing-plugin"], - }), - ); + expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledOnce(); }); it("keeps origin filters with no declared web candidates scoped-empty", () => { @@ -110,7 +115,7 @@ describe("resolveManifestDeclaredWebProviderCandidatePluginIds", () => { configKey: "webSearch", }), ).toEqual(["alpha", "beta"]); - expect(mocks.loadPluginRegistrySnapshot).toHaveBeenCalledTimes(1); + expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledTimes(1); expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/web-provider-resolution-shared.ts b/src/plugins/web-provider-resolution-shared.ts index 41b5a3efc28..38f5d43ebc7 100644 --- a/src/plugins/web-provider-resolution-shared.ts +++ b/src/plugins/web-provider-resolution-shared.ts @@ -1,7 +1,7 @@ import { resolveBundledPluginCompatibleLoadValues } from "./activation-context.js"; import type { PluginLoadOptions } from "./loader.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js"; import { createPluginIdScopeSet, normalizePluginIdScope } from "./plugin-scope.js"; export type WebProviderContract = "webSearchProviders" | "webFetchProviders"; @@ -66,13 +66,13 @@ function loadInstalledWebProviderManifestRecords(params: { env?: PluginLoadOptions["env"]; pluginIds?: readonly string[]; }): readonly PluginManifestRecord[] { - return loadPluginManifestRegistryForPluginRegistry({ - config: params.config, + const records = loadPluginMetadataSnapshot({ + config: params.config ?? {}, workspaceDir: params.workspaceDir, - env: params.env, - pluginIds: params.pluginIds, - includeDisabled: true, + env: params.env ?? process.env, }).plugins; + const pluginIdSet = createPluginIdScopeSet(params.pluginIds); + return pluginIdSet ? records.filter((plugin) => pluginIdSet.has(plugin.id)) : records; } export function resolveManifestDeclaredWebProviderCandidatePluginIds(params: { diff --git a/src/plugins/web-search-credential-presence.ts b/src/plugins/web-search-credential-presence.ts index 2fbc5f7ceff..f409a3bf09a 100644 --- a/src/plugins/web-search-credential-presence.ts +++ b/src/plugins/web-search-credential-presence.ts @@ -1,6 +1,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { PluginManifestRecord } from "./manifest-registry.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js"; function hasConfiguredCredentialValue(value: unknown): boolean { if (typeof value === "string") { @@ -42,10 +42,9 @@ function hasManifestWebSearchEnvCredentialCandidate(params: { if (!env) { return false; } - return loadPluginManifestRegistryForPluginRegistry({ + return loadPluginMetadataSnapshot({ config: params.config, env, - includeDisabled: true, }).plugins.some((plugin) => { if (params.origin && plugin.origin !== params.origin) { return false; diff --git a/src/secrets/channel-env-vars.dynamic.test.ts b/src/secrets/channel-env-vars.dynamic.test.ts index fdac309be96..cdf8be529a5 100644 --- a/src/secrets/channel-env-vars.dynamic.test.ts +++ b/src/secrets/channel-env-vars.dynamic.test.ts @@ -18,6 +18,10 @@ const pluginRegistryMocks = vi.hoisted(() => { loadPluginManifestRegistryForInstalledIndex: loadManifestRegistry, loadPluginManifestRegistryForPluginRegistry: loadManifestRegistry, loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })), + loadPluginMetadataSnapshot: vi.fn(() => ({ + plugins: loadManifestRegistry().plugins, + manifestRegistry: loadManifestRegistry(), + })), }; }); @@ -32,6 +36,10 @@ vi.mock("../plugins/plugin-registry.js", () => ({ loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot, })); +vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ + loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot, +})); + describe("channel env vars dynamic manifest metadata", () => { beforeEach(() => { vi.resetModules(); @@ -42,6 +50,7 @@ describe("channel env vars dynamic manifest metadata", () => { }); pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset(); pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] }); + pluginRegistryMocks.loadPluginMetadataSnapshot.mockClear(); }); it("includes later-installed plugin env vars without a bundled generated map", async () => { diff --git a/src/secrets/channel-env-vars.ts b/src/secrets/channel-env-vars.ts index 59b8a8983b4..0ca3f696e54 100644 --- a/src/secrets/channel-env-vars.ts +++ b/src/secrets/channel-env-vars.ts @@ -1,5 +1,5 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; export { isSafeChannelEnvVarTriggerName } from "./channel-env-var-names.js"; type ChannelEnvVarLookupParams = { @@ -32,14 +32,13 @@ function appendUniqueEnvVarCandidates( export function resolveChannelEnvVars( params?: ChannelEnvVarLookupParams, ): Record { - const registry = loadPluginManifestRegistryForPluginRegistry({ - config: params?.config, + const snapshot = loadPluginMetadataSnapshot({ + config: params?.config ?? {}, workspaceDir: params?.workspaceDir, - env: params?.env, - includeDisabled: true, + env: params?.env ?? process.env, }); const candidates: Record = {}; - for (const plugin of registry.plugins) { + for (const plugin of snapshot.plugins) { if (!plugin.channelEnvVars) { continue; } diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index d9d8e3b6a40..52cdd32a586 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -1,5 +1,5 @@ import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; -import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js"; +import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { loadBundledChannelSecretContractApi } from "./channel-contract-api.js"; import type { SecretTargetRegistryEntry } from "./target-registry-types.js"; @@ -47,13 +47,11 @@ function hasWebProviderContract( return (plugin.contracts?.[contract]?.length ?? 0) > 0; } -function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] { +function listBundledWebProviderSecretTargetRegistryEntries( + bundledPlugins: readonly PluginManifestRecord[], +): SecretTargetRegistryEntry[] { const entries: SecretTargetRegistryEntry[] = []; - for (const record of loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true }) - .plugins) { - if (record.origin !== "bundled") { - continue; - } + for (const record of bundledPlugins) { for (const config of WEB_PROVIDER_SECRET_CONFIGS) { if ( hasWebProviderContract(record, config.contract) && @@ -66,14 +64,12 @@ function listBundledWebProviderSecretTargetRegistryEntries(): SecretTargetRegist return entries.toSorted((left, right) => left.id.localeCompare(right.id)); } -function listBundledPluginConfigSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] { +function listBundledPluginConfigSecretTargetRegistryEntries( + bundledPlugins: readonly PluginManifestRecord[], +): SecretTargetRegistryEntry[] { const entries: SecretTargetRegistryEntry[] = []; const seen = new Set(); - for (const record of loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true }) - .plugins) { - if (record.origin !== "bundled") { - continue; - } + for (const record of bundledPlugins) { const secretInputs = record.configContracts?.secretInputs?.paths ?? []; for (const secretInput of secretInputs) { const entry = createPluginOpenClawConfigSecretTargetEntry(record.id, secretInput.path); @@ -88,14 +84,12 @@ function listBundledPluginConfigSecretTargetRegistryEntries(): SecretTargetRegis return entries.toSorted((left, right) => left.id.localeCompare(right.id)); } -function listChannelSecretTargetRegistryEntries(): SecretTargetRegistryEntry[] { +function listChannelSecretTargetRegistryEntries( + bundledPlugins: readonly PluginManifestRecord[], +): SecretTargetRegistryEntry[] { const entries: SecretTargetRegistryEntry[] = []; - for (const record of loadPluginManifestRegistryForPluginRegistry({ includeDisabled: true }) - .plugins) { - if (record.origin !== "bundled") { - continue; - } + for (const record of bundledPlugins) { const channelIds = record.channels; if (channelIds.length === 0) { continue; @@ -455,11 +449,15 @@ export function getSecretTargetRegistry(): SecretTargetRegistryEntry[] { if (cachedSecretTargetRegistry) { return cachedSecretTargetRegistry; } + const bundledPlugins = loadPluginMetadataSnapshot({ + config: {}, + env: process.env, + }).plugins.filter((record) => record.origin === "bundled"); cachedSecretTargetRegistry = [ ...CORE_SECRET_TARGET_REGISTRY, - ...listBundledWebProviderSecretTargetRegistryEntries(), - ...listBundledPluginConfigSecretTargetRegistryEntries(), - ...listChannelSecretTargetRegistryEntries(), + ...listBundledWebProviderSecretTargetRegistryEntries(bundledPlugins), + ...listBundledPluginConfigSecretTargetRegistryEntries(bundledPlugins), + ...listChannelSecretTargetRegistryEntries(bundledPlugins), ]; return cachedSecretTargetRegistry; }