Skip to content

Commit 5893780

Browse files
committed
perf: reuse plugin metadata snapshots
1 parent 2656f13 commit 5893780

28 files changed

Lines changed: 397 additions & 486 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
66

77
### Changes
88

9+
- Gateway/perf: reuse immutable plugin metadata snapshots across startup, config, model, channel, setup, and secret metadata readers so hot paths avoid repeated plugin file stats and manifest registry reloads.
910
- Gateway/perf: lazy-load startup-idle plugin work, core gateway method handlers, and the embedded ACPX runtime so Gateway health and ready signals no longer wait on unused handler trees or ACPX probes.
1011
- Gateway/perf: cache plugin SDK public-surface alias maps and skip irrelevant macOS Linuxbrew PATH probes so Gateway startup avoids repeated filesystem walks and slow missing-directory stats.
1112
- Meeting Notes: add a source-only external meeting-notes plugin and SDK source-provider contract outside the core npm package, with auto-start capture config, manual transcript imports, read-only `openclaw meeting-notes` CLI access, and Discord voice as the first live source.

src/agents/model-catalog.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ let modelSupportsInput: typeof import("./model-catalog.js").modelSupportsInput;
1313
let resetModelCatalogCacheForTest: typeof import("./model-catalog.js").resetModelCatalogCacheForTest;
1414
let augmentCatalogMock: ReturnType<typeof vi.fn>;
1515
let ensureOpenClawModelsJsonMock: ReturnType<typeof vi.fn>;
16-
let currentPluginMetadataSnapshotMock: ReturnType<typeof vi.fn>;
17-
let loadPluginMetadataSnapshotMock: ReturnType<typeof vi.fn>;
16+
let currentPluginMetadataSnapshotMock: ReturnType<typeof vi.fn<(...args: unknown[]) => unknown>>;
17+
let loadPluginMetadataSnapshotMock: ReturnType<typeof vi.fn<(...args: unknown[]) => unknown>>;
1818
let readFileMock: ReturnType<typeof vi.fn>;
1919

2020
vi.mock("./model-suppression.runtime.js", () => ({
@@ -184,13 +184,15 @@ describe("loadModelCatalog", () => {
184184
vi.doMock("../plugins/provider-runtime.runtime.js", () => ({
185185
augmentModelCatalogWithProviderPlugins: vi.fn().mockResolvedValue([]),
186186
}));
187-
currentPluginMetadataSnapshotMock = vi.fn();
188-
loadPluginMetadataSnapshotMock = vi.fn();
187+
currentPluginMetadataSnapshotMock = vi.fn<(...args: unknown[]) => unknown>();
188+
loadPluginMetadataSnapshotMock = vi.fn<(...args: unknown[]) => unknown>();
189189
vi.doMock("../plugins/current-plugin-metadata-snapshot.js", () => ({
190190
getCurrentPluginMetadataSnapshot: currentPluginMetadataSnapshotMock,
191191
}));
192192
vi.doMock("../plugins/plugin-metadata-snapshot.js", () => ({
193193
loadPluginMetadataSnapshot: loadPluginMetadataSnapshotMock,
194+
resolvePluginMetadataSnapshot: (...args: unknown[]) =>
195+
currentPluginMetadataSnapshotMock(...args) ?? loadPluginMetadataSnapshotMock(...args),
194196
}));
195197

196198
({
@@ -215,7 +217,7 @@ describe("loadModelCatalog", () => {
215217
ensureOpenClawModelsJsonMock.mockClear();
216218
augmentCatalogMock.mockClear();
217219
currentPluginMetadataSnapshotMock.mockReset();
218-
currentPluginMetadataSnapshotMock.mockReturnValue(emptyPluginMetadataSnapshot());
220+
currentPluginMetadataSnapshotMock.mockReturnValue(undefined);
219221
loadPluginMetadataSnapshotMock.mockReset();
220222
loadPluginMetadataSnapshotMock.mockReturnValue(emptyPluginMetadataSnapshot());
221223
});
@@ -511,7 +513,7 @@ describe("loadModelCatalog", () => {
511513
});
512514

513515
it("normalizes persisted read-only catalog rows with manifest model id policies", async () => {
514-
currentPluginMetadataSnapshotMock.mockReturnValueOnce(modelIdNormalizationSnapshot());
516+
currentPluginMetadataSnapshotMock.mockReturnValue(modelIdNormalizationSnapshot());
515517
readFileMock.mockResolvedValueOnce(
516518
JSON.stringify({
517519
providers: {

src/agents/model-catalog.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
isManifestPluginAvailableForControlPlane,
1010
loadManifestMetadataSnapshot,
1111
} from "../plugins/manifest-contract-eligibility.js";
12-
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
12+
import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
1313
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
1414
import { augmentModelCatalogWithProviderPlugins } from "../plugins/provider-runtime.runtime.js";
1515
import { createLazyImportLoader } from "../shared/lazy-promise.js";
@@ -137,22 +137,20 @@ export function loadManifestModelCatalog(params: {
137137
fallbackToMetadataScan?: boolean;
138138
metadataSnapshot?: PluginMetadataSnapshot;
139139
}): ModelCatalogEntry[] {
140-
const snapshot =
141-
params.metadataSnapshot ??
142-
getCurrentPluginMetadataSnapshot({
143-
config: params.config,
144-
env: params.env,
145-
...(params.workspaceDir !== undefined ? { workspaceDir: params.workspaceDir } : {}),
146-
...(params.workspaceDir === undefined ? { allowWorkspaceScopedSnapshot: true } : {}),
147-
});
148140
const resolvedSnapshot =
149-
snapshot ??
141+
params.metadataSnapshot ??
150142
(params.fallbackToMetadataScan === false
151-
? undefined
152-
: loadPluginMetadataSnapshot({
143+
? getCurrentPluginMetadataSnapshot({
144+
config: params.config,
145+
env: params.env,
146+
...(params.workspaceDir !== undefined ? { workspaceDir: params.workspaceDir } : {}),
147+
...(params.workspaceDir === undefined ? { allowWorkspaceScopedSnapshot: true } : {}),
148+
})
149+
: resolvePluginMetadataSnapshot({
153150
config: params.config,
154151
...(params.workspaceDir !== undefined ? { workspaceDir: params.workspaceDir } : {}),
155152
env: params.env ?? process.env,
153+
allowWorkspaceScopedCurrent: params.workspaceDir === undefined,
156154
}));
157155
if (!resolvedSnapshot) {
158156
return [];
@@ -362,9 +360,10 @@ function loadReadOnlyStaticModelCatalog(params?: {
362360

363361
const configuredManifestPlugins = hasConfiguredProviderRowsNeedingManifestLookup(cfg)
364362
? (params?.metadataSnapshot?.plugins ??
365-
loadPluginMetadataSnapshot({
363+
resolvePluginMetadataSnapshot({
366364
config: cfg,
367365
env: process.env,
366+
allowWorkspaceScopedCurrent: true,
368367
}).plugins)
369368
: [];
370369
const configuredModels = buildConfiguredModelCatalog({

src/agents/models-config.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import {
88
} from "../config/config.js";
99
import { createConfigRuntimeEnv } from "../config/env-vars.js";
1010
import { privateFileStore } from "../infra/private-file-store.js";
11-
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
1211
import { resolveInstalledManifestRegistryIndexFingerprint } from "../plugins/manifest-registry-installed.js";
1312
import {
14-
loadPluginMetadataSnapshot,
13+
resolvePluginMetadataSnapshot,
1514
type PluginMetadataSnapshot,
1615
} from "../plugins/plugin-metadata-snapshot.js";
1716
import {
@@ -175,14 +174,11 @@ export async function ensureOpenClawModelsJson(
175174
: resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
176175
const pluginMetadataSnapshot =
177176
options.pluginMetadataSnapshot ??
178-
getCurrentPluginMetadataSnapshot({
179-
config: cfg,
180-
...(workspaceDir ? { workspaceDir } : {}),
181-
}) ??
182-
loadPluginMetadataSnapshot({
177+
resolvePluginMetadataSnapshot({
183178
config: cfg,
184179
env: createConfigRuntimeEnv(cfg),
185180
...(workspaceDir ? { workspaceDir } : {}),
181+
allowWorkspaceScopedCurrent: workspaceDir === undefined,
186182
});
187183
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg);
188184
const targetPath = path.join(agentDir, "models.json");

src/agents/skills/plugin-skills.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ vi.mock("../../plugins/plugin-registry.js", () => ({
4343

4444
vi.mock("../../plugins/plugin-metadata-snapshot.js", () => ({
4545
loadPluginMetadataSnapshot: hoisted.loadPluginMetadataSnapshot,
46+
resolvePluginMetadataSnapshot: hoisted.loadPluginMetadataSnapshot,
4647
}));
4748

4849
let resolvePluginSkillDirs: typeof import("./plugin-skills.js").resolvePluginSkillDirs;

src/agents/skills/plugin-skills.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import {
99
resolveEffectivePluginActivationState,
1010
resolveMemorySlotDecision,
1111
} from "../../plugins/config-policy.js";
12-
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
13-
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
12+
import { resolvePluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
1413
import { hasKind } from "../../plugins/slots.js";
1514
import { isPathInsideWithRealpath } from "../../security/scan-paths.js";
1615
import { CONFIG_DIR } from "../../utils.js";
@@ -33,17 +32,12 @@ export function resolvePluginSkillDirs(params: {
3332
return [];
3433
}
3534
const config = params.config ?? {};
36-
const metadataSnapshot =
37-
getCurrentPluginMetadataSnapshot({
38-
config,
39-
env: process.env,
40-
workspaceDir,
41-
}) ??
42-
loadPluginMetadataSnapshot({
43-
workspaceDir,
44-
config,
45-
env: process.env,
46-
});
35+
const metadataSnapshot = resolvePluginMetadataSnapshot({
36+
workspaceDir,
37+
config,
38+
env: process.env,
39+
allowWorkspaceScopedCurrent: true,
40+
});
4741
const registry = metadataSnapshot.manifestRegistry;
4842
if (registry.plugins.length === 0) {
4943
publishPluginSkills([], {

src/agents/tools/manifest-capability-availability.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import type { OpenClawConfig } from "../../config/types.openclaw.js";
22
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
3-
import {
4-
isManifestPluginAvailableForControlPlane,
5-
loadManifestContractSnapshot,
6-
} from "../../plugins/manifest-contract-eligibility.js";
3+
import { isManifestPluginAvailableForControlPlane } from "../../plugins/manifest-contract-eligibility.js";
74
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
85
import {
96
hasNonEmptyManifestEnvCandidate,
107
manifestConfigSignalPasses,
118
manifestPluginSetupProviderEnvVars,
129
manifestProviderBaseUrlGuardPasses,
1310
} from "../../plugins/manifest-tool-availability.js";
14-
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
11+
import { resolvePluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
1512
import type { PluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.types.js";
1613
import { getActivePluginRegistryWorkspaceDirFromState } from "../../plugins/runtime-state.js";
1714
import { listProfilesForProvider } from "../auth-profiles/profile-list.js";
@@ -81,23 +78,12 @@ export function loadCapabilityMetadataSnapshot(params: {
8178
env?: NodeJS.ProcessEnv;
8279
}): Pick<PluginMetadataSnapshot, "index" | "plugins"> {
8380
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
84-
const current = getCurrentPluginMetadataSnapshot({
85-
config: params.config,
81+
return resolvePluginMetadataSnapshot({
82+
config: params.config ?? {},
83+
env: params.env ?? process.env,
8684
...(workspaceDir ? { workspaceDir } : {}),
85+
allowWorkspaceScopedCurrent: workspaceDir === undefined,
8786
});
88-
if (current) {
89-
return current;
90-
}
91-
return workspaceDir
92-
? loadManifestContractSnapshot({
93-
config: params.config,
94-
env: params.env,
95-
workspaceDir,
96-
})
97-
: loadPluginMetadataSnapshot({
98-
config: params.config ?? {},
99-
env: params.env ?? process.env,
100-
});
10187
}
10288

10389
export function hasSnapshotCapabilityAvailability(params: {

src/channels/plugins/read-only-command-defaults.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const loadPluginMetadataSnapshot = vi.hoisted(() => vi.fn());
44

55
vi.mock("../../plugins/plugin-metadata-snapshot.js", () => ({
66
loadPluginMetadataSnapshot,
7+
resolvePluginMetadataSnapshot: loadPluginMetadataSnapshot,
78
}));
89

910
import { resolveReadOnlyChannelCommandDefaults } from "./read-only-command-defaults.js";
@@ -59,6 +60,7 @@ describe("resolveReadOnlyChannelCommandDefaults", () => {
5960
nativeSkillsAutoEnabled: false,
6061
});
6162
expect(loadPluginMetadataSnapshot).toHaveBeenCalledWith({
63+
allowWorkspaceScopedCurrent: true,
6264
config: {},
6365
env,
6466
stateDir: "/state",

src/channels/plugins/read-only-command-defaults.ts

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { OpenClawConfig } from "../../config/types.openclaw.js";
22
import { isBlockedObjectKey } from "../../infra/prototype-keys.js";
3-
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
43
import { isInstalledPluginEnabled } from "../../plugins/installed-plugin-index.js";
54
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
6-
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
5+
import { resolvePluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
76
import { normalizeOptionalString } from "../../shared/string-coerce.js";
87
import type { ChannelPlugin } from "./types.plugin.js";
98

@@ -66,22 +65,13 @@ export function resolveReadOnlyChannelCommandDefaults(
6665
return undefined;
6766
}
6867
const env = options.env ?? process.env;
69-
const snapshot =
70-
options.stateDir === undefined
71-
? getCurrentPluginMetadataSnapshot({
72-
config: options.config,
73-
env,
74-
workspaceDir: options.workspaceDir,
75-
})
76-
: undefined;
77-
const resolvedSnapshot =
78-
snapshot ??
79-
loadPluginMetadataSnapshot({
80-
config: options.config,
81-
stateDir: options.stateDir,
82-
workspaceDir: options.workspaceDir,
83-
env,
84-
});
68+
const resolvedSnapshot = resolvePluginMetadataSnapshot({
69+
config: options.config,
70+
stateDir: options.stateDir,
71+
workspaceDir: options.workspaceDir,
72+
env,
73+
allowWorkspaceScopedCurrent: true,
74+
});
8575
for (const record of resolvedSnapshot.plugins) {
8676
if (!record.channels.includes(normalizedChannelId)) {
8777
continue;

src/channels/plugins/read-only.ts

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ import {
1010
listConfiguredChannelIdsForReadOnlyScope,
1111
resolveDiscoverableScopedChannelPluginIds,
1212
} from "../../plugins/channel-plugin-ids.js";
13-
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
1413
import {
1514
channelPluginIdBelongsToManifest,
1615
resolveSetupChannelRegistration,
1716
} from "../../plugins/loader-channel-setup.js";
1817
import type { PluginManifestRecord } from "../../plugins/manifest-registry.js";
1918
import type { PluginDiagnostic } from "../../plugins/manifest-types.js";
20-
import { loadPluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
19+
import { resolvePluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js";
2120
import {
2221
getCachedPluginModuleLoader,
2322
type PluginModuleLoaderCache,
@@ -759,22 +758,13 @@ export function resolveReadOnlyChannelPluginsForConfig(
759758
): ReadOnlyChannelPluginResolution {
760759
const env = options.env ?? process.env;
761760
const workspaceDir = resolveReadOnlyWorkspaceDir(cfg, options);
762-
const metadataSnapshot =
763-
options.stateDir === undefined
764-
? getCurrentPluginMetadataSnapshot({
765-
config: cfg,
766-
env,
767-
workspaceDir,
768-
})
769-
: undefined;
770-
const manifestRecords =
771-
metadataSnapshot?.plugins ??
772-
loadPluginMetadataSnapshot({
773-
config: cfg,
774-
stateDir: options.stateDir,
775-
workspaceDir,
776-
env,
777-
}).plugins;
761+
const manifestRecords = resolvePluginMetadataSnapshot({
762+
config: cfg,
763+
stateDir: options.stateDir,
764+
workspaceDir,
765+
env,
766+
allowWorkspaceScopedCurrent: true,
767+
}).plugins;
778768
const bundledManifestRecords = listBundledChannelManifestRecords(manifestRecords);
779769
const externalManifestRecords = listExternalChannelManifestRecords(manifestRecords);
780770
const configuredChannelIds = [

0 commit comments

Comments
 (0)