mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
refactor(plugins): trim persisted plugin registry state
This commit is contained in:
@@ -93,6 +93,7 @@ describe("listManifestInstalledChannelIds", () => {
|
||||
diagnostics: [],
|
||||
},
|
||||
contribution: "channels",
|
||||
config: autoEnabledConfig,
|
||||
});
|
||||
expect(installedIds).toEqual(new Set(["slack"]));
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export function listManifestInstalledChannelIds(params: {
|
||||
env: params.env ?? process.env,
|
||||
});
|
||||
return new Set(
|
||||
listPluginContributionIds({ index, contribution: "channels" }).map(
|
||||
listPluginContributionIds({ index, contribution: "channels", config: resolvedConfig }).map(
|
||||
(channelId) => channelId as ChannelChoice,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -41,6 +41,7 @@ function collectMatchingContributionOwners(
|
||||
index: PluginRegistrySnapshot,
|
||||
contribution: "providers" | "cliBackends",
|
||||
providerFilter: string,
|
||||
cfg: OpenClawConfig,
|
||||
options: { includeDisabled?: boolean } = {},
|
||||
): string[] {
|
||||
if (contribution === "providers") {
|
||||
@@ -49,6 +50,7 @@ function collectMatchingContributionOwners(
|
||||
index,
|
||||
providerId: providerFilter,
|
||||
includeDisabled: options.includeDisabled,
|
||||
config: cfg,
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -58,6 +60,7 @@ function collectMatchingContributionOwners(
|
||||
contribution: "cliBackends",
|
||||
matches: (contributionId) => normalizeProviderId(contributionId) === providerFilter,
|
||||
includeDisabled: options.includeDisabled,
|
||||
config: cfg,
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -72,17 +75,17 @@ function resolveInstalledIndexPluginIdsForProviderFilter(params: {
|
||||
env: params.env,
|
||||
});
|
||||
const pluginIds = [
|
||||
...collectMatchingContributionOwners(index, "providers", params.providerFilter),
|
||||
...collectMatchingContributionOwners(index, "cliBackends", params.providerFilter),
|
||||
...collectMatchingContributionOwners(index, "providers", params.providerFilter, params.cfg),
|
||||
...collectMatchingContributionOwners(index, "cliBackends", params.providerFilter, params.cfg),
|
||||
];
|
||||
if (pluginIds.length > 0) {
|
||||
return [...new Set(pluginIds)].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
const disabledPluginIds = [
|
||||
...collectMatchingContributionOwners(index, "providers", params.providerFilter, {
|
||||
...collectMatchingContributionOwners(index, "providers", params.providerFilter, params.cfg, {
|
||||
includeDisabled: true,
|
||||
}),
|
||||
...collectMatchingContributionOwners(index, "cliBackends", params.providerFilter, {
|
||||
...collectMatchingContributionOwners(index, "cliBackends", params.providerFilter, params.cfg, {
|
||||
includeDisabled: true,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -27,7 +27,8 @@ function createIndex(overrides: Partial<InstalledPluginIndex> = {}): InstalledPl
|
||||
version: 1,
|
||||
hostContractVersion: "2026.4.25",
|
||||
compatRegistryVersion: "compat-v1",
|
||||
generatedAt: "2026-04-25T12:00:00.000Z",
|
||||
policyHash: "policy-v1",
|
||||
generatedAtMs: 1777118400000,
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "demo",
|
||||
@@ -35,7 +36,6 @@ function createIndex(overrides: Partial<InstalledPluginIndex> = {}): InstalledPl
|
||||
manifestHash: "manifest-hash",
|
||||
rootDir: "/plugins/demo",
|
||||
origin: "global",
|
||||
enabled: true,
|
||||
contributions: {
|
||||
providers: ["demo"],
|
||||
channels: ["demo-chat"],
|
||||
@@ -174,7 +174,7 @@ describe("installed plugin index persistence", () => {
|
||||
refreshReasons: ["policy-changed"],
|
||||
persisted: current,
|
||||
current: {
|
||||
plugins: [expect.objectContaining({ pluginId: "demo", enabled: false })],
|
||||
plugins: [expect.objectContaining({ pluginId: "demo" })],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ const InstalledPluginIndexRecordSchema = z
|
||||
packageJsonHash: z.string().optional(),
|
||||
rootDir: z.string(),
|
||||
origin: z.string(),
|
||||
enabled: z.boolean(),
|
||||
enabledByDefault: z.boolean().optional(),
|
||||
contributions: InstalledPluginIndexContributionsSchema,
|
||||
compat: z.array(z.string()),
|
||||
})
|
||||
@@ -80,7 +80,8 @@ const InstalledPluginIndexSchema = z
|
||||
version: z.literal(INSTALLED_PLUGIN_INDEX_VERSION),
|
||||
hostContractVersion: z.string(),
|
||||
compatRegistryVersion: z.string(),
|
||||
generatedAt: z.string(),
|
||||
policyHash: z.string(),
|
||||
generatedAtMs: z.number(),
|
||||
refreshReason: z.string().optional(),
|
||||
plugins: z.array(InstalledPluginIndexRecordSchema),
|
||||
diagnostics: z.array(PluginDiagnosticSchema),
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("installed plugin index", () => {
|
||||
|
||||
expect(index).toMatchObject({
|
||||
version: 1,
|
||||
generatedAt: "2026-04-25T12:00:00.000Z",
|
||||
generatedAtMs: 1777118400000,
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "demo",
|
||||
@@ -162,7 +162,6 @@ describe("installed plugin index", () => {
|
||||
packageVersion: "1.2.3",
|
||||
origin: "global",
|
||||
rootDir: fixture.rootDir,
|
||||
enabled: true,
|
||||
packageInstall: {
|
||||
defaultChoice: "npm",
|
||||
npm: {
|
||||
@@ -221,7 +220,6 @@ describe("installed plugin index", () => {
|
||||
const record = getInstalledPluginRecord(index, "demo");
|
||||
expect(record).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: true,
|
||||
});
|
||||
expect(record?.installRecord).toBeUndefined();
|
||||
expect(isInstalledPluginEnabled(index, "demo")).toBe(true);
|
||||
@@ -249,17 +247,27 @@ describe("installed plugin index", () => {
|
||||
});
|
||||
|
||||
expect(listInstalledPluginRecords(index).map((plugin) => plugin.pluginId)).toEqual(["demo"]);
|
||||
expect(listEnabledInstalledPluginRecords(index)).toEqual([]);
|
||||
const config = {
|
||||
plugins: {
|
||||
entries: {
|
||||
demo: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(listEnabledInstalledPluginRecords(index, config)).toEqual([]);
|
||||
expect(getInstalledPluginRecord(index, "demo")).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: false,
|
||||
});
|
||||
expect(isInstalledPluginEnabled(index, "demo")).toBe(false);
|
||||
expect(listInstalledPluginContributionIds(index, "providers")).toEqual([]);
|
||||
expect(isInstalledPluginEnabled(index, "demo", config)).toBe(false);
|
||||
expect(listInstalledPluginContributionIds(index, "providers", { config })).toEqual([]);
|
||||
expect(
|
||||
listInstalledPluginContributionIds(index, "providers", { includeDisabled: true }),
|
||||
).toEqual(["demo"]);
|
||||
expect(resolveInstalledPluginContributionOwners(index, "providers", "demo")).toEqual([]);
|
||||
expect(
|
||||
resolveInstalledPluginContributionOwners(index, "providers", "demo", { config }),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
resolveInstalledPluginContributionOwners(index, "providers", "demo", {
|
||||
includeDisabled: true,
|
||||
@@ -517,7 +525,17 @@ describe("installed plugin index", () => {
|
||||
env: hermeticEnv(),
|
||||
});
|
||||
|
||||
expect(index.plugins[0]?.enabled).toBe(false);
|
||||
expect(
|
||||
isInstalledPluginEnabled(index, "demo", {
|
||||
plugins: {
|
||||
entries: {
|
||||
demo: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(index.plugins[0]?.contributions.providers).toEqual(["demo"]);
|
||||
});
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export type InstalledPluginIndexRecord = {
|
||||
packageJsonHash?: string;
|
||||
rootDir: string;
|
||||
origin: PluginManifestRecord["origin"];
|
||||
enabled: boolean;
|
||||
enabledByDefault?: boolean;
|
||||
contributions: InstalledPluginIndexContributions;
|
||||
compat: readonly PluginCompatCode[];
|
||||
};
|
||||
@@ -100,7 +100,8 @@ export type InstalledPluginIndex = {
|
||||
version: typeof INSTALLED_PLUGIN_INDEX_VERSION;
|
||||
hostContractVersion: string;
|
||||
compatRegistryVersion: string;
|
||||
generatedAt: string;
|
||||
policyHash: string;
|
||||
generatedAtMs: number;
|
||||
refreshReason?: InstalledPluginIndexRefreshReason;
|
||||
plugins: readonly InstalledPluginIndexRecord[];
|
||||
diagnostics: readonly PluginDiagnostic[];
|
||||
@@ -321,6 +322,40 @@ function resolveCompatRegistryVersion(): string {
|
||||
);
|
||||
}
|
||||
|
||||
function resolvePolicyHash(config: OpenClawConfig | undefined): string {
|
||||
const normalized = normalizePluginsConfigWithResolver(config?.plugins);
|
||||
const channelPolicy: Record<string, boolean> = {};
|
||||
const channels = config?.channels;
|
||||
if (channels && typeof channels === "object" && !Array.isArray(channels)) {
|
||||
for (const [channelId, value] of Object.entries(channels)) {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
const enabled = (value as Record<string, unknown>).enabled;
|
||||
if (typeof enabled === "boolean") {
|
||||
channelPolicy[channelId] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashJson({
|
||||
plugins: {
|
||||
enabled: normalized.enabled,
|
||||
allow: normalized.allow,
|
||||
deny: normalized.deny,
|
||||
slots: normalized.slots,
|
||||
entries: Object.fromEntries(
|
||||
Object.entries(normalized.entries)
|
||||
.flatMap(([pluginId, entry]) =>
|
||||
typeof entry.enabled === "boolean" ? [[pluginId, entry.enabled] as const] : [],
|
||||
)
|
||||
.toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
),
|
||||
},
|
||||
channels: Object.fromEntries(
|
||||
Object.entries(channelPolicy).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function resolveRegistry(params: LoadInstalledPluginIndexParams): {
|
||||
registry: PluginManifestRegistry;
|
||||
candidates: readonly PluginCandidate[];
|
||||
@@ -365,9 +400,8 @@ function buildInstalledPluginIndex(
|
||||
const env = params.env ?? process.env;
|
||||
const { candidates, registry } = resolveRegistry(params);
|
||||
const candidateByRootDir = buildCandidateLookup(candidates);
|
||||
const normalizedConfig = normalizePluginsConfigWithResolver(params.config?.plugins);
|
||||
const diagnostics: PluginDiagnostic[] = [...registry.diagnostics];
|
||||
const generatedAt = (params.now?.() ?? new Date()).toISOString();
|
||||
const generatedAtMs = (params.now?.() ?? new Date()).getTime();
|
||||
const plugins = registry.plugins.map((record): InstalledPluginIndexRecord => {
|
||||
const candidate = candidateByRootDir.get(record.rootDir);
|
||||
const packageJsonPath = resolvePackageJsonPath(candidate);
|
||||
@@ -388,24 +422,18 @@ function buildInstalledPluginIndex(
|
||||
required: false,
|
||||
})
|
||||
: undefined;
|
||||
const enabled = resolveEffectiveEnableState({
|
||||
id: record.id,
|
||||
origin: record.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: record.enabledByDefault,
|
||||
}).enabled;
|
||||
|
||||
const indexRecord: InstalledPluginIndexRecord = {
|
||||
pluginId: record.id,
|
||||
manifestPath: record.manifestPath,
|
||||
manifestHash,
|
||||
rootDir: record.rootDir,
|
||||
origin: record.origin,
|
||||
enabled,
|
||||
contributions: buildContributions(record),
|
||||
compat: collectCompatCodes(record),
|
||||
};
|
||||
if (record.enabledByDefault === true) {
|
||||
indexRecord.enabledByDefault = true;
|
||||
}
|
||||
if (candidate?.packageName) {
|
||||
indexRecord.packageName = candidate.packageName;
|
||||
}
|
||||
@@ -432,7 +460,8 @@ function buildInstalledPluginIndex(
|
||||
version: INSTALLED_PLUGIN_INDEX_VERSION,
|
||||
hostContractVersion: resolveCompatibilityHostVersion(env),
|
||||
compatRegistryVersion: resolveCompatRegistryVersion(),
|
||||
generatedAt,
|
||||
policyHash: resolvePolicyHash(params.config),
|
||||
generatedAtMs,
|
||||
...(params.refreshReason ? { refreshReason: params.refreshReason } : {}),
|
||||
plugins,
|
||||
diagnostics,
|
||||
@@ -459,8 +488,19 @@ export function listInstalledPluginRecords(
|
||||
|
||||
export function listEnabledInstalledPluginRecords(
|
||||
index: InstalledPluginIndex,
|
||||
config?: OpenClawConfig,
|
||||
): readonly InstalledPluginIndexRecord[] {
|
||||
return index.plugins.filter((plugin) => plugin.enabled);
|
||||
const normalizedConfig = normalizePluginsConfigWithResolver(config?.plugins);
|
||||
return index.plugins.filter(
|
||||
(plugin) =>
|
||||
resolveEffectiveEnableState({
|
||||
id: plugin.pluginId,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
}).enabled,
|
||||
);
|
||||
}
|
||||
|
||||
export function getInstalledPluginRecord(
|
||||
@@ -470,21 +510,38 @@ export function getInstalledPluginRecord(
|
||||
return index.plugins.find((plugin) => plugin.pluginId === pluginId);
|
||||
}
|
||||
|
||||
export function isInstalledPluginEnabled(index: InstalledPluginIndex, pluginId: string): boolean {
|
||||
return getInstalledPluginRecord(index, pluginId)?.enabled === true;
|
||||
export function isInstalledPluginEnabled(
|
||||
index: InstalledPluginIndex,
|
||||
pluginId: string,
|
||||
config?: OpenClawConfig,
|
||||
): boolean {
|
||||
const record = getInstalledPluginRecord(index, pluginId);
|
||||
if (!record) {
|
||||
return false;
|
||||
}
|
||||
const normalizedConfig = normalizePluginsConfigWithResolver(config?.plugins);
|
||||
return resolveEffectiveEnableState({
|
||||
id: record.pluginId,
|
||||
origin: record.origin,
|
||||
config: normalizedConfig,
|
||||
rootConfig: config,
|
||||
enabledByDefault: record.enabledByDefault,
|
||||
}).enabled;
|
||||
}
|
||||
|
||||
function resolveContributionRecordSet(
|
||||
index: InstalledPluginIndex,
|
||||
options: { includeDisabled?: boolean },
|
||||
options: { includeDisabled?: boolean; config?: OpenClawConfig },
|
||||
): readonly InstalledPluginIndexRecord[] {
|
||||
return options.includeDisabled ? index.plugins : listEnabledInstalledPluginRecords(index);
|
||||
return options.includeDisabled
|
||||
? index.plugins
|
||||
: listEnabledInstalledPluginRecords(index, options.config);
|
||||
}
|
||||
|
||||
export function listInstalledPluginContributionIds(
|
||||
index: InstalledPluginIndex,
|
||||
contribution: InstalledPluginContributionKey,
|
||||
options: { includeDisabled?: boolean } = {},
|
||||
options: { includeDisabled?: boolean; config?: OpenClawConfig } = {},
|
||||
): readonly string[] {
|
||||
return sortUnique(
|
||||
resolveContributionRecordSet(index, options).flatMap(
|
||||
@@ -497,7 +554,7 @@ export function resolveInstalledPluginContributionOwners(
|
||||
index: InstalledPluginIndex,
|
||||
contribution: InstalledPluginContributionKey,
|
||||
matches: string | ((contributionId: string) => boolean),
|
||||
options: { includeDisabled?: boolean } = {},
|
||||
options: { includeDisabled?: boolean; config?: OpenClawConfig } = {},
|
||||
): readonly string[] {
|
||||
const matcher =
|
||||
typeof matches === "string" ? (contributionId: string) => contributionId === matches : matches;
|
||||
@@ -598,6 +655,9 @@ export function diffInstalledPluginIndexInvalidationReasons(
|
||||
if (previous.compatRegistryVersion !== current.compatRegistryVersion) {
|
||||
reasons.add("compat-registry-changed");
|
||||
}
|
||||
if (previous.policyHash !== current.policyHash) {
|
||||
reasons.add("policy-changed");
|
||||
}
|
||||
|
||||
const previousByPluginId = new Map(previous.plugins.map((plugin) => [plugin.pluginId, plugin]));
|
||||
const currentByPluginId = new Map(current.plugins.map((plugin) => [plugin.pluginId, plugin]));
|
||||
@@ -614,9 +674,6 @@ export function diffInstalledPluginIndexInvalidationReasons(
|
||||
) {
|
||||
reasons.add("source-changed");
|
||||
}
|
||||
if (previousPlugin.enabled !== currentPlugin.enabled) {
|
||||
reasons.add("policy-changed");
|
||||
}
|
||||
if (previousPlugin.manifestHash !== currentPlugin.manifestHash) {
|
||||
reasons.add("stale-manifest");
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ describe("plugin registry facade", () => {
|
||||
expect(listPluginRecords({ index }).map((plugin) => plugin.pluginId)).toEqual(["demo"]);
|
||||
expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: true,
|
||||
});
|
||||
expect(isPluginEnabled({ index, pluginId: "demo" })).toBe(true);
|
||||
expect(listPluginContributionIds({ index, contribution: "providers" })).toEqual(["demo"]);
|
||||
@@ -133,12 +132,21 @@ describe("plugin registry facade", () => {
|
||||
|
||||
expect(getPluginRecord({ index, pluginId: "demo" })).toMatchObject({
|
||||
pluginId: "demo",
|
||||
enabled: false,
|
||||
});
|
||||
expect(resolveProviderOwners({ index, providerId: "demo" })).toEqual([]);
|
||||
expect(resolveProviderOwners({ index, providerId: "demo", includeDisabled: true })).toEqual([
|
||||
"demo",
|
||||
]);
|
||||
const config = {
|
||||
plugins: {
|
||||
entries: {
|
||||
demo: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(isPluginEnabled({ index, pluginId: "demo", config })).toBe(false);
|
||||
expect(resolveProviderOwners({ index, providerId: "demo", config })).toEqual([]);
|
||||
expect(
|
||||
resolveProviderOwners({ index, providerId: "demo", config, includeDisabled: true }),
|
||||
).toEqual(["demo"]);
|
||||
});
|
||||
|
||||
it("exposes explicit persisted registry inspect and refresh operations", async () => {
|
||||
|
||||
@@ -83,7 +83,7 @@ export function getPluginRecord(params: GetPluginRecordParams): PluginRegistryRe
|
||||
}
|
||||
|
||||
export function isPluginEnabled(params: GetPluginRecordParams): boolean {
|
||||
return isInstalledPluginEnabled(resolveSnapshot(params), params.pluginId);
|
||||
return isInstalledPluginEnabled(resolveSnapshot(params), params.pluginId, params.config);
|
||||
}
|
||||
|
||||
export function listPluginContributionIds(
|
||||
@@ -91,6 +91,7 @@ export function listPluginContributionIds(
|
||||
): readonly string[] {
|
||||
return listInstalledPluginContributionIds(resolveSnapshot(params), params.contribution, {
|
||||
includeDisabled: params.includeDisabled,
|
||||
config: params.config,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,6 +104,7 @@ export function resolvePluginContributionOwners(
|
||||
params.matches,
|
||||
{
|
||||
includeDisabled: params.includeDisabled,
|
||||
config: params.config,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export function resolveInstalledPluginProviderContributionIds(
|
||||
index,
|
||||
contribution: "providers",
|
||||
includeDisabled: params.includeDisabled,
|
||||
config: params.config,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user