mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-21 01:51:39 -06:00
bcc27f6726
* perf(cli): carry prepared plugin metadata through cold read paths * perf(plugins): thread prepared metadata into auto-enable detection --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
136 lines
5.1 KiB
TypeScript
136 lines
5.1 KiB
TypeScript
// `plugins doctor` reports effective-only plugins from the metadata snapshot it already
|
|
// built, so asking for effective ids must not re-derive plugin discovery.
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterAll, beforeEach, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
|
import { createColdPluginFixture } from "./test-helpers/cold-plugin-fixtures.js";
|
|
|
|
const counters = vi.hoisted(() => ({ manifestRegistryRebuilds: 0, discoveryScans: 0 }));
|
|
|
|
vi.mock("./plugin-registry-contributions.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("./plugin-registry-contributions.js")>();
|
|
return {
|
|
...actual,
|
|
loadPluginManifestRegistryForPluginRegistry: (
|
|
...args: Parameters<typeof actual.loadPluginManifestRegistryForPluginRegistry>
|
|
) => {
|
|
counters.manifestRegistryRebuilds += 1;
|
|
return actual.loadPluginManifestRegistryForPluginRegistry(...args);
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("./discovery.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("./discovery.js")>();
|
|
return {
|
|
...actual,
|
|
discoverOpenClawPlugins: (...args: Parameters<typeof actual.discoverOpenClawPlugins>) => {
|
|
counters.discoveryScans += 1;
|
|
return actual.discoverOpenClawPlugins(...args);
|
|
},
|
|
};
|
|
});
|
|
|
|
const { buildPluginDiagnosticsReport } = await import("./status.js");
|
|
const { resolveEffectivePluginIds } = await import("./effective-plugin-ids.js");
|
|
const { loadPluginMetadataSnapshot } = await import("./plugin-metadata-snapshot.js");
|
|
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-effective-plugin-ids-"));
|
|
|
|
function coldPluginRoot(pluginId: string, channelId: string): string {
|
|
const rootDir = path.join(tempRoot, pluginId);
|
|
fs.mkdirSync(rootDir, { recursive: true });
|
|
createColdPluginFixture({
|
|
rootDir,
|
|
pluginId,
|
|
channelId,
|
|
providerId: `${pluginId}-provider`,
|
|
packageName: `@example/${pluginId}`,
|
|
});
|
|
return rootDir;
|
|
}
|
|
|
|
const channelOwnerRoot = coldPluginRoot("cold-plugin", "cold-channel");
|
|
const otherRoot = coldPluginRoot("other-plugin", "other-channel");
|
|
|
|
const config: OpenClawConfig = {
|
|
channels: { "cold-channel": { enabled: true } },
|
|
plugins: {
|
|
load: { paths: [channelOwnerRoot, otherRoot] },
|
|
entries: { "cold-plugin": { enabled: true }, "other-plugin": { enabled: true } },
|
|
},
|
|
} as OpenClawConfig;
|
|
|
|
function countReport(params: { effectiveOnly: boolean; onlyPluginIds?: readonly string[] }): {
|
|
rebuilds: number;
|
|
scans: number;
|
|
ids: string[];
|
|
} {
|
|
counters.manifestRegistryRebuilds = 0;
|
|
counters.discoveryScans = 0;
|
|
const report = buildPluginDiagnosticsReport({ config, env: process.env, ...params });
|
|
return {
|
|
rebuilds: counters.manifestRegistryRebuilds,
|
|
scans: counters.discoveryScans,
|
|
ids: report.plugins.map((plugin) => plugin.id).toSorted(),
|
|
};
|
|
}
|
|
|
|
function countResolve(metadataSnapshot: PluginMetadataSnapshot): {
|
|
rebuilds: number;
|
|
ids: string[];
|
|
} {
|
|
counters.manifestRegistryRebuilds = 0;
|
|
const ids = resolveEffectivePluginIds({ config, env: process.env, metadataSnapshot });
|
|
return { rebuilds: counters.manifestRegistryRebuilds, ids };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.stubEnv("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
|
|
vi.stubEnv("OPENCLAW_HOME", path.join(tempRoot, "home"));
|
|
vi.stubEnv("OPENCLAW_STATE_DIR", path.join(tempRoot, "state"));
|
|
});
|
|
|
|
afterAll(() => {
|
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it("does not re-derive discovery when reporting effective-only plugins", () => {
|
|
const all = countReport({ effectiveOnly: false });
|
|
const effective = countReport({ effectiveOnly: true });
|
|
|
|
// The effective-only filter still selects the configured channel owner.
|
|
expect(effective.ids).toEqual(["cold-plugin", "other-plugin"]);
|
|
// ...and it reuses the snapshot the report already built instead of rediscovering.
|
|
expect({
|
|
rebuilds: effective.rebuilds,
|
|
scansMoreThanFullReport: effective.scans > all.scans,
|
|
}).toEqual({ rebuilds: 0, scansMoreThanFullReport: false });
|
|
});
|
|
|
|
// A supplied snapshot is an optimization, never an input to the answer.
|
|
it("only reuses a snapshot that answers for the whole config", () => {
|
|
const env = process.env;
|
|
const withoutSnapshot = resolveEffectivePluginIds({ config, env });
|
|
const full = countResolve(loadPluginMetadataSnapshot({ config, env }));
|
|
// `recordPluginInstallSource` asks for one plugin's effective state, which scopes the
|
|
// snapshot to that plugin and truncates its manifest set to that plugin alone.
|
|
const scoped = countResolve(
|
|
loadPluginMetadataSnapshot({ config, env, pluginIds: ["other-plugin"] }),
|
|
);
|
|
|
|
expect({ full: full.ids, scoped: scoped.ids }).toEqual({
|
|
full: withoutSnapshot,
|
|
scoped: withoutSnapshot,
|
|
});
|
|
// A whole-config snapshot is reused; a plugin-scoped one cannot stand in for it.
|
|
expect({ fullReused: full.rebuilds === 0, scopedReused: scoped.rebuilds === 0 }).toEqual({
|
|
fullReused: true,
|
|
scopedReused: false,
|
|
});
|
|
});
|