Skip to content

Commit 3e15090

Browse files
committed
refactor: route plugin metadata consumers through snapshots
1 parent 06b5282 commit 3e15090

16 files changed

Lines changed: 156 additions & 110 deletions

src/agents/pi-project-settings-snapshot.ts

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {
1010
normalizePluginsConfigWithResolver,
1111
resolveEffectivePluginActivationState,
1212
} from "../plugins/config-policy.js";
13-
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
14-
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
13+
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
1514
import { isRecord } from "../utils.js";
1615
import { loadEmbeddedPiMcpConfig } from "./embedded-pi-mcp.js";
1716

@@ -69,33 +68,6 @@ function loadBundleSettingsFile(params: {
6968
}
7069
}
7170

72-
function buildRegistryPluginIdAliases(
73-
registry: PluginManifestRegistry,
74-
): Readonly<Record<string, string>> {
75-
return Object.fromEntries(
76-
registry.plugins
77-
.flatMap((record) => [
78-
...(record.providers ?? [])
79-
.filter((providerId) => providerId !== record.id)
80-
.map((providerId) => [providerId, record.id] as const),
81-
...(record.legacyPluginIds ?? []).map(
82-
(legacyPluginId) => [legacyPluginId, record.id] as const,
83-
),
84-
])
85-
.toSorted(([left], [right]) => left.localeCompare(right)),
86-
);
87-
}
88-
89-
function createRegistryPluginIdNormalizer(
90-
registry: PluginManifestRegistry,
91-
): (id: string) => string {
92-
const aliases = buildRegistryPluginIdAliases(registry);
93-
return (id: string) => {
94-
const trimmed = id.trim();
95-
return aliases[trimmed] ?? trimmed;
96-
};
97-
}
98-
9971
export function loadEnabledBundlePiSettingsSnapshot(params: {
10072
cwd: string;
10173
cfg?: OpenClawConfig;
@@ -104,18 +76,19 @@ export function loadEnabledBundlePiSettingsSnapshot(params: {
10476
if (!workspaceDir) {
10577
return {};
10678
}
107-
const registry = loadPluginManifestRegistryForPluginRegistry({
79+
const metadataSnapshot = loadPluginMetadataSnapshot({
10880
workspaceDir,
109-
config: params.cfg,
110-
includeDisabled: true,
81+
config: params.cfg ?? {},
82+
env: process.env,
11183
});
84+
const registry = metadataSnapshot.manifestRegistry;
11285
if (registry.plugins.length === 0) {
11386
return {};
11487
}
11588

11689
const normalizedPlugins = normalizePluginsConfigWithResolver(
11790
params.cfg?.plugins,
118-
createRegistryPluginIdNormalizer(registry),
91+
metadataSnapshot.normalizePluginId,
11992
);
12093
let snapshot: PiSettingsSnapshot = {};
12194

src/agents/pi-project-settings.bundle.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,42 @@ vi.mock("../plugins/plugin-registry.js", async () => {
7979
};
8080
});
8181

82+
vi.mock("../plugins/plugin-metadata-snapshot.js", async () => {
83+
const fs = await import("node:fs");
84+
const path = await import("node:path");
85+
const loadRegistry = (params: { workspaceDir?: string }) => {
86+
const rootDir = path.join(
87+
params.workspaceDir ?? "",
88+
".openclaw",
89+
"extensions",
90+
"claude-bundle",
91+
);
92+
if (!fs.existsSync(path.join(rootDir, ".claude-plugin", "plugin.json"))) {
93+
return { plugins: [], diagnostics: [] };
94+
}
95+
const resolvedRootDir = fs.realpathSync(rootDir);
96+
return {
97+
diagnostics: [],
98+
plugins: [
99+
{
100+
id: "claude-bundle",
101+
origin: "workspace",
102+
format: "bundle",
103+
bundleFormat: "claude",
104+
settingsFiles: ["settings.json"],
105+
rootDir: resolvedRootDir,
106+
},
107+
],
108+
};
109+
};
110+
return {
111+
loadPluginMetadataSnapshot: (params: { workspaceDir?: string }) => ({
112+
manifestRegistry: loadRegistry(params),
113+
normalizePluginId: (id: string) => id.trim(),
114+
}),
115+
};
116+
});
117+
82118
vi.mock("./embedded-pi-mcp.js", async () => {
83119
const fs = await import("node:fs");
84120
const path = await import("node:path");

src/config/plugin-auto-enable.shared.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type PluginManifestRecord,
1010
type PluginManifestRegistry,
1111
} from "../plugins/manifest-registry.js";
12-
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
12+
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
1313
import { resolveOwningPluginIdsForModelRef } from "../plugins/providers.js";
1414
import { resolvePluginSetupAutoEnableReasons } from "../plugins/setup-registry.js";
1515
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
@@ -220,16 +220,13 @@ function resolvePluginsWithOwnedToolConfig(
220220

221221
function resolvePluginIdForConfiguredWebFetchProvider(
222222
providerId: string | undefined,
223-
env: NodeJS.ProcessEnv,
223+
registry: PluginManifestRegistry,
224224
): string | undefined {
225225
const normalizedProviderId = normalizeOptionalLowercaseString(providerId);
226226
if (!normalizedProviderId) {
227227
return undefined;
228228
}
229-
return loadPluginManifestRegistryForPluginRegistry({
230-
env,
231-
includeDisabled: true,
232-
}).plugins.find(
229+
return registry.plugins.find(
233230
(plugin) =>
234231
plugin.origin === "bundled" &&
235232
(plugin.contracts?.webFetchProviders ?? []).some(
@@ -623,7 +620,7 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
623620
: undefined;
624621
const webFetchPluginId = resolvePluginIdForConfiguredWebFetchProvider(
625622
webFetchProvider,
626-
params.env,
623+
params.registry,
627624
);
628625
if (webFetchPluginId) {
629626
changes.push({
@@ -869,11 +866,10 @@ export function resolvePluginAutoEnableManifestRegistry(params: {
869866
return (
870867
params.manifestRegistry ??
871868
(configMayNeedPluginManifestRegistry(params.config, params.env)
872-
? loadPluginManifestRegistryForPluginRegistry({
869+
? loadPluginMetadataSnapshot({
873870
config: params.config,
874871
env: params.env,
875-
includeDisabled: true,
876-
})
872+
}).manifestRegistry
877873
: EMPTY_PLUGIN_MANIFEST_REGISTRY)
878874
);
879875
}

src/config/runtime-schema.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ vi.mock("../plugins/plugin-registry.js", () => ({
3434
mockLoadPluginManifestRegistry(...args),
3535
}));
3636

37+
vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({
38+
loadPluginMetadataSnapshot: (...args: unknown[]) => ({
39+
manifestRegistry: mockLoadPluginManifestRegistry(...args),
40+
}),
41+
}));
42+
3743
vi.mock("../plugins/current-plugin-metadata-snapshot.js", () => ({
3844
getCurrentPluginMetadataSnapshot: (...args: unknown[]) =>
3945
mockGetCurrentPluginMetadataSnapshot(...args),
@@ -309,7 +315,7 @@ describe("loadGatewayRuntimeConfigSchema", () => {
309315

310316
expect(mockLoadPluginManifestRegistry).toHaveBeenCalledTimes(3);
311317
for (const call of mockLoadPluginManifestRegistry.mock.calls) {
312-
expect(call[0]).toMatchObject({ includeDisabled: true });
318+
expect(call[0]).toHaveProperty("config");
313319
expect(call[0]).not.toHaveProperty("bundledChannelConfigCollector");
314320
}
315321
expect(getActivePluginRegistry()).toBe(activeRegistry);

src/config/runtime-schema.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
22
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
3-
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
3+
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
44
import {
55
collectChannelSchemaMetadata,
66
collectPluginSchemaMetadata,
@@ -15,14 +15,11 @@ function loadManifestRegistry(config: OpenClawConfig, env?: NodeJS.ProcessEnv) {
1515
if (currentSnapshot) {
1616
return currentSnapshot.manifestRegistry;
1717
}
18-
return loadPluginManifestRegistryForPluginRegistry({
18+
return loadPluginMetadataSnapshot({
1919
config,
20-
// Bundled channel schemas are already generated into the base schema; avoid
21-
// loading plugin config-schema modules on every config.get/config.schema.
22-
env,
20+
env: env ?? process.env,
2321
workspaceDir,
24-
includeDisabled: true,
25-
});
22+
}).manifestRegistry;
2623
}
2724

2825
export function loadGatewayRuntimeConfigSchema(): ConfigSchemaResponse {

src/config/validation.channel-metadata.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,23 @@ vi.mock("../plugins/plugin-registry.js", () => ({
120120
loadPluginManifestRegistryForPluginRegistry: () => mockLoadPluginManifestRegistry(),
121121
}));
122122

123+
vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({
124+
loadPluginMetadataSnapshot: () => ({
125+
manifestRegistry: mockLoadPluginManifestRegistry(),
126+
}),
127+
}));
128+
123129
vi.mock("../plugins/doctor-contract-registry.js", () => ({
124130
collectRelevantDoctorPluginIds: () => [],
125131
listPluginDoctorLegacyConfigRules: () => [],
126132
applyPluginDoctorCompatibilityMigrations: () => ({ next: null, changes: [] }),
127133
}));
128134

135+
vi.mock("../secrets/target-registry-data.js", () => ({
136+
getCoreSecretTargetRegistry: () => [],
137+
getSecretTargetRegistry: () => [],
138+
}));
139+
129140
vi.mock("../channels/plugins/legacy-config.js", () => ({
130141
collectChannelLegacyConfigRules: () => [],
131142
}));

src/config/validation.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
import { loadInstalledPluginIndexInstallRecordsSync } from "../plugins/installed-plugin-index-record-reader.js";
1717
import { resolveManifestCommandAliasOwnerInRegistry } from "../plugins/manifest-command-aliases.js";
1818
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
19-
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
20-
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
19+
import {
20+
loadPluginMetadataSnapshot,
21+
type PluginMetadataSnapshot,
22+
} from "../plugins/plugin-metadata-snapshot.js";
2123
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
2224
import { hasKind } from "../plugins/slots.js";
2325
import { collectLegacySecretRefEnvMarkerCandidates } from "../secrets/legacy-secretref-env-marker.js";
@@ -821,12 +823,11 @@ function validateConfigObjectWithPluginsBase(
821823
return registryInfo;
822824
}
823825
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
824-
const registry = loadPluginManifestRegistryForPluginRegistry({
826+
const registry = loadPluginMetadataSnapshot({
825827
config,
826828
workspaceDir: workspaceDir ?? undefined,
827-
env: opts.env,
828-
includeDisabled: true,
829-
});
829+
env: opts.env ?? process.env,
830+
}).manifestRegistry;
830831
registryInfo = { registry };
831832
return registryInfo;
832833
};

src/config/zod-schema.providers.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from "zod";
22
import { collectBundledChannelConfigs } from "../plugins/bundled-channel-config-metadata.js";
33
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
44
import type { PluginManifest } from "../plugins/manifest.js";
5-
import { loadPluginManifestRegistryForPluginRegistry } from "../plugins/plugin-registry.js";
5+
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
66
import type { ChannelsConfig } from "./types.channels.js";
77
import { ChannelHeartbeatVisibilitySchema } from "./zod-schema.channels.js";
88
import { ContextVisibilityModeSchema, GroupPolicySchema } from "./zod-schema.core.js";
@@ -87,9 +87,7 @@ function normalizeBundledChannelConfigs(
8787
let next: ChannelsConfig | undefined;
8888
let registry: PluginManifestRegistry | undefined;
8989
for (const channelId of Object.keys(value)) {
90-
registry ??= loadPluginManifestRegistryForPluginRegistry({
91-
includeDisabled: true,
92-
});
90+
registry ??= loadPluginMetadataSnapshot({ config: {}, env: process.env }).manifestRegistry;
9391
const runtimeSchema = getDirectChannelRuntimeSchema(channelId, registry);
9492
if (!runtimeSchema) {
9593
continue;

src/plugins/provider-auth-choices.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const pluginRegistryMocks = vi.hoisted(() => ({
44
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
55
loadPluginManifestRegistryForPluginRegistry: vi.fn(),
66
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
7+
loadPluginMetadataSnapshot: vi.fn(),
78
}));
89

910
vi.mock("./manifest-registry-installed.js", () => ({
@@ -23,6 +24,14 @@ vi.mock("../plugins/plugin-registry.js", () => ({
2324
loadPluginRegistrySnapshot: pluginRegistryMocks.loadPluginRegistrySnapshot,
2425
}));
2526

27+
vi.mock("./plugin-metadata-snapshot.js", () => ({
28+
loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot,
29+
}));
30+
31+
vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({
32+
loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot,
33+
}));
34+
2635
vi.resetModules();
2736

2837
const {
@@ -53,6 +62,10 @@ function setManifestPlugins(plugins: Array<Record<string, unknown>>) {
5362
pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry.mockReturnValue({
5463
plugins,
5564
});
65+
pluginRegistryMocks.loadPluginMetadataSnapshot.mockReturnValue({
66+
plugins,
67+
manifestRegistry: { plugins },
68+
});
5669
}
5770

5871
function expectResolvedProviderAuthChoices(params: {
@@ -88,6 +101,11 @@ describe("provider auth choice manifest helpers", () => {
88101
});
89102
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReset();
90103
pluginRegistryMocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
104+
pluginRegistryMocks.loadPluginMetadataSnapshot.mockReset();
105+
pluginRegistryMocks.loadPluginMetadataSnapshot.mockReturnValue({
106+
plugins: [],
107+
manifestRegistry: { plugins: [] },
108+
});
91109
resetProviderAuthAliasMapCacheForTest();
92110
});
93111

@@ -572,7 +590,7 @@ describe("provider auth choice manifest helpers", () => {
572590
]);
573591

574592
const resolvedProviderId = resolveProviderIdForAuth("fixture-provider-plan");
575-
expect(pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry).toHaveBeenCalled();
593+
expect(pluginRegistryMocks.loadPluginMetadataSnapshot).toHaveBeenCalled();
576594
expect(resolvedProviderId).toBe("fixture-provider");
577595
expect(
578596
resolveManifestProviderApiKeyChoice({

src/plugins/provider-auth-choices.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
33
import { sanitizeForLog } from "../terminal/ansi.js";
44
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
55
import type { PluginManifestRecord } from "./manifest-registry.js";
6+
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
67
import type { PluginOrigin } from "./plugin-origin.types.js";
7-
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
88

99
export type ProviderAuthChoiceMetadata = {
1010
pluginId: string;
@@ -180,12 +180,12 @@ function resolveManifestProviderAuthChoiceCandidates(params?: {
180180
env?: NodeJS.ProcessEnv;
181181
includeUntrustedWorkspacePlugins?: boolean;
182182
}): ProviderAuthChoiceCandidate[] {
183-
const registry = loadPluginManifestRegistryForPluginRegistry({
184-
config: params?.config,
183+
const metadataSnapshot = loadPluginMetadataSnapshot({
184+
config: params?.config ?? {},
185185
workspaceDir: params?.workspaceDir,
186-
env: params?.env,
187-
includeDisabled: true,
186+
env: params?.env ?? process.env,
188187
});
188+
const registry = metadataSnapshot.manifestRegistry;
189189
const normalizedConfig = normalizePluginsConfig(params?.config?.plugins);
190190
return registry.plugins.flatMap((plugin) => {
191191
if (

0 commit comments

Comments
 (0)