Skip to content

Commit 88d8d6a

Browse files
authored
perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts (#84283)
* perf(plugins): extend discovery threading to loader, manifest registry, installed-index, and config contracts Follow-up to #75451. Threads optional discovery?: PluginDiscoveryResult through the remaining helpers that still call discoverOpenClawPlugins internally during startup: - loadOpenClawPlugins / loadOpenClawPluginCliRegistry (src/plugins/loader.ts): add discovery? to PluginLoadOptions and consult it before falling back to an internal scan at both call sites. - loadPluginManifestRegistry (src/plugins/manifest-registry.ts): accept discovery? as a more ergonomic alternative to the existing candidates? / diagnostics? pair; candidates? still wins when both are supplied. - resolveInstalledPluginIndexRegistry (src/plugins/installed-plugin-index-registry.ts): add discovery? to LoadInstalledPluginIndexParams and use it when candidates aren't supplied. - resolvePluginConfigContractsById (src/plugins/config-contracts.ts): add discovery? and thread it into the bundled-fallback discovery call. Add discovery-threading.test.ts asserting each entry point skips its internal discoverOpenClawPlugins call when discovery is supplied, calls it when nothing is supplied, and prefers explicit candidates over discovery when both are present (6 tests, all pass). discoverOpenClawPlugins remains stateless; sharing is function-scoped per src/plugins/CLAUDE.md guidance. Backward compatible: every change is additive (new optional param). * perf(plugins): drop verbose JSDoc from discovery? params
1 parent b9a2c11 commit 88d8d6a

6 files changed

Lines changed: 107 additions & 24 deletions

src/plugins/config-contracts.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { OpenClawConfig } from "../config/types.openclaw.js";
22
import { isRecord } from "../utils.js";
3-
import { discoverOpenClawPlugins } from "./discovery.js";
3+
import { discoverOpenClawPlugins, type PluginDiscoveryResult } from "./discovery.js";
44
import { loadPluginManifestRegistry } from "./manifest-registry.js";
55
import type { PluginManifestConfigContracts } from "./manifest.js";
66
import type { PluginOrigin } from "./plugin-origin.types.js";
@@ -114,6 +114,7 @@ export function resolvePluginConfigContractsById(params: {
114114
fallbackToBundledMetadataForResolvedBundled?: boolean;
115115
fallbackBundledPluginIds?: readonly string[];
116116
pluginIds: readonly string[];
117+
discovery?: PluginDiscoveryResult;
117118
}): ReadonlyMap<string, PluginConfigContractMetadata> {
118119
const matches = new Map<string, PluginConfigContractMetadata>();
119120
const pluginIds = [
@@ -132,10 +133,12 @@ export function resolvePluginConfigContractsById(params: {
132133
if (bundledContractFallbacks.has(pluginId)) {
133134
return bundledContractFallbacks.get(pluginId);
134135
}
135-
const discovery = discoverOpenClawPlugins({
136-
workspaceDir: params.workspaceDir,
137-
env: params.env,
138-
});
136+
const discovery =
137+
params.discovery ??
138+
discoverOpenClawPlugins({
139+
workspaceDir: params.workspaceDir,
140+
env: params.env,
141+
});
139142
const registry = loadPluginManifestRegistry({
140143
config: params.config,
141144
workspaceDir: params.workspaceDir,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import type { PluginDiscoveryResult } from "./discovery.js";
3+
4+
const discoverOpenClawPluginsMock = vi.fn();
5+
6+
vi.mock("./discovery.js", async (importOriginal) => {
7+
const actual = await importOriginal<typeof import("./discovery.js")>();
8+
return {
9+
...actual,
10+
discoverOpenClawPlugins: (...args: unknown[]) => discoverOpenClawPluginsMock(...args),
11+
};
12+
});
13+
14+
const { loadPluginManifestRegistry } = await import("./manifest-registry.js");
15+
const { resolveInstalledPluginIndexRegistry } =
16+
await import("./installed-plugin-index-registry.js");
17+
18+
const emptyDiscovery: PluginDiscoveryResult = { candidates: [], diagnostics: [] };
19+
20+
describe("discovery threading", () => {
21+
beforeEach(() => {
22+
discoverOpenClawPluginsMock.mockReset();
23+
discoverOpenClawPluginsMock.mockReturnValue(emptyDiscovery);
24+
});
25+
26+
describe("loadPluginManifestRegistry", () => {
27+
it("skips internal discoverOpenClawPlugins when discovery is supplied", () => {
28+
loadPluginManifestRegistry({ discovery: emptyDiscovery });
29+
expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled();
30+
});
31+
32+
it("calls discoverOpenClawPlugins when neither discovery nor candidates supplied", () => {
33+
loadPluginManifestRegistry({});
34+
expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1);
35+
});
36+
37+
it("prefers explicit candidates over discovery when both are supplied", () => {
38+
loadPluginManifestRegistry({ candidates: [], diagnostics: [], discovery: emptyDiscovery });
39+
expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled();
40+
});
41+
});
42+
43+
describe("resolveInstalledPluginIndexRegistry", () => {
44+
it("skips internal discoverOpenClawPlugins when discovery is supplied", () => {
45+
resolveInstalledPluginIndexRegistry({ discovery: emptyDiscovery, installRecords: {} });
46+
expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled();
47+
});
48+
49+
it("calls discoverOpenClawPlugins when neither discovery nor candidates supplied", () => {
50+
resolveInstalledPluginIndexRegistry({ installRecords: {} });
51+
expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1);
52+
});
53+
54+
it("prefers explicit candidates over discovery when both are supplied", () => {
55+
resolveInstalledPluginIndexRegistry({
56+
candidates: [],
57+
discovery: emptyDiscovery,
58+
installRecords: {},
59+
});
60+
expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled();
61+
});
62+
});
63+
});

src/plugins/installed-plugin-index-registry.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ export function resolveInstalledPluginIndexRegistry(params: LoadInstalledPluginI
2525
const normalized = normalizePluginsConfig(params.config?.plugins);
2626
const installRecords =
2727
params.installRecords ?? loadInstalledPluginIndexInstallRecordsSync({ env: params.env });
28-
const discovery = discoverOpenClawPlugins({
29-
workspaceDir: params.workspaceDir,
30-
extraPaths: normalized.loadPaths,
31-
env: params.env,
32-
installRecords,
33-
});
28+
const discovery =
29+
params.discovery ??
30+
discoverOpenClawPlugins({
31+
workspaceDir: params.workspaceDir,
32+
extraPaths: normalized.loadPaths,
33+
env: params.env,
34+
installRecords,
35+
});
3436
return {
3537
candidates: discovery.candidates,
3638
registry: loadPluginManifestRegistry({

src/plugins/installed-plugin-index-types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { OpenClawConfig } from "../config/types.js";
22
import type { PluginInstallRecord } from "../config/types.plugins.js";
33
import type { PluginCompatCode } from "./compat/registry.js";
4-
import type { PluginCandidate } from "./discovery.js";
4+
import type { PluginCandidate, PluginDiscoveryResult } from "./discovery.js";
55
import type { PluginInstallSourceInfo } from "./install-source-info.js";
66
import type { InstalledPluginFileSignature } from "./installed-plugin-index-hash.js";
77
import type { PluginManifestRecord } from "./manifest-registry.js";
@@ -130,6 +130,7 @@ export type LoadInstalledPluginIndexParams = {
130130
installRecords?: Record<string, PluginInstallRecord>;
131131
candidates?: PluginCandidate[];
132132
diagnostics?: PluginDiagnostic[];
133+
discovery?: PluginDiscoveryResult;
133134
now?: () => Date;
134135
};
135136

src/plugins/loader.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ import {
5050
type NormalizedPluginsConfig,
5151
} from "./config-state.js";
5252
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
53-
import { discoverOpenClawPlugins, type PluginCandidate } from "./discovery.js";
53+
import {
54+
discoverOpenClawPlugins,
55+
type PluginCandidate,
56+
type PluginDiscoveryResult,
57+
} from "./discovery.js";
5458
import { shouldRejectHardlinkedPluginFiles } from "./hardlink-policy.js";
5559
import { getGlobalHookRunner, initializeGlobalHookRunner } from "./hook-runner-global.js";
5660
import { toSafeImportPath } from "./import-specifier.js";
@@ -198,6 +202,7 @@ export type PluginLoadOptions = {
198202
loadModules?: boolean;
199203
throwOnLoadError?: boolean;
200204
manifestRegistry?: PluginManifestRegistry;
205+
discovery?: PluginDiscoveryResult;
201206
};
202207

203208
function detailPluginStartupTrace(
@@ -1690,12 +1695,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
16901695
candidates: createPluginCandidatesFromManifestRegistry(suppliedManifestRegistry),
16911696
diagnostics: [] as PluginDiagnostic[],
16921697
}
1693-
: discoverOpenClawPlugins({
1698+
: (options.discovery ??
1699+
discoverOpenClawPlugins({
16941700
workspaceDir: options.workspaceDir,
16951701
extraPaths: normalized.loadPaths,
16961702
env,
16971703
installRecords,
1698-
});
1704+
}));
16991705
const manifestRegistry =
17001706
suppliedManifestRegistry ??
17011707
loadPluginManifestRegistry({
@@ -2559,12 +2565,14 @@ export async function loadOpenClawPluginCliRegistry(
25592565
activateGlobalSideEffects: false,
25602566
});
25612567

2562-
const discovery = discoverOpenClawPlugins({
2563-
workspaceDir: options.workspaceDir,
2564-
extraPaths: normalized.loadPaths,
2565-
env,
2566-
installRecords,
2567-
});
2568+
const discovery =
2569+
options.discovery ??
2570+
discoverOpenClawPlugins({
2571+
workspaceDir: options.workspaceDir,
2572+
extraPaths: normalized.loadPaths,
2573+
env,
2574+
installRecords,
2575+
});
25682576
const manifestRegistry = loadPluginManifestRegistry({
25692577
config: cfg,
25702578
workspaceDir: options.workspaceDir,

src/plugins/manifest-registry.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { resolveUserPath } from "../utils.js";
1010
import { resolveCompatibilityHostVersion } from "../version.js";
1111
import { loadBundleManifest } from "./bundle-manifest.js";
1212
import { normalizePluginsConfigWithResolver } from "./config-policy.js";
13-
import { discoverOpenClawPlugins, type PluginCandidate } from "./discovery.js";
13+
import {
14+
discoverOpenClawPlugins,
15+
type PluginCandidate,
16+
type PluginDiscoveryResult,
17+
} from "./discovery.js";
1418
import { shouldRejectHardlinkedPluginFiles } from "./hardlink-policy.js";
1519
import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader.js";
1620
import type { PluginManifestCommandAlias } from "./manifest-command-aliases.js";
@@ -916,6 +920,7 @@ export function loadPluginManifestRegistry(
916920
diagnostics?: PluginDiagnostic[];
917921
installRecords?: Record<string, PluginInstallRecord>;
918922
bundledChannelConfigCollector?: BundledChannelConfigCollector;
923+
discovery?: PluginDiscoveryResult;
919924
} = {},
920925
): PluginManifestRegistry {
921926
const config = params.config ?? {};
@@ -936,12 +941,13 @@ export function loadPluginManifestRegistry(
936941
candidates: params.candidates,
937942
diagnostics: params.diagnostics ?? [],
938943
}
939-
: discoverOpenClawPlugins({
944+
: (params.discovery ??
945+
discoverOpenClawPlugins({
940946
workspaceDir: params.workspaceDir,
941947
extraPaths: normalized.loadPaths,
942948
env,
943949
installRecords: getInstallRecords(),
944-
});
950+
}));
945951
const diagnostics: PluginDiagnostic[] = [...discovery.diagnostics];
946952
const candidates: PluginCandidate[] = discovery.candidates;
947953
const records: PluginManifestRecord[] = [];

0 commit comments

Comments
 (0)