refactor: reuse startup plugin metadata snapshot

This commit is contained in:
Shakker
2026-04-27 16:18:55 +01:00
parent ca4f964547
commit 9de2bc6ffc
8 changed files with 110 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import JSON5 from "json5";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope-config.js";
import { ensureOwnerDisplaySecret } from "../agents/owner-display.js";
import { applyRuntimeLegacyConfigMigrations } from "../commands/doctor/shared/runtime-compat-api.js";
import { loadDotEnv } from "../infra/dotenv.js";
@@ -23,6 +24,10 @@ import {
resolveInstalledPluginIndexRecordsStorePath,
writePersistedInstalledPluginIndexInstallRecordsSync,
} from "../plugins/installed-plugin-index-records.js";
import {
loadPluginMetadataSnapshot,
type PluginMetadataSnapshot,
} from "../plugins/plugin-metadata-snapshot.js";
import { sanitizeTerminalText } from "../terminal/safe-text.js";
import { isRecord } from "../utils.js";
import { VERSION } from "../version.js";
@@ -1166,6 +1171,7 @@ function createConfigFileSnapshot(params: {
issues: ConfigFileSnapshot["issues"];
warnings: ConfigFileSnapshot["warnings"];
legacyIssues: LegacyConfigIssue[];
pluginMetadataSnapshot?: PluginMetadataSnapshot;
}): ConfigFileSnapshot {
const sourceConfig = asResolvedSourceConfig(params.sourceConfig);
const runtimeConfig = asRuntimeConfig(params.runtimeConfig);
@@ -1183,6 +1189,9 @@ function createConfigFileSnapshot(params: {
issues: params.issues,
warnings: params.warnings,
legacyIssues: params.legacyIssues,
...(params.pluginMetadataSnapshot
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
: {}),
};
}
@@ -1745,10 +1754,24 @@ export function createConfigIO(
? hashConfigRaw(installMigration.persistedRootRaw)
: hash;
fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
let pluginMetadataSnapshot: PluginMetadataSnapshot | undefined;
const loadValidationPluginMetadataSnapshot = (config: OpenClawConfig) => {
if (pluginMetadataSnapshot) {
return pluginMetadataSnapshot;
}
const defaultAgentId = resolveDefaultAgentId(config);
pluginMetadataSnapshot = loadPluginMetadataSnapshot({
config,
workspaceDir: resolveAgentWorkspaceDir(config, defaultAgentId),
env: deps.env,
});
return pluginMetadataSnapshot;
};
const validated = await deps.measure("config.snapshot.read.validate", () =>
validateConfigObjectWithPlugins(effectiveConfigRaw, {
env: deps.env,
pluginValidation: overrides.pluginValidation,
loadPluginMetadataSnapshot: loadValidationPluginMetadataSnapshot,
}),
);
if (!validated.ok) {
@@ -1789,6 +1812,7 @@ export function createConfigIO(
issues: [],
warnings: [...validated.warnings, ...envVarWarnings],
legacyIssues: legacyResolution.sourceLegacyIssues,
pluginMetadataSnapshot,
}),
envSnapshotForRestore: readResolution.envSnapshotForRestore,
}),

View File

@@ -1,3 +1,4 @@
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import type {
SilentReplyPolicyShape,
SilentReplyRewriteShape,
@@ -184,4 +185,5 @@ export type ConfigFileSnapshot = {
issues: ConfigValidationIssue[];
warnings: ConfigValidationIssue[];
legacyIssues: LegacyConfigIssue[];
pluginMetadataSnapshot?: PluginMetadataSnapshot;
};

View File

@@ -239,6 +239,7 @@ describe("validateConfigObjectWithPlugins bundled allowlist compatibility", () =
const result = validateConfigObjectWithPlugins(
{
plugins: {
allow: ["opik"],
entries: {
opik: {
enabled: true,
@@ -261,4 +262,30 @@ describe("validateConfigObjectWithPlugins bundled allowlist compatibility", () =
});
}
});
it("loads a plugin metadata snapshot once during plugin validation", () => {
const loadPluginMetadataSnapshot = vi.fn((_config: unknown) => ({
manifestRegistry: createPluginConfigSchemaRegistry(),
}));
const result = validateConfigObjectWithPlugins(
{
plugins: {
allow: ["opik"],
entries: {
opik: {
enabled: true,
},
},
},
},
{
loadPluginMetadataSnapshot,
},
);
expect(result.ok).toBe(true);
expect(loadPluginMetadataSnapshot).toHaveBeenCalledOnce();
expect(mockLoadPluginManifestRegistry).not.toHaveBeenCalled();
});
});

View File

@@ -711,6 +711,9 @@ type ValidateConfigWithPluginsParams = {
env?: NodeJS.ProcessEnv;
pluginValidation?: "full" | "skip";
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "manifestRegistry">;
loadPluginMetadataSnapshot?: (
config: OpenClawConfig,
) => Pick<PluginMetadataSnapshot, "manifestRegistry">;
};
export function validateConfigObjectWithPlugins(
@@ -722,6 +725,7 @@ export function validateConfigObjectWithPlugins(
env: params?.env,
pluginValidation: params?.pluginValidation ?? "full",
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
loadPluginMetadataSnapshot: params?.loadPluginMetadataSnapshot,
});
}
@@ -734,6 +738,7 @@ export function validateConfigObjectRawWithPlugins(
env: params?.env,
pluginValidation: params?.pluginValidation ?? "full",
pluginMetadataSnapshot: params?.pluginMetadataSnapshot,
loadPluginMetadataSnapshot: params?.loadPluginMetadataSnapshot,
});
}
@@ -810,6 +815,11 @@ function validateConfigObjectWithPluginsBase(
};
const loadValidationRegistry = (): RegistryInfo => {
const pluginMetadataSnapshot = opts.loadPluginMetadataSnapshot?.(config);
if (pluginMetadataSnapshot) {
registryInfo = { registry: pluginMetadataSnapshot.manifestRegistry };
return registryInfo;
}
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
const registry = loadPluginManifestRegistryForPluginRegistry({
config,

View File

@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
const applyPluginAutoEnable = vi.hoisted(() =>
vi.fn((params: { config: unknown }) => ({
@@ -22,6 +23,45 @@ const resolveBundledRuntimeDependencyPackageInstallRoot = vi.hoisted(() =>
vi.fn((_packageRoot: string, _params: unknown) => "/runtime"),
);
const pluginManifestRegistry = vi.hoisted(() => ({ plugins: [], diagnostics: [] }));
const pluginMetadataSnapshot = vi.hoisted(
(): PluginMetadataSnapshot => ({
index: {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "policy",
generatedAtMs: 0,
installRecords: {},
plugins: [],
diagnostics: [],
},
registryDiagnostics: [],
manifestRegistry: pluginManifestRegistry,
plugins: [],
diagnostics: [],
byPluginId: new Map(),
normalizePluginId: (pluginId) => pluginId,
owners: {
channels: new Map(),
channelConfigs: new Map(),
providers: new Map(),
modelCatalogProviders: new Map(),
cliBackends: new Map(),
setupProviders: new Map(),
commandAliases: new Map(),
contracts: new Map(),
},
metrics: {
registrySnapshotMs: 0,
manifestRegistryMs: 0,
ownerMapsMs: 0,
totalMs: 0,
indexPluginCount: 0,
manifestPluginCount: 0,
},
}),
);
const pluginLookUpTableMetrics = vi.hoisted(() => ({
registrySnapshotMs: 0,
manifestRegistryMs: 0,
@@ -259,6 +299,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
cfgAtStart: runtimeConfig,
activationSourceConfig: sourceConfig,
startupRuntimeConfig: runtimeConfig,
pluginMetadataSnapshot,
minimalTestGateway: false,
log,
});
@@ -270,6 +311,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => {
expect(loadPluginLookUpTable).toHaveBeenCalledWith(
expect.objectContaining({
activationSourceConfig: sourceConfig,
metadataSnapshot: pluginMetadataSnapshot,
config: expect.objectContaining({
channels: expect.objectContaining({
telegram: expect.objectContaining({

View File

@@ -10,6 +10,7 @@ import {
scanBundledPluginRuntimeDeps,
} from "../plugins/bundled-runtime-deps.js";
import { loadPluginLookUpTable } from "../plugins/plugin-lookup-table.js";
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import { createEmptyPluginRegistry } from "../plugins/registry.js";
import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js";
import { mergeActivationSectionsIntoRuntimeConfig } from "./plugin-activation-runtime-config.js";
@@ -95,6 +96,7 @@ export async function prepareGatewayPluginBootstrap(params: {
cfgAtStart: OpenClawConfig;
activationSourceConfig?: OpenClawConfig;
startupRuntimeConfig: OpenClawConfig;
pluginMetadataSnapshot?: PluginMetadataSnapshot;
minimalTestGateway: boolean;
log: GatewayPluginBootstrapLog;
}) {
@@ -149,6 +151,7 @@ export async function prepareGatewayPluginBootstrap(params: {
workspaceDir: defaultWorkspaceDir,
env: process.env,
activationSourceConfig,
metadataSnapshot: params.pluginMetadataSnapshot,
});
const deferredConfiguredChannelPluginIds = [
...(pluginLookUpTable?.startup.configuredDeferredChannelPluginIds ?? []),

View File

@@ -411,6 +411,7 @@ export async function startGatewayServer(
cfgAtStart,
activationSourceConfig: startupActivationSourceConfig,
startupRuntimeConfig,
pluginMetadataSnapshot: configSnapshot.pluginMetadataSnapshot,
minimalTestGateway,
log,
}),