fix: preserve scoped bundled plugin metadata lookup

This commit is contained in:
Peter Steinberger
2026-05-02 02:59:34 +01:00
parent 0a6c9ca9ee
commit 7d827a8022
4 changed files with 119 additions and 11 deletions

View File

@@ -0,0 +1,42 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
listBundledChannelPluginMetadata,
resolveBundledChannelWorkspacePath,
} from "./bundled-channel-runtime.js";
const tempRoots: string[] = [];
function createTempRoot(): string {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-empty-bundled-root-"));
tempRoots.push(tempRoot);
return tempRoot;
}
afterEach(() => {
for (const tempRoot of tempRoots.splice(0)) {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
describe("bundled channel runtime metadata", () => {
it("preserves explicit empty bundled roots", () => {
const tempRoot = createTempRoot();
expect(listBundledChannelPluginMetadata({ rootDir: tempRoot })).toEqual([]);
expect(resolveBundledChannelWorkspacePath({ rootDir: tempRoot, pluginId: "telegram" })).toBe(
null,
);
});
it("preserves explicit missing bundled scan roots", () => {
const tempRoot = createTempRoot();
const missingScanDir = path.join(tempRoot, "missing-extensions");
expect(
listBundledChannelPluginMetadata({ rootDir: tempRoot, scanDir: missingScanDir }),
).toEqual([]);
});
});

View File

@@ -10,6 +10,11 @@ type BundledChannelEntryPathPair = {
built: string;
};
type BundledMetadataScope =
| { kind: "default" }
| { kind: "empty" }
| { kind: "env"; env: NodeJS.ProcessEnv };
export type BundledChannelPluginMetadata = {
dirName: string;
source: BundledChannelEntryPathPair;
@@ -22,22 +27,28 @@ export type BundledChannelPluginMetadata = {
rootDir: string;
};
function resolveBundledMetadataEnv(params?: {
function resolveBundledMetadataScope(params?: {
rootDir?: string;
scanDir?: string;
}): NodeJS.ProcessEnv | undefined {
}): BundledMetadataScope {
const overrideDir = params?.scanDir
? path.resolve(params.scanDir)
: params?.rootDir
? resolveBundledPluginsDirForRoot(params.rootDir)
: undefined;
if (!overrideDir) {
return undefined;
return params?.rootDir ? { kind: "empty" } : { kind: "default" };
}
if (!fs.existsSync(overrideDir)) {
return { kind: "empty" };
}
return {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: overrideDir,
OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
kind: "env",
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: overrideDir,
OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
},
};
}
@@ -87,8 +98,12 @@ export function listBundledChannelPluginMetadata(params?: {
includeChannelConfigs?: boolean;
includeSyntheticChannelConfigs?: boolean;
}): readonly BundledChannelPluginMetadata[] {
const scope = resolveBundledMetadataScope(params);
if (scope.kind === "empty") {
return [];
}
return loadPluginManifestRegistryForPluginRegistry({
env: resolveBundledMetadataEnv(params),
env: scope.kind === "env" ? scope.env : undefined,
includeDisabled: true,
}).plugins.flatMap((record) => toBundledChannelPluginMetadata(record) ?? []);
}

View File

@@ -178,6 +178,55 @@ describe("normalizePluginsConfig", () => {
expect(result.entries["unknown-plugin-four"]?.enabled).toBe(true);
expect(discoverPlugins).toHaveBeenCalledTimes(1);
});
it("keeps alias lookup limited to bundled plugin manifests", async () => {
vi.resetModules();
const discovery = await import("./discovery.js");
const manifest = await import("./manifest.js");
const discoverPlugins = vi.spyOn(discovery, "discoverOpenClawPlugins").mockReturnValue({
candidates: [
{
idHint: "anthropic",
source: "/tmp/openclaw-bundled-anthropic/index.js",
rootDir: "/tmp/openclaw-bundled-anthropic",
origin: "bundled",
bundledManifest: {
id: "anthropic",
configSchema: {},
providers: ["anthropic"],
},
},
{
idHint: "external-anthropic",
source: "/tmp/openclaw-global-anthropic/index.js",
rootDir: "/tmp/openclaw-global-anthropic",
origin: "global",
},
],
diagnostics: [],
});
const loadManifest = vi.spyOn(manifest, "loadPluginManifest").mockReturnValue({
ok: true,
manifestPath: "/tmp/openclaw-global-anthropic/openclaw.plugin.json",
manifest: {
id: "external-anthropic",
configSchema: {},
providers: ["anthropic"],
},
});
const { normalizePluginsConfig: normalizeFreshPluginsConfig } =
await import("./config-state.js");
discoverPlugins.mockClear();
loadManifest.mockClear();
const result = normalizeFreshPluginsConfig({
deny: ["anthropic"],
});
expect(result.deny).toEqual(["anthropic"]);
expect(discoverPlugins).toHaveBeenCalledTimes(1);
expect(loadManifest).not.toHaveBeenCalled();
});
});
describe("resolveEffectiveEnableState", () => {

View File

@@ -49,10 +49,12 @@ const BUILT_IN_PLUGIN_ALIAS_LOOKUP = new Map<string, string>([
function getBundledPluginAliasLookup(): ReadonlyMap<string, string> {
const lookup = new Map<string, string>();
for (const candidate of discoverOpenClawPlugins({}).candidates) {
const manifestResult =
candidate.origin === "bundled" && candidate.bundledManifest
? { ok: true as const, manifest: candidate.bundledManifest }
: loadPluginManifest(candidate.rootDir, candidate.origin !== "bundled");
if (candidate.origin !== "bundled") {
continue;
}
const manifestResult = candidate.bundledManifest
? { ok: true as const, manifest: candidate.bundledManifest }
: loadPluginManifest(candidate.rootDir, false);
if (!manifestResult.ok) {
continue;
}