Files
openclaw/src/plugins/loader.cli-metadata.test.ts
Gustavo Madeira Santana d5166718bc test(matrix): cover destructive E2EE backup recovery flows (#71311)
Merged via squash.

Prepared head SHA: fd5fc06007
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-25 17:48:18 -04:00

930 lines
26 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterAll, afterEach, describe, expect, it } from "vitest";
import {
defineBundledChannelEntry,
type OpenClawPluginApi,
} from "../plugin-sdk/channel-entry-contract.js";
import { loadOpenClawPluginCliRegistry, loadOpenClawPlugins } from "./loader.js";
import {
cleanupPluginLoaderFixturesForTest,
EMPTY_PLUGIN_SCHEMA,
inlineChannelPluginEntryFactorySource,
makeTempDir,
resetPluginLoaderTestStateForTest,
useNoBundledPlugins,
writePlugin,
} from "./loader.test-fixtures.js";
afterEach(() => {
resetPluginLoaderTestStateForTest();
});
afterAll(() => {
cleanupPluginLoaderFixturesForTest();
});
describe("plugin loader CLI metadata", () => {
it("suppresses trust warning logs during CLI metadata loads", async () => {
useNoBundledPlugins();
const stateDir = makeTempDir();
const globalDir = path.join(stateDir, "extensions", "rogue");
fs.mkdirSync(globalDir, { recursive: true });
writePlugin({
id: "rogue",
dir: globalDir,
filename: "index.cjs",
body: `module.exports = {
id: "rogue",
register(api) {
api.registerCli(() => {}, {
descriptors: [
{
name: "rogue",
description: "Rogue CLI metadata",
hasSubcommands: true,
},
],
});
},
};`,
});
const warnings: string[] = [];
const registry = await loadOpenClawPluginCliRegistry({
env: { ...process.env, OPENCLAW_STATE_DIR: stateDir },
logger: {
info: () => {},
warn: (msg: string) => warnings.push(msg),
error: () => {},
debug: () => {},
},
config: {
plugins: {
enabled: true,
},
},
});
expect(warnings).toEqual([]);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain("rogue");
});
it("passes validated plugin config into non-activating CLI metadata loads", async () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "config-cli",
filename: "config-cli.cjs",
body: `module.exports = {
id: "config-cli",
register(api) {
if (!api.pluginConfig || api.pluginConfig.token !== "ok") {
throw new Error("missing plugin config");
}
api.registerCli(() => {}, {
descriptors: [
{
name: "cfg",
description: "Config-backed CLI command",
hasSubcommands: true,
},
],
});
},
};`,
});
fs.writeFileSync(
path.join(plugin.dir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "config-cli",
configSchema: {
type: "object",
additionalProperties: false,
properties: {
token: { type: "string" },
},
required: ["token"],
},
},
null,
2,
),
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["config-cli"],
entries: {
"config-cli": {
config: {
token: "ok",
},
},
},
},
},
});
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain("cfg");
expect(registry.plugins.find((entry) => entry.id === "config-cli")?.status).toBe("loaded");
});
it("uses the real channel entry in cli-metadata mode for CLI metadata capture", async () => {
useNoBundledPlugins();
const pluginDir = makeTempDir();
const fullMarker = path.join(pluginDir, "full-loaded.txt");
const modeMarker = path.join(pluginDir, "registration-mode.txt");
const runtimeMarker = path.join(pluginDir, "runtime-set.txt");
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/cli-metadata-channel",
openclaw: { extensions: ["./index.cjs"], setupEntry: "./setup-entry.cjs" },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "cli-metadata-channel",
configSchema: EMPTY_PLUGIN_SCHEMA,
channels: ["cli-metadata-channel"],
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`${inlineChannelPluginEntryFactorySource()}
require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
module.exports = {
...defineChannelPluginEntry({
id: "cli-metadata-channel",
name: "CLI Metadata Channel",
description: "cli metadata channel",
setRuntime() {
require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "loaded", "utf-8");
},
plugin: {
id: "cli-metadata-channel",
meta: {
id: "cli-metadata-channel",
label: "CLI Metadata Channel",
selectionLabel: "CLI Metadata Channel",
docsPath: "/channels/cli-metadata-channel",
blurb: "cli metadata channel",
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({ accountId: "default" }),
},
outbound: { deliveryMode: "direct" },
},
registerCliMetadata(api) {
require("node:fs").writeFileSync(
${JSON.stringify(modeMarker)},
String(api.registrationMode),
"utf-8",
);
api.registerCli(() => {}, {
descriptors: [
{
name: "cli-metadata-channel",
description: "Channel CLI metadata",
hasSubcommands: true,
},
],
});
},
registerFull() {
throw new Error("full channel entry should not run during CLI metadata capture");
},
}),
};`,
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.cjs"),
`throw new Error("setup entry should not load during CLI metadata capture");`,
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
load: { paths: [pluginDir] },
allow: ["cli-metadata-channel"],
},
},
});
expect(fs.existsSync(fullMarker)).toBe(true);
expect(fs.existsSync(runtimeMarker)).toBe(false);
expect(fs.readFileSync(modeMarker, "utf-8")).toBe("cli-metadata");
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain(
"cli-metadata-channel",
);
});
it("skips bundled channel full entries that do not provide a dedicated cli-metadata entry", async () => {
const bundledRoot = makeTempDir();
const pluginDir = path.join(bundledRoot, "bundled-skip-channel");
const fullMarker = path.join(pluginDir, "full-loaded.txt");
fs.mkdirSync(pluginDir, { recursive: true });
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledRoot;
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/bundled-skip-channel",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "bundled-skip-channel",
configSchema: EMPTY_PLUGIN_SCHEMA,
channels: ["bundled-skip-channel"],
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
module.exports = {
id: "bundled-skip-channel",
register() {
throw new Error("bundled channel full entry should not load during CLI metadata capture");
},
};`,
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
allow: ["bundled-skip-channel"],
entries: {
"bundled-skip-channel": {
enabled: true,
},
},
},
},
});
expect(fs.existsSync(fullMarker)).toBe(false);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).not.toContain(
"bundled-skip-channel",
);
expect(registry.plugins.find((entry) => entry.id === "bundled-skip-channel")?.status).toBe(
"loaded",
);
});
it("prefers bundled channel cli-metadata entries over full channel entries", async () => {
const bundledRoot = makeTempDir();
const pluginDir = path.join(bundledRoot, "bundled-cli-channel");
const fullMarker = path.join(pluginDir, "full-loaded.txt");
const cliMarker = path.join(pluginDir, "cli-loaded.txt");
fs.mkdirSync(pluginDir, { recursive: true });
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledRoot;
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/bundled-cli-channel",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "bundled-cli-channel",
configSchema: EMPTY_PLUGIN_SCHEMA,
channels: ["bundled-cli-channel"],
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
module.exports = {
id: "bundled-cli-channel",
register() {
throw new Error("bundled channel full entry should not load during CLI metadata capture");
},
};`,
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "cli-metadata.cjs"),
`module.exports = {
id: "bundled-cli-channel",
register(api) {
require("node:fs").writeFileSync(${JSON.stringify(cliMarker)}, "loaded", "utf-8");
api.registerCli(() => {}, {
descriptors: [
{
name: "bundled-cli-channel",
description: "Bundled channel CLI metadata",
hasSubcommands: true,
},
],
});
},
};`,
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
allow: ["bundled-cli-channel"],
entries: {
"bundled-cli-channel": {
enabled: true,
},
},
},
},
});
expect(fs.existsSync(fullMarker)).toBe(false);
expect(fs.existsSync(cliMarker)).toBe(true);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain(
"bundled-cli-channel",
);
});
it("skips bundled non-channel full entries that do not provide a dedicated cli-metadata entry", async () => {
const bundledRoot = makeTempDir();
const pluginDir = path.join(bundledRoot, "bundled-skip-provider");
const fullMarker = path.join(pluginDir, "full-loaded.txt");
fs.mkdirSync(pluginDir, { recursive: true });
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledRoot;
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/bundled-skip-provider",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "bundled-skip-provider",
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
module.exports = {
id: "bundled-skip-provider",
register() {
throw new Error("bundled provider full entry should not load during CLI metadata capture");
},
};`,
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
allow: ["bundled-skip-provider"],
entries: {
"bundled-skip-provider": {
enabled: true,
},
},
},
},
});
expect(fs.existsSync(fullMarker)).toBe(false);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).not.toContain(
"bundled-skip-provider",
);
expect(registry.plugins.find((entry) => entry.id === "bundled-skip-provider")?.status).toBe(
"loaded",
);
});
it("collects channel CLI metadata during full plugin loads", () => {
useNoBundledPlugins();
const pluginDir = makeTempDir();
const modeMarker = path.join(pluginDir, "registration-mode.txt");
const fullMarker = path.join(pluginDir, "full-loaded.txt");
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/full-cli-metadata-channel",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "full-cli-metadata-channel",
configSchema: EMPTY_PLUGIN_SCHEMA,
channels: ["full-cli-metadata-channel"],
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`${inlineChannelPluginEntryFactorySource()}
module.exports = {
...defineChannelPluginEntry({
id: "full-cli-metadata-channel",
name: "Full CLI Metadata Channel",
description: "full cli metadata channel",
plugin: {
id: "full-cli-metadata-channel",
meta: {
id: "full-cli-metadata-channel",
label: "Full CLI Metadata Channel",
selectionLabel: "Full CLI Metadata Channel",
docsPath: "/channels/full-cli-metadata-channel",
blurb: "full cli metadata channel",
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({ accountId: "default" }),
},
outbound: { deliveryMode: "direct" },
},
registerCliMetadata(api) {
require("node:fs").writeFileSync(
${JSON.stringify(modeMarker)},
String(api.registrationMode),
"utf-8",
);
api.registerCli(() => {}, {
descriptors: [
{
name: "full-cli-metadata-channel",
description: "Full-load channel CLI metadata",
hasSubcommands: true,
},
],
});
},
registerFull() {
require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
},
}),
};`,
"utf-8",
);
const registry = loadOpenClawPlugins({
cache: false,
config: {
plugins: {
load: { paths: [pluginDir] },
allow: ["full-cli-metadata-channel"],
},
},
});
expect(fs.readFileSync(modeMarker, "utf-8")).toBe("full");
expect(fs.existsSync(fullMarker)).toBe(true);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain(
"full-cli-metadata-channel",
);
});
it("collects channel CLI metadata during discovery plugin loads", () => {
useNoBundledPlugins();
const pluginDir = makeTempDir();
const modeMarker = path.join(pluginDir, "registration-mode.txt");
const fullMarker = path.join(pluginDir, "full-loaded.txt");
const runtimeMarker = path.join(pluginDir, "runtime-set.txt");
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
name: "@openclaw/discovery-cli-metadata-channel",
openclaw: { extensions: ["./index.cjs"] },
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "discovery-cli-metadata-channel",
configSchema: EMPTY_PLUGIN_SCHEMA,
channels: ["discovery-cli-metadata-channel"],
},
null,
2,
),
"utf-8",
);
fs.writeFileSync(
path.join(pluginDir, "index.cjs"),
`${inlineChannelPluginEntryFactorySource()}
module.exports = {
...defineChannelPluginEntry({
id: "discovery-cli-metadata-channel",
name: "Discovery CLI Metadata Channel",
description: "discovery cli metadata channel",
setRuntime() {
require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "loaded", "utf-8");
},
plugin: {
id: "discovery-cli-metadata-channel",
meta: {
id: "discovery-cli-metadata-channel",
label: "Discovery CLI Metadata Channel",
selectionLabel: "Discovery CLI Metadata Channel",
docsPath: "/channels/discovery-cli-metadata-channel",
blurb: "discovery cli metadata channel",
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({ accountId: "default" }),
},
outbound: { deliveryMode: "direct" },
},
registerCliMetadata(api) {
require("node:fs").writeFileSync(
${JSON.stringify(modeMarker)},
String(api.registrationMode),
"utf-8",
);
api.registerCli(() => {}, {
descriptors: [
{
name: "discovery-cli-metadata-channel",
description: "Discovery-load channel CLI metadata",
hasSubcommands: true,
},
],
});
},
registerFull() {
require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
},
}),
};`,
"utf-8",
);
const registry = loadOpenClawPlugins({
activate: false,
cache: false,
config: {
plugins: {
load: { paths: [pluginDir] },
allow: ["discovery-cli-metadata-channel"],
entries: {
"discovery-cli-metadata-channel": {
enabled: true,
},
},
},
},
});
expect(fs.readFileSync(modeMarker, "utf-8")).toBe("discovery");
expect(fs.existsSync(fullMarker)).toBe(false);
expect(fs.existsSync(runtimeMarker)).toBe(true);
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).toContain(
"discovery-cli-metadata-channel",
);
});
it("sets bundled channel runtime before discovery CLI metadata registration", () => {
const pluginDir = makeTempDir();
const runtimeMarker = path.join(pluginDir, "runtime-set.txt");
const channelPluginPath = path.join(pluginDir, "channel.cjs");
const runtimePath = path.join(pluginDir, "runtime.cjs");
fs.writeFileSync(
channelPluginPath,
`exports.plugin = {
id: "bundled-discovery-cli",
meta: {
id: "bundled-discovery-cli",
label: "Bundled Discovery CLI",
selectionLabel: "Bundled Discovery CLI",
docsPath: "/channels/bundled-discovery-cli",
blurb: "bundled discovery cli",
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({ accountId: "default" }),
},
outbound: { deliveryMode: "direct" },
};`,
"utf-8",
);
fs.writeFileSync(
runtimePath,
`exports.setRuntime = () => {
require("node:fs").writeFileSync(${JSON.stringify(runtimeMarker)}, "loaded", "utf-8");
};`,
"utf-8",
);
const commands: string[] = [];
const channels: string[] = [];
const entry = defineBundledChannelEntry({
id: "bundled-discovery-cli",
name: "Bundled Discovery CLI",
description: "bundled discovery cli",
importMetaUrl: pathToFileURL(path.join(pluginDir, "index.cjs")).href,
plugin: {
specifier: "./channel.cjs",
exportName: "plugin",
},
runtime: {
specifier: "./runtime.cjs",
exportName: "setRuntime",
},
registerCliMetadata(api) {
api.registerCli(() => {}, {
descriptors: [
{
name: "bundled-discovery-cli",
description: "Bundled discovery CLI metadata",
hasSubcommands: true,
},
],
});
},
registerFull() {
throw new Error("full registration should not run during discovery");
},
});
entry.register({
registrationMode: "discovery",
runtime: {} as OpenClawPluginApi["runtime"],
registerChannel: (registration) => {
const plugin = "plugin" in registration ? registration.plugin : registration;
channels.push(plugin.id);
},
registerCli: (_register, options) => {
commands.push(...(options?.descriptors ?? []).map((descriptor) => descriptor.name));
},
} as OpenClawPluginApi);
expect(channels).toEqual(["bundled-discovery-cli"]);
expect(fs.existsSync(runtimeMarker)).toBe(true);
expect(commands).toEqual(["bundled-discovery-cli"]);
});
it("sanitizes plugin CLI descriptor descriptions and rejects unsafe command names", async () => {
useNoBundledPlugins();
const unsafeDescription =
"Open \u001B]8;;https://example.test\u0007link\u001B]8;;\u0007 now\u001B[2J";
const plugin = writePlugin({
id: "unsafe-cli-descriptors",
filename: "unsafe-cli-descriptors.cjs",
body: `module.exports = {
id: "unsafe-cli-descriptors",
register(api) {
api.registerCli(() => {}, {
commands: ["bad\\ncommand"],
descriptors: [
{
name: "safe-command",
description: ${JSON.stringify(unsafeDescription)},
hasSubcommands: false,
},
{
name: "bad\\nname",
description: "Bad descriptor",
hasSubcommands: false,
},
],
});
},
};`,
});
const registry = await loadOpenClawPluginCliRegistry({
cache: false,
config: {
plugins: {
load: { paths: [plugin.dir] },
allow: ["unsafe-cli-descriptors"],
},
},
});
expect(registry.cliRegistrars).toHaveLength(1);
expect(registry.cliRegistrars[0]?.commands).toEqual(["safe-command"]);
expect(registry.cliRegistrars[0]?.descriptors).toEqual([
{
name: "safe-command",
description: "Open link now",
hasSubcommands: false,
},
]);
expect(registry.diagnostics.map((diag) => diag.message)).toEqual([
'invalid cli descriptor name: "bad\\nname"',
'invalid cli command name: "bad\\ncommand"',
]);
});
it("rejects async plugin registration when collecting CLI metadata", async () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "async-cli",
filename: "async-cli.cjs",
body: `module.exports = {
id: "async-cli",
async register(api) {
await Promise.resolve();
api.registerCli(() => {}, {
descriptors: [
{
name: "async-cli",
description: "Async CLI metadata",
hasSubcommands: true,
},
],
});
},
};`,
});
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["async-cli"],
},
},
});
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).not.toContain("async-cli");
const loaded = registry.plugins.find((entry) => entry.id === "async-cli");
expect(loaded?.status).toBe("error");
expect(loaded?.failurePhase).toBe("register");
expect(loaded?.error).toContain("plugin register must be synchronous");
});
it("applies memory slot gating to non-bundled CLI metadata loads", async () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "memory-external",
filename: "memory-external.cjs",
body: `module.exports = {
id: "memory-external",
kind: "memory",
register(api) {
api.registerCli(() => {}, {
descriptors: [
{
name: "memory-external",
description: "External memory CLI metadata",
hasSubcommands: true,
},
],
});
},
};`,
});
fs.writeFileSync(
path.join(plugin.dir, "openclaw.plugin.json"),
JSON.stringify(
{
id: "memory-external",
kind: "memory",
configSchema: EMPTY_PLUGIN_SCHEMA,
},
null,
2,
),
"utf-8",
);
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["memory-external"],
slots: { memory: "memory-other" },
},
},
});
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).not.toContain(
"memory-external",
);
const memory = registry.plugins.find((entry) => entry.id === "memory-external");
expect(memory?.status).toBe("disabled");
expect(memory?.error ?? "").toContain('memory slot set to "memory-other"');
});
it("re-evaluates memory slot gating after resolving exported plugin kind", async () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "memory-export-only",
filename: "memory-export-only.cjs",
body: `module.exports = {
id: "memory-export-only",
kind: "memory",
register(api) {
api.registerCli(() => {}, {
descriptors: [
{
name: "memory-export-only",
description: "Export-only memory CLI metadata",
hasSubcommands: true,
},
],
});
},
};`,
});
const registry = await loadOpenClawPluginCliRegistry({
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["memory-export-only"],
slots: { memory: "memory-other" },
},
},
});
expect(registry.cliRegistrars.flatMap((entry) => entry.commands)).not.toContain(
"memory-export-only",
);
const memory = registry.plugins.find((entry) => entry.id === "memory-export-only");
expect(memory?.status).toBe("disabled");
expect(memory?.error ?? "").toContain('memory slot set to "memory-other"');
});
});