mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 02:10:24 +00:00
fix: propagate workspaceDir to snapshot plugin loads (#61138)
* 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>
This commit is contained in:
@@ -89,12 +89,14 @@ type ServerPluginBootstrapModule = typeof import("./server-plugin-bootstrap.js")
|
||||
type PluginRuntimeModule = typeof import("../plugins/runtime/index.js");
|
||||
type GatewayRequestScopeModule = typeof import("../plugins/runtime/gateway-request-scope.js");
|
||||
type MethodScopesModule = typeof import("./method-scopes.js");
|
||||
type RuntimeStateModule = typeof import("../plugins/runtime-state.js");
|
||||
|
||||
let serverPluginsModule: ServerPluginsModule;
|
||||
let serverPluginBootstrapModule: ServerPluginBootstrapModule;
|
||||
let runtimeModule: PluginRuntimeModule;
|
||||
let gatewayRequestScopeModule: GatewayRequestScopeModule;
|
||||
let methodScopesModule: MethodScopesModule;
|
||||
let getActivePluginRegistryWorkspaceDirFromState: typeof import("../plugins/runtime-state.js").getActivePluginRegistryWorkspaceDirFromState;
|
||||
|
||||
function createTestLog() {
|
||||
return {
|
||||
@@ -131,6 +133,8 @@ async function loadTestModules() {
|
||||
runtimeModule = await import("../plugins/runtime/index.js");
|
||||
gatewayRequestScopeModule = await import("../plugins/runtime/gateway-request-scope.js");
|
||||
methodScopesModule = await import("./method-scopes.js");
|
||||
const runtimeStateModule: RuntimeStateModule = await import("../plugins/runtime-state.js");
|
||||
({ getActivePluginRegistryWorkspaceDirFromState } = runtimeStateModule);
|
||||
}
|
||||
|
||||
async function createSubagentRuntime(
|
||||
@@ -337,6 +341,20 @@ describe("loadGatewayPlugins", () => {
|
||||
expect(result.gatewayMethods).toEqual(["sessions.get"]);
|
||||
});
|
||||
|
||||
test("stores workspaceDir on the active registry when startup scope is empty", () => {
|
||||
resolveGatewayStartupPluginIds.mockReturnValue([]);
|
||||
|
||||
serverPluginsModule.loadGatewayPlugins({
|
||||
cfg: {},
|
||||
workspaceDir: "/tmp/gateway-workspace",
|
||||
log: createTestLog(),
|
||||
coreGatewayHandlers: {},
|
||||
baseMethods: [],
|
||||
});
|
||||
|
||||
expect(getActivePluginRegistryWorkspaceDirFromState()).toBe("/tmp/gateway-workspace");
|
||||
});
|
||||
|
||||
test("loads gateway plugins from the auto-enabled config snapshot", async () => {
|
||||
const autoEnabledConfig = { channels: { slack: { enabled: true } }, autoEnabled: true };
|
||||
applyPluginAutoEnable.mockReturnValue({
|
||||
|
||||
@@ -429,7 +429,7 @@ export function loadGatewayPlugins(params: {
|
||||
});
|
||||
if (pluginIds.length === 0) {
|
||||
const pluginRegistry = createEmptyPluginRegistry();
|
||||
setActivePluginRegistry(pluginRegistry, undefined, "gateway-bindable");
|
||||
setActivePluginRegistry(pluginRegistry, undefined, "gateway-bindable", params.workspaceDir);
|
||||
return {
|
||||
pluginRegistry,
|
||||
gatewayMethods: [...params.baseMethods],
|
||||
|
||||
@@ -945,8 +945,9 @@ function activatePluginRegistry(
|
||||
registry: PluginRegistry,
|
||||
cacheKey: string,
|
||||
runtimeSubagentMode: "default" | "explicit" | "gateway-bindable",
|
||||
workspaceDir?: string,
|
||||
): void {
|
||||
setActivePluginRegistry(registry, cacheKey, runtimeSubagentMode);
|
||||
setActivePluginRegistry(registry, cacheKey, runtimeSubagentMode, workspaceDir);
|
||||
initializeGlobalHookRunner(registry);
|
||||
}
|
||||
|
||||
@@ -986,7 +987,12 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
runtime: cached.memoryRuntime,
|
||||
});
|
||||
if (shouldActivate) {
|
||||
activatePluginRegistry(cached.registry, cacheKey, runtimeSubagentMode);
|
||||
activatePluginRegistry(
|
||||
cached.registry,
|
||||
cacheKey,
|
||||
runtimeSubagentMode,
|
||||
options.workspaceDir,
|
||||
);
|
||||
}
|
||||
return cached.registry;
|
||||
}
|
||||
@@ -1623,7 +1629,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
});
|
||||
}
|
||||
if (shouldActivate) {
|
||||
activatePluginRegistry(registry, cacheKey, runtimeSubagentMode);
|
||||
activatePluginRegistry(registry, cacheKey, runtimeSubagentMode, options.workspaceDir);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "./providers.js";
|
||||
import { resolvePluginProviders } from "./providers.runtime.js";
|
||||
import { resolvePluginCacheInputs } from "./roots.js";
|
||||
import { getActivePluginRegistryWorkspaceDirFromState } from "./runtime-state.js";
|
||||
import type {
|
||||
ProviderAuthDoctorHintContext,
|
||||
ProviderAugmentModelCatalogContext,
|
||||
@@ -134,13 +135,14 @@ function resolveProviderPluginsForHooks(params: {
|
||||
onlyPluginIds?: string[];
|
||||
}): ProviderPlugin[] {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
|
||||
const cacheBucket = resolveHookProviderCacheBucket({
|
||||
config: params.config,
|
||||
env,
|
||||
});
|
||||
const cacheKey = buildHookProviderCacheKey({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
env,
|
||||
});
|
||||
@@ -150,6 +152,7 @@ function resolveProviderPluginsForHooks(params: {
|
||||
}
|
||||
const resolved = resolvePluginProviders({
|
||||
...params,
|
||||
workspaceDir,
|
||||
env,
|
||||
activate: false,
|
||||
cache: false,
|
||||
@@ -165,9 +168,10 @@ function resolveProviderPluginsForCatalogHooks(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): ProviderPlugin[] {
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
|
||||
const onlyPluginIds = resolveCatalogHookProviderPluginIds({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
if (onlyPluginIds.length === 0) {
|
||||
@@ -175,6 +179,7 @@ function resolveProviderPluginsForCatalogHooks(params: {
|
||||
}
|
||||
return resolveProviderPluginsForHooks({
|
||||
...params,
|
||||
workspaceDir,
|
||||
onlyPluginIds,
|
||||
});
|
||||
}
|
||||
@@ -185,10 +190,11 @@ export function resolveProviderRuntimePlugin(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): ProviderPlugin | undefined {
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
|
||||
const owningPluginIds = resolveOwningPluginIdsForProvider({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
if (!owningPluginIds || owningPluginIds.length === 0) {
|
||||
@@ -196,6 +202,7 @@ export function resolveProviderRuntimePlugin(params: {
|
||||
}
|
||||
return resolveProviderPluginsForHooks({
|
||||
...params,
|
||||
workspaceDir,
|
||||
onlyPluginIds: owningPluginIds,
|
||||
}).find((plugin) => matchesProviderId(plugin, params.provider));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
resolveOwningPluginIdsForModelRefs,
|
||||
withBundledProviderVitestCompat,
|
||||
} from "./providers.js";
|
||||
import { getActivePluginRegistryWorkspaceDir } from "./runtime.js";
|
||||
import type { ProviderPlugin } from "./types.js";
|
||||
|
||||
const log = createSubsystemLogger("plugins");
|
||||
@@ -33,11 +34,12 @@ export function resolvePluginProviders(params: {
|
||||
mode?: "runtime" | "setup";
|
||||
}): ProviderPlugin[] {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const modelOwnedPluginIds = params.modelRefs?.length
|
||||
? resolveOwningPluginIdsForModelRefs({
|
||||
models: params.modelRefs,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
})
|
||||
: [];
|
||||
@@ -52,7 +54,7 @@ export function resolvePluginProviders(params: {
|
||||
if (params.mode === "setup") {
|
||||
const providerPluginIds = resolveDiscoveredProviderPluginIds({
|
||||
config: runtimeConfig,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: requestedPluginIds,
|
||||
});
|
||||
@@ -66,7 +68,7 @@ export function resolvePluginProviders(params: {
|
||||
}),
|
||||
activationSourceConfig: runtimeConfig,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: providerPluginIds,
|
||||
pluginSdkResolution: params.pluginSdkResolution,
|
||||
@@ -82,7 +84,7 @@ export function resolvePluginProviders(params: {
|
||||
const activation = resolveBundledPluginCompatibleActivationInputs({
|
||||
rawConfig: runtimeConfig,
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
onlyPluginIds: requestedPluginIds,
|
||||
applyAutoEnable: true,
|
||||
compatMode: {
|
||||
@@ -101,7 +103,7 @@ export function resolvePluginProviders(params: {
|
||||
: activation.config;
|
||||
const providerPluginIds = resolveEnabledProviderPluginIds({
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: requestedPluginIds,
|
||||
});
|
||||
@@ -109,7 +111,7 @@ export function resolvePluginProviders(params: {
|
||||
config,
|
||||
activationSourceConfig: activation.activationSourceConfig,
|
||||
autoEnabledReasons: activation.autoEnabledReasons,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: providerPluginIds,
|
||||
pluginSdkResolution: params.pluginSdkResolution,
|
||||
|
||||
@@ -10,6 +10,7 @@ type LoadOpenClawPlugins = typeof import("./loader.js").loadOpenClawPlugins;
|
||||
type LoadPluginManifestRegistry =
|
||||
typeof import("./manifest-registry.js").loadPluginManifestRegistry;
|
||||
type ApplyPluginAutoEnable = typeof import("../config/plugin-auto-enable.js").applyPluginAutoEnable;
|
||||
type SetActivePluginRegistry = typeof import("./runtime.js").setActivePluginRegistry;
|
||||
|
||||
const resolveRuntimePluginRegistryMock = vi.fn<ResolveRuntimePluginRegistry>();
|
||||
const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
|
||||
@@ -20,6 +21,7 @@ let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOw
|
||||
let resolveOwningPluginIdsForModelRef: typeof import("./providers.js").resolveOwningPluginIdsForModelRef;
|
||||
let resolveEnabledProviderPluginIds: typeof import("./providers.js").resolveEnabledProviderPluginIds;
|
||||
let resolvePluginProviders: typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
let setActivePluginRegistry: SetActivePluginRegistry;
|
||||
|
||||
function createManifestProviderPlugin(params: {
|
||||
id: string;
|
||||
@@ -270,9 +272,11 @@ describe("resolvePluginProviders", () => {
|
||||
resolveEnabledProviderPluginIds,
|
||||
} = await import("./providers.js"));
|
||||
({ resolvePluginProviders } = await import("./providers.runtime.js"));
|
||||
({ setActivePluginRegistry } = await import("./runtime.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(createEmptyPluginRegistry());
|
||||
resolveRuntimePluginRegistryMock.mockReset();
|
||||
loadOpenClawPluginsMock.mockReset();
|
||||
const provider: ProviderPlugin = {
|
||||
@@ -560,6 +564,32 @@ describe("resolvePluginProviders", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("inherits workspaceDir from the active registry when provider resolution omits it", () => {
|
||||
setActivePluginRegistry(
|
||||
createEmptyPluginRegistry(),
|
||||
undefined,
|
||||
"default",
|
||||
"/workspace/runtime",
|
||||
);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["google"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["google"],
|
||||
});
|
||||
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/workspace/runtime",
|
||||
cache: false,
|
||||
activate: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
provider: "minimax-portal",
|
||||
|
||||
@@ -30,3 +30,8 @@ export function getActivePluginChannelRegistryFromState(): PluginRegistry | null
|
||||
const state = getPluginRegistryState();
|
||||
return state?.channel.registry ?? state?.activeRegistry ?? null;
|
||||
}
|
||||
|
||||
export function getActivePluginRegistryWorkspaceDirFromState(): string | undefined {
|
||||
const state = getPluginRegistryState();
|
||||
return state?.workspaceDir ?? undefined;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ const state: RegistryState = (() => {
|
||||
version: 0,
|
||||
},
|
||||
key: null,
|
||||
workspaceDir: null,
|
||||
runtimeSubagentMode: "default",
|
||||
importedPluginIds: new Set<string>(),
|
||||
};
|
||||
@@ -70,12 +71,14 @@ export function setActivePluginRegistry(
|
||||
registry: PluginRegistry,
|
||||
cacheKey?: string,
|
||||
runtimeSubagentMode: "default" | "explicit" | "gateway-bindable" = "default",
|
||||
workspaceDir?: string,
|
||||
) {
|
||||
state.activeRegistry = registry;
|
||||
state.activeVersion += 1;
|
||||
syncTrackedSurface(state.httpRoute, registry, true);
|
||||
syncTrackedSurface(state.channel, registry, true);
|
||||
state.key = cacheKey ?? null;
|
||||
state.workspaceDir = workspaceDir ?? null;
|
||||
state.runtimeSubagentMode = runtimeSubagentMode;
|
||||
}
|
||||
|
||||
@@ -83,6 +86,10 @@ export function getActivePluginRegistry(): PluginRegistry | null {
|
||||
return state.activeRegistry;
|
||||
}
|
||||
|
||||
export function getActivePluginRegistryWorkspaceDir(): string | undefined {
|
||||
return state.workspaceDir ?? undefined;
|
||||
}
|
||||
|
||||
export function requireActivePluginRegistry(): PluginRegistry {
|
||||
if (!state.activeRegistry) {
|
||||
state.activeRegistry = createEmptyPluginRegistry();
|
||||
@@ -222,6 +229,7 @@ export function resetPluginRuntimeStateForTest(): void {
|
||||
installSurfaceRegistry(state.httpRoute, null, false);
|
||||
installSurfaceRegistry(state.channel, null, false);
|
||||
state.key = null;
|
||||
state.workspaceDir = null;
|
||||
state.runtimeSubagentMode = "default";
|
||||
state.importedPluginIds.clear();
|
||||
}
|
||||
|
||||
@@ -168,4 +168,91 @@ describe("resolvePluginWebFetchProviders", () => {
|
||||
]);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
resolveManifestContractPluginIds,
|
||||
type PluginManifestRecord,
|
||||
} from "./manifest-registry.js";
|
||||
import { getActivePluginRegistryWorkspaceDir } from "./runtime.js";
|
||||
import type { PluginWebFetchProviderEntry } from "./types.js";
|
||||
import {
|
||||
resolveBundledWebFetchResolutionConfig,
|
||||
@@ -127,14 +128,16 @@ function resolveWebFetchLoadOptions(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}) {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const { config, activationSourceConfig, autoEnabledReasons } =
|
||||
resolveBundledWebFetchResolutionConfig({
|
||||
...params,
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
const onlyPluginIds = resolveWebFetchCandidatePluginIds({
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -144,7 +147,7 @@ function resolveWebFetchLoadOptions(params: {
|
||||
config,
|
||||
activationSourceConfig,
|
||||
autoEnabledReasons,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
cache: params.cache ?? false,
|
||||
activate: params.activate ?? false,
|
||||
...(onlyPluginIds ? { onlyPluginIds } : {}),
|
||||
@@ -180,11 +183,12 @@ export function resolvePluginWebFetchProviders(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}): PluginWebFetchProviderEntry[] {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
if (params.mode === "setup") {
|
||||
const pluginIds =
|
||||
resolveWebFetchCandidatePluginIds({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -199,7 +203,7 @@ export function resolvePluginWebFetchProviders(params: {
|
||||
}),
|
||||
activationSourceConfig: params.config,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: pluginIds,
|
||||
cache: params.cache ?? false,
|
||||
@@ -213,7 +217,7 @@ export function resolvePluginWebFetchProviders(params: {
|
||||
params.activate !== true && params.cache !== true && shouldUsePluginSnapshotCache(env);
|
||||
const cacheKey = buildWebFetchSnapshotCacheKey({
|
||||
config: cacheOwnerConfig,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
bundledAllowlistCompat: params.bundledAllowlistCompat,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -227,7 +231,7 @@ export function resolvePluginWebFetchProviders(params: {
|
||||
return cached.providers;
|
||||
}
|
||||
}
|
||||
const loadOptions = resolveWebFetchLoadOptions(params);
|
||||
const loadOptions = resolveWebFetchLoadOptions({ ...params, workspaceDir });
|
||||
// Keep repeated runtime reads on the already-compatible active registry when
|
||||
// possible, then fall back to a fresh snapshot load only when necessary.
|
||||
const resolved = mapRegistryWebFetchProviders({
|
||||
@@ -266,7 +270,12 @@ export function resolveRuntimeWebFetchProviders(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}): PluginWebFetchProviderEntry[] {
|
||||
const runtimeRegistry = resolveRuntimePluginRegistry(
|
||||
params.config === undefined ? undefined : resolveWebFetchLoadOptions(params),
|
||||
params.config === undefined
|
||||
? undefined
|
||||
: resolveWebFetchLoadOptions({
|
||||
...params,
|
||||
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDir(),
|
||||
}),
|
||||
);
|
||||
if (runtimeRegistry) {
|
||||
return mapRegistryWebFetchProviders({
|
||||
|
||||
@@ -398,6 +398,35 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
expectScopedWebSearchCandidates(["brave"]);
|
||||
});
|
||||
|
||||
it("uses the active registry workspace for candidate discovery and snapshot loads when workspaceDir is omitted", () => {
|
||||
const env = createWebSearchEnv();
|
||||
const rawConfig = createBraveAllowConfig();
|
||||
|
||||
setActivePluginRegistry(
|
||||
createEmptyPluginRegistry(),
|
||||
undefined,
|
||||
"default",
|
||||
"/tmp/runtime-workspace",
|
||||
);
|
||||
|
||||
resolvePluginWebSearchProviders({
|
||||
config: rawConfig,
|
||||
bundledAllowlistCompat: true,
|
||||
env,
|
||||
});
|
||||
|
||||
expect(loadPluginManifestRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/runtime-workspace",
|
||||
}),
|
||||
);
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: "/tmp/runtime-workspace",
|
||||
onlyPluginIds: ["brave"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
it("memoizes snapshot provider resolution for the same config and env", () => {
|
||||
expectSnapshotMemoization({
|
||||
config: createBraveAllowConfig(),
|
||||
@@ -451,6 +480,72 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("inherits workspaceDir from the active registry for compatible web-search snapshot reuse", () => {
|
||||
const env = createWebSearchEnv();
|
||||
const rawConfig = createBraveAllowConfig();
|
||||
const { config, activationSourceConfig, autoEnabledReasons } =
|
||||
webSearchProvidersSharedModule.resolveBundledWebSearchResolutionConfig({
|
||||
config: rawConfig,
|
||||
bundledAllowlistCompat: true,
|
||||
workspaceDir: DEFAULT_WEB_SEARCH_WORKSPACE,
|
||||
env,
|
||||
});
|
||||
const { cacheKey } = loaderModule.__testing.resolvePluginLoadCacheContext({
|
||||
config,
|
||||
activationSourceConfig,
|
||||
autoEnabledReasons,
|
||||
workspaceDir: DEFAULT_WEB_SEARCH_WORKSPACE,
|
||||
env,
|
||||
onlyPluginIds: ["brave"],
|
||||
cache: false,
|
||||
activate: false,
|
||||
});
|
||||
const registry = createEmptyPluginRegistry();
|
||||
registry.webSearchProviders.push(
|
||||
createRuntimeWebSearchProvider({
|
||||
pluginId: "brave",
|
||||
pluginName: "Brave",
|
||||
id: "brave",
|
||||
label: "Brave Search",
|
||||
hint: "Brave runtime provider",
|
||||
envVar: "BRAVE_API_KEY",
|
||||
signupUrl: "https://example.com/brave",
|
||||
credentialPath: "plugins.entries.brave.config.webSearch.apiKey",
|
||||
}),
|
||||
);
|
||||
setActivePluginRegistry(registry, cacheKey, "default", DEFAULT_WEB_SEARCH_WORKSPACE);
|
||||
|
||||
const providers = resolvePluginWebSearchProviders({
|
||||
config: rawConfig,
|
||||
bundledAllowlistCompat: true,
|
||||
env,
|
||||
});
|
||||
|
||||
expectRuntimeProviderResolution(providers, ["brave:brave"]);
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keys web-search snapshot memoization by the inherited active workspace", () => {
|
||||
const env = createWebSearchEnv();
|
||||
const rawConfig = createBraveAllowConfig();
|
||||
|
||||
setActivePluginRegistry(createEmptyPluginRegistry(), undefined, "default", "/tmp/workspace-a");
|
||||
resolvePluginWebSearchProviders({
|
||||
config: rawConfig,
|
||||
bundledAllowlistCompat: true,
|
||||
env,
|
||||
});
|
||||
|
||||
setActivePluginRegistry(createEmptyPluginRegistry(), undefined, "default", "/tmp/workspace-b");
|
||||
resolvePluginWebSearchProviders({
|
||||
config: rawConfig,
|
||||
bundledAllowlistCompat: true,
|
||||
env,
|
||||
});
|
||||
|
||||
expectLoaderCallCount(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "invalidates the snapshot cache when config contents change in place",
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
resolveManifestContractPluginIds,
|
||||
type PluginManifestRecord,
|
||||
} from "./manifest-registry.js";
|
||||
import { getActivePluginRegistryWorkspaceDir } from "./runtime.js";
|
||||
import type { PluginWebSearchProviderEntry } from "./types.js";
|
||||
import {
|
||||
resolveBundledWebSearchResolutionConfig,
|
||||
@@ -126,14 +127,16 @@ function resolveWebSearchLoadOptions(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}) {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const { config, activationSourceConfig, autoEnabledReasons } =
|
||||
resolveBundledWebSearchResolutionConfig({
|
||||
...params,
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
const onlyPluginIds = resolveWebSearchCandidatePluginIds({
|
||||
config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -143,7 +146,7 @@ function resolveWebSearchLoadOptions(params: {
|
||||
config,
|
||||
activationSourceConfig,
|
||||
autoEnabledReasons,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
cache: params.cache ?? false,
|
||||
activate: params.activate ?? false,
|
||||
...(onlyPluginIds ? { onlyPluginIds } : {}),
|
||||
@@ -179,11 +182,12 @@ export function resolvePluginWebSearchProviders(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}): PluginWebSearchProviderEntry[] {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
if (params.mode === "setup") {
|
||||
const pluginIds =
|
||||
resolveWebSearchCandidatePluginIds({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -198,7 +202,7 @@ export function resolvePluginWebSearchProviders(params: {
|
||||
}),
|
||||
activationSourceConfig: params.config,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
env,
|
||||
onlyPluginIds: pluginIds,
|
||||
cache: params.cache ?? false,
|
||||
@@ -212,7 +216,7 @@ export function resolvePluginWebSearchProviders(params: {
|
||||
params.activate !== true && params.cache !== true && shouldUsePluginSnapshotCache(env);
|
||||
const cacheKey = buildWebSearchSnapshotCacheKey({
|
||||
config: cacheOwnerConfig,
|
||||
workspaceDir: params.workspaceDir,
|
||||
workspaceDir,
|
||||
bundledAllowlistCompat: params.bundledAllowlistCompat,
|
||||
onlyPluginIds: params.onlyPluginIds,
|
||||
origin: params.origin,
|
||||
@@ -226,7 +230,7 @@ export function resolvePluginWebSearchProviders(params: {
|
||||
return cached.providers;
|
||||
}
|
||||
}
|
||||
const loadOptions = resolveWebSearchLoadOptions(params);
|
||||
const loadOptions = resolveWebSearchLoadOptions({ ...params, workspaceDir });
|
||||
// Prefer the compatible active registry so repeated runtime reads do not
|
||||
// re-import the same plugin set through the snapshot path.
|
||||
const resolved = mapRegistryWebSearchProviders({
|
||||
@@ -265,7 +269,12 @@ export function resolveRuntimeWebSearchProviders(params: {
|
||||
origin?: PluginManifestRecord["origin"];
|
||||
}): PluginWebSearchProviderEntry[] {
|
||||
const runtimeRegistry = resolveRuntimePluginRegistry(
|
||||
params.config === undefined ? undefined : resolveWebSearchLoadOptions(params),
|
||||
params.config === undefined
|
||||
? undefined
|
||||
: resolveWebSearchLoadOptions({
|
||||
...params,
|
||||
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDir(),
|
||||
}),
|
||||
);
|
||||
if (runtimeRegistry) {
|
||||
return mapRegistryWebSearchProviders({
|
||||
|
||||
Reference in New Issue
Block a user