mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:30:42 +00:00
refactor: extract plugin metadata snapshot
This commit is contained in:
@@ -184,4 +184,49 @@ describe("loadPluginLookUpTable", () => {
|
||||
expect(table.startup.configuredDeferredChannelPluginIds).toEqual([]);
|
||||
expect(table.startup.pluginIds).toEqual(["telegram"]);
|
||||
});
|
||||
|
||||
it("derives startup ids from a provided metadata snapshot without reloading manifests", async () => {
|
||||
const plugins = [
|
||||
createManifestRecord({
|
||||
id: "telegram",
|
||||
origin: "bundled",
|
||||
channels: ["telegram"],
|
||||
}),
|
||||
];
|
||||
const index = createIndex(plugins);
|
||||
const manifestRegistry: PluginManifestRegistry = {
|
||||
plugins,
|
||||
diagnostics: [],
|
||||
};
|
||||
loadPluginManifestRegistryForInstalledIndex.mockReturnValue(manifestRegistry);
|
||||
const { loadPluginMetadataSnapshot } = await import("./plugin-metadata-snapshot.js");
|
||||
const { loadPluginLookUpTable } = await import("./plugin-lookup-table.js");
|
||||
|
||||
const metadataSnapshot = loadPluginMetadataSnapshot({
|
||||
config: {
|
||||
channels: {
|
||||
telegram: { token: "configured" },
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
env: {},
|
||||
index,
|
||||
});
|
||||
loadPluginManifestRegistryForInstalledIndex.mockClear();
|
||||
|
||||
const table = loadPluginLookUpTable({
|
||||
config: {
|
||||
channels: {
|
||||
telegram: { token: "configured" },
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
env: {},
|
||||
metadataSnapshot,
|
||||
});
|
||||
|
||||
expect(loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
|
||||
expect(table.manifestRegistry).toBe(manifestRegistry);
|
||||
expect(table.startup.pluginIds).toEqual(["telegram"]);
|
||||
expect(table.metrics.indexPluginCount).toBe(1);
|
||||
expect(table.metrics.manifestPluginCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,26 +5,14 @@ import {
|
||||
resolveGatewayStartupPluginIdsFromRegistry,
|
||||
} from "./channel-plugin-ids.js";
|
||||
import { hashJson } from "./installed-plugin-index-hash.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginDiagnostic } from "./manifest-types.js";
|
||||
import { createPluginRegistryIdNormalizer } from "./plugin-registry-contributions.js";
|
||||
import {
|
||||
loadPluginRegistrySnapshotWithMetadata,
|
||||
type PluginRegistrySnapshot,
|
||||
type PluginRegistrySnapshotDiagnostic,
|
||||
} from "./plugin-registry-snapshot.js";
|
||||
loadPluginMetadataSnapshot,
|
||||
type PluginMetadataSnapshot,
|
||||
type PluginMetadataSnapshotOwnerMaps,
|
||||
} from "./plugin-metadata-snapshot.js";
|
||||
import type { PluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
|
||||
|
||||
export type PluginLookUpTableOwnerMaps = {
|
||||
channels: ReadonlyMap<string, readonly string[]>;
|
||||
channelConfigs: ReadonlyMap<string, readonly string[]>;
|
||||
providers: ReadonlyMap<string, readonly string[]>;
|
||||
modelCatalogProviders: ReadonlyMap<string, readonly string[]>;
|
||||
cliBackends: ReadonlyMap<string, readonly string[]>;
|
||||
setupProviders: ReadonlyMap<string, readonly string[]>;
|
||||
commandAliases: ReadonlyMap<string, readonly string[]>;
|
||||
contracts: ReadonlyMap<string, readonly string[]>;
|
||||
};
|
||||
export type PluginLookUpTableOwnerMaps = PluginMetadataSnapshotOwnerMaps;
|
||||
|
||||
export type PluginLookUpTableStartupPlan = {
|
||||
channelPluginIds: readonly string[];
|
||||
@@ -44,18 +32,14 @@ export type PluginLookUpTableMetrics = {
|
||||
deferredChannelPluginCount: number;
|
||||
};
|
||||
|
||||
export type PluginLookUpTable = {
|
||||
export type PluginLookUpTable = PluginMetadataSnapshot & {
|
||||
key: string;
|
||||
index: PluginRegistrySnapshot;
|
||||
registryDiagnostics: readonly PluginRegistrySnapshotDiagnostic[];
|
||||
manifestRegistry: PluginManifestRegistry;
|
||||
plugins: readonly PluginManifestRecord[];
|
||||
diagnostics: readonly PluginDiagnostic[];
|
||||
byPluginId: ReadonlyMap<string, PluginManifestRecord>;
|
||||
normalizePluginId: (pluginId: string) => string;
|
||||
owners: PluginLookUpTableOwnerMaps;
|
||||
startup: PluginLookUpTableStartupPlan;
|
||||
metrics: PluginLookUpTableMetrics;
|
||||
metrics: PluginMetadataSnapshot["metrics"] &
|
||||
Pick<
|
||||
PluginLookUpTableMetrics,
|
||||
"startupPlanMs" | "startupPluginCount" | "deferredChannelPluginCount"
|
||||
>;
|
||||
};
|
||||
|
||||
export type LoadPluginLookUpTableParams = {
|
||||
@@ -64,97 +48,19 @@ export type LoadPluginLookUpTableParams = {
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
index?: PluginRegistrySnapshot;
|
||||
metadataSnapshot?: PluginMetadataSnapshot;
|
||||
};
|
||||
|
||||
function appendOwner(owners: Map<string, string[]>, ownedId: string, pluginId: string): void {
|
||||
const existing = owners.get(ownedId);
|
||||
if (existing) {
|
||||
existing.push(pluginId);
|
||||
return;
|
||||
}
|
||||
owners.set(ownedId, [pluginId]);
|
||||
}
|
||||
|
||||
function freezeOwnerMap(owners: Map<string, string[]>): ReadonlyMap<string, readonly string[]> {
|
||||
return new Map(
|
||||
[...owners.entries()].map(([ownedId, pluginIds]) => [ownedId, Object.freeze([...pluginIds])]),
|
||||
);
|
||||
}
|
||||
|
||||
function buildOwnerMaps(plugins: readonly PluginManifestRecord[]): PluginLookUpTableOwnerMaps {
|
||||
const channels = new Map<string, string[]>();
|
||||
const channelConfigs = new Map<string, string[]>();
|
||||
const providers = new Map<string, string[]>();
|
||||
const modelCatalogProviders = new Map<string, string[]>();
|
||||
const cliBackends = new Map<string, string[]>();
|
||||
const setupProviders = new Map<string, string[]>();
|
||||
const commandAliases = new Map<string, string[]>();
|
||||
const contracts = new Map<string, string[]>();
|
||||
|
||||
for (const plugin of plugins) {
|
||||
for (const channelId of plugin.channels) {
|
||||
appendOwner(channels, channelId, plugin.id);
|
||||
}
|
||||
for (const channelId of Object.keys(plugin.channelConfigs ?? {})) {
|
||||
appendOwner(channelConfigs, channelId, plugin.id);
|
||||
}
|
||||
for (const providerId of plugin.providers) {
|
||||
appendOwner(providers, providerId, plugin.id);
|
||||
}
|
||||
for (const providerId of Object.keys(plugin.modelCatalog?.providers ?? {})) {
|
||||
appendOwner(modelCatalogProviders, providerId, plugin.id);
|
||||
}
|
||||
for (const cliBackendId of plugin.cliBackends) {
|
||||
appendOwner(cliBackends, cliBackendId, plugin.id);
|
||||
}
|
||||
for (const cliBackendId of plugin.setup?.cliBackends ?? []) {
|
||||
appendOwner(cliBackends, cliBackendId, plugin.id);
|
||||
}
|
||||
for (const setupProvider of plugin.setup?.providers ?? []) {
|
||||
appendOwner(setupProviders, setupProvider.id, plugin.id);
|
||||
}
|
||||
for (const commandAlias of plugin.commandAliases ?? []) {
|
||||
appendOwner(commandAliases, commandAlias.name, plugin.id);
|
||||
}
|
||||
for (const [contract, values] of Object.entries(plugin.contracts ?? {})) {
|
||||
if (Array.isArray(values) && values.length > 0) {
|
||||
appendOwner(contracts, contract, plugin.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
channels: freezeOwnerMap(channels),
|
||||
channelConfigs: freezeOwnerMap(channelConfigs),
|
||||
providers: freezeOwnerMap(providers),
|
||||
modelCatalogProviders: freezeOwnerMap(modelCatalogProviders),
|
||||
cliBackends: freezeOwnerMap(cliBackends),
|
||||
setupProviders: freezeOwnerMap(setupProviders),
|
||||
commandAliases: freezeOwnerMap(commandAliases),
|
||||
contracts: freezeOwnerMap(contracts),
|
||||
};
|
||||
}
|
||||
|
||||
export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): PluginLookUpTable {
|
||||
const totalStartedAt = performance.now();
|
||||
const registryStartedAt = performance.now();
|
||||
const registryResult = loadPluginRegistrySnapshotWithMetadata({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
...(params.index ? { index: params.index } : {}),
|
||||
});
|
||||
const registrySnapshotMs = performance.now() - registryStartedAt;
|
||||
const index = registryResult.snapshot;
|
||||
const manifestStartedAt = performance.now();
|
||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
||||
index,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
includeDisabled: true,
|
||||
});
|
||||
const manifestRegistryMs = performance.now() - manifestStartedAt;
|
||||
const metadataSnapshot =
|
||||
params.metadataSnapshot ??
|
||||
loadPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
...(params.index ? { index: params.index } : {}),
|
||||
});
|
||||
const { index, manifestRegistry } = metadataSnapshot;
|
||||
const startupPlanStartedAt = performance.now();
|
||||
const channelPluginIds = resolveChannelPluginIdsFromRegistry({ manifestRegistry });
|
||||
const configuredDeferredChannelPluginIds = resolveConfiguredDeferredChannelPluginIdsFromRegistry({
|
||||
@@ -173,19 +79,14 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
|
||||
manifestRegistry,
|
||||
});
|
||||
const startupPlanMs = performance.now() - startupPlanStartedAt;
|
||||
const normalizePluginId = createPluginRegistryIdNormalizer(index, { manifestRegistry });
|
||||
const byPluginId = new Map(manifestRegistry.plugins.map((plugin) => [plugin.id, plugin]));
|
||||
const ownerMapsStartedAt = performance.now();
|
||||
const owners = buildOwnerMaps(manifestRegistry.plugins);
|
||||
const ownerMapsMs = performance.now() - ownerMapsStartedAt;
|
||||
const startup = {
|
||||
channelPluginIds,
|
||||
configuredDeferredChannelPluginIds,
|
||||
pluginIds,
|
||||
};
|
||||
const totalMs = performance.now() - totalStartedAt;
|
||||
|
||||
return {
|
||||
...metadataSnapshot,
|
||||
key: hashJson({
|
||||
policyHash: index.policyHash,
|
||||
generatedAtMs: index.generatedAtMs,
|
||||
@@ -196,23 +97,10 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
|
||||
]),
|
||||
startup,
|
||||
}),
|
||||
index,
|
||||
registryDiagnostics: registryResult.diagnostics,
|
||||
manifestRegistry,
|
||||
plugins: manifestRegistry.plugins,
|
||||
diagnostics: manifestRegistry.diagnostics,
|
||||
byPluginId,
|
||||
normalizePluginId,
|
||||
owners,
|
||||
startup,
|
||||
metrics: {
|
||||
registrySnapshotMs,
|
||||
manifestRegistryMs,
|
||||
...metadataSnapshot.metrics,
|
||||
startupPlanMs,
|
||||
ownerMapsMs,
|
||||
totalMs,
|
||||
indexPluginCount: index.plugins.length,
|
||||
manifestPluginCount: manifestRegistry.plugins.length,
|
||||
startupPluginCount: pluginIds.length,
|
||||
deferredChannelPluginCount: configuredDeferredChannelPluginIds.length,
|
||||
},
|
||||
|
||||
169
src/plugins/plugin-metadata-snapshot.ts
Normal file
169
src/plugins/plugin-metadata-snapshot.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginDiagnostic } from "./manifest-types.js";
|
||||
import { createPluginRegistryIdNormalizer } from "./plugin-registry-contributions.js";
|
||||
import {
|
||||
loadPluginRegistrySnapshotWithMetadata,
|
||||
type PluginRegistrySnapshot,
|
||||
type PluginRegistrySnapshotDiagnostic,
|
||||
} from "./plugin-registry-snapshot.js";
|
||||
|
||||
export type PluginMetadataSnapshotOwnerMaps = {
|
||||
channels: ReadonlyMap<string, readonly string[]>;
|
||||
channelConfigs: ReadonlyMap<string, readonly string[]>;
|
||||
providers: ReadonlyMap<string, readonly string[]>;
|
||||
modelCatalogProviders: ReadonlyMap<string, readonly string[]>;
|
||||
cliBackends: ReadonlyMap<string, readonly string[]>;
|
||||
setupProviders: ReadonlyMap<string, readonly string[]>;
|
||||
commandAliases: ReadonlyMap<string, readonly string[]>;
|
||||
contracts: ReadonlyMap<string, readonly string[]>;
|
||||
};
|
||||
|
||||
export type PluginMetadataSnapshotMetrics = {
|
||||
registrySnapshotMs: number;
|
||||
manifestRegistryMs: number;
|
||||
ownerMapsMs: number;
|
||||
totalMs: number;
|
||||
indexPluginCount: number;
|
||||
manifestPluginCount: number;
|
||||
};
|
||||
|
||||
export type PluginMetadataSnapshot = {
|
||||
index: PluginRegistrySnapshot;
|
||||
registryDiagnostics: readonly PluginRegistrySnapshotDiagnostic[];
|
||||
manifestRegistry: PluginManifestRegistry;
|
||||
plugins: readonly PluginManifestRecord[];
|
||||
diagnostics: readonly PluginDiagnostic[];
|
||||
byPluginId: ReadonlyMap<string, PluginManifestRecord>;
|
||||
normalizePluginId: (pluginId: string) => string;
|
||||
owners: PluginMetadataSnapshotOwnerMaps;
|
||||
metrics: PluginMetadataSnapshotMetrics;
|
||||
};
|
||||
|
||||
export type LoadPluginMetadataSnapshotParams = {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
index?: PluginRegistrySnapshot;
|
||||
};
|
||||
|
||||
function appendOwner(owners: Map<string, string[]>, ownedId: string, pluginId: string): void {
|
||||
const existing = owners.get(ownedId);
|
||||
if (existing) {
|
||||
existing.push(pluginId);
|
||||
return;
|
||||
}
|
||||
owners.set(ownedId, [pluginId]);
|
||||
}
|
||||
|
||||
function freezeOwnerMap(owners: Map<string, string[]>): ReadonlyMap<string, readonly string[]> {
|
||||
return new Map(
|
||||
[...owners.entries()].map(([ownedId, pluginIds]) => [ownedId, Object.freeze([...pluginIds])]),
|
||||
);
|
||||
}
|
||||
|
||||
export function buildPluginMetadataOwnerMaps(
|
||||
plugins: readonly PluginManifestRecord[],
|
||||
): PluginMetadataSnapshotOwnerMaps {
|
||||
const channels = new Map<string, string[]>();
|
||||
const channelConfigs = new Map<string, string[]>();
|
||||
const providers = new Map<string, string[]>();
|
||||
const modelCatalogProviders = new Map<string, string[]>();
|
||||
const cliBackends = new Map<string, string[]>();
|
||||
const setupProviders = new Map<string, string[]>();
|
||||
const commandAliases = new Map<string, string[]>();
|
||||
const contracts = new Map<string, string[]>();
|
||||
|
||||
for (const plugin of plugins) {
|
||||
for (const channelId of plugin.channels) {
|
||||
appendOwner(channels, channelId, plugin.id);
|
||||
}
|
||||
for (const channelId of Object.keys(plugin.channelConfigs ?? {})) {
|
||||
appendOwner(channelConfigs, channelId, plugin.id);
|
||||
}
|
||||
for (const providerId of plugin.providers) {
|
||||
appendOwner(providers, providerId, plugin.id);
|
||||
}
|
||||
for (const providerId of Object.keys(plugin.modelCatalog?.providers ?? {})) {
|
||||
appendOwner(modelCatalogProviders, providerId, plugin.id);
|
||||
}
|
||||
for (const cliBackendId of plugin.cliBackends) {
|
||||
appendOwner(cliBackends, cliBackendId, plugin.id);
|
||||
}
|
||||
for (const cliBackendId of plugin.setup?.cliBackends ?? []) {
|
||||
appendOwner(cliBackends, cliBackendId, plugin.id);
|
||||
}
|
||||
for (const setupProvider of plugin.setup?.providers ?? []) {
|
||||
appendOwner(setupProviders, setupProvider.id, plugin.id);
|
||||
}
|
||||
for (const commandAlias of plugin.commandAliases ?? []) {
|
||||
appendOwner(commandAliases, commandAlias.name, plugin.id);
|
||||
}
|
||||
for (const [contract, values] of Object.entries(plugin.contracts ?? {})) {
|
||||
if (Array.isArray(values) && values.length > 0) {
|
||||
appendOwner(contracts, contract, plugin.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
channels: freezeOwnerMap(channels),
|
||||
channelConfigs: freezeOwnerMap(channelConfigs),
|
||||
providers: freezeOwnerMap(providers),
|
||||
modelCatalogProviders: freezeOwnerMap(modelCatalogProviders),
|
||||
cliBackends: freezeOwnerMap(cliBackends),
|
||||
setupProviders: freezeOwnerMap(setupProviders),
|
||||
commandAliases: freezeOwnerMap(commandAliases),
|
||||
contracts: freezeOwnerMap(contracts),
|
||||
};
|
||||
}
|
||||
|
||||
export function loadPluginMetadataSnapshot(
|
||||
params: LoadPluginMetadataSnapshotParams,
|
||||
): PluginMetadataSnapshot {
|
||||
const totalStartedAt = performance.now();
|
||||
const registryStartedAt = performance.now();
|
||||
const registryResult = loadPluginRegistrySnapshotWithMetadata({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
...(params.index ? { index: params.index } : {}),
|
||||
});
|
||||
const registrySnapshotMs = performance.now() - registryStartedAt;
|
||||
const index = registryResult.snapshot;
|
||||
const manifestStartedAt = performance.now();
|
||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
||||
index,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
includeDisabled: true,
|
||||
});
|
||||
const manifestRegistryMs = performance.now() - manifestStartedAt;
|
||||
const normalizePluginId = createPluginRegistryIdNormalizer(index, { manifestRegistry });
|
||||
const byPluginId = new Map(manifestRegistry.plugins.map((plugin) => [plugin.id, plugin]));
|
||||
const ownerMapsStartedAt = performance.now();
|
||||
const owners = buildPluginMetadataOwnerMaps(manifestRegistry.plugins);
|
||||
const ownerMapsMs = performance.now() - ownerMapsStartedAt;
|
||||
const totalMs = performance.now() - totalStartedAt;
|
||||
|
||||
return {
|
||||
index,
|
||||
registryDiagnostics: registryResult.diagnostics,
|
||||
manifestRegistry,
|
||||
plugins: manifestRegistry.plugins,
|
||||
diagnostics: manifestRegistry.diagnostics,
|
||||
byPluginId,
|
||||
normalizePluginId,
|
||||
owners,
|
||||
metrics: {
|
||||
registrySnapshotMs,
|
||||
manifestRegistryMs,
|
||||
ownerMapsMs,
|
||||
totalMs,
|
||||
indexPluginCount: index.plugins.length,
|
||||
manifestPluginCount: manifestRegistry.plugins.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user