import fs from "node:fs" ;
import path from "node:path" ;
import { afterEach, describe, expect, it, vi } from "vitest" ;
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js" ;
import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js" ;
import { clearPluginDiscoveryCache } from "../plugins/discovery.js" ;
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js" ;
import {
evaluateBundledPluginPublicSurfaceAccess,
resetFacadeActivationCheckRuntimeStateForTest,
resolveBundledPluginPublicSurfaceAccess as resolveActivationCheckBundledPluginPublicSurfaceAccess,
throwForBundledPluginPublicSurfaceAccess,
} from "./facade-activation-check.runtime.js" ;
import {
__testing,
listImportedBundledPluginFacadeIds,
loadBundledPluginPublicSurfaceModuleSync,
resetFacadeRuntimeStateForTest,
} from "./facade-runtime.js" ;
import {
createBundledPluginPublicSurfaceFixture,
createPluginSdkTestHarness,
createThrowingBundledPluginPublicSurfaceFixture,
} from "./test-helpers.js" ;
const { createTempDirSync } = createPluginSdkTestHarness();
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
function createBundledPluginDir(prefix: string, marker: string): string {
return createBundledPluginPublicSurfaceFixture({ createTempDirSync, marker, prefix });
}
function createThrowingPluginDir(prefix: string): string {
return createThrowingBundledPluginPublicSurfaceFixture({ createTempDirSync, prefix });
}
afterEach(() => {
vi.restoreAllMocks();
clearRuntimeConfigSnapshot();
resetFacadeRuntimeStateForTest();
resetFacadeActivationCheckRuntimeStateForTest();
clearPluginDiscoveryCache();
clearPluginManifestRegistryCache();
vi.doUnmock("../plugins/manifest-registry.js" );
if (originalBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
}
if (originalStateDir === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = originalStateDir;
}
});
describe("plugin-sdk facade runtime" , () => {
it("honors bundled plugin dir overrides outside the package root" , () => {
const overrideA = createBundledPluginDir("openclaw-facade-runtime-a-" , "override-a" );
const overrideB = createBundledPluginDir("openclaw-facade-runtime-b-" , "override-b" );
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideA;
const fromA = __testing.resolveFacadeModuleLocation({
dirName: "demo" ,
artifactBasename: "api.js" ,
});
expect(fromA).toEqual({
modulePath: path.join(overrideA, "demo" , "api.js" ),
boundaryRoot: overrideA,
});
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideB;
const fromB = __testing.resolveFacadeModuleLocation({
dirName: "demo" ,
artifactBasename: "api.js" ,
});
expect(fromB).toEqual({
modulePath: path.join(overrideB, "demo" , "api.js" ),
boundaryRoot: overrideB,
});
});
it("returns the same object identity on repeated calls (sentinel consistency)" , () => {
const dir = createBundledPluginDir("openclaw-facade-identity-" , "identity-check" );
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
const location = {
modulePath: path.join(dir, "demo" , "api.js" ),
boundaryRoot: dir,
};
const loader = vi.fn(() => ({ marker: "identity-check" }));
const first = __testing.loadFacadeModuleAtLocationSync<{ marker: string }>({
location,
trackedPluginId: "demo" ,
loadModule: loader,
});
const second = __testing.loadFacadeModuleAtLocationSync<{ marker: string }>({
location,
trackedPluginId: "demo" ,
loadModule: loader,
});
expect(first).toBe(second);
expect(first.marker).toBe("identity-check" );
expect(listImportedBundledPluginFacadeIds()).toEqual(["demo" ]);
expect(loader).toHaveBeenCalledTimes(1 );
});
it("breaks circular facade re-entry during module evaluation" , () => {
const dir = createBundledPluginDir("openclaw-facade-circular-" , "circular-ok" );
const location = {
modulePath: path.join(dir, "demo" , "api.js" ),
boundaryRoot: dir,
};
let reentered: { marker?: string } | undefined;
const loader = vi.fn(() => {
reentered = __testing.loadFacadeModuleAtLocationSync<{ marker?: string }>({
location,
trackedPluginId: "demo" ,
loadModule: loader,
});
return { marker: "circular-ok" };
});
const loaded = __testing.loadFacadeModuleAtLocationSync<{ marker: string }>({
location,
trackedPluginId: "demo" ,
loadModule: loader,
});
expect(loaded.marker).toBe("circular-ok" );
expect(reentered).toBe(loaded);
expect(reentered?.marker).toBe("circular-ok" );
expect(loader).toHaveBeenCalledTimes(1 );
});
it("back-fills the sentinel before post-load facade tracking re-enters" , () => {
const dir = createBundledPluginDir("openclaw-facade-post-load-" , "post-load-ok" );
const location = {
modulePath: path.join(dir, "demo" , "api.js" ),
boundaryRoot: dir,
};
const reentryMarkers: Array<string | undefined> = [];
const loader = vi.fn(() => ({ marker: "post-load-ok" }));
const loaded = __testing.loadFacadeModuleAtLocationSync<{ marker: string }>({
location,
trackedPluginId: () => {
const reentered = __testing.loadFacadeModuleAtLocationSync<{ marker?: string }>({
location,
trackedPluginId: "demo" ,
loadModule: loader,
});
reentryMarkers.push(reentered.marker);
return "demo" ;
},
loadModule: loader,
});
expect(loaded.marker).toBe("post-load-ok" );
expect(reentryMarkers.length).toBeGreaterThan(0 );
expect(reentryMarkers.every((marker) => marker === "post-load-ok" )).toBe(true );
expect(listImportedBundledPluginFacadeIds()).toEqual(["demo" ]);
expect(loader).toHaveBeenCalledTimes(1 );
});
it("clears the cache on load failure so retries re-execute" , () => {
const dir = createThrowingPluginDir("openclaw-facade-throw-" );
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
expect(() =>
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
dirName: "bad" ,
artifactBasename: "api.js" ,
}),
).toThrow("plugin load failure" );
expect(listImportedBundledPluginFacadeIds()).toEqual([]);
// A second call must also throw (not return a stale empty sentinel).
expect(() =>
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
dirName: "bad" ,
artifactBasename: "api.js" ,
}),
).toThrow("plugin load failure" );
});
it("blocks runtime-api facade loads for bundled plugins that are not activated" , () => {
const access = evaluateBundledPluginPublicSurfaceAccess({
params: {
dirName: "discord" ,
artifactBasename: "runtime-api.js" ,
},
manifestRecord: {
id: "discord" ,
origin: "bundled" ,
enabledByDefault: false ,
rootDir: "/tmp/discord" ,
channels: ["discord" ],
},
config: {},
normalizedPluginsConfig: normalizePluginsConfig(),
activationSource: createPluginActivationSource({ config: {} }),
autoEnabledReasons: {},
});
expect(access.allowed).toBe(false );
expect(access.pluginId).toBe("discord" );
expect(access.reason).toBeTruthy();
expect(() =>
throwForBundledPluginPublicSurfaceAccess({
access,
request: {
dirName: "discord" ,
artifactBasename: "runtime-api.js" ,
},
}),
).toThrow(/Bundled plugin public surface access blocked/);
expect(access.allowed).toBe(false );
});
it("allows runtime-api facade loads when the bundled plugin is explicitly enabled" , () => {
const dir = createTempDirSync("openclaw-facade-runtime-enabled-" );
fs.mkdirSync(path.join(dir, "discord" ), { recursive: true });
fs.writeFileSync(
path.join(dir, "discord" , "runtime-api.js" ),
'export const marker = "runtime-api-enabled";\n' ,
"utf8" ,
);
const config = {
plugins: {
entries: {
discord: {
enabled: true ,
},
},
},
} as const ;
const access = evaluateBundledPluginPublicSurfaceAccess({
params: {
dirName: "discord" ,
artifactBasename: "runtime-api.js" ,
},
manifestRecord: {
id: "discord" ,
origin: "bundled" ,
enabledByDefault: false ,
rootDir: "/tmp/discord" ,
channels: ["discord" ],
},
config,
normalizedPluginsConfig: normalizePluginsConfig(config.plugins),
activationSource: createPluginActivationSource({ config }),
autoEnabledReasons: {},
});
const loader = vi.fn(() => ({ marker: "runtime-api-enabled" }));
const location = {
modulePath: path.join(dir, "discord" , "runtime-api.js" ),
boundaryRoot: dir,
};
expect(access.allowed).toBe(true );
const loaded = __testing.loadFacadeModuleAtLocationSync<{ marker: string }>({
location,
trackedPluginId: "discord" ,
loadModule: loader,
});
expect(loaded.marker).toBe("runtime-api-enabled" );
expect(loader).toHaveBeenCalledTimes(1 );
});
it("resolves a globally-installed plugin whose rootDir basename matches the dirName" , () => {
const lineDir = createTempDirSync("openclaw-facade-global-line-" );
fs.mkdirSync(lineDir, { recursive: true });
fs.writeFileSync(
path.join(lineDir, "runtime-api.js" ),
'export const marker = "global-line";\n' ,
"utf8" ,
);
fs.writeFileSync(
path.join(lineDir, "package.json" ),
JSON.stringify({
name: "@openclaw/line" ,
version: "0.0.0" ,
openclaw: {
extensions: ["./runtime-api.js" ],
channel: { id: "line" },
},
}),
"utf8" ,
);
fs.writeFileSync(
path.join(lineDir, "openclaw.plugin.json" ),
JSON.stringify({
id: "line" ,
channels: ["line" ],
configSchema: { type: "object" , additionalProperties: false , properties: {} },
}),
"utf8" ,
);
expect(
__testing.resolveRegistryPluginModuleLocationFromRegistry({
registry: [
{
id: "line" ,
rootDir: lineDir,
channels: ["line" ],
},
],
dirName: "line" ,
artifactBasename: "runtime-api.js" ,
}),
).toEqual({
modulePath: path.join(lineDir, "runtime-api.js" ),
boundaryRoot: lineDir,
});
});
it("resolves a globally-installed plugin with an encoded scoped rootDir basename" , () => {
const encodedDir = createTempDirSync("openclaw-facade-encoded-line-" );
fs.mkdirSync(encodedDir, { recursive: true });
fs.writeFileSync(
path.join(encodedDir, "runtime-api.js" ),
'export const marker = "encoded-global-line";\n' ,
"utf8" ,
);
fs.writeFileSync(
path.join(encodedDir, "package.json" ),
JSON.stringify({
name: "@openclaw/line" ,
version: "0.0.0" ,
openclaw: {
extensions: ["./runtime-api.js" ],
channel: { id: "line" },
},
}),
"utf8" ,
);
fs.writeFileSync(
path.join(encodedDir, "openclaw.plugin.json" ),
JSON.stringify({
id: "line" ,
channels: ["line" ],
configSchema: { type: "object" , additionalProperties: false , properties: {} },
}),
"utf8" ,
);
expect(
__testing.resolveRegistryPluginModuleLocationFromRegistry({
registry: [
{
id: "line" ,
rootDir: encodedDir,
channels: ["line" ],
},
],
dirName: "line" ,
artifactBasename: "runtime-api.js" ,
}),
).toEqual({
modulePath: path.join(encodedDir, "runtime-api.js" ),
boundaryRoot: encodedDir,
});
});
it("keeps shared runtime-core facades available without plugin activation" , () => {
setRuntimeConfigSnapshot({});
for (const dirName of ["speech-core" , "image-generation-core" , "media-understanding-core" ]) {
expect(
resolveActivationCheckBundledPluginPublicSurfaceAccess({
dirName,
artifactBasename: "runtime-api.js" ,
location: null ,
sourceExtensionsRoot: "" ,
resolutionKey: `runtime-core:${dirName}`,
}),
).toEqual({
allowed: true ,
pluginId: dirName,
});
}
});
it("prefers the source runtime snapshot for facade activation checks" , () => {
const dir = createTempDirSync("openclaw-facade-source-snapshot-" );
fs.mkdirSync(path.join(dir, "demo" ), { recursive: true });
fs.writeFileSync(
path.join(dir, "demo" , "runtime-api.js" ),
'export const marker = "source-snapshot";\n' ,
"utf8" ,
);
fs.writeFileSync(
path.join(dir, "demo" , "openclaw.plugin.json" ),
JSON.stringify({
id: "demo" ,
}),
"utf8" ,
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
setRuntimeConfigSnapshot(
{
plugins: {},
},
{
plugins: {
entries: {
demo: {
enabled: true ,
},
},
},
},
);
expect(
resolveActivationCheckBundledPluginPublicSurfaceAccess({
dirName: "demo" ,
artifactBasename: "runtime-api.js" ,
location: {
modulePath: path.join(dir, "demo" , "runtime-api.js" ),
boundaryRoot: dir,
},
sourceExtensionsRoot: dir,
resolutionKey: "source-snapshot-demo" ,
}),
).toEqual({
allowed: true ,
pluginId: "demo" ,
});
});
});
Messung V0.5 in Prozent C=100 H=98 G=98
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland