Files
openclaw/src/plugins/loader.discovery-and-security.test-utils.ts
Peter Steinberger f81f9d8570 chore: enforce max-lines suppression ratchet (#107315)
* ci: enforce max-lines suppression ratchet

* chore: prune stale max-lines suppression

* fix: close max-lines ratchet enforcement gaps

* fix: harden max-lines ratchet checks

* fix(ci): satisfy max-lines ratchet checks

* style: format max-lines declarations

* fix(ci): match oxlint suppression grammar

* test: isolate max-lines git fixtures

* chore: prune resolved max-lines debt

* test: skip newline path fixture on Windows

* fix: harden max-lines suppression ratchet

* chore: refresh max-lines baseline

* fix: close max-lines ratchet bypasses

* fix: derive ratchet base from PR merge tree

* fix: support older Git in staged ratchet

* fix: align max-lines declarations and baseline

* chore: refresh max-lines baseline for current main

* fix: exclude generated wizard locales from max-lines

* chore: prune resolved max-lines debt
2026-07-14 09:27:02 -07:00

1853 lines
61 KiB
TypeScript

// Imported by loader.test.ts to keep its mocked suite in one Vitest module graph.
import fs from "node:fs";
import path from "node:path";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import { setRuntimeConfigSnapshot } from "../config/runtime-snapshot.js";
import { emitDiagnosticEvent, waitForDiagnosticEventsDrained } from "../infra/diagnostic-events.js";
import { toSafeImportPath } from "../shared/import-specifier.js";
import { withEnv } from "../test-utils/env.js";
import { writePersistedInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-records.js";
import { warnWhenAllowlistIsOpen } from "./loader-provenance.js";
import { loadOpenClawPluginCliRegistry, loadOpenClawPlugins } from "./loader.js";
import {
clearPluginLoaderCache,
EMPTY_PLUGIN_SCHEMA,
makeTempDir,
mkdirSafe,
type PluginLoadConfig,
type PluginRegistry,
useNoBundledPlugins,
writePlugin,
} from "./loader.test-fixtures.js";
import {
simplePluginBody,
updatePluginManifest,
memoryPluginBody,
setupBundledDreamingMemoryPlugins,
writeBundledPlugin,
makeOpenClawDevSourceRoot,
writeWorkspacePlugin,
withStateDir,
loadRegistryFromSinglePlugin,
runRegistryScenarios,
runScenarioCases,
expectOpenAllowWarnings,
expectLoadedPluginProvenance,
expectPluginSourcePrecedence,
expectPluginOriginAndStatus,
expectDiagnosticContaining,
expectNoDiagnosticContaining,
createWarningLogger,
createEnvResolvedPluginFixture,
expectEscapingEntryRejected,
globalAfterEach0,
globalAfterAll1,
} from "./loader.test-harness.js";
afterEach(globalAfterEach0);
afterAll(globalAfterAll1);
describe("loadOpenClawPlugins", () => {
it("ignores unknown typed hooks from plugins and keeps loading", () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "hook-unknown",
filename: "hook-unknown.cjs",
body: `module.exports = { id: "hook-unknown", register(api) {
api.on("totally_unknown_hook_name", () => ({ foo: "bar" }));
api.on(123, () => ({ foo: "baz" }));
api.on("before_model_resolve", () => ({ providerOverride: "demo-provider" }));
} };`,
});
const registry = loadRegistryFromSinglePlugin({
plugin,
pluginConfig: {
allow: ["hook-unknown"],
entries: {
"hook-unknown": {
hooks: {
allowConversationAccess: true,
},
},
},
},
});
expect(registry.plugins.find((entry) => entry.id === "hook-unknown")?.status).toBe("loaded");
expect(registry.typedHooks.map((entry) => entry.hookName)).toEqual(["before_model_resolve"]);
const unknownHookDiagnostics = registry.diagnostics.filter((diag) =>
diag.message.includes('unknown typed hook "'),
);
expect(unknownHookDiagnostics).toHaveLength(2);
expect(
unknownHookDiagnostics.some((diag) =>
diag.message.includes('unknown typed hook "totally_unknown_hook_name" ignored'),
),
).toBe(true);
expect(
unknownHookDiagnostics.some((diag) =>
diag.message.includes('unknown typed hook "123" ignored'),
),
).toBe(true);
});
it("enforces memory slot loading rules", () => {
const scenarios = [
{
label: "enforces memory slot selection",
loadRegistry: () => {
const memoryA = writePlugin({
id: "memory-a",
body: memoryPluginBody("memory-a"),
});
const memoryB = writePlugin({
id: "memory-b",
body: memoryPluginBody("memory-b"),
});
return withEnv(
{
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
},
() =>
loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [memoryA.file, memoryB.file] },
slots: { memory: "memory-b" },
},
},
}),
);
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const a = registry.plugins.find((entry) => entry.id === "memory-a");
const b = registry.plugins.find((entry) => entry.id === "memory-b");
expect(b?.status).toBe("loaded");
expect(a?.status).toBe("disabled");
},
},
{
label: "skips importing bundled memory plugins that are disabled by memory slot",
loadRegistry: () => {
const bundledDir = makeTempDir();
const memoryADir = path.join(bundledDir, "memory-a");
const memoryBDir = path.join(bundledDir, "memory-b");
mkdirSafe(memoryADir);
mkdirSafe(memoryBDir);
writePlugin({
id: "memory-a",
dir: memoryADir,
filename: "index.cjs",
body: `throw new Error("memory-a should not be imported when slot selects memory-b");`,
});
writePlugin({
id: "memory-b",
dir: memoryBDir,
filename: "index.cjs",
body: memoryPluginBody("memory-b"),
});
fs.writeFileSync(
path.join(memoryADir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "memory-a",
kind: "memory",
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(memoryBDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "memory-b",
kind: "memory",
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["memory-a", "memory-b"],
slots: { memory: "memory-b" },
entries: {
"memory-a": { enabled: true },
"memory-b": { enabled: true },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const a = registry.plugins.find((entry) => entry.id === "memory-a");
const b = registry.plugins.find((entry) => entry.id === "memory-b");
expect(a?.status).toBe("disabled");
expect(a?.error ?? "").toContain('memory slot set to "memory-b"');
expect(b?.status).toBe("loaded");
},
},
{
label:
"loads dreaming engine through a restrictive allowlist when selected memory slot enables dreaming",
loadRegistry: () => {
const { selectedId } = setupBundledDreamingMemoryPlugins();
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: [selectedId],
slots: { memory: selectedId },
entries: {
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("loaded");
expect(core?.enabled).toBe(true);
expect(lance?.status).toBe("loaded");
expect(lance?.memorySlotSelected).toBe(true);
},
},
{
label: "keeps restrictive allowlist dreaming sidecar in manifest-only snapshots",
loadRegistry: () => {
const { selectedId } = setupBundledDreamingMemoryPlugins({
coreBody: `throw new Error("manifest-only snapshot should not import memory-core");`,
});
return loadOpenClawPlugins({
cache: false,
activate: false,
loadModules: false,
config: {
plugins: {
allow: [selectedId],
slots: { memory: selectedId },
entries: {
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("loaded");
expect(lance?.status).toBe("loaded");
expect(lance?.memorySlotSelected).toBe(true);
},
},
{
label: "keeps denied dreaming sidecars fail-closed under restrictive allowlists",
loadRegistry: () => {
const { selectedId } = setupBundledDreamingMemoryPlugins({
coreBody: `throw new Error("denied memory-core should not load");`,
});
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: [selectedId],
deny: ["memory-core"],
slots: { memory: selectedId },
entries: {
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("disabled");
expect(core?.error).toBe("blocked by denylist");
expect(lance?.status).toBe("loaded");
},
},
{
label: "keeps explicitly disabled dreaming sidecars fail-closed",
loadRegistry: () => {
const { selectedId } = setupBundledDreamingMemoryPlugins({
coreBody: `throw new Error("disabled memory-core should not load");`,
});
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: [selectedId],
slots: { memory: selectedId },
entries: {
"memory-core": { enabled: false },
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("disabled");
expect(core?.error).toBe("disabled in config");
expect(lance?.status).toBe("loaded");
},
},
{
label: "does not authorize dreaming sidecars for non-memory selected slots",
loadRegistry: () => {
const { selectedId } = setupBundledDreamingMemoryPlugins({
selectedKind: "utility",
coreBody: `throw new Error("non-memory selected slot should not load memory-core");`,
});
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: [selectedId],
slots: { memory: selectedId },
entries: {
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const selected = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("disabled");
expect(core?.error).toBe("not in allowlist");
expect(selected?.status).toBe("loaded");
},
},
{
label:
"loads dreaming engine alongside a different memory slot plugin when dreaming is enabled",
loadRegistry: () => {
const bundledDir = makeTempDir();
const memoryCoreDir = path.join(bundledDir, "memory-core");
const memoryLanceDir = path.join(bundledDir, "memory-lancedb");
mkdirSafe(memoryCoreDir);
mkdirSafe(memoryLanceDir);
writePlugin({
id: "memory-core",
dir: memoryCoreDir,
filename: "index.cjs",
body: memoryPluginBody("memory-core"),
});
writePlugin({
id: "memory-lancedb",
dir: memoryLanceDir,
filename: "index.cjs",
body: memoryPluginBody("memory-lancedb"),
});
const openSchema = { type: "object", additionalProperties: true };
fs.writeFileSync(
path.join(memoryCoreDir, "openclaw.plugin.json"),
JSON.stringify(
{ id: "memory-core", kind: "memory", configSchema: EMPTY_PLUGIN_SCHEMA },
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(memoryLanceDir, "openclaw.plugin.json"),
JSON.stringify(
{ id: "memory-lancedb", kind: "memory", configSchema: openSchema },
null,
2,
),
"utf-8",
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["memory-core", "memory-lancedb"],
slots: { memory: "memory-lancedb" },
entries: {
"memory-core": { enabled: true },
"memory-lancedb": { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("loaded");
expect(lance?.status).toBe("loaded");
expect(lance?.memorySlotSelected).toBe(true);
expect(core?.memorySlotSelected).not.toBe(true);
},
},
{
label: "excludes dreaming engine when dreaming is disabled and it is not the slot",
loadRegistry: () => {
const bundledDir = makeTempDir();
const memoryCoreDir = path.join(bundledDir, "memory-core");
const memoryLanceDir = path.join(bundledDir, "memory-lancedb");
mkdirSafe(memoryCoreDir);
mkdirSafe(memoryLanceDir);
writePlugin({
id: "memory-core",
dir: memoryCoreDir,
filename: "index.cjs",
body: `throw new Error("memory-core should not load when dreaming is disabled");`,
});
writePlugin({
id: "memory-lancedb",
dir: memoryLanceDir,
filename: "index.cjs",
body: memoryPluginBody("memory-lancedb"),
});
fs.writeFileSync(
path.join(memoryCoreDir, "openclaw.plugin.json"),
JSON.stringify(
{ id: "memory-core", kind: "memory", configSchema: EMPTY_PLUGIN_SCHEMA },
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(memoryLanceDir, "openclaw.plugin.json"),
JSON.stringify(
{ id: "memory-lancedb", kind: "memory", configSchema: EMPTY_PLUGIN_SCHEMA },
null,
2,
),
"utf-8",
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["memory-core", "memory-lancedb"],
slots: { memory: "memory-lancedb" },
entries: {
"memory-core": { enabled: true },
"memory-lancedb": { enabled: true },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
const lance = registry.plugins.find((entry) => entry.id === "memory-lancedb");
expect(core?.status).toBe("disabled");
expect(lance?.status).toBe("loaded");
},
},
{
label: 'keeps memory slot "none" disabled even with stale memory-core dreaming config',
loadRegistry: () => {
const bundledDir = makeTempDir();
const memoryCoreDir = path.join(bundledDir, "memory-core");
mkdirSafe(memoryCoreDir);
writePlugin({
id: "memory-core",
dir: memoryCoreDir,
filename: "index.cjs",
body: `throw new Error("memory-core should not load when memory slot is none");`,
});
fs.writeFileSync(
path.join(memoryCoreDir, "openclaw.plugin.json"),
JSON.stringify(
{ id: "memory-core", kind: "memory", configSchema: EMPTY_PLUGIN_SCHEMA },
null,
2,
),
"utf-8",
);
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["memory-core"],
slots: { memory: "none" },
entries: {
"memory-core": { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const core = registry.plugins.find((entry) => entry.id === "memory-core");
expect(core?.status).toBe("disabled");
},
},
{
label: "disables memory plugins when slot is none",
loadRegistry: () => {
const memory = writePlugin({
id: "memory-off",
body: memoryPluginBody("memory-off"),
});
return withEnv(
{
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
},
() =>
loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [memory.file] },
slots: { memory: "none" },
},
},
}),
);
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
const entry = registry.plugins.find((item) => item.id === "memory-off");
expect(entry?.status).toBe("disabled");
},
},
] as const;
runRegistryScenarios(scenarios, ({ loadRegistry }) => loadRegistry());
});
it("loads dreaming sidecar metadata through a restrictive selected-memory allowlist", async () => {
const { selectedId } = setupBundledDreamingMemoryPlugins();
const registry = await loadOpenClawPluginCliRegistry({
cache: false,
config: {
plugins: {
allow: [selectedId],
slots: { memory: selectedId },
entries: {
[selectedId]: { enabled: true, config: { dreaming: { enabled: true } } },
},
},
},
});
expect(registry.plugins.map((entry) => entry.id)).toEqual(["memory-core", selectedId]);
expect(registry.plugins.find((entry) => entry.id === "memory-core")?.status).toBe("loaded");
expect(registry.plugins.find((entry) => entry.id === selectedId)?.status).toBe("loaded");
});
it("resolves duplicate plugin ids by source precedence", () => {
const scenarios = [
{
label: "config load overrides bundled",
pluginId: "shadow",
bundledFilename: "shadow.cjs",
loadRegistry: () => {
writeBundledPlugin({
id: "shadow",
body: simplePluginBody("shadow"),
filename: "shadow.cjs",
});
const override = writePlugin({
id: "shadow",
body: simplePluginBody("shadow"),
});
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [override.file] },
entries: {
shadow: { enabled: true },
},
},
},
});
},
expectedLoadedOrigin: "config",
expectedDisabledOrigin: "bundled",
assert: expectPluginSourcePrecedence,
},
{
label: "bundled beats auto-discovered global duplicate",
pluginId: "demo-bundled-duplicate",
bundledFilename: "index.cjs",
loadRegistry: () => {
writeBundledPlugin({
id: "demo-bundled-duplicate",
body: simplePluginBody("demo-bundled-duplicate"),
});
return withStateDir((stateDir) => {
const globalDir = path.join(stateDir, "extensions", "demo-bundled-duplicate");
mkdirSafe(globalDir);
writePlugin({
id: "demo-bundled-duplicate",
body: simplePluginBody("demo-bundled-duplicate"),
dir: globalDir,
filename: "index.cjs",
});
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["demo-bundled-duplicate"],
entries: {
"demo-bundled-duplicate": { enabled: true },
},
},
},
});
});
},
expectedLoadedOrigin: "bundled",
expectedDisabledOrigin: "global",
expectedDisabledError: "overridden by bundled plugin",
assert: expectPluginSourcePrecedence,
},
{
label: "installed global beats bundled duplicate",
pluginId: "demo-installed-duplicate",
bundledFilename: "index.cjs",
loadRegistry: () => {
writeBundledPlugin({
id: "demo-installed-duplicate",
body: simplePluginBody("demo-installed-duplicate"),
});
return withStateDir((stateDir) => {
const globalDir = path.join(stateDir, "extensions", "demo-installed-duplicate");
mkdirSafe(globalDir);
writePlugin({
id: "demo-installed-duplicate",
body: simplePluginBody("demo-installed-duplicate"),
dir: globalDir,
filename: "index.cjs",
});
writePersistedInstalledPluginIndexInstallRecordsSync(
{
"demo-installed-duplicate": {
source: "npm",
installPath: globalDir,
},
},
{ stateDir },
);
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["demo-installed-duplicate"],
entries: {
"demo-installed-duplicate": { enabled: true },
},
},
},
});
});
},
expectedLoadedOrigin: "global",
expectedDisabledOrigin: "bundled",
expectedDisabledError: "overridden by global plugin",
expectDuplicateWarning: false,
assert: expectPluginSourcePrecedence,
},
{
label: "dev source bundled beats installed global duplicate",
pluginId: "demo-dev-source-duplicate",
bundledFilename: "index.cjs",
loadRegistry: () => {
const devSourceRoot = makeOpenClawDevSourceRoot();
const bundledPluginsDir = path.join(devSourceRoot, "extensions");
writeBundledPlugin({
id: "demo-dev-source-duplicate",
body: simplePluginBody("demo-dev-source-duplicate"),
bundledDir: path.join(bundledPluginsDir, "demo-dev-source-duplicate"),
});
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
return withEnv({ OPENCLAW_DEV_SOURCE_ROOT: devSourceRoot }, () =>
withStateDir((stateDir) => {
const globalDir = path.join(stateDir, "extensions", "demo-dev-source-duplicate");
mkdirSafe(globalDir);
writePlugin({
id: "demo-dev-source-duplicate",
body: simplePluginBody("demo-dev-source-duplicate"),
dir: globalDir,
filename: "index.cjs",
});
writePersistedInstalledPluginIndexInstallRecordsSync(
{
"demo-dev-source-duplicate": {
source: "npm",
installPath: globalDir,
},
},
{ stateDir },
);
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["demo-dev-source-duplicate"],
entries: {
"demo-dev-source-duplicate": { enabled: true },
},
},
},
});
}),
);
},
expectedLoadedOrigin: "bundled",
expectedDisabledOrigin: "global",
expectedDisabledError: "overridden by bundled plugin",
assert: expectPluginSourcePrecedence,
},
{
label: "transient installed memory plugin beats bundled duplicate",
pluginId: "memory-lancedb",
bundledFilename: "index.cjs",
loadRegistry: () => {
writeBundledPlugin({
id: "memory-lancedb",
body: memoryPluginBody("memory-lancedb"),
});
return withStateDir((stateDir) => {
const globalDir = path.join(stateDir, "node_modules", "@openclaw", "memory-lancedb");
mkdirSafe(globalDir);
const globalPlugin = writePlugin({
id: "memory-lancedb",
body: `module.exports = {
id: "memory-lancedb",
kind: "memory",
register(api) {
api.registerTool({
name: "memory_recall",
description: "Recall memories",
parameters: {},
execute: async () => ({ content: [{ type: "text", text: "ok" }] }),
});
},
};`,
dir: globalDir,
filename: "index.cjs",
});
updatePluginManifest(globalPlugin, {
kind: "memory",
contracts: { tools: ["memory_recall"] },
});
fs.writeFileSync(
path.join(globalDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/memory-lancedb",
version: "2026.5.12-beta.1",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
return loadOpenClawPlugins({
cache: false,
config: {
plugins: {
allow: ["memory-lancedb"],
slots: { memory: "memory-lancedb" },
entries: {
"memory-lancedb": { enabled: true },
},
installs: {
"memory-lancedb": {
source: "npm",
spec: "@openclaw/memory-lancedb",
resolvedName: "@openclaw/memory-lancedb",
resolvedVersion: "2026.5.12-beta.1",
installPath: globalDir,
},
},
},
},
});
});
},
expectedLoadedOrigin: "global",
expectedDisabledOrigin: "bundled",
expectedDisabledError: "overridden by global plugin",
expectDuplicateWarning: false,
assert: (
registry: PluginRegistry,
scenario: Parameters<typeof expectPluginSourcePrecedence>[1],
) => {
expectPluginSourcePrecedence(registry, scenario);
expect(
registry.tools.flatMap((entry) => entry.names),
scenario.label,
).toContain("memory_recall");
},
},
] as const;
runRegistryScenarios(scenarios, (scenario) => scenario.loadRegistry());
});
it("warns about open allowlists only for auto-discovered plugins", () => {
useNoBundledPlugins();
clearPluginLoaderCache();
const scenarios = [
{
label: "explicit config path stays quiet",
pluginId: "warn-open-allow-config",
loads: 1,
expectedWarnings: 0,
loadRegistry: (warnings: string[]) => {
const plugin = writePlugin({
id: "warn-open-allow-config",
body: simplePluginBody("warn-open-allow-config"),
});
return loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
config: {
plugins: {
load: { paths: [plugin.file] },
},
},
});
},
},
{
label: "workspace discovery warns once",
pluginId: "warn-open-allow-workspace",
loads: 2,
expectedWarnings: 1,
loadRegistry: (() => {
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-open-allow-workspace",
});
return (warnings: string[]) =>
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
},
},
});
})(),
},
] as const;
runScenarioCases(scenarios, (scenario) => {
const warnings: string[] = [];
for (let index = 0; index < scenario.loads; index += 1) {
scenario.loadRegistry(warnings);
}
expectOpenAllowWarnings({
warnings,
pluginId: scenario.pluginId,
expectedWarnings: scenario.expectedWarnings,
label: scenario.label,
});
});
});
it("stays quiet when every non-bundled plugin is explicitly enabled", () => {
useNoBundledPlugins();
clearPluginLoaderCache();
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-explicitly-enabled-plugin",
});
const warnings: string[] = [];
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
entries: { "warn-explicitly-enabled-plugin": { enabled: true } },
},
},
});
expect(warnings.join("\n")).not.toContain("plugins.allow is empty");
});
it("warns when plugins.allow entries do not match any discovered plugin ids", () => {
useNoBundledPlugins();
clearPluginLoaderCache();
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-mismatch-allow-plugin",
});
const warnings: string[] = [];
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
// User configured a channel-style id that does not match the real plugin id.
allow: ["warn-mismatch-allow-channel"],
},
},
});
const emptyWarnings = warnings.filter((msg) => msg.includes("plugins.allow is empty"));
const mismatchWarnings = warnings.filter((msg) =>
msg.includes("do not match any discovered plugin ids"),
);
expect(emptyWarnings, "should not emit empty-allowlist warning").toHaveLength(0);
expect(mismatchWarnings, "should emit mismatch warning once").toHaveLength(1);
expect(mismatchWarnings[0]).toContain(`"warn-mismatch-allow-channel"`);
expect(mismatchWarnings[0]).toContain("warn-mismatch-allow-plugin");
expect(mismatchWarnings[0]).toContain("Use the plugin id");
});
it("stays quiet when plugins.allow contains at least one matching plugin id", () => {
useNoBundledPlugins();
clearPluginLoaderCache();
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-partial-allow-plugin",
});
const warnings: string[] = [];
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
// Allow contains one real plugin id plus a stray channel-style entry.
allow: ["warn-partial-allow-plugin", "warn-partial-allow-channel"],
},
},
});
const openAllowWarnings = warnings.filter(
(msg) =>
msg.includes("plugins.allow is empty") ||
msg.includes("do not match any discovered plugin ids"),
);
expect(openAllowWarnings, "should not emit allowlist warning when one id matches").toHaveLength(
0,
);
});
it("stays quiet when plugins.allow matches only bundled plugin ids, even while workspace/global plugins are present", () => {
// Regression for Codex P2 feedback on #68389: the mismatch warning should be computed
// against the full discovered plugin set (bundled + workspace + global), not only the
// workspace/global subset that the warning talks about. An allowlist that intentionally
// trusts bundled ids like ["telegram"] is valid and must not trip the mismatch path just
// because some unrelated non-bundled plugin happens to be auto-discoverable.
useNoBundledPlugins();
clearPluginLoaderCache();
writeBundledPlugin({
id: "warn-bundled-allow-only-plugin",
body: simplePluginBody("warn-bundled-allow-only-plugin"),
});
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-noise-workspace-plugin",
});
const warnings: string[] = [];
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
// Allowlist intentionally only trusts a bundled plugin id.
allow: ["warn-bundled-allow-only-plugin"],
},
},
});
const openAllowWarnings = warnings.filter(
(msg) =>
msg.includes("plugins.allow is empty") ||
msg.includes("do not match any discovered plugin ids"),
);
expect(
openAllowWarnings,
"bundled-only allowlists should not trip the mismatch warning",
).toHaveLength(0);
});
it("includes actionable plugins.allow remediation hints in the open allowlist warning", () => {
useNoBundledPlugins();
clearPluginLoaderCache();
const { workspaceDir } = writeWorkspacePlugin({
id: "warn-open-allow-remediation",
});
const warnings: string[] = [];
loadOpenClawPlugins({
cache: false,
workspaceDir,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
},
},
});
const openAllowWarning = warnings.find((msg) => msg.includes("plugins.allow is empty"));
expect(openAllowWarning).toBeDefined();
expect(openAllowWarning).toContain('"warn-open-allow-remediation"');
expect(openAllowWarning).toContain('"plugins": { "allow": [');
expect(openAllowWarning).toContain("openclaw plugins list --enabled --verbose");
expect(openAllowWarning).toContain("openclaw plugins inspect warn-open-allow-remediation");
});
it("includes actionable plugins.allow remediation hints in the untracked-provenance warning", () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => {
const globalDir = path.join(stateDir, "extensions", "warn-untracked-remediation");
mkdirSafe(globalDir);
writePlugin({
id: "warn-untracked-remediation",
body: simplePluginBody("warn-untracked-remediation"),
dir: globalDir,
filename: "index.cjs",
});
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
},
},
});
const untrackedWarning = warnings.find(
(msg) =>
msg.includes("warn-untracked-remediation") &&
msg.includes("loaded without install/load-path provenance"),
);
expect(untrackedWarning).toBeDefined();
expect(untrackedWarning).toContain('"warn-untracked-remediation"');
expect(untrackedWarning).toContain("openclaw plugins inspect warn-untracked-remediation");
expect(untrackedWarning).toContain("reinstall from a trusted source");
const diagnostic = registry.diagnostics.find(
(entry) =>
entry.pluginId === "warn-untracked-remediation" &&
entry.message.includes("loaded without install/load-path provenance"),
);
expect(diagnostic?.message).toContain('"warn-untracked-remediation"');
expect(diagnostic?.message).toContain("openclaw plugins inspect warn-untracked-remediation");
expect(diagnostic?.message).toContain("reinstall from a trusted source");
});
});
it("omits the truncated plugins.allow snippet when more than six plugins are discovered", () => {
const ids = Array.from({ length: 8 }, (_, index) => `discovered-plugin-${index + 1}`);
const warnings: string[] = [];
const seenWarningKeys = new Set<string>();
const cache = {
hasOpenAllowlistWarning(key: string) {
return seenWarningKeys.has(key);
},
recordOpenAllowlistWarning(key: string) {
seenWarningKeys.add(key);
},
};
warnWhenAllowlistIsOpen({
emitWarning: true,
logger: createWarningLogger(warnings),
pluginsEnabled: true,
allow: [],
warningCacheKey: "truncated",
warningCache: cache,
discoverablePlugins: ids.map((id) => ({ id, source: `/tmp/${id}`, origin: "global" })),
});
expect(warnings).toHaveLength(1);
const message = warnings[0] ?? "";
expect(message).toContain("plugins.allow is empty");
expect(message).toContain("(+2 more)");
expect(message).not.toContain('"plugins": { "allow": [');
expect(message).toContain("openclaw plugins list --enabled --verbose");
expect(message).toContain("openclaw plugins inspect <id>");
});
it("handles workspace-discovered plugins according to trust and precedence", () => {
useNoBundledPlugins();
const scenarios = [
{
label: "untrusted workspace plugins stay disabled",
pluginId: "workspace-helper",
loadRegistry: () => {
const { workspaceDir } = writeWorkspacePlugin({
id: "workspace-helper",
});
return loadOpenClawPlugins({
cache: false,
workspaceDir,
config: {
plugins: {
enabled: true,
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
expectPluginOriginAndStatus({
registry,
pluginId: "workspace-helper",
origin: "workspace",
status: "disabled",
label: "untrusted workspace plugins stay disabled",
errorIncludes: "workspace plugin (disabled by default)",
});
},
},
{
label: "trusted workspace plugins load",
pluginId: "workspace-helper",
loadRegistry: () => {
const { workspaceDir } = writeWorkspacePlugin({
id: "workspace-helper",
});
return loadOpenClawPlugins({
cache: false,
workspaceDir,
config: {
plugins: {
enabled: true,
allow: ["workspace-helper"],
},
},
});
},
assert: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
expectPluginOriginAndStatus({
registry,
pluginId: "workspace-helper",
origin: "workspace",
status: "loaded",
label: "trusted workspace plugins load",
});
},
},
{
label: "bundled plugins stay ahead of trusted workspace duplicates",
pluginId: "shadowed",
expectedLoadedOrigin: "bundled",
expectedDisabledOrigin: "workspace",
expectedDisabledError: "overridden by bundled plugin",
loadRegistry: () => {
writeBundledPlugin({
id: "shadowed",
});
const { workspaceDir } = writeWorkspacePlugin({
id: "shadowed",
});
return loadOpenClawPlugins({
cache: false,
workspaceDir,
config: {
plugins: {
enabled: true,
allow: ["shadowed"],
entries: {
shadowed: { enabled: true },
},
},
},
});
},
assert: (registry: PluginRegistry) => {
expectPluginSourcePrecedence(registry, {
pluginId: "shadowed",
expectedLoadedOrigin: "bundled",
expectedDisabledOrigin: "workspace",
expectedDisabledError: "overridden by bundled plugin",
label: "bundled plugins stay ahead of trusted workspace duplicates",
});
},
},
] as const;
runRegistryScenarios(scenarios, (scenario) => scenario.loadRegistry());
});
it("loads bundled plugins when manifest metadata opts into default enablement", () => {
const { bundledDir, plugin } = writeBundledPlugin({
id: "profile-aware",
body: simplePluginBody("profile-aware"),
});
fs.writeFileSync(
path.join(plugin.dir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "profile-aware",
enabledByDefault: true,
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
const registry = loadOpenClawPlugins({
cache: false,
workspaceDir: bundledDir,
config: {
plugins: {
enabled: true,
},
},
});
const bundledPlugin = registry.plugins.find((entry) => entry.id === "profile-aware");
expect(bundledPlugin?.origin).toBe("bundled");
expect(bundledPlugin?.status).toBe("loaded");
});
it("keeps scoped and unscoped plugin ids distinct", () => {
useNoBundledPlugins();
const scoped = writePlugin({
id: "@team/shadowed",
body: simplePluginBody("@team/shadowed"),
filename: "scoped.cjs",
});
const unscoped = writePlugin({
id: "shadowed",
body: simplePluginBody("shadowed"),
filename: "unscoped.cjs",
});
const registry = loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [scoped.file, unscoped.file] },
allow: ["@team/shadowed", "shadowed"],
},
},
});
expect(registry.plugins.find((entry) => entry.id === "@team/shadowed")?.status).toBe("loaded");
expect(registry.plugins.find((entry) => entry.id === "shadowed")?.status).toBe("loaded");
expectNoDiagnosticContaining({ registry, message: "duplicate plugin id" });
});
it("evaluates load-path provenance warnings", () => {
useNoBundledPlugins();
const scenarios = [
{
label: "does not warn when loaded non-bundled plugin is in plugins.allow",
loadRegistry: () => {
return withStateDir((stateDir) => {
const globalDir = path.join(stateDir, "extensions", "rogue");
mkdirSafe(globalDir);
writePlugin({
id: "rogue",
body: simplePluginBody("rogue"),
dir: globalDir,
filename: "index.cjs",
});
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
config: {
plugins: {
allow: ["rogue"],
},
},
});
return { registry, warnings, pluginId: "rogue", expectWarning: false };
});
},
},
{
label: "warns when loaded non-bundled plugin has no provenance and no allowlist is set",
loadRegistry: () => {
const stateDir = makeTempDir();
return withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => {
const globalDir = path.join(stateDir, "extensions", "rogue");
mkdirSafe(globalDir);
writePlugin({
id: "rogue",
body: `module.exports = { id: "rogue", register() {} };`,
dir: globalDir,
filename: "index.cjs",
});
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
},
},
});
return { registry, warnings, pluginId: "rogue", expectWarning: true };
});
},
},
{
label: "does not warn about missing provenance for env-resolved load paths",
loadRegistry: () => {
const { plugin, env } = createEnvResolvedPluginFixture("tracked-load-path");
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
env,
config: {
plugins: {
load: { paths: ["~/plugins/tracked-load-path"] },
allow: [plugin.id],
},
},
});
return {
registry,
warnings,
pluginId: plugin.id,
expectWarning: false,
expectedSource: plugin.file,
};
},
},
{
label: "does not warn about missing provenance for env-resolved install paths",
loadRegistry: () => {
const { plugin, env } = createEnvResolvedPluginFixture("tracked-install-path");
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
env,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: [plugin.id],
installs: {
[plugin.id]: {
source: "path",
installPath: `~/plugins/${plugin.id}`,
sourcePath: `~/plugins/${plugin.id}`,
},
},
},
},
});
return {
registry,
warnings,
pluginId: plugin.id,
expectWarning: false,
expectedSource: plugin.file,
};
},
},
{
label: "does not warn when install paths resolve through a symlinked state root",
loadRegistry: () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
const realHome = path.join(stateDir, "real-home");
const linkedHome = path.join(stateDir, "linked-home");
mkdirSafe(realHome);
fs.symlinkSync(realHome, linkedHome, process.platform === "win32" ? "junction" : "dir");
const pluginDir = path.join(
realHome,
".openclaw",
"npm",
"node_modules",
"@example",
"tracked-symlink-install",
);
mkdirSafe(pluginDir);
const plugin = writePlugin({
id: "tracked-symlink-install",
body: simplePluginBody("tracked-symlink-install"),
dir: pluginDir,
filename: "index.cjs",
});
writePersistedInstalledPluginIndexInstallRecordsSync(
{
[plugin.id]: {
source: "npm",
spec: "@example/tracked-symlink-install@1.0.0",
installPath: path.join(
linkedHome,
".openclaw",
"npm",
"node_modules",
"@example",
"tracked-symlink-install",
),
version: "1.0.0",
},
},
{ stateDir },
);
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
env: {
...process.env,
OPENCLAW_STATE_DIR: stateDir,
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
},
config: {
plugins: {
enabled: true,
},
},
});
return {
registry,
warnings,
pluginId: plugin.id,
expectWarning: false,
};
},
},
] as const;
runScenarioCases(scenarios, (scenario) => {
const loadedScenario = scenario.loadRegistry();
const expectedSource =
"expectedSource" in loadedScenario && typeof loadedScenario.expectedSource === "string"
? loadedScenario.expectedSource
: undefined;
expectLoadedPluginProvenance({
scenario,
...loadedScenario,
expectedSource,
});
});
});
it("uses the source runtime snapshot allowlist for plugin trust checks", () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => {
const globalDir = path.join(stateDir, "extensions", "trusted-plugin");
mkdirSafe(globalDir);
writePlugin({
id: "trusted-plugin",
body: simplePluginBody("trusted-plugin"),
dir: globalDir,
filename: "index.cjs",
});
const untrustedDir = path.join(stateDir, "extensions", "untrusted-plugin");
mkdirSafe(untrustedDir);
writePlugin({
id: "untrusted-plugin",
body: simplePluginBody("untrusted-plugin"),
dir: untrustedDir,
filename: "index.cjs",
});
const runtimeConfig = {
plugins: {
enabled: true,
allow: ["runtime-added-plugin"],
},
} satisfies PluginLoadConfig;
const sourceConfig = {
plugins: {
enabled: true,
allow: ["trusted-plugin"],
},
} satisfies PluginLoadConfig;
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
cache: false,
logger: createWarningLogger(warnings),
config: runtimeConfig,
});
expect(registry.plugins.find((entry) => entry.id === "trusted-plugin")?.status).toBe(
"loaded",
);
const untrustedPlugin = registry.plugins.find((entry) => entry.id === "untrusted-plugin");
expect(untrustedPlugin?.status).toBe("disabled");
expect(untrustedPlugin?.error).toBe("not in allowlist");
expect(warnings.join("\n")).not.toContain("plugins.allow is empty");
expect(
warnings.filter(
(message) =>
message.includes("trusted-plugin") &&
message.includes("loaded without install/load-path provenance"),
),
).toEqual([]);
});
});
it.each([
{
name: "rejects plugin entry files that escape plugin root via symlink",
id: "symlinked",
linkKind: "symlink" as const,
},
{
name: "rejects plugin entry files that escape plugin root via hardlink",
id: "hardlinked",
linkKind: "hardlink" as const,
skip: process.platform === "win32",
},
])("$name", ({ id, linkKind, skip }) => {
if (skip) {
return;
}
expectEscapingEntryRejected({
id,
linkKind,
sourceBody: `module.exports = { id: "${id}", register() { throw new Error("should not run"); } };`,
});
});
it("allows bundled plugin entry files that are hardlinked aliases", () => {
if (process.platform === "win32") {
return;
}
const bundledDir = makeTempDir();
const pluginDir = path.join(bundledDir, "hardlinked-bundled");
mkdirSafe(pluginDir);
const outsideDir = makeTempDir();
const outsideEntry = path.join(outsideDir, "outside.cjs");
fs.writeFileSync(
outsideEntry,
'module.exports = { id: "hardlinked-bundled", register() {} };',
"utf-8",
);
const plugin = writePlugin({
id: "hardlinked-bundled",
body: 'module.exports = { id: "hardlinked-bundled", register() {} };',
dir: pluginDir,
filename: "index.cjs",
});
fs.rmSync(plugin.file);
try {
fs.linkSync(outsideEntry, plugin.file);
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
return;
}
throw err;
}
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledDir;
const registry = loadOpenClawPlugins({
cache: false,
workspaceDir: bundledDir,
config: {
plugins: {
entries: {
"hardlinked-bundled": { enabled: true },
},
allow: ["hardlinked-bundled"],
},
},
});
const record = registry.plugins.find((entry) => entry.id === "hardlinked-bundled");
expect(record?.status).toBe("loaded");
expectNoDiagnosticContaining({ registry, message: "unsafe plugin path" });
});
it("preserves runtime reflection semantics when runtime is lazily initialized", () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
const plugin = writePlugin({
id: "runtime-introspection",
filename: "runtime-introspection.cjs",
body: `module.exports = { id: "runtime-introspection", register(api) {
const runtime = api.runtime ?? {};
const keys = Object.keys(runtime);
for (const key of ["channel", "mediaUnderstanding", "llm"]) {
if (!keys.includes(key)) {
throw new Error("runtime " + key + " key missing");
}
if (!(key in runtime)) {
throw new Error("runtime " + key + " missing from has check");
}
if (!Object.getOwnPropertyDescriptor(runtime, key)) {
throw new Error("runtime " + key + " descriptor missing");
}
}
} };`,
});
const registry = withEnv({ OPENCLAW_STATE_DIR: stateDir }, () =>
loadRegistryFromSinglePlugin({
plugin,
pluginConfig: {
allow: ["runtime-introspection"],
},
options: {
onlyPluginIds: ["runtime-introspection"],
},
}),
);
const record = registry.plugins.find((entry) => entry.id === "runtime-introspection");
expect(record?.status).toBe("loaded");
});
it("supports legacy plugins importing monolithic plugin-sdk root", () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "legacy-root-import",
filename: "legacy-root-import.cjs",
body: `module.exports = {
id: "legacy-root-import",
configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(),
register() {},
};`,
});
const registry = withEnv({ OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" }, () =>
loadOpenClawPlugins({
cache: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["legacy-root-import"],
},
},
}),
);
const record = registry.plugins.find((entry) => entry.id === "legacy-root-import");
expect(
record?.status,
JSON.stringify({ error: record?.error, diagnostics: registry.diagnostics }, null, 2),
).toBe("loaded");
});
it("supports legacy plugins subscribing to diagnostic events from the root sdk", async () => {
useNoBundledPlugins();
const seenKey = "__openclawLegacyRootDiagnosticSeen";
delete (globalThis as Record<string, unknown>)[seenKey];
const plugin = writePlugin({
id: "legacy-root-diagnostic-listener",
filename: "legacy-root-diagnostic-listener.cjs",
body: `module.exports = {
id: "legacy-root-diagnostic-listener",
configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(),
register() {
const { onDiagnosticEvent } = require("openclaw/plugin-sdk");
if (typeof onDiagnosticEvent !== "function") {
throw new Error("missing onDiagnosticEvent root export");
}
globalThis.${seenKey} = [];
onDiagnosticEvent((event) => {
globalThis.${seenKey}.push({
type: event.type,
sessionKey: event.sessionKey,
});
});
},
};`,
});
try {
const registry = withEnv(
{ OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" },
() =>
loadOpenClawPlugins({
cache: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["legacy-root-diagnostic-listener"],
},
},
}),
);
const record = registry.plugins.find(
(entry) => entry.id === "legacy-root-diagnostic-listener",
);
expect(
record?.status,
JSON.stringify({ error: record?.error, diagnostics: registry.diagnostics }, null, 2),
).toBe("loaded");
emitDiagnosticEvent({
type: "model.usage",
sessionKey: "agent:main:test:dm:peer",
usage: { total: 1 },
});
await waitForDiagnosticEventsDrained();
expect((globalThis as Record<string, unknown>)[seenKey]).toEqual([
{
type: "model.usage",
sessionKey: "agent:main:test:dm:peer",
},
]);
} finally {
delete (globalThis as Record<string, unknown>)[seenKey];
}
});
it("suppresses trust warning logs for non-activating snapshot loads", () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
withEnv({ OPENCLAW_STATE_DIR: stateDir }, () => {
const globalDir = path.join(stateDir, "extensions", "rogue");
mkdirSafe(globalDir);
writePlugin({
id: "rogue",
body: simplePluginBody("rogue"),
dir: globalDir,
filename: "index.cjs",
});
const warnings: string[] = [];
const registry = loadOpenClawPlugins({
activate: false,
cache: false,
logger: createWarningLogger(warnings),
config: {
plugins: {
enabled: true,
},
},
});
expect(warnings).toStrictEqual([]);
expectDiagnosticContaining({
registry,
level: "warn",
pluginId: "rogue",
message: "loaded without install/load-path provenance",
});
});
});
it("loads source TypeScript plugins that route through local runtime shims", () => {
const plugin = writePlugin({
id: "source-runtime-shim",
filename: "source-runtime-shim.ts",
body: `import "./runtime-shim.ts";
export default {
id: "source-runtime-shim",
register() {},
};`,
});
fs.writeFileSync(
path.join(plugin.dir, "runtime-shim.ts"),
`import { helperValue } from "./helper.js";
export const runtimeValue = helperValue;`,
"utf-8",
);
fs.writeFileSync(
path.join(plugin.dir, "helper.ts"),
`export const helperValue = "ok";`,
"utf-8",
);
const registry = loadOpenClawPlugins({
cache: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["source-runtime-shim"],
},
},
});
const record = registry.plugins.find((entry) => entry.id === "source-runtime-shim");
expect(record?.status).toBe("loaded");
});
it("converts Windows absolute import specifiers to file URLs only for module loading", () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
try {
expect(toSafeImportPath("C:\\Users\\alice\\plugin\\index.mjs")).toBe(
"file:///C:/Users/alice/plugin/index.mjs",
);
expect(toSafeImportPath("C:\\Users\\alice\\plugin folder\\x#y.mjs")).toBe(
"file:///C:/Users/alice/plugin%20folder/x%23y.mjs",
);
expect(toSafeImportPath("\\\\server\\share\\plugin\\index.mjs")).toBe(
"file://server/share/plugin/index.mjs",
);
expect(toSafeImportPath("file:///C:/Users/alice/plugin/index.mjs")).toBe(
"file:///C:/Users/alice/plugin/index.mjs",
);
expect(toSafeImportPath("./relative/index.mjs")).toBe("./relative/index.mjs");
} finally {
platformSpy.mockRestore();
}
});
});
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */