mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:50:42 +00:00
refactor: unify plugin startup metadata planning
This commit is contained in:
@@ -43,11 +43,21 @@ vi.mock("../channels/config-presence.js", () => ({
|
|||||||
hasMeaningfulChannelConfig,
|
hasMeaningfulChannelConfig,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./manifest-registry-installed.js", () => ({
|
vi.mock("./manifest-registry-installed.js", async (importOriginal) => {
|
||||||
loadPluginManifestRegistryForInstalledIndex,
|
const actual = await importOriginal<typeof import("./manifest-registry-installed.js")>();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
loadPluginManifestRegistryForInstalledIndex,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("./plugin-registry-snapshot.js", () => ({ loadPluginRegistrySnapshot }));
|
vi.mock("./plugin-registry-snapshot.js", () => ({
|
||||||
|
loadPluginRegistrySnapshot,
|
||||||
|
loadPluginRegistrySnapshotWithMetadata: (params: unknown) => ({
|
||||||
|
snapshot: loadPluginRegistrySnapshot(params),
|
||||||
|
diagnostics: [],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("./plugin-registry-contributions.js", async (importOriginal) => {
|
vi.mock("./plugin-registry-contributions.js", async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import("./plugin-registry-contributions.js")>();
|
const actual = await importOriginal<typeof import("./plugin-registry-contributions.js")>();
|
||||||
@@ -62,6 +72,7 @@ import {
|
|||||||
listConfiguredAnnounceChannelIdsForConfig,
|
listConfiguredAnnounceChannelIdsForConfig,
|
||||||
listConfiguredChannelIdsForReadOnlyScope,
|
listConfiguredChannelIdsForReadOnlyScope,
|
||||||
listExplicitConfiguredChannelIdsForConfig,
|
listExplicitConfiguredChannelIdsForConfig,
|
||||||
|
loadGatewayStartupPluginPlan,
|
||||||
resolveConfiguredChannelPresencePolicy,
|
resolveConfiguredChannelPresencePolicy,
|
||||||
resolveConfiguredDeferredChannelPluginIds,
|
resolveConfiguredDeferredChannelPluginIds,
|
||||||
resolveConfiguredChannelPluginIds,
|
resolveConfiguredChannelPluginIds,
|
||||||
@@ -854,6 +865,31 @@ describe("resolveGatewayStartupPluginIds", () => {
|
|||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads channel, deferred, and startup plugin ids from one manifest registry", () => {
|
||||||
|
const registry = createManifestRegistryFixture();
|
||||||
|
const index = createInstalledPluginIndexFixture(registry);
|
||||||
|
loadPluginRegistrySnapshot.mockReset().mockReturnValue(index);
|
||||||
|
loadPluginManifestRegistryForInstalledIndex.mockReset().mockReturnValue(registry);
|
||||||
|
|
||||||
|
const plan = loadGatewayStartupPluginPlan({
|
||||||
|
config: {
|
||||||
|
channels: {
|
||||||
|
"demo-channel": {
|
||||||
|
token: "configured",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as OpenClawConfig,
|
||||||
|
workspaceDir: "/tmp",
|
||||||
|
env: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan.channelPluginIds).toContain("demo-channel");
|
||||||
|
expect(plan.pluginIds).toContain("demo-channel");
|
||||||
|
expect(plan.configuredDeferredChannelPluginIds).toEqual([]);
|
||||||
|
expect(loadPluginRegistrySnapshot).toHaveBeenCalledOnce();
|
||||||
|
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it("does not treat explicitly disabled stale channel config as deferred startup intent", () => {
|
it("does not treat explicitly disabled stale channel config as deferred startup intent", () => {
|
||||||
useManifestRegistryFixture(createManifestRegistryFixtureWithWorkspaceDemoChannel());
|
useManifestRegistryFixture(createManifestRegistryFixtureWithWorkspaceDemoChannel());
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export {
|
|||||||
resolveChannelPluginIdsFromRegistry,
|
resolveChannelPluginIdsFromRegistry,
|
||||||
resolveConfiguredDeferredChannelPluginIds,
|
resolveConfiguredDeferredChannelPluginIds,
|
||||||
resolveConfiguredDeferredChannelPluginIdsFromRegistry,
|
resolveConfiguredDeferredChannelPluginIdsFromRegistry,
|
||||||
|
loadGatewayStartupPluginPlan,
|
||||||
resolveGatewayStartupPluginIds,
|
resolveGatewayStartupPluginIds,
|
||||||
|
resolveGatewayStartupPluginPlanFromRegistry,
|
||||||
resolveGatewayStartupPluginIdsFromRegistry,
|
resolveGatewayStartupPluginIdsFromRegistry,
|
||||||
|
type GatewayStartupPluginPlan,
|
||||||
} from "./gateway-startup-plugin-ids.js";
|
} from "./gateway-startup-plugin-ids.js";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "./plugin-control-plane-context.js";
|
} from "./plugin-control-plane-context.js";
|
||||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
||||||
|
|
||||||
export function resolvePluginMetadataSnapshotConfigFingerprint(
|
export function resolvePluginMetadataControlPlaneFingerprint(
|
||||||
config?: OpenClawConfig,
|
config?: OpenClawConfig,
|
||||||
options: Omit<ResolvePluginControlPlaneContextParams, "config"> = {},
|
options: Omit<ResolvePluginControlPlaneContextParams, "config"> = {},
|
||||||
): string {
|
): string {
|
||||||
@@ -30,7 +30,7 @@ export function setCurrentPluginMetadataSnapshot(
|
|||||||
setCurrentPluginMetadataSnapshotState(
|
setCurrentPluginMetadataSnapshotState(
|
||||||
snapshot,
|
snapshot,
|
||||||
snapshot
|
snapshot
|
||||||
? resolvePluginMetadataSnapshotConfigFingerprint(options.config, {
|
? resolvePluginMetadataControlPlaneFingerprint(options.config, {
|
||||||
env: options.env,
|
env: options.env,
|
||||||
index: snapshot.index,
|
index: snapshot.index,
|
||||||
policyHash: snapshot.policyHash,
|
policyHash: snapshot.policyHash,
|
||||||
@@ -63,15 +63,12 @@ export function getCurrentPluginMetadataSnapshot(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (params.config) {
|
if (params.config) {
|
||||||
const requestedConfigFingerprint = resolvePluginMetadataSnapshotConfigFingerprint(
|
const requestedConfigFingerprint = resolvePluginMetadataControlPlaneFingerprint(params.config, {
|
||||||
params.config,
|
env: params.env,
|
||||||
{
|
index: snapshot.index,
|
||||||
env: params.env,
|
policyHash: snapshot.policyHash,
|
||||||
index: snapshot.index,
|
workspaceDir: params.workspaceDir,
|
||||||
policyHash: snapshot.policyHash,
|
});
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (configFingerprint && configFingerprint !== requestedConfigFingerprint) {
|
if (configFingerprint && configFingerprint !== requestedConfigFingerprint) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|||||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||||
import {
|
import {
|
||||||
listExplicitConfiguredChannelIdsForConfig,
|
listExplicitConfiguredChannelIdsForConfig,
|
||||||
|
loadGatewayStartupPluginPlan,
|
||||||
resolveConfiguredChannelPluginIds,
|
resolveConfiguredChannelPluginIds,
|
||||||
resolveGatewayStartupPluginIds,
|
|
||||||
} from "./channel-plugin-ids.js";
|
} from "./channel-plugin-ids.js";
|
||||||
import { normalizePluginsConfig } from "./config-state.js";
|
import { normalizePluginsConfig } from "./config-state.js";
|
||||||
import { passesManifestOwnerBasePolicy } from "./manifest-owner-policy.js";
|
import { passesManifestOwnerBasePolicy } from "./manifest-owner-policy.js";
|
||||||
@@ -155,12 +155,12 @@ export function resolveEffectivePluginIds(params: {
|
|||||||
})) {
|
})) {
|
||||||
ids.add(pluginId);
|
ids.add(pluginId);
|
||||||
}
|
}
|
||||||
for (const pluginId of resolveGatewayStartupPluginIds({
|
for (const pluginId of loadGatewayStartupPluginPlan({
|
||||||
config: effectiveConfig,
|
config: effectiveConfig,
|
||||||
activationSourceConfig: params.config,
|
activationSourceConfig: params.config,
|
||||||
workspaceDir: params.workspaceDir,
|
workspaceDir: params.workspaceDir,
|
||||||
env: params.env,
|
env: params.env,
|
||||||
})) {
|
}).pluginIds) {
|
||||||
ids.add(pluginId);
|
ids.add(pluginId);
|
||||||
}
|
}
|
||||||
return [...ids].toSorted((left, right) => left.localeCompare(right));
|
return [...ids].toSorted((left, right) => left.localeCompare(right));
|
||||||
|
|||||||
@@ -15,13 +15,23 @@ import { hasExplicitChannelConfig } from "./channel-presence-policy.js";
|
|||||||
import { collectPluginConfigContractMatches } from "./config-contracts.js";
|
import { collectPluginConfigContractMatches } from "./config-contracts.js";
|
||||||
import { resolveEffectivePluginActivationState } from "./config-state.js";
|
import { resolveEffectivePluginActivationState } from "./config-state.js";
|
||||||
import type { InstalledPluginIndexRecord } from "./installed-plugin-index.js";
|
import type { InstalledPluginIndexRecord } from "./installed-plugin-index.js";
|
||||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
|
||||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||||
|
import {
|
||||||
|
isPluginMetadataSnapshotCompatible,
|
||||||
|
loadPluginMetadataSnapshot,
|
||||||
|
type PluginMetadataSnapshot,
|
||||||
|
} from "./plugin-metadata-snapshot.js";
|
||||||
import {
|
import {
|
||||||
createPluginRegistryIdNormalizer,
|
createPluginRegistryIdNormalizer,
|
||||||
normalizePluginsConfigWithRegistry,
|
normalizePluginsConfigWithRegistry,
|
||||||
} from "./plugin-registry-contributions.js";
|
} from "./plugin-registry-contributions.js";
|
||||||
import { loadPluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
|
import type { PluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
|
||||||
|
|
||||||
|
export type GatewayStartupPluginPlan = {
|
||||||
|
channelPluginIds: readonly string[];
|
||||||
|
configuredDeferredChannelPluginIds: readonly string[];
|
||||||
|
pluginIds: readonly string[];
|
||||||
|
};
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||||
@@ -235,19 +245,7 @@ export function resolveChannelPluginIds(params: {
|
|||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
}): string[] {
|
}): string[] {
|
||||||
const index = loadPluginRegistrySnapshot({
|
return [...loadGatewayStartupPluginPlan(params).channelPluginIds];
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
});
|
|
||||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
|
||||||
index,
|
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
includeDisabled: true,
|
|
||||||
});
|
|
||||||
return resolveChannelPluginIdsFromRegistry({ manifestRegistry });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveChannelPluginIdsFromRegistry(params: {
|
export function resolveChannelPluginIdsFromRegistry(params: {
|
||||||
@@ -262,7 +260,7 @@ export function resolveChannelPluginIdsFromRegistry(params: {
|
|||||||
export function resolveConfiguredDeferredChannelPluginIdsFromRegistry(params: {
|
export function resolveConfiguredDeferredChannelPluginIdsFromRegistry(params: {
|
||||||
config: OpenClawConfig;
|
config: OpenClawConfig;
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
index: ReturnType<typeof loadPluginRegistrySnapshot>;
|
index: PluginRegistrySnapshot;
|
||||||
manifestRegistry: PluginManifestRegistry;
|
manifestRegistry: PluginManifestRegistry;
|
||||||
}): string[] {
|
}): string[] {
|
||||||
const configuredChannelIds = new Set(listPotentialEnabledChannelIds(params.config, params.env));
|
const configuredChannelIds = new Set(listPotentialEnabledChannelIds(params.config, params.env));
|
||||||
@@ -302,33 +300,25 @@ export function resolveConfiguredDeferredChannelPluginIds(params: {
|
|||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
}): string[] {
|
}): string[] {
|
||||||
const index = loadPluginRegistrySnapshot({
|
return [...loadGatewayStartupPluginPlan(params).configuredDeferredChannelPluginIds];
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
});
|
|
||||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
|
||||||
index,
|
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
includeDisabled: true,
|
|
||||||
});
|
|
||||||
return resolveConfiguredDeferredChannelPluginIdsFromRegistry({
|
|
||||||
config: params.config,
|
|
||||||
env: params.env,
|
|
||||||
index,
|
|
||||||
manifestRegistry,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveGatewayStartupPluginIdsFromRegistry(params: {
|
export function resolveGatewayStartupPluginPlanFromRegistry(params: {
|
||||||
config: OpenClawConfig;
|
config: OpenClawConfig;
|
||||||
activationSourceConfig?: OpenClawConfig;
|
activationSourceConfig?: OpenClawConfig;
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
index: ReturnType<typeof loadPluginRegistrySnapshot>;
|
index: PluginRegistrySnapshot;
|
||||||
manifestRegistry: PluginManifestRegistry;
|
manifestRegistry: PluginManifestRegistry;
|
||||||
}): string[] {
|
}): GatewayStartupPluginPlan {
|
||||||
|
const channelPluginIds = resolveChannelPluginIdsFromRegistry({
|
||||||
|
manifestRegistry: params.manifestRegistry,
|
||||||
|
});
|
||||||
|
const configuredDeferredChannelPluginIds = resolveConfiguredDeferredChannelPluginIdsFromRegistry({
|
||||||
|
config: params.config,
|
||||||
|
env: params.env,
|
||||||
|
index: params.index,
|
||||||
|
manifestRegistry: params.manifestRegistry,
|
||||||
|
});
|
||||||
const configuredChannelIds = new Set(listPotentialEnabledChannelIds(params.config, params.env));
|
const configuredChannelIds = new Set(listPotentialEnabledChannelIds(params.config, params.env));
|
||||||
const pluginsConfig = normalizePluginsConfigWithRegistry(params.config.plugins, params.index, {
|
const pluginsConfig = normalizePluginsConfigWithRegistry(params.config.plugins, params.index, {
|
||||||
manifestRegistry: params.manifestRegistry,
|
manifestRegistry: params.manifestRegistry,
|
||||||
@@ -358,7 +348,7 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
|
|||||||
manifestRegistry: params.manifestRegistry,
|
manifestRegistry: params.manifestRegistry,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
return params.index.plugins
|
const pluginIds = params.index.plugins
|
||||||
.filter((plugin) => {
|
.filter((plugin) => {
|
||||||
const manifest = findManifestPlugin(manifestLookup, plugin.pluginId);
|
const manifest = findManifestPlugin(manifestLookup, plugin.pluginId);
|
||||||
if (
|
if (
|
||||||
@@ -427,6 +417,57 @@ export function resolveGatewayStartupPluginIdsFromRegistry(params: {
|
|||||||
return activationState.source === "explicit" || activationState.source === "default";
|
return activationState.source === "explicit" || activationState.source === "default";
|
||||||
})
|
})
|
||||||
.map((plugin) => plugin.pluginId);
|
.map((plugin) => plugin.pluginId);
|
||||||
|
return {
|
||||||
|
channelPluginIds,
|
||||||
|
configuredDeferredChannelPluginIds,
|
||||||
|
pluginIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveGatewayStartupPluginIdsFromRegistry(params: {
|
||||||
|
config: OpenClawConfig;
|
||||||
|
activationSourceConfig?: OpenClawConfig;
|
||||||
|
env: NodeJS.ProcessEnv;
|
||||||
|
index: PluginRegistrySnapshot;
|
||||||
|
manifestRegistry: PluginManifestRegistry;
|
||||||
|
}): string[] {
|
||||||
|
return [...resolveGatewayStartupPluginPlanFromRegistry(params).pluginIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadGatewayStartupPluginPlan(params: {
|
||||||
|
config: OpenClawConfig;
|
||||||
|
activationSourceConfig?: OpenClawConfig;
|
||||||
|
workspaceDir?: string;
|
||||||
|
env: NodeJS.ProcessEnv;
|
||||||
|
index?: PluginRegistrySnapshot;
|
||||||
|
metadataSnapshot?: PluginMetadataSnapshot;
|
||||||
|
}): GatewayStartupPluginPlan {
|
||||||
|
const snapshotConfig = params.activationSourceConfig ?? params.config;
|
||||||
|
const metadataSnapshot =
|
||||||
|
params.metadataSnapshot &&
|
||||||
|
isPluginMetadataSnapshotCompatible({
|
||||||
|
snapshot: params.metadataSnapshot,
|
||||||
|
config: snapshotConfig,
|
||||||
|
env: params.env,
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
index: params.index,
|
||||||
|
})
|
||||||
|
? params.metadataSnapshot
|
||||||
|
: loadPluginMetadataSnapshot({
|
||||||
|
config: snapshotConfig,
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
env: params.env,
|
||||||
|
...(params.index ? { index: params.index } : {}),
|
||||||
|
});
|
||||||
|
return resolveGatewayStartupPluginPlanFromRegistry({
|
||||||
|
config: params.config,
|
||||||
|
...(params.activationSourceConfig !== undefined
|
||||||
|
? { activationSourceConfig: params.activationSourceConfig }
|
||||||
|
: {}),
|
||||||
|
env: params.env,
|
||||||
|
index: metadataSnapshot.index,
|
||||||
|
manifestRegistry: metadataSnapshot.manifestRegistry,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveGatewayStartupPluginIds(params: {
|
export function resolveGatewayStartupPluginIds(params: {
|
||||||
@@ -435,25 +476,5 @@ export function resolveGatewayStartupPluginIds(params: {
|
|||||||
workspaceDir?: string;
|
workspaceDir?: string;
|
||||||
env: NodeJS.ProcessEnv;
|
env: NodeJS.ProcessEnv;
|
||||||
}): string[] {
|
}): string[] {
|
||||||
const index = loadPluginRegistrySnapshot({
|
return [...loadGatewayStartupPluginPlan(params).pluginIds];
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
});
|
|
||||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
|
||||||
index,
|
|
||||||
config: params.config,
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
env: params.env,
|
|
||||||
includeDisabled: true,
|
|
||||||
});
|
|
||||||
return resolveGatewayStartupPluginIdsFromRegistry({
|
|
||||||
config: params.config,
|
|
||||||
...(params.activationSourceConfig !== undefined
|
|
||||||
? { activationSourceConfig: params.activationSourceConfig }
|
|
||||||
: {}),
|
|
||||||
env: params.env,
|
|
||||||
index,
|
|
||||||
manifestRegistry,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
import {
|
import {
|
||||||
resolveChannelPluginIdsFromRegistry,
|
resolveGatewayStartupPluginPlanFromRegistry,
|
||||||
resolveConfiguredDeferredChannelPluginIdsFromRegistry,
|
type GatewayStartupPluginPlan,
|
||||||
resolveGatewayStartupPluginIdsFromRegistry,
|
|
||||||
} from "./channel-plugin-ids.js";
|
} from "./channel-plugin-ids.js";
|
||||||
import { hashJson } from "./installed-plugin-index-hash.js";
|
import { hashJson } from "./installed-plugin-index-hash.js";
|
||||||
import {
|
import {
|
||||||
@@ -15,11 +14,7 @@ import type { PluginRegistrySnapshot } from "./plugin-registry-snapshot.js";
|
|||||||
|
|
||||||
export type PluginLookUpTableOwnerMaps = PluginMetadataSnapshotOwnerMaps;
|
export type PluginLookUpTableOwnerMaps = PluginMetadataSnapshotOwnerMaps;
|
||||||
|
|
||||||
export type PluginLookUpTableStartupPlan = {
|
export type PluginLookUpTableStartupPlan = GatewayStartupPluginPlan;
|
||||||
channelPluginIds: readonly string[];
|
|
||||||
configuredDeferredChannelPluginIds: readonly string[];
|
|
||||||
pluginIds: readonly string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PluginLookUpTableMetrics = {
|
export type PluginLookUpTableMetrics = {
|
||||||
registrySnapshotMs: number;
|
registrySnapshotMs: number;
|
||||||
@@ -72,14 +67,7 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
|
|||||||
});
|
});
|
||||||
const { index, manifestRegistry } = metadataSnapshot;
|
const { index, manifestRegistry } = metadataSnapshot;
|
||||||
const startupPlanStartedAt = performance.now();
|
const startupPlanStartedAt = performance.now();
|
||||||
const channelPluginIds = resolveChannelPluginIdsFromRegistry({ manifestRegistry });
|
const startup = resolveGatewayStartupPluginPlanFromRegistry({
|
||||||
const configuredDeferredChannelPluginIds = resolveConfiguredDeferredChannelPluginIdsFromRegistry({
|
|
||||||
config: params.config,
|
|
||||||
env: params.env,
|
|
||||||
index,
|
|
||||||
manifestRegistry,
|
|
||||||
});
|
|
||||||
const pluginIds = resolveGatewayStartupPluginIdsFromRegistry({
|
|
||||||
config: params.config,
|
config: params.config,
|
||||||
...(params.activationSourceConfig !== undefined
|
...(params.activationSourceConfig !== undefined
|
||||||
? { activationSourceConfig: params.activationSourceConfig }
|
? { activationSourceConfig: params.activationSourceConfig }
|
||||||
@@ -89,11 +77,6 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
|
|||||||
manifestRegistry,
|
manifestRegistry,
|
||||||
});
|
});
|
||||||
const startupPlanMs = performance.now() - startupPlanStartedAt;
|
const startupPlanMs = performance.now() - startupPlanStartedAt;
|
||||||
const startup = {
|
|
||||||
channelPluginIds,
|
|
||||||
configuredDeferredChannelPluginIds,
|
|
||||||
pluginIds,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...metadataSnapshot,
|
...metadataSnapshot,
|
||||||
@@ -112,8 +95,8 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
|
|||||||
...metadataSnapshot.metrics,
|
...metadataSnapshot.metrics,
|
||||||
startupPlanMs,
|
startupPlanMs,
|
||||||
totalMs: metadataSnapshot.metrics.totalMs + startupPlanMs,
|
totalMs: metadataSnapshot.metrics.totalMs + startupPlanMs,
|
||||||
startupPluginCount: pluginIds.length,
|
startupPluginCount: startup.pluginIds.length,
|
||||||
deferredChannelPluginCount: configuredDeferredChannelPluginIds.length,
|
deferredChannelPluginCount: startup.configuredDeferredChannelPluginIds.length,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type {
|
|||||||
PluginMetadataSnapshotRegistryDiagnostic,
|
PluginMetadataSnapshotRegistryDiagnostic,
|
||||||
} from "./plugin-metadata-snapshot.types.js";
|
} from "./plugin-metadata-snapshot.types.js";
|
||||||
|
|
||||||
function resolvePluginMetadataSnapshotConfigFingerprint(
|
function resolvePluginMetadataControlPlaneFingerprint(
|
||||||
params: Pick<LoadPluginMetadataSnapshotParams, "config" | "env" | "workspaceDir"> & {
|
params: Pick<LoadPluginMetadataSnapshotParams, "config" | "env" | "workspaceDir"> & {
|
||||||
index?: InstalledPluginIndex;
|
index?: InstalledPluginIndex;
|
||||||
policyHash?: string;
|
policyHash?: string;
|
||||||
@@ -60,7 +60,7 @@ export function isPluginMetadataSnapshotCompatible(params: {
|
|||||||
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&
|
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&
|
||||||
(!params.snapshot.configFingerprint ||
|
(!params.snapshot.configFingerprint ||
|
||||||
params.snapshot.configFingerprint ===
|
params.snapshot.configFingerprint ===
|
||||||
resolvePluginMetadataSnapshotConfigFingerprint({
|
resolvePluginMetadataControlPlaneFingerprint({
|
||||||
config: params.config,
|
config: params.config,
|
||||||
env,
|
env,
|
||||||
index: params.index ?? params.snapshot.index,
|
index: params.index ?? params.snapshot.index,
|
||||||
@@ -195,7 +195,7 @@ function loadPluginMetadataSnapshotImpl(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
policyHash: index.policyHash,
|
policyHash: index.policyHash,
|
||||||
configFingerprint: resolvePluginMetadataSnapshotConfigFingerprint({
|
configFingerprint: resolvePluginMetadataControlPlaneFingerprint({
|
||||||
config: params.config,
|
config: params.config,
|
||||||
env: params.env,
|
env: params.env,
|
||||||
index,
|
index,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
PluginManifestRegistry,
|
PluginManifestRegistry,
|
||||||
} from "./manifest-registry.js";
|
} from "./manifest-registry.js";
|
||||||
import { isPackageIncludedInCoreBundle } from "./manifest.js";
|
import { isPackageIncludedInCoreBundle } from "./manifest.js";
|
||||||
|
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
||||||
import type { PluginOrigin } from "./plugin-origin.types.js";
|
import type { PluginOrigin } from "./plugin-origin.types.js";
|
||||||
import {
|
import {
|
||||||
createPluginRegistryIdNormalizer,
|
createPluginRegistryIdNormalizer,
|
||||||
@@ -28,22 +29,10 @@ export {
|
|||||||
type PluginRegistryIdNormalizerOptions,
|
type PluginRegistryIdNormalizerOptions,
|
||||||
} from "./plugin-registry-id-normalizer.js";
|
} from "./plugin-registry-id-normalizer.js";
|
||||||
|
|
||||||
export type PluginLookUpTable = {
|
export type PluginLookUpTable = Pick<
|
||||||
index: PluginRegistrySnapshot;
|
PluginMetadataSnapshot,
|
||||||
manifestRegistry: PluginManifestRegistry;
|
"index" | "manifestRegistry" | "plugins" | "normalizePluginId" | "owners"
|
||||||
plugins: readonly PluginManifestRecord[];
|
>;
|
||||||
normalizePluginId: (pluginId: string) => string;
|
|
||||||
owners: {
|
|
||||||
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 PluginRegistryContributionOptions = LoadPluginRegistryParams & {
|
export type PluginRegistryContributionOptions = LoadPluginRegistryParams & {
|
||||||
includeDisabled?: boolean;
|
includeDisabled?: boolean;
|
||||||
|
|||||||
@@ -153,6 +153,41 @@ describe("bundled plugin public surface loader", () => {
|
|||||||
expect(createJiti).not.toHaveBeenCalled();
|
expect(createJiti).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not cache missing public artifact locations", async () => {
|
||||||
|
vi.doMock("./native-module-require.js", () => ({
|
||||||
|
tryNativeRequireJavaScriptModule: (modulePath: string) => ({
|
||||||
|
ok: true,
|
||||||
|
moduleExport: { marker: path.basename(path.dirname(modulePath)) },
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
vi.resetModules();
|
||||||
|
|
||||||
|
const publicSurfaceLoader = await importFreshModule<
|
||||||
|
typeof import("./public-surface-loader.js")
|
||||||
|
>(import.meta.url, "./public-surface-loader.js?scope=missing-location-retry");
|
||||||
|
const tempRoot = createTempDir();
|
||||||
|
const bundledPluginsDir = path.join(tempRoot, "dist");
|
||||||
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
publicSurfaceLoader.resolveBundledPluginPublicArtifactPath({
|
||||||
|
dirName: "demo",
|
||||||
|
artifactBasename: "api.js",
|
||||||
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
const modulePath = path.join(bundledPluginsDir, "demo", "api.js");
|
||||||
|
fs.mkdirSync(path.dirname(modulePath), { recursive: true });
|
||||||
|
fs.writeFileSync(modulePath, 'export const marker = "demo";\n', "utf8");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
publicSurfaceLoader.loadBundledPluginPublicArtifactModuleSync<{ marker: string }>({
|
||||||
|
dirName: "demo",
|
||||||
|
artifactBasename: "api.js",
|
||||||
|
}).marker,
|
||||||
|
).toBe("demo");
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects public artifacts that change after boundary validation", async () => {
|
it("rejects public artifacts that change after boundary validation", async () => {
|
||||||
const createJiti = vi.fn(() => vi.fn(() => ({ marker: "should-not-load" })));
|
const createJiti = vi.fn(() => vi.fn(() => ({ marker: "should-not-load" })));
|
||||||
vi.doMock("jiti", () => ({
|
vi.doMock("jiti", () => ({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const publicSurfaceLocationCache = new Map<
|
|||||||
{
|
{
|
||||||
modulePath: string;
|
modulePath: string;
|
||||||
boundaryRoot: string;
|
boundaryRoot: string;
|
||||||
} | null
|
}
|
||||||
>();
|
>();
|
||||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||||
|
|
||||||
@@ -84,11 +84,14 @@ function resolvePublicSurfaceLocation(params: {
|
|||||||
artifactBasename: string;
|
artifactBasename: string;
|
||||||
}): { modulePath: string; boundaryRoot: string } | null {
|
}): { modulePath: string; boundaryRoot: string } | null {
|
||||||
const key = createResolutionKey(params);
|
const key = createResolutionKey(params);
|
||||||
if (publicSurfaceLocationCache.has(key)) {
|
const cached = publicSurfaceLocationCache.get(key);
|
||||||
return publicSurfaceLocationCache.get(key) ?? null;
|
if (cached) {
|
||||||
|
return cached;
|
||||||
}
|
}
|
||||||
const resolved = resolvePublicSurfaceLocationUncached(params);
|
const resolved = resolvePublicSurfaceLocationUncached(params);
|
||||||
publicSurfaceLocationCache.set(key, resolved);
|
if (resolved) {
|
||||||
|
publicSurfaceLocationCache.set(key, resolved);
|
||||||
|
}
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user