mirror of https://github.com/openclaw/openclaw.git
test: dedupe plugin provider helper suites
This commit is contained in:
parent
7e921050e3
commit
9155f3914a
|
|
@ -105,6 +105,46 @@ function setBundledCapabilityFixture(contractKey: string) {
|
|||
});
|
||||
}
|
||||
|
||||
function setActiveSpeechCapabilityRegistry(providerId: string) {
|
||||
const active = createEmptyPluginRegistry();
|
||||
active.speechProviders.push({
|
||||
pluginId: providerId,
|
||||
pluginName: "OpenAI",
|
||||
source: "test",
|
||||
provider: {
|
||||
id: providerId,
|
||||
label: "OpenAI",
|
||||
isConfigured: () => true,
|
||||
synthesize: async () => ({
|
||||
audioBuffer: Buffer.from("x"),
|
||||
outputFormat: "mp3",
|
||||
voiceCompatible: false,
|
||||
fileExtension: ".mp3",
|
||||
}),
|
||||
},
|
||||
});
|
||||
setActivePluginRegistry(active);
|
||||
}
|
||||
|
||||
function expectCompatChainApplied(params: {
|
||||
key: "speechProviders" | "mediaUnderstandingProviders" | "imageGenerationProviders";
|
||||
contractKey: string;
|
||||
cfg: OpenClawConfig;
|
||||
allowlistCompat: { plugins: { allow: string[] } };
|
||||
enablementCompat: {
|
||||
plugins: {
|
||||
allow: string[];
|
||||
entries: { openai: { enabled: boolean } };
|
||||
};
|
||||
};
|
||||
}) {
|
||||
setBundledCapabilityFixture(params.contractKey);
|
||||
mocks.withBundledPluginAllowlistCompat.mockReturnValue(params.allowlistCompat);
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(params.enablementCompat);
|
||||
mocks.withBundledPluginVitestCompat.mockReturnValue(params.enablementCompat);
|
||||
resolvePluginCapabilityProviders({ key: params.key, cfg: params.cfg });
|
||||
expectBundledCompatLoadPath(params);
|
||||
}
|
||||
describe("resolvePluginCapabilityProviders", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
|
|
@ -154,14 +194,9 @@ describe("resolvePluginCapabilityProviders", () => {
|
|||
["imageGenerationProviders", "imageGenerationProviders"],
|
||||
] as const)("applies bundled compat before fallback loading for %s", (key, contractKey) => {
|
||||
const { cfg, allowlistCompat, enablementCompat } = createCompatChainConfig();
|
||||
setBundledCapabilityFixture(contractKey);
|
||||
mocks.withBundledPluginAllowlistCompat.mockReturnValue(allowlistCompat);
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(enablementCompat);
|
||||
mocks.withBundledPluginVitestCompat.mockReturnValue(enablementCompat);
|
||||
|
||||
resolvePluginCapabilityProviders({ key, cfg });
|
||||
|
||||
expectBundledCompatLoadPath({
|
||||
expectCompatChainApplied({
|
||||
key,
|
||||
contractKey,
|
||||
cfg,
|
||||
allowlistCompat,
|
||||
enablementCompat,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ function createOwnedAdapterEntry(id: string) {
|
|||
};
|
||||
}
|
||||
|
||||
function expectRegisteredProviderState(params: {
|
||||
entry: {
|
||||
adapter: MemoryEmbeddingProviderAdapter;
|
||||
ownerPluginId?: string;
|
||||
};
|
||||
expectedList?: Array<{
|
||||
adapter: MemoryEmbeddingProviderAdapter;
|
||||
ownerPluginId?: string;
|
||||
}>;
|
||||
}) {
|
||||
expectRegisteredProviderEntry(params.entry.adapter.id, params.entry);
|
||||
if (params.expectedList) {
|
||||
expect(listRegisteredMemoryEmbeddingProviders()).toEqual(params.expectedList);
|
||||
}
|
||||
}
|
||||
|
||||
function expectMemoryEmbeddingProviderIds(expectedIds: readonly string[]) {
|
||||
expect(listMemoryEmbeddingProviders().map((adapter) => adapter.id)).toEqual([...expectedIds]);
|
||||
}
|
||||
|
|
@ -81,14 +97,11 @@ describe("memory embedding provider registry", () => {
|
|||
expectList: false,
|
||||
},
|
||||
] as const)("$name", ({ entry, setup, expectList }) => {
|
||||
const expectedEntry = entry;
|
||||
|
||||
setup(entry);
|
||||
|
||||
expectRegisteredProviderEntry(entry.adapter.id, expectedEntry);
|
||||
if (expectList) {
|
||||
expect(listRegisteredMemoryEmbeddingProviders()).toEqual([expectedEntry]);
|
||||
}
|
||||
expectRegisteredProviderState({
|
||||
entry,
|
||||
...(expectList ? { expectedList: [entry] } : {}),
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the registry", () => {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ function createManifestPlugin(id: string, providerAuthChoices: Array<Record<stri
|
|||
};
|
||||
}
|
||||
|
||||
function createProviderAuthChoice(overrides: Record<string, unknown>) {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
function setManifestPlugins(plugins: Array<Record<string, unknown>>) {
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins,
|
||||
|
|
@ -36,21 +40,26 @@ function expectDeprecatedAuthChoice(choiceIds: string[], expectedChoiceId?: stri
|
|||
}
|
||||
}
|
||||
|
||||
function setSingleManifestProviderAuthChoices(
|
||||
pluginId: string,
|
||||
providerAuthChoices: Array<Record<string, unknown>>,
|
||||
) {
|
||||
setManifestPlugins([createManifestPlugin(pluginId, providerAuthChoices)]);
|
||||
}
|
||||
|
||||
describe("provider auth choice manifest helpers", () => {
|
||||
it("flattens manifest auth choices", () => {
|
||||
setManifestPlugins([
|
||||
createManifestPlugin("openai", [
|
||||
{
|
||||
provider: "openai",
|
||||
method: "api-key",
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
onboardingScopes: ["text-inference"],
|
||||
optionKey: "openaiApiKey",
|
||||
cliFlag: "--openai-api-key",
|
||||
cliOption: "--openai-api-key <key>",
|
||||
},
|
||||
]),
|
||||
setSingleManifestProviderAuthChoices("openai", [
|
||||
createProviderAuthChoice({
|
||||
provider: "openai",
|
||||
method: "api-key",
|
||||
choiceId: "openai-api-key",
|
||||
choiceLabel: "OpenAI API key",
|
||||
onboardingScopes: ["text-inference"],
|
||||
optionKey: "openaiApiKey",
|
||||
cliFlag: "--openai-api-key",
|
||||
cliOption: "--openai-api-key <key>",
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(resolveManifestProviderAuthChoices()).toEqual([
|
||||
|
|
@ -74,7 +83,7 @@ describe("provider auth choice manifest helpers", () => {
|
|||
name: "deduplicates flag metadata by option key + flag",
|
||||
plugins: [
|
||||
createManifestPlugin("moonshot", [
|
||||
{
|
||||
createProviderAuthChoice({
|
||||
provider: "moonshot",
|
||||
method: "api-key",
|
||||
choiceId: "moonshot-api-key",
|
||||
|
|
@ -83,8 +92,8 @@ describe("provider auth choice manifest helpers", () => {
|
|||
cliFlag: "--moonshot-api-key",
|
||||
cliOption: "--moonshot-api-key <key>",
|
||||
cliDescription: "Moonshot API key",
|
||||
},
|
||||
{
|
||||
}),
|
||||
createProviderAuthChoice({
|
||||
provider: "moonshot",
|
||||
method: "api-key-cn",
|
||||
choiceId: "moonshot-api-key-cn",
|
||||
|
|
@ -93,7 +102,7 @@ describe("provider auth choice manifest helpers", () => {
|
|||
cliFlag: "--moonshot-api-key",
|
||||
cliOption: "--moonshot-api-key <key>",
|
||||
cliDescription: "Moonshot API key",
|
||||
},
|
||||
}),
|
||||
]),
|
||||
],
|
||||
run: () =>
|
||||
|
|
@ -111,12 +120,12 @@ describe("provider auth choice manifest helpers", () => {
|
|||
name: "resolves deprecated auth-choice aliases through manifest metadata",
|
||||
plugins: [
|
||||
createManifestPlugin("minimax", [
|
||||
{
|
||||
createProviderAuthChoice({
|
||||
provider: "minimax",
|
||||
method: "api-global",
|
||||
choiceId: "minimax-global-api",
|
||||
deprecatedChoiceIds: ["minimax", "minimax-api"],
|
||||
},
|
||||
}),
|
||||
]),
|
||||
],
|
||||
run: () => {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,19 @@ function createCatalogRuntimeContext() {
|
|||
};
|
||||
}
|
||||
|
||||
function expectNormalizedDiscoveryResult(params: {
|
||||
provider: ProviderPlugin;
|
||||
result: Parameters<typeof normalizePluginDiscoveryResult>[0]["result"];
|
||||
expected: Record<string, unknown>;
|
||||
}) {
|
||||
expect(
|
||||
normalizePluginDiscoveryResult({
|
||||
provider: params.provider,
|
||||
result: params.result,
|
||||
}),
|
||||
).toEqual(params.expected);
|
||||
}
|
||||
|
||||
describe("groupPluginDiscoveryProvidersByOrder", () => {
|
||||
it.each([
|
||||
{
|
||||
|
|
@ -132,8 +145,7 @@ describe("normalizePluginDiscoveryResult", () => {
|
|||
},
|
||||
},
|
||||
] as const)("$name", ({ provider, result, expected }) => {
|
||||
const normalized = normalizePluginDiscoveryResult({ provider, result });
|
||||
expect(normalized).toEqual(expected);
|
||||
expectNormalizedDiscoveryResult({ provider, result, expected });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ function expectProviderModels(
|
|||
expect(providers?.[providerId]).toMatchObject(expected);
|
||||
}
|
||||
|
||||
function expectDefaultPrimaryModel(cfg: OpenClawConfig, modelRef: string) {
|
||||
expect(cfg.agents?.defaults?.model).toEqual({
|
||||
primary: modelRef,
|
||||
});
|
||||
}
|
||||
|
||||
function createDemoProviderParams(params?: {
|
||||
providerId?: string;
|
||||
baseUrl?: string;
|
||||
|
|
@ -135,9 +141,7 @@ describe("provider onboarding preset appliers", () => {
|
|||
{ id: "b", name: "Model B" },
|
||||
],
|
||||
});
|
||||
expect(cfg.agents?.defaults?.model).toEqual({
|
||||
primary: "demo/a",
|
||||
});
|
||||
expectDefaultPrimaryModel(cfg, "demo/a");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,20 @@ function expectNormalizedProviderFixture(params: {
|
|||
return result;
|
||||
}
|
||||
|
||||
function expectProviderNormalizationResult(params: {
|
||||
provider: ProviderPlugin;
|
||||
expectedProvider?: Record<string, unknown>;
|
||||
expectedDiagnostics?: ReadonlyArray<{ level: PluginDiagnostic["level"]; message: string }>;
|
||||
expectedDiagnosticText?: readonly string[];
|
||||
assert?: (
|
||||
provider: ReturnType<typeof normalizeRegisteredProvider>,
|
||||
diagnostics: PluginDiagnostic[],
|
||||
) => void;
|
||||
}) {
|
||||
const { diagnostics, provider } = expectNormalizedProviderFixture(params);
|
||||
params.assert?.(provider, diagnostics);
|
||||
}
|
||||
|
||||
describe("normalizeRegisteredProvider", () => {
|
||||
it.each([
|
||||
{
|
||||
|
|
@ -187,16 +201,12 @@ describe("normalizeRegisteredProvider", () => {
|
|||
] as const)(
|
||||
"$name",
|
||||
({ provider: inputProvider, expectedProvider, expectedDiagnostics, assert }) => {
|
||||
const { diagnostics, provider } = expectNormalizedProviderFixture({
|
||||
expectProviderNormalizationResult({
|
||||
provider: inputProvider,
|
||||
...(expectedProvider ? { expectedProvider } : {}),
|
||||
...(expectedDiagnostics ? { expectedDiagnostics } : {}),
|
||||
...(assert ? { assert } : {}),
|
||||
});
|
||||
|
||||
if (assert) {
|
||||
assert(provider, diagnostics);
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,32 @@ function createWizardRuntimeParams(params?: {
|
|||
};
|
||||
}
|
||||
|
||||
function expectWizardResolutionCount(params: {
|
||||
provider: ProviderPlugin;
|
||||
config?: object;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
expectedCount: number;
|
||||
}) {
|
||||
setResolvedProviders(params.provider);
|
||||
resolveProviderWizardOptions(
|
||||
createWizardRuntimeParams({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
}),
|
||||
);
|
||||
resolveProviderWizardOptions(
|
||||
createWizardRuntimeParams({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
}),
|
||||
);
|
||||
expectProviderResolutionCall({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
count: params.expectedCount,
|
||||
});
|
||||
}
|
||||
|
||||
function expectProviderResolutionCall(params?: {
|
||||
config?: object;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
|
|
@ -112,21 +138,6 @@ function setResolvedProviders(...providers: ProviderPlugin[]) {
|
|||
resolvePluginProviders.mockReturnValue(providers);
|
||||
}
|
||||
|
||||
function resolveWizardOptionsTwice(params: {
|
||||
config?: object;
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
}) {
|
||||
const runtimeParams = createWizardRuntimeParams(params);
|
||||
resolveProviderWizardOptions(runtimeParams);
|
||||
resolveProviderWizardOptions(runtimeParams);
|
||||
}
|
||||
|
||||
function expectWizardProviderCacheMiss(params: { config?: object; env: NodeJS.ProcessEnv }) {
|
||||
resolveWizardOptionsTwice(params);
|
||||
expectProviderResolutionCall({ ...params, count: 2 });
|
||||
}
|
||||
|
||||
function expectSingleWizardChoice(params: {
|
||||
provider: ProviderPlugin;
|
||||
choice: string;
|
||||
|
|
@ -363,11 +374,12 @@ describe("provider wizard boundaries", () => {
|
|||
}),
|
||||
},
|
||||
] as const)("$name", ({ env }) => {
|
||||
const provider = createSglangSetupProvider();
|
||||
const config = createSglangConfig();
|
||||
setResolvedProviders(provider);
|
||||
|
||||
expectWizardProviderCacheMiss({ config, env });
|
||||
expectWizardResolutionCount({
|
||||
provider: createSglangSetupProvider(),
|
||||
config: createSglangConfig(),
|
||||
env,
|
||||
expectedCount: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("expires provider-wizard memoization after the shortest plugin cache ttl", () => {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,17 @@ describe("gateway request scope", () => {
|
|||
expect(runtimeScope.getPluginRuntimeGatewayRequestScope()).toEqual(expected);
|
||||
}
|
||||
|
||||
async function expectGatewayScopeWithPluginId(pluginId: string) {
|
||||
await withTestGatewayScope(async (runtimeScope) => {
|
||||
await runtimeScope.withPluginRuntimePluginIdScope(pluginId, async () => {
|
||||
expectGatewayScope(runtimeScope, {
|
||||
...TEST_SCOPE,
|
||||
pluginId,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("reuses AsyncLocalStorage across reloaded module instances", async () => {
|
||||
const first = await importGatewayRequestScopeModule();
|
||||
|
||||
|
|
@ -42,13 +53,6 @@ describe("gateway request scope", () => {
|
|||
});
|
||||
|
||||
it("attaches plugin id to the active scope", async () => {
|
||||
await withTestGatewayScope(async (runtimeScope) => {
|
||||
await runtimeScope.withPluginRuntimePluginIdScope("voice-call", async () => {
|
||||
expectGatewayScope(runtimeScope, {
|
||||
...TEST_SCOPE,
|
||||
pluginId: "voice-call",
|
||||
});
|
||||
});
|
||||
});
|
||||
await expectGatewayScopeWithPluginId("voice-call");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,6 +56,20 @@ function expectFunctionKeys(value: Record<string, unknown>, keys: readonly strin
|
|||
});
|
||||
}
|
||||
|
||||
function expectRunCommandOutcome(params: {
|
||||
runtime: ReturnType<typeof createPluginRuntime>;
|
||||
expected: "resolve" | "reject";
|
||||
commandResult: ReturnType<typeof createCommandResult>;
|
||||
}) {
|
||||
const command = params.runtime.system.runCommandWithTimeout(["echo", "hello"], {
|
||||
timeoutMs: 1000,
|
||||
});
|
||||
if (params.expected === "resolve") {
|
||||
return expect(command).resolves.toEqual(params.commandResult);
|
||||
}
|
||||
return expect(command).rejects.toThrow("boom");
|
||||
}
|
||||
|
||||
describe("plugin runtime command execution", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
|
|
@ -83,12 +97,7 @@ describe("plugin runtime command execution", () => {
|
|||
}
|
||||
|
||||
const runtime = createPluginRuntime();
|
||||
const command = runtime.system.runCommandWithTimeout(["echo", "hello"], { timeoutMs: 1000 });
|
||||
if (expected === "resolve") {
|
||||
await expect(command).resolves.toEqual(commandResult);
|
||||
} else {
|
||||
await expect(command).rejects.toThrow("boom");
|
||||
}
|
||||
await expectRunCommandOutcome({ runtime, expected, commandResult });
|
||||
expect(runCommandWithTimeoutMock).toHaveBeenCalledWith(["echo", "hello"], { timeoutMs: 1000 });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,47 @@ function expectSnapshotMemoization(params: {
|
|||
expectLoaderCallCount(params.expectedLoaderCalls);
|
||||
}
|
||||
|
||||
function expectAutoEnabledWebSearchLoad(params: {
|
||||
rawConfig: { plugins?: Record<string, unknown> };
|
||||
expectedAllow: readonly string[];
|
||||
}) {
|
||||
expect(applyPluginAutoEnableSpy).toHaveBeenCalledWith({
|
||||
config: params.rawConfig,
|
||||
env: createWebSearchEnv(),
|
||||
});
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: expect.arrayContaining([...params.expectedAllow]),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function expectSnapshotLoaderCalls(params: {
|
||||
config: { plugins?: Record<string, unknown> };
|
||||
env: NodeJS.ProcessEnv;
|
||||
mutate: () => void;
|
||||
expectedLoaderCalls: number;
|
||||
}) {
|
||||
resolvePluginWebSearchProviders(
|
||||
createSnapshotParams({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
}),
|
||||
);
|
||||
params.mutate();
|
||||
resolvePluginWebSearchProviders(
|
||||
createSnapshotParams({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
}),
|
||||
);
|
||||
expectLoaderCallCount(params.expectedLoaderCalls);
|
||||
}
|
||||
|
||||
describe("resolvePluginWebSearchProviders", () => {
|
||||
beforeAll(async () => {
|
||||
({ createEmptyPluginRegistry } = await import("./registry.js"));
|
||||
|
|
@ -272,19 +313,10 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config: rawConfig }));
|
||||
|
||||
expect(applyPluginAutoEnableSpy).toHaveBeenCalledWith({
|
||||
config: rawConfig,
|
||||
env: createWebSearchEnv(),
|
||||
expectAutoEnabledWebSearchLoad({
|
||||
rawConfig,
|
||||
expectedAllow: ["brave", "perplexity"],
|
||||
});
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: expect.arrayContaining(["brave", "perplexity"]),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("scopes plugin loading to manifest-declared web-search candidates", () => {
|
||||
|
|
@ -301,16 +333,29 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("invalidates the snapshot cache when config or env contents change in place", () => {
|
||||
it.each([
|
||||
{
|
||||
name: "invalidates the snapshot cache when config contents change in place",
|
||||
mutate: (config: { plugins?: Record<string, unknown> }, _env: NodeJS.ProcessEnv) => {
|
||||
config.plugins = { allow: ["perplexity"] };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalidates the snapshot cache when env contents change in place",
|
||||
mutate: (_config: { plugins?: Record<string, unknown> }, env: NodeJS.ProcessEnv) => {
|
||||
env.OPENCLAW_HOME = "/tmp/openclaw-home-b";
|
||||
},
|
||||
},
|
||||
] as const)("$name", ({ mutate }) => {
|
||||
const config = createBraveAllowConfig();
|
||||
const env = createWebSearchEnv({ OPENCLAW_HOME: "/tmp/openclaw-home-a" });
|
||||
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
config.plugins.allow = ["perplexity"];
|
||||
env.OPENCLAW_HOME = "/tmp/openclaw-home-b";
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
|
||||
expectLoaderCallCount(2);
|
||||
expectSnapshotLoaderCalls({
|
||||
config,
|
||||
env,
|
||||
mutate: () => mutate(config, env),
|
||||
expectedLoaderCalls: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
|
@ -380,13 +425,14 @@ describe("resolvePluginWebSearchProviders", () => {
|
|||
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS: "1000",
|
||||
});
|
||||
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
|
||||
env.OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS = "5";
|
||||
|
||||
resolvePluginWebSearchProviders(createSnapshotParams({ config, env }));
|
||||
|
||||
expectLoaderCallCount(2);
|
||||
expectSnapshotLoaderCalls({
|
||||
config,
|
||||
env,
|
||||
mutate: () => {
|
||||
env.OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS = "5";
|
||||
},
|
||||
expectedLoaderCalls: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the active plugin registry for runtime resolution", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue