Files
openclaw/src/channels/plugins/bundled.shape-guard.test.ts
2026-04-23 03:17:06 +01:00

960 lines
35 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../../test/helpers/import-fresh.ts";
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
const bundledChannelEntrypointPaths = ["index.ts", "channel-entry.ts", "setup-entry.ts"] as const;
type BundledEntrySource = { built?: string; source?: string };
function restoreBundledPluginsDir(previousBundledPluginsDir: string | undefined) {
if (previousBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = previousBundledPluginsDir;
}
}
function alphaChannelMetadata({ includeSetup = false }: { includeSetup?: boolean } = {}) {
return {
dirName: "alpha",
manifest: {
id: "alpha",
channels: ["alpha"],
},
source: {
source: "./index.js",
built: "./index.js",
},
...(includeSetup
? {
setupSource: {
source: "./setup-entry.js",
built: "./setup-entry.js",
},
}
: {}),
};
}
function resolveAlphaDistExtensionEntry(
rootDir: string,
entry: BundledEntrySource,
pluginDirName?: string,
) {
return path.join(
rootDir,
"dist",
"extensions",
pluginDirName ?? "alpha",
(entry.built ?? entry.source ?? "./index.js").replace(/^\.\//u, ""),
);
}
function mockAlphaDistExtensionRuntime() {
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: () => [alphaChannelMetadata({ includeSetup: true })],
resolveBundledChannelGeneratedPath: resolveAlphaDistExtensionEntry,
}));
}
function collectBundledChannelEntrypointOffenders(
bundledPluginRoots: string[],
isOffender: (source: string, filePath: string) => boolean,
) {
const offenders: string[] = [];
for (const extensionDir of bundledPluginRoots) {
for (const relativePath of bundledChannelEntrypointPaths) {
const filePath = path.join(extensionDir, relativePath);
if (!fs.existsSync(filePath)) {
continue;
}
const source = fs.readFileSync(filePath, "utf8");
const usesEntryHelpers =
source.includes("defineBundledChannelEntry") ||
source.includes("defineBundledChannelSetupEntry");
if (usesEntryHelpers && isOffender(source, filePath)) {
offenders.push(path.relative(process.cwd(), filePath));
}
}
}
return offenders;
}
afterEach(() => {
vi.resetModules();
vi.doUnmock("../../plugins/bundled-channel-runtime.js");
vi.doUnmock("../../plugins/bundled-plugin-metadata.js");
vi.doUnmock("../../plugins/discovery.js");
vi.doUnmock("../../plugins/manifest-registry.js");
vi.doUnmock("../../plugins/channel-catalog-registry.js");
vi.doUnmock("../../infra/boundary-file-read.js");
vi.doUnmock("jiti");
});
describe("bundled channel entry shape guards", () => {
const bundledPluginRoots = loadPluginManifestRegistry({ cache: true, config: {} })
.plugins.filter((plugin) => plugin.origin === "bundled")
.map((plugin) => plugin.rootDir);
it("treats missing bundled discovery results as empty", async () => {
vi.doMock("../../plugins/bundled-channel-runtime.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../../plugins/bundled-channel-runtime.js")>();
return {
...actual,
listBundledChannelPluginMetadata: () => [],
};
});
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=missing-bundled-discovery",
);
expect(bundled.listBundledChannelPlugins()).toEqual([]);
expect(bundled.listBundledChannelSetupPlugins()).toEqual([]);
});
it("loads real bundled channel entry contracts from the source tree", async () => {
vi.doMock("../../plugins/bundled-channel-runtime.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../../plugins/bundled-channel-runtime.js")>();
return {
...actual,
listBundledChannelPluginMetadata: (params: {
includeChannelConfigs: boolean;
includeSyntheticChannelConfigs: boolean;
}) =>
actual
.listBundledChannelPluginMetadata(params)
.filter((metadata) => metadata.manifest.id === "slack"),
};
});
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=real-bundled-source-tree",
);
expect(bundled.listBundledChannelPluginIds()).toEqual(["slack"]);
expect(bundled.hasBundledChannelEntryFeature("slack", "accountInspect")).toBe(true);
});
it("fills sparse bundled channel plugin metadata from package metadata", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-metadata-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginDir = path.join(tempRoot, "dist", "extensions", "alpha");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
"const plugin = {",
" id: 'alpha',",
" meta: { id: 'alpha' },",
" capabilities: { chatTypes: ['direct'] },",
" config: {},",
"};",
"export default {",
" kind: 'bundled-channel-entry',",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" register() {},",
" loadChannelPlugin() { return plugin; },",
"};",
"",
].join("\n"),
"utf8",
);
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: () => [
{
...alphaChannelMetadata(),
packageManifest: {
channel: {
id: "alpha",
label: "Alpha",
selectionLabel: "Use Alpha",
docsPath: "/channels/alpha",
blurb: "Alpha channel metadata.",
},
},
},
],
resolveBundledChannelGeneratedPath: (
_rootDir: string,
entry: { built?: string; source?: string },
) =>
path.join(pluginDir, (entry.built ?? entry.source ?? "./index.js").replace(/^\.\//u, "")),
}));
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(tempRoot, "dist", "extensions");
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-package-metadata",
);
const plugin = bundled.requireBundledChannelPlugin("alpha");
expect(plugin.meta).toMatchObject({
id: "alpha",
label: "Alpha",
selectionLabel: "Use Alpha",
docsPath: "/channels/alpha",
blurb: "Alpha channel metadata.",
});
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("uses the active bundled plugin root override for channel entry loading", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-override-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginDir = path.join(tempRoot, "dist", "extensions", "alpha");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
"globalThis.__bundledOverrideRuntime = undefined;",
"const plugin = { id: 'alpha', meta: {}, capabilities: {}, config: {} };",
"export default {",
" kind: 'bundled-channel-entry',",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" register() {},",
" loadChannelPlugin() { return plugin; },",
" setChannelRuntime(runtime) { globalThis.__bundledOverrideRuntime = runtime.marker; },",
"};",
"",
].join("\n"),
"utf8",
);
let metadataRootDir: string | undefined;
let generatedRootDir: string | undefined;
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: (params?: { rootDir?: string }) => {
metadataRootDir = params?.rootDir;
return [alphaChannelMetadata()];
},
resolveBundledChannelGeneratedPath: (
rootDir: string,
entry: { built?: string; source?: string },
pluginDirName?: string,
) => {
generatedRootDir = rootDir;
return path.join(
rootDir,
"dist",
"extensions",
pluginDirName ?? "alpha",
(entry.built ?? entry.source ?? "./index.js").replace(/^\.\//u, ""),
);
},
}));
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(tempRoot, "dist", "extensions");
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-override-root",
);
bundled.setBundledChannelRuntime("alpha", { marker: "ok" } as never);
const testGlobal = globalThis as typeof globalThis & {
__bundledOverrideRuntime?: unknown;
};
expect(metadataRootDir).toBe(tempRoot);
expect(generatedRootDir).toBe(tempRoot);
expect(testGlobal.__bundledOverrideRuntime).toBe("ok");
expect(bundled.requireBundledChannelPlugin("alpha").id).toBe("alpha");
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(tempRoot, { recursive: true, force: true });
delete (globalThis as { __bundledOverrideRuntime?: unknown }).__bundledOverrideRuntime;
}
});
it("treats direct bundled plugin-tree overrides as scan roots", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-direct-override-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginsRoot = path.join(tempRoot, "bundled-plugins");
const pluginDir = path.join(pluginsRoot, "alpha");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
"globalThis.__bundledOverrideRuntime = undefined;",
"const plugin = { id: 'alpha', meta: {}, capabilities: {}, config: {} };",
"export default {",
" kind: 'bundled-channel-entry',",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" register() {},",
" loadChannelPlugin() { return plugin; },",
" setChannelRuntime(runtime) { globalThis.__bundledOverrideRuntime = runtime.marker; },",
"};",
"",
].join("\n"),
"utf8",
);
let metadataScanDir: string | undefined;
let generatedRootDir: string | undefined;
let generatedScanDir: string | undefined;
vi.doMock("../../plugins/bundled-channel-runtime.js", () => ({
listBundledChannelPluginMetadata: (params?: { rootDir?: string; scanDir?: string }) => {
metadataScanDir = params?.scanDir;
return [alphaChannelMetadata()];
},
resolveBundledChannelGeneratedPath: (
rootDir: string,
entry: { built?: string; source?: string },
pluginDirName?: string,
scanDir?: string,
) => {
generatedRootDir = rootDir;
generatedScanDir = scanDir;
return path.join(
scanDir ?? path.join(rootDir, "dist", "extensions"),
pluginDirName ?? "alpha",
(entry.built ?? entry.source ?? "./index.js").replace(/^\.\//u, ""),
);
},
}));
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = pluginsRoot;
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-direct-override-root",
);
bundled.setBundledChannelRuntime("alpha", { marker: "ok" } as never);
const testGlobal = globalThis as typeof globalThis & {
__bundledOverrideRuntime?: unknown;
};
expect(metadataScanDir).toBe(pluginsRoot);
expect(generatedRootDir).toBe(pluginsRoot);
expect(generatedScanDir).toBe(pluginsRoot);
expect(testGlobal.__bundledOverrideRuntime).toBe("ok");
expect(bundled.requireBundledChannelPlugin("alpha").id).toBe("alpha");
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(tempRoot, { recursive: true, force: true });
delete (globalThis as { __bundledOverrideRuntime?: unknown }).__bundledOverrideRuntime;
}
});
it("partitions bundled channel lazy caches by active bundled root without re-importing", async () => {
const rootA = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-root-a-"));
const rootB = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-root-b-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const testGlobal = globalThis as typeof globalThis & {
__bundledRootRuntime?: unknown;
};
const writeBundledRoot = (rootDir: string, label: string) => {
const pluginDir = path.join(rootDir, "dist", "extensions", "alpha");
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
`globalThis.__bundledRootRuntime = globalThis.__bundledRootRuntime ?? [];`,
"export default {",
" kind: 'bundled-channel-entry',",
" id: 'alpha',",
` name: ${JSON.stringify(`Alpha ${label}`)},`,
` description: ${JSON.stringify(`Alpha ${label}`)},`,
" register() {},",
" loadChannelPlugin() {",
" return {",
" id: 'alpha',",
` meta: { id: 'alpha', label: ${JSON.stringify(`Alpha ${label}`)} },`,
" capabilities: {},",
" config: {},",
` secrets: { secretTargetRegistryEntries: [{ id: ${JSON.stringify(`channels.alpha.${label}.token`)}, targetType: 'channel' }] },`,
" };",
" },",
" loadChannelSecrets() {",
` return { secretTargetRegistryEntries: [{ id: ${JSON.stringify(`channels.alpha.${label}.entry-token`)}, targetType: 'channel' }] };`,
" },",
" setChannelRuntime(runtime) {",
` globalThis.__bundledRootRuntime.push(${JSON.stringify(`entry:${label}`)} + ':' + String(runtime.marker));`,
" },",
"};",
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.js"),
[
"export default {",
" kind: 'bundled-channel-setup-entry',",
" loadSetupPlugin() {",
" return {",
" id: 'alpha',",
` meta: { id: 'alpha', label: ${JSON.stringify(`Setup ${label}`)} },`,
" capabilities: {},",
" config: {},",
` secrets: { secretTargetRegistryEntries: [{ id: ${JSON.stringify(`channels.alpha.${label}.setup-plugin-token`)}, targetType: 'channel' }] },`,
" };",
" },",
" loadSetupSecrets() {",
` return { secretTargetRegistryEntries: [{ id: ${JSON.stringify(`channels.alpha.${label}.setup-entry-token`)}, targetType: 'channel' }] };`,
" },",
"};",
"",
].join("\n"),
"utf8",
);
};
writeBundledRoot(rootA, "A");
writeBundledRoot(rootB, "B");
mockAlphaDistExtensionRuntime();
try {
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-root-partition",
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(rootA, "dist", "extensions");
expect(bundled.requireBundledChannelPlugin("alpha").meta.label).toBe("Alpha A");
expect(bundled.getBundledChannelSetupPlugin("alpha")?.meta.label).toBe("Setup A");
expect(bundled.getBundledChannelSecrets("alpha")?.secretTargetRegistryEntries?.[0]?.id).toBe(
"channels.alpha.A.entry-token",
);
expect(
bundled.getBundledChannelSetupSecrets("alpha")?.secretTargetRegistryEntries?.[0]?.id,
).toBe("channels.alpha.A.setup-entry-token");
bundled.setBundledChannelRuntime("alpha", { marker: "first" } as never);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(rootB, "dist", "extensions");
expect(bundled.requireBundledChannelPlugin("alpha").meta.label).toBe("Alpha B");
expect(bundled.getBundledChannelSetupPlugin("alpha")?.meta.label).toBe("Setup B");
expect(bundled.getBundledChannelSecrets("alpha")?.secretTargetRegistryEntries?.[0]?.id).toBe(
"channels.alpha.B.entry-token",
);
expect(
bundled.getBundledChannelSetupSecrets("alpha")?.secretTargetRegistryEntries?.[0]?.id,
).toBe("channels.alpha.B.setup-entry-token");
bundled.setBundledChannelRuntime("alpha", { marker: "second" } as never);
expect(testGlobal.__bundledRootRuntime).toEqual(["entry:A:first", "entry:B:second"]);
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(rootA, { recursive: true, force: true });
fs.rmSync(rootB, { recursive: true, force: true });
delete testGlobal.__bundledRootRuntime;
}
});
it("loads setup-entry feature plugins without loading the main channel entry", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-setup-only-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginDir = path.join(root, "dist", "extensions", "alpha");
const testGlobal = globalThis as typeof globalThis & {
__bundledSetupOnlyMainLoaded?: boolean;
__bundledSetupOnlySetupLoaded?: number;
__bundledSetupOnlyPluginLoaded?: boolean;
};
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
"globalThis.__bundledSetupOnlyMainLoaded = true;",
"throw new Error('main entry loaded');",
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.js"),
[
"globalThis.__bundledSetupOnlySetupLoaded = (globalThis.__bundledSetupOnlySetupLoaded ?? 0) + 1;",
"export default {",
" kind: 'bundled-channel-setup-entry',",
" features: { legacyStateMigrations: true },",
" loadSetupPlugin() {",
" globalThis.__bundledSetupOnlyPluginLoaded = true;",
" throw new Error('setup plugin loaded');",
" },",
" loadLegacyStateMigrationDetector() {",
" return ({ oauthDir }) => [{",
" kind: 'copy',",
" label: 'Alpha state',",
" sourcePath: oauthDir + '/legacy.json',",
" targetPath: oauthDir + '/alpha/legacy.json',",
" }];",
" },",
"};",
"",
].join("\n"),
"utf8",
);
mockAlphaDistExtensionRuntime();
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(root, "dist", "extensions");
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-setup-only-feature",
);
const detectors = bundled.listBundledChannelLegacyStateMigrationDetectors();
expect(
detectors.map((detector) =>
detector({ cfg: {}, env: {}, stateDir: "/state", oauthDir: "/oauth" } as never),
),
).toEqual([
[
{
kind: "copy",
label: "Alpha state",
sourcePath: "/oauth/legacy.json",
targetPath: "/oauth/alpha/legacy.json",
},
],
]);
expect(testGlobal.__bundledSetupOnlySetupLoaded).toBe(1);
expect(testGlobal.__bundledSetupOnlyMainLoaded).toBeUndefined();
expect(testGlobal.__bundledSetupOnlyPluginLoaded).toBeUndefined();
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(root, { recursive: true, force: true });
delete testGlobal.__bundledSetupOnlyMainLoaded;
delete testGlobal.__bundledSetupOnlySetupLoaded;
delete testGlobal.__bundledSetupOnlyPluginLoaded;
}
});
it("does not load bundled setup entries through external staged runtime deps during discovery", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-setup-runtime-deps-"));
const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-stage-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const previousPluginStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR;
const pluginDir = path.join(root, "dist", "extensions", "alpha");
const testGlobal = globalThis as typeof globalThis & {
__bundledSetupRuntimeDepMarker?: string;
};
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(root, "package.json"),
JSON.stringify({ name: "openclaw", version: "2026.4.21" }),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "@openclaw/alpha",
version: "2026.4.21",
type: "module",
dependencies: {
"alpha-runtime-dep": "1.0.0",
},
}),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.js"),
[
"import { marker } from 'alpha-runtime-dep';",
"globalThis.__bundledSetupRuntimeDepMarker = marker;",
"export default {",
" kind: 'bundled-channel-setup-entry',",
" loadSetupPlugin() {",
" return { id: 'alpha', meta: { label: marker }, config: {} };",
" },",
"};",
"",
].join("\n"),
"utf8",
);
process.env.OPENCLAW_PLUGIN_STAGE_DIR = stageRoot;
const { resolveBundledRuntimeDependencyInstallRoot } =
await import("../../plugins/bundled-runtime-deps.js");
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginDir);
const depRoot = path.join(installRoot, "node_modules", "alpha-runtime-dep");
fs.mkdirSync(depRoot, { recursive: true });
fs.writeFileSync(
path.join(depRoot, "package.json"),
JSON.stringify({
name: "alpha-runtime-dep",
version: "1.0.0",
type: "module",
main: "index.js",
}),
"utf8",
);
fs.writeFileSync(path.join(depRoot, "index.js"), "export const marker = 'staged-alpha';\n");
mockAlphaDistExtensionRuntime();
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(root, "dist", "extensions");
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-setup-runtime-deps",
);
expect(bundled.getBundledChannelSetupPlugin("alpha")).toBeUndefined();
expect(testGlobal.__bundledSetupRuntimeDepMarker).toBeUndefined();
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
if (previousPluginStageDir === undefined) {
delete process.env.OPENCLAW_PLUGIN_STAGE_DIR;
} else {
process.env.OPENCLAW_PLUGIN_STAGE_DIR = previousPluginStageDir;
}
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(stageRoot, { recursive: true, force: true });
delete testGlobal.__bundledSetupRuntimeDepMarker;
}
});
it("swallows and caches bundled plugin and setup load failures", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-load-failure-"));
const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const pluginDir = path.join(root, "dist", "extensions", "alpha");
const testGlobal = globalThis as typeof globalThis & {
__bundledPluginFailureLoads?: number;
__bundledSetupFailureLoads?: number;
__bundledSecretsFailureLoads?: number;
__bundledSetupSecretsFailureLoads?: number;
};
fs.mkdirSync(pluginDir, { recursive: true });
fs.writeFileSync(
path.join(root, "package.json"),
JSON.stringify({ name: "openclaw", version: "2026.4.21" }),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "index.js"),
[
"export default {",
" kind: 'bundled-channel-entry',",
" id: 'alpha',",
" name: 'Alpha',",
" description: 'Alpha',",
" register() {},",
" loadChannelSecrets() {",
" globalThis.__bundledSecretsFailureLoads = (globalThis.__bundledSecretsFailureLoads ?? 0) + 1;",
" throw new Error('missing channel secrets dep');",
" },",
" loadChannelPlugin() {",
" globalThis.__bundledPluginFailureLoads = (globalThis.__bundledPluginFailureLoads ?? 0) + 1;",
" throw new Error('missing channel plugin dep');",
" },",
"};",
"",
].join("\n"),
"utf8",
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.js"),
[
"export default {",
" kind: 'bundled-channel-setup-entry',",
" loadSetupSecrets() {",
" globalThis.__bundledSetupSecretsFailureLoads = (globalThis.__bundledSetupSecretsFailureLoads ?? 0) + 1;",
" throw new Error('missing setup secrets dep');",
" },",
" loadSetupPlugin() {",
" globalThis.__bundledSetupFailureLoads = (globalThis.__bundledSetupFailureLoads ?? 0) + 1;",
" throw new Error('missing setup plugin dep');",
" },",
"};",
"",
].join("\n"),
"utf8",
);
mockAlphaDistExtensionRuntime();
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(root, "dist", "extensions");
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=bundled-load-failure",
);
expect(bundled.getBundledChannelPlugin("alpha")).toBeUndefined();
expect(bundled.getBundledChannelPlugin("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSetupPlugin("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSetupPlugin("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSecrets("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSecrets("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSetupSecrets("alpha")).toBeUndefined();
expect(bundled.getBundledChannelSetupSecrets("alpha")).toBeUndefined();
expect(testGlobal.__bundledPluginFailureLoads).toBe(1);
expect(testGlobal.__bundledSetupFailureLoads).toBe(1);
expect(testGlobal.__bundledSecretsFailureLoads).toBe(1);
expect(testGlobal.__bundledSetupSecretsFailureLoads).toBe(1);
} finally {
restoreBundledPluginsDir(previousBundledPluginsDir);
fs.rmSync(root, { recursive: true, force: true });
delete testGlobal.__bundledPluginFailureLoads;
delete testGlobal.__bundledSetupFailureLoads;
delete testGlobal.__bundledSecretsFailureLoads;
delete testGlobal.__bundledSetupSecretsFailureLoads;
}
});
it("keeps channel entrypoints on the dedicated entry-contract SDK surface", () => {
const offenders = collectBundledChannelEntrypointOffenders(
bundledPluginRoots,
(source) =>
!source.includes('from "openclaw/plugin-sdk/channel-entry-contract"') ||
source.includes('from "openclaw/plugin-sdk/core"') ||
source.includes('from "openclaw/plugin-sdk/channel-core"'),
);
expect(offenders).toEqual([]);
});
it("keeps setup-entry legacy feature hints mirrored in package metadata", () => {
const offenders: string[] = [];
for (const extensionDir of bundledPluginRoots) {
const setupEntryPath = path.join(extensionDir, "setup-entry.ts");
const packageJsonPath = path.join(extensionDir, "package.json");
if (!fs.existsSync(setupEntryPath) || !fs.existsSync(packageJsonPath)) {
continue;
}
const setupEntrySource = fs.readFileSync(setupEntryPath, "utf8");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
openclaw?: {
setupFeatures?: Record<string, boolean>;
};
};
for (const feature of ["legacyStateMigrations", "legacySessionSurfaces"]) {
const usesFeature = setupEntrySource.includes(`${feature}: true`);
const hasHint = packageJson.openclaw?.setupFeatures?.[feature] === true;
if (usesFeature !== hasHint) {
offenders.push(`${path.relative(process.cwd(), extensionDir)}:${feature}`);
}
}
}
expect(offenders).toEqual([]);
});
it("keeps bundled channel entrypoints free of static src imports", () => {
const offenders = collectBundledChannelEntrypointOffenders(bundledPluginRoots, (source) =>
/^(?:import|export)\s.+["']\.\/src\//mu.test(source),
);
expect(offenders).toEqual([]);
});
it("keeps channel implementations off the broad core SDK surface", () => {
const offenders: string[] = [];
for (const extensionDir of bundledPluginRoots) {
for (const relativePath of ["src/channel.ts", "src/plugin.ts"]) {
const filePath = path.join(extensionDir, relativePath);
if (!fs.existsSync(filePath)) {
continue;
}
const source = fs.readFileSync(filePath, "utf8");
if (!source.includes("createChatChannelPlugin")) {
continue;
}
if (source.includes('from "openclaw/plugin-sdk/core"')) {
offenders.push(path.relative(process.cwd(), filePath));
}
}
}
expect(offenders).toEqual([]);
});
it("keeps plugin-sdk channel-core free of chat metadata bootstrap imports", () => {
const source = fs.readFileSync(path.resolve("src/plugin-sdk/channel-core.ts"), "utf8");
expect(source.includes("../channels/chat-meta.js")).toBe(false);
expect(source.includes("getChatChannelMeta")).toBe(false);
});
it("keeps bundled hot runtime barrels off the broad core SDK surface", () => {
const offenders = [
"extensions/googlechat/runtime-api.ts",
"extensions/irc/src/runtime-api.ts",
"extensions/matrix/src/runtime-api.ts",
].filter((filePath) =>
fs.readFileSync(path.resolve(filePath), "utf8").includes("openclaw/plugin-sdk/core"),
);
expect(offenders).toEqual([]);
});
it("keeps runtime helper surfaces off bootstrap-registry", () => {
const offenders = [
"src/config/markdown-tables.ts",
"src/config/sessions/group.ts",
"src/channels/plugins/setup-helpers.ts",
"src/plugin-sdk/extension-shared.ts",
].filter((filePath) =>
fs.readFileSync(path.resolve(filePath), "utf8").includes("bootstrap-registry.js"),
);
expect(offenders).toEqual([]);
});
it("keeps extension-shared off the broad runtime barrel", () => {
const source = fs.readFileSync(path.resolve("src/plugin-sdk/extension-shared.ts"), "utf8");
expect(source.includes('from "./runtime.js"')).toBe(false);
});
it("keeps nextcloud-talk's private SDK surface off the broad runtime barrel", () => {
const source = fs.readFileSync(path.resolve("src/plugin-sdk/nextcloud-talk.ts"), "utf8");
expect(source.includes('from "./runtime.js"')).toBe(false);
});
it("keeps bundled doctor surfaces off the broad runtime barrel", () => {
const offenders = [
"extensions/discord/src/doctor.ts",
"extensions/matrix/src/doctor.ts",
"extensions/slack/src/doctor.ts",
"extensions/telegram/src/doctor.ts",
"extensions/zalouser/src/doctor.ts",
].filter((filePath) =>
fs
.readFileSync(path.resolve(filePath), "utf8")
.includes('from "openclaw/plugin-sdk/runtime"'),
);
expect(offenders).toEqual([]);
});
it("breaks reentrant bundled channel discovery cycles with an empty fallback", async () => {
const pluginDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-reentrant-"));
const modulePath = path.join(pluginDir, "index.js");
fs.writeFileSync(modulePath, "export {};\n", "utf8");
vi.doMock("../../plugins/bundled-plugin-metadata.js", async (importOriginal) => {
const actual =
await importOriginal<typeof import("../../plugins/bundled-plugin-metadata.js")>();
return {
...actual,
listBundledPluginMetadata: () => [
{
dirName: "alpha",
idHint: "alpha",
source: {
source: "./index.js",
built: "./index.js",
},
manifest: {
id: "alpha",
channels: ["alpha"],
},
},
],
resolveBundledPluginGeneratedPath: () => modulePath,
};
});
vi.doMock("../../infra/boundary-file-read.js", () => ({
openBoundaryFileSync: ({ absolutePath }: { absolutePath: string }) => ({
ok: true,
path: absolutePath,
fd: fs.openSync(absolutePath, "r"),
}),
}));
vi.doMock("../../plugins/channel-catalog-registry.js", () => ({
listChannelCatalogEntries: () => [],
}));
let reentered = false;
vi.doMock("jiti", () => ({
createJiti: () => {
return () => {
if (!reentered) {
reentered = true;
expect(bundled.listBundledChannelPlugins()).toEqual([]);
}
return {
default: {
kind: "bundled-channel-entry",
id: "alpha",
name: "Alpha",
description: "Alpha",
configSchema: {},
register() {},
loadChannelPlugin() {
return {
id: "alpha",
meta: {},
capabilities: {},
config: {},
};
},
},
};
};
},
}));
const bundled = await importFreshModule<typeof import("./bundled.js")>(
import.meta.url,
"./bundled.js?scope=reentrant-bundled-discovery",
);
expect(bundled.listBundledChannelPlugins()).toHaveLength(1);
expect(reentered).toBe(true);
});
it("keeps private src runtime barrels from forwarding to parent runtime barrels that export local plugins", () => {
const offenders: string[] = [];
for (const extensionDir of bundledPluginRoots) {
const privateRuntimePath = path.join(extensionDir, "src", "runtime-api.ts");
const publicRuntimePath = path.join(extensionDir, "runtime-api.ts");
if (!fs.existsSync(privateRuntimePath) || !fs.existsSync(publicRuntimePath)) {
continue;
}
const privateRuntimeSource = fs.readFileSync(privateRuntimePath, "utf8");
const publicRuntimeSource = fs.readFileSync(publicRuntimePath, "utf8");
const forwardsParentRuntime =
privateRuntimeSource.includes('export * from "../runtime-api.js"') ||
privateRuntimeSource.includes("export * from '../runtime-api.js'");
const exportsLocalPlugin =
publicRuntimeSource.includes('from "./src/channel.js"') &&
/export\s+\{\s*[\w$]+Plugin\s*\}\s+from\s+["']\.\/src\/channel\.js["']/u.test(
publicRuntimeSource,
);
if (forwardsParentRuntime && exportsLocalPlugin) {
offenders.push(path.relative(process.cwd(), publicRuntimePath));
}
}
expect(offenders).toEqual([]);
});
});