mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-20 01:21:41 -06:00
1aedd8f37e
* fix(memory): activate configured providers in CLI commands * fix(memory): resolve configured embedding provider owners * test(memory): keep metadata mock unset by default * fix(memory): preserve plugin allowlist policy * test(memory): classify plugin-owned root command
274 lines
9.4 KiB
TypeScript
274 lines
9.4 KiB
TypeScript
// Runtime registry loader tests cover the surviving process-root load scopes.
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
loadOpenClawPlugins: vi.fn<typeof import("../loader.js").loadOpenClawPlugins>(),
|
|
resolveConfiguredChannelPluginIds:
|
|
vi.fn<typeof import("../channel-plugin-ids.js").resolveConfiguredChannelPluginIds>(),
|
|
resolveChannelPluginIds:
|
|
vi.fn<typeof import("../channel-plugin-ids.js").resolveChannelPluginIds>(),
|
|
resolveEffectivePluginIds:
|
|
vi.fn<typeof import("../effective-plugin-ids.js").resolveEffectivePluginIds>(),
|
|
collectConfiguredMemoryEmbeddingProviderIds:
|
|
vi.fn<
|
|
typeof import("../gateway-startup-plugin-ids.js").collectConfiguredMemoryEmbeddingProviderIds
|
|
>(),
|
|
applyPluginAutoEnable:
|
|
vi.fn<typeof import("../../config/plugin-auto-enable.js").applyPluginAutoEnable>(),
|
|
resolvePluginMetadataSnapshot:
|
|
vi.fn<typeof import("../plugin-metadata-snapshot.js").resolvePluginMetadataSnapshot>(),
|
|
isPluginMetadataSnapshotCompatible:
|
|
vi.fn<typeof import("../plugin-metadata-snapshot.js").isPluginMetadataSnapshotCompatible>(),
|
|
resolveAgentWorkspaceDir: vi.fn<
|
|
typeof import("../../agents/agent-scope.js").resolveAgentWorkspaceDir
|
|
>(() => "/resolved-workspace"),
|
|
resolveDefaultAgentId: vi.fn<typeof import("../../agents/agent-scope.js").resolveDefaultAgentId>(
|
|
() => "default",
|
|
),
|
|
}));
|
|
|
|
vi.mock("../loader.js", () => ({
|
|
loadOpenClawPlugins: (...args: Parameters<typeof mocks.loadOpenClawPlugins>) =>
|
|
mocks.loadOpenClawPlugins(...args),
|
|
}));
|
|
|
|
vi.mock("../channel-plugin-ids.js", () => ({
|
|
resolveConfiguredChannelPluginIds: (
|
|
...args: Parameters<typeof mocks.resolveConfiguredChannelPluginIds>
|
|
) => mocks.resolveConfiguredChannelPluginIds(...args),
|
|
resolveChannelPluginIds: (...args: Parameters<typeof mocks.resolveChannelPluginIds>) =>
|
|
mocks.resolveChannelPluginIds(...args),
|
|
}));
|
|
|
|
vi.mock("../effective-plugin-ids.js", () => ({
|
|
resolveEffectivePluginIds: (...args: Parameters<typeof mocks.resolveEffectivePluginIds>) =>
|
|
mocks.resolveEffectivePluginIds(...args),
|
|
}));
|
|
|
|
vi.mock("../gateway-startup-plugin-ids.js", () => ({
|
|
collectConfiguredMemoryEmbeddingProviderIds: (
|
|
...args: Parameters<typeof mocks.collectConfiguredMemoryEmbeddingProviderIds>
|
|
) => mocks.collectConfiguredMemoryEmbeddingProviderIds(...args),
|
|
}));
|
|
|
|
vi.mock("../../config/plugin-auto-enable.js", () => ({
|
|
applyPluginAutoEnable: (...args: Parameters<typeof mocks.applyPluginAutoEnable>) =>
|
|
mocks.applyPluginAutoEnable(...args),
|
|
}));
|
|
|
|
vi.mock("../plugin-metadata-snapshot.js", () => ({
|
|
resolvePluginMetadataSnapshot: (
|
|
...args: Parameters<typeof mocks.resolvePluginMetadataSnapshot>
|
|
) => mocks.resolvePluginMetadataSnapshot(...args),
|
|
isPluginMetadataSnapshotCompatible: (
|
|
...args: Parameters<typeof mocks.isPluginMetadataSnapshotCompatible>
|
|
) => mocks.isPluginMetadataSnapshotCompatible(...args),
|
|
}));
|
|
|
|
vi.mock("../../agents/agent-scope.js", () => ({
|
|
resolveAgentWorkspaceDir: (...args: Parameters<typeof mocks.resolveAgentWorkspaceDir>) =>
|
|
mocks.resolveAgentWorkspaceDir(...args),
|
|
resolveDefaultAgentId: (...args: Parameters<typeof mocks.resolveDefaultAgentId>) =>
|
|
mocks.resolveDefaultAgentId(...args),
|
|
}));
|
|
|
|
import { ensurePluginRegistryLoaded } from "./runtime-registry-loader.js";
|
|
|
|
function useMemoryProviderOwner(params: {
|
|
adapterId: string;
|
|
contract: "embeddingProviders" | "memoryEmbeddingProviders";
|
|
pluginId: string;
|
|
}): void {
|
|
mocks.resolvePluginMetadataSnapshot.mockReturnValue({
|
|
policyHash: "test",
|
|
index: {
|
|
installRecords: {},
|
|
plugins: [
|
|
{
|
|
pluginId: params.pluginId,
|
|
contributions: {
|
|
contracts: { [params.contract]: [params.adapterId] },
|
|
},
|
|
},
|
|
],
|
|
},
|
|
manifestRegistry: { plugins: [], diagnostics: [] },
|
|
} as never);
|
|
}
|
|
|
|
function requireLoadOptions(): Record<string, unknown> {
|
|
const options = mocks.loadOpenClawPlugins.mock.calls[0]?.[0];
|
|
if (!options) {
|
|
throw new Error("expected plugin load options");
|
|
}
|
|
return options as Record<string, unknown>;
|
|
}
|
|
|
|
describe("ensurePluginRegistryLoaded", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.resolvePluginMetadataSnapshot.mockReset();
|
|
mocks.isPluginMetadataSnapshotCompatible.mockReturnValue(true);
|
|
mocks.applyPluginAutoEnable.mockImplementation((params) => ({
|
|
config: params.config ?? {},
|
|
changes: [],
|
|
autoEnabledReasons: {},
|
|
}));
|
|
});
|
|
|
|
it("loads configured channel owners through the canonical root loader", () => {
|
|
const config = { channels: { demo: { enabled: true } } };
|
|
mocks.resolveConfiguredChannelPluginIds.mockReturnValue(["demo-channel"]);
|
|
|
|
ensurePluginRegistryLoaded({ scope: "configured-channels", config: config as never });
|
|
|
|
expect(mocks.resolveConfiguredChannelPluginIds).toHaveBeenCalledWith(
|
|
expect.objectContaining({ config, workspaceDir: "/resolved-workspace" }),
|
|
);
|
|
expect(requireLoadOptions()).toEqual(
|
|
expect.objectContaining({
|
|
onlyPluginIds: ["demo-channel"],
|
|
throwOnLoadError: true,
|
|
workspaceDir: "/resolved-workspace",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("keeps an empty configured-channel scope empty", () => {
|
|
mocks.resolveConfiguredChannelPluginIds.mockReturnValue([]);
|
|
|
|
ensurePluginRegistryLoaded({ scope: "configured-channels", config: {} });
|
|
|
|
expect(requireLoadOptions().onlyPluginIds).toEqual([]);
|
|
});
|
|
|
|
it("loads effective plugin ids for the all scope", () => {
|
|
const config = { plugins: { enabled: true } };
|
|
mocks.resolveEffectivePluginIds.mockReturnValue(["demo", "memory-core"]);
|
|
|
|
ensurePluginRegistryLoaded({ scope: "all", config });
|
|
|
|
expect(mocks.resolveEffectivePluginIds).toHaveBeenCalledWith({
|
|
config,
|
|
env: process.env,
|
|
workspaceDir: "/resolved-workspace",
|
|
});
|
|
expect(requireLoadOptions()).toEqual(
|
|
expect.objectContaining({
|
|
onlyPluginIds: ["demo", "memory-core"],
|
|
throwOnLoadError: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("loads only the selected memory backend and embedding provider owners", () => {
|
|
const config = {
|
|
memory: { search: { provider: "openai" } },
|
|
plugins: {
|
|
allow: ["acpx", "memory-core"],
|
|
slots: { memory: "memory-core" },
|
|
entries: { unrelated: { enabled: true } },
|
|
},
|
|
};
|
|
mocks.collectConfiguredMemoryEmbeddingProviderIds.mockReturnValue(new Set(["openai"]));
|
|
|
|
ensurePluginRegistryLoaded({ scope: "memory", config });
|
|
|
|
expect(mocks.collectConfiguredMemoryEmbeddingProviderIds).toHaveBeenCalledWith(config);
|
|
expect(requireLoadOptions()).toEqual(
|
|
expect.objectContaining({
|
|
config,
|
|
activationSourceConfig: config,
|
|
onlyPluginIds: ["memory-core", "openai"],
|
|
throwOnLoadError: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
adapterId: "gemini",
|
|
contract: "memoryEmbeddingProviders" as const,
|
|
pluginId: "google",
|
|
},
|
|
{
|
|
adapterId: "local",
|
|
contract: "embeddingProviders" as const,
|
|
pluginId: "llama-cpp",
|
|
},
|
|
])("loads the $pluginId owner for the $adapterId memory adapter", (provider) => {
|
|
const config = {
|
|
memory: { search: { provider: provider.adapterId } },
|
|
plugins: { slots: { memory: "memory-core" } },
|
|
};
|
|
mocks.collectConfiguredMemoryEmbeddingProviderIds.mockReturnValue(
|
|
new Set([provider.adapterId]),
|
|
);
|
|
useMemoryProviderOwner(provider);
|
|
|
|
ensurePluginRegistryLoaded({ scope: "memory", config });
|
|
|
|
expect(requireLoadOptions().onlyPluginIds).toEqual(
|
|
[provider.pluginId, "memory-core"].toSorted(),
|
|
);
|
|
});
|
|
|
|
it("keeps a denied memory provider owner denied", () => {
|
|
const config = {
|
|
memory: { search: { provider: "gemini" } },
|
|
plugins: {
|
|
allow: ["memory-core"],
|
|
deny: ["google"],
|
|
slots: { memory: "memory-core" },
|
|
},
|
|
};
|
|
mocks.collectConfiguredMemoryEmbeddingProviderIds.mockReturnValue(new Set(["gemini"]));
|
|
useMemoryProviderOwner({
|
|
adapterId: "gemini",
|
|
contract: "memoryEmbeddingProviders",
|
|
pluginId: "google",
|
|
});
|
|
|
|
ensurePluginRegistryLoaded({ scope: "memory", config });
|
|
|
|
const options = requireLoadOptions();
|
|
expect(options.onlyPluginIds).toEqual(["google", "memory-core"]);
|
|
expect(options.config).toEqual(config);
|
|
expect(options.activationSourceConfig).toEqual(config);
|
|
});
|
|
|
|
it("keeps an explicitly disabled memory provider owner disabled", () => {
|
|
const config = {
|
|
memory: { search: { provider: "local" } },
|
|
plugins: {
|
|
entries: { "llama-cpp": { enabled: false } },
|
|
slots: { memory: "memory-core" },
|
|
},
|
|
};
|
|
mocks.collectConfiguredMemoryEmbeddingProviderIds.mockReturnValue(new Set(["local"]));
|
|
useMemoryProviderOwner({
|
|
adapterId: "local",
|
|
contract: "embeddingProviders",
|
|
pluginId: "llama-cpp",
|
|
});
|
|
|
|
ensurePluginRegistryLoaded({ scope: "memory", config });
|
|
|
|
const options = requireLoadOptions();
|
|
expect(options.onlyPluginIds).toEqual(["llama-cpp", "memory-core"]);
|
|
expect(options.config).toEqual(config);
|
|
expect(options.activationSourceConfig).toEqual(config);
|
|
});
|
|
|
|
it("keeps an empty memory scope empty when no backend is selected", () => {
|
|
mocks.collectConfiguredMemoryEmbeddingProviderIds.mockReturnValue(new Set());
|
|
|
|
ensurePluginRegistryLoaded({
|
|
scope: "memory",
|
|
config: { plugins: { slots: { memory: "none" } } },
|
|
});
|
|
|
|
expect(requireLoadOptions().onlyPluginIds).toEqual([]);
|
|
});
|
|
});
|