mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-13 10:11:20 +00:00
* plugins: include resolved workspaceDir in provider hook cache keys resolveProviderPluginsForHooks, resolveProviderPluginsForCatalogHooks, and resolveProviderRuntimePlugin used the raw params.workspaceDir for cache keys and plugin-id discovery while resolvePluginProviders already fell back to the active registry workspace. Resolve workspaceDir once at the top of each function so cache keys, candidate filtering, and loading all use the same workspace root. * fix(plugins): inherit runtime workspace for snapshot loads * test(gateway): stub runtime registry seam * fix(plugins): restore workspace fallback after rebase --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
259 lines
8.3 KiB
TypeScript
259 lines
8.3 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createEmptyPluginRegistry } from "./registry.js";
|
|
|
|
type LoaderModule = typeof import("./loader.js");
|
|
type ManifestRegistryModule = typeof import("./manifest-registry.js");
|
|
type RuntimeModule = typeof import("./runtime.js");
|
|
type WebFetchProvidersRuntimeModule = typeof import("./web-fetch-providers.runtime.js");
|
|
type WebFetchProvidersSharedModule = typeof import("./web-fetch-providers.shared.js");
|
|
|
|
let loaderModule: LoaderModule;
|
|
let manifestRegistryModule: ManifestRegistryModule;
|
|
let webFetchProvidersSharedModule: WebFetchProvidersSharedModule;
|
|
let loadOpenClawPluginsMock: ReturnType<typeof vi.fn>;
|
|
let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"];
|
|
let resolvePluginWebFetchProviders: WebFetchProvidersRuntimeModule["resolvePluginWebFetchProviders"];
|
|
let resetWebFetchProviderSnapshotCacheForTests: WebFetchProvidersRuntimeModule["__testing"]["resetWebFetchProviderSnapshotCacheForTests"];
|
|
|
|
const DEFAULT_WORKSPACE = "/tmp/workspace";
|
|
|
|
function createWebFetchEnv(overrides?: Partial<NodeJS.ProcessEnv>) {
|
|
return {
|
|
OPENCLAW_HOME: "/tmp/openclaw-home",
|
|
...overrides,
|
|
} as NodeJS.ProcessEnv;
|
|
}
|
|
|
|
function createFirecrawlAllowConfig() {
|
|
return {
|
|
plugins: {
|
|
allow: ["firecrawl"],
|
|
},
|
|
};
|
|
}
|
|
|
|
function createManifestRegistryFixture() {
|
|
return {
|
|
plugins: [
|
|
{
|
|
id: "firecrawl",
|
|
origin: "bundled",
|
|
rootDir: "/tmp/firecrawl",
|
|
source: "/tmp/firecrawl/index.js",
|
|
manifestPath: "/tmp/firecrawl/openclaw.plugin.json",
|
|
channels: [],
|
|
providers: [],
|
|
skills: [],
|
|
hooks: [],
|
|
configUiHints: { "webFetch.apiKey": { label: "key" } },
|
|
},
|
|
{
|
|
id: "noise",
|
|
origin: "bundled",
|
|
rootDir: "/tmp/noise",
|
|
source: "/tmp/noise/index.js",
|
|
manifestPath: "/tmp/noise/openclaw.plugin.json",
|
|
channels: [],
|
|
providers: [],
|
|
skills: [],
|
|
hooks: [],
|
|
configUiHints: { unrelated: { label: "nope" } },
|
|
},
|
|
],
|
|
diagnostics: [],
|
|
};
|
|
}
|
|
|
|
function createRuntimeWebFetchProvider() {
|
|
return {
|
|
pluginId: "firecrawl",
|
|
pluginName: "Firecrawl",
|
|
source: "test" as const,
|
|
provider: {
|
|
id: "firecrawl",
|
|
label: "Firecrawl",
|
|
hint: "Firecrawl runtime provider",
|
|
envVars: ["FIRECRAWL_API_KEY"],
|
|
placeholder: "firecrawl-...",
|
|
signupUrl: "https://example.com/firecrawl",
|
|
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
|
|
getCredentialValue: () => "configured",
|
|
setCredentialValue: () => {},
|
|
createTool: () => ({
|
|
description: "firecrawl",
|
|
parameters: {},
|
|
execute: async () => ({}),
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("resolvePluginWebFetchProviders", () => {
|
|
beforeAll(async () => {
|
|
loaderModule = await import("./loader.js");
|
|
manifestRegistryModule = await import("./manifest-registry.js");
|
|
webFetchProvidersSharedModule = await import("./web-fetch-providers.shared.js");
|
|
({ setActivePluginRegistry } = await import("./runtime.js"));
|
|
({
|
|
resolvePluginWebFetchProviders,
|
|
__testing: { resetWebFetchProviderSnapshotCacheForTests },
|
|
} = await import("./web-fetch-providers.runtime.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
resetWebFetchProviderSnapshotCacheForTests();
|
|
vi.spyOn(manifestRegistryModule, "loadPluginManifestRegistry").mockReturnValue(
|
|
createManifestRegistryFixture() as ManifestRegistryModule["loadPluginManifestRegistry"] extends (
|
|
...args: unknown[]
|
|
) => infer R
|
|
? R
|
|
: never,
|
|
);
|
|
loadOpenClawPluginsMock = vi
|
|
.spyOn(loaderModule, "loadOpenClawPlugins")
|
|
.mockImplementation(() => {
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.webFetchProviders = [createRuntimeWebFetchProvider()];
|
|
return registry;
|
|
});
|
|
setActivePluginRegistry(createEmptyPluginRegistry());
|
|
});
|
|
|
|
afterEach(() => {
|
|
setActivePluginRegistry(createEmptyPluginRegistry());
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("falls back to the plugin loader when no compatible active registry exists", () => {
|
|
const providers = resolvePluginWebFetchProviders({});
|
|
|
|
expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([
|
|
"firecrawl:firecrawl",
|
|
]);
|
|
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("reuses a compatible active registry for snapshot resolution when config is provided", () => {
|
|
const env = createWebFetchEnv();
|
|
const rawConfig = createFirecrawlAllowConfig();
|
|
const { config, activationSourceConfig, autoEnabledReasons } =
|
|
webFetchProvidersSharedModule.resolveBundledWebFetchResolutionConfig({
|
|
config: rawConfig,
|
|
bundledAllowlistCompat: true,
|
|
env,
|
|
});
|
|
const { cacheKey } = loaderModule.__testing.resolvePluginLoadCacheContext({
|
|
config,
|
|
activationSourceConfig,
|
|
autoEnabledReasons,
|
|
workspaceDir: DEFAULT_WORKSPACE,
|
|
env,
|
|
onlyPluginIds: ["firecrawl"],
|
|
cache: false,
|
|
activate: false,
|
|
});
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.webFetchProviders.push(createRuntimeWebFetchProvider());
|
|
setActivePluginRegistry(registry, cacheKey);
|
|
|
|
const providers = resolvePluginWebFetchProviders({
|
|
config: rawConfig,
|
|
bundledAllowlistCompat: true,
|
|
workspaceDir: DEFAULT_WORKSPACE,
|
|
env,
|
|
});
|
|
|
|
expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([
|
|
"firecrawl:firecrawl",
|
|
]);
|
|
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("inherits workspaceDir from the active registry for compatible web-fetch snapshot reuse", () => {
|
|
const env = createWebFetchEnv();
|
|
const rawConfig = createFirecrawlAllowConfig();
|
|
const { config, activationSourceConfig, autoEnabledReasons } =
|
|
webFetchProvidersSharedModule.resolveBundledWebFetchResolutionConfig({
|
|
config: rawConfig,
|
|
bundledAllowlistCompat: true,
|
|
workspaceDir: DEFAULT_WORKSPACE,
|
|
env,
|
|
});
|
|
const { cacheKey } = loaderModule.__testing.resolvePluginLoadCacheContext({
|
|
config,
|
|
activationSourceConfig,
|
|
autoEnabledReasons,
|
|
workspaceDir: DEFAULT_WORKSPACE,
|
|
env,
|
|
onlyPluginIds: ["firecrawl"],
|
|
cache: false,
|
|
activate: false,
|
|
});
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.webFetchProviders.push(createRuntimeWebFetchProvider());
|
|
setActivePluginRegistry(registry, cacheKey, "default", DEFAULT_WORKSPACE);
|
|
|
|
const providers = resolvePluginWebFetchProviders({
|
|
config: rawConfig,
|
|
bundledAllowlistCompat: true,
|
|
env,
|
|
});
|
|
|
|
expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([
|
|
"firecrawl:firecrawl",
|
|
]);
|
|
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("uses the active registry workspace for candidate discovery and snapshot loads when workspaceDir is omitted", () => {
|
|
const env = createWebFetchEnv();
|
|
const rawConfig = createFirecrawlAllowConfig();
|
|
|
|
setActivePluginRegistry(
|
|
createEmptyPluginRegistry(),
|
|
undefined,
|
|
"default",
|
|
"/tmp/runtime-workspace",
|
|
);
|
|
|
|
resolvePluginWebFetchProviders({
|
|
config: rawConfig,
|
|
bundledAllowlistCompat: true,
|
|
env,
|
|
});
|
|
|
|
expect(manifestRegistryModule.loadPluginManifestRegistry).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceDir: "/tmp/runtime-workspace",
|
|
}),
|
|
);
|
|
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspaceDir: "/tmp/runtime-workspace",
|
|
onlyPluginIds: ["firecrawl"],
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("invalidates web-fetch snapshot memoization when the active registry workspace changes", () => {
|
|
const env = createWebFetchEnv();
|
|
const config = createFirecrawlAllowConfig();
|
|
|
|
setActivePluginRegistry(createEmptyPluginRegistry(), undefined, "default", "/tmp/workspace-a");
|
|
resolvePluginWebFetchProviders({
|
|
config,
|
|
bundledAllowlistCompat: true,
|
|
env,
|
|
});
|
|
|
|
setActivePluginRegistry(createEmptyPluginRegistry(), undefined, "default", "/tmp/workspace-b");
|
|
resolvePluginWebFetchProviders({
|
|
config,
|
|
bundledAllowlistCompat: true,
|
|
env,
|
|
});
|
|
|
|
expect(loadOpenClawPluginsMock).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|