mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-18 04:04:27 +00:00
feat(plugins): narrow explicit provider loads from manifests (#65259)
* feat(plugins): narrow explicit provider loads from manifests * fix(plugins): preserve setup trust filtering for explicit owners * fix(plugins): respect runtime owner trust and disablement * fix(plugins): preserve provider owner policy bounds
This commit is contained in:
@@ -527,9 +527,12 @@ actual behavior such as hooks, tools, commands, or provider flows.
|
||||
Optional manifest `activation` and `setup` blocks stay on the control plane.
|
||||
They are metadata-only descriptors for activation planning and setup discovery;
|
||||
they do not replace runtime registration, `register(...)`, or `setupEntry`.
|
||||
The first activation consumer now uses manifest command hints to narrow CLI
|
||||
plugin loading when a primary command is known, instead of always loading every
|
||||
CLI-capable plugin up front.
|
||||
The first live activation consumers now use manifest command and provider hints
|
||||
to narrow plugin loading before broader registry materialization:
|
||||
|
||||
- CLI loading narrows to plugins that own the requested primary command
|
||||
- explicit provider setup/runtime resolution narrows to plugins that own the
|
||||
requested provider id
|
||||
|
||||
Setup discovery now prefers descriptor-owned ids such as `setup.providers` and
|
||||
`setup.cliBackends` to narrow candidate plugins before it falls back to
|
||||
|
||||
@@ -222,8 +222,8 @@ should activate it later.
|
||||
This block is metadata only. It does not register runtime behavior, and it does
|
||||
not replace `register(...)`, `setupEntry`, or other runtime/plugin entrypoints.
|
||||
Current consumers use it as a narrowing hint before broader plugin loading, so
|
||||
missing activation metadata only costs performance; it should not change
|
||||
correctness.
|
||||
missing activation metadata usually only costs performance; it should not
|
||||
change correctness while legacy manifest ownership fallbacks still exist.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -245,9 +245,13 @@ correctness.
|
||||
| `onRoutes` | No | `string[]` | Route kinds that should activate this plugin. |
|
||||
| `onCapabilities` | No | `Array<"provider" \| "channel" \| "tool" \| "hook">` | Broad capability hints used by control-plane activation planning. |
|
||||
|
||||
For command-triggered planning specifically, OpenClaw still falls back to
|
||||
legacy `commandAliases[].cliCommand` or `commandAliases[].name` when a plugin
|
||||
has not added explicit `activation.onCommands` metadata yet.
|
||||
Current live consumers:
|
||||
|
||||
- command-triggered CLI planning falls back to legacy
|
||||
`commandAliases[].cliCommand` or `commandAliases[].name`
|
||||
- provider-triggered setup/runtime planning falls back to legacy
|
||||
`providers[]` and top-level `cliBackends[]` ownership when explicit provider
|
||||
activation metadata is missing
|
||||
|
||||
## setup reference
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ export type BundledPluginCompatibleActivationInputs = PluginActivationInputs & {
|
||||
export function withActivatedPluginIds(params: {
|
||||
config?: OpenClawConfig;
|
||||
pluginIds: readonly string[];
|
||||
overrideGlobalDisable?: boolean;
|
||||
overrideExplicitDisable?: boolean;
|
||||
}): OpenClawConfig | undefined {
|
||||
if (params.pluginIds.length === 0) {
|
||||
return params.config;
|
||||
@@ -64,12 +66,14 @@ export function withActivatedPluginIds(params: {
|
||||
continue;
|
||||
}
|
||||
allow.add(normalized);
|
||||
const existingEntry = entries[normalized];
|
||||
entries[normalized] = {
|
||||
...entries[normalized],
|
||||
enabled: true,
|
||||
...existingEntry,
|
||||
enabled: existingEntry?.enabled !== false || params.overrideExplicitDisable === true,
|
||||
};
|
||||
}
|
||||
const forcePluginsEnabled = params.config?.plugins?.enabled === false;
|
||||
const forcePluginsEnabled =
|
||||
params.overrideGlobalDisable === true && params.config?.plugins?.enabled === false;
|
||||
return {
|
||||
...params.config,
|
||||
plugins: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { withActivatedPluginIds } from "./activation-context.js";
|
||||
import { resolveBundledPluginCompatibleActivationInputs } from "./activation-context.js";
|
||||
import { resolveManifestActivationPluginIds } from "./activation-planner.js";
|
||||
import {
|
||||
isPluginRegistryLoadInFlight,
|
||||
loadOpenClawPlugins,
|
||||
@@ -7,6 +8,8 @@ import {
|
||||
type PluginLoadOptions,
|
||||
} from "./loader.js";
|
||||
import {
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveDiscoverableProviderOwnerPluginIds,
|
||||
resolveDiscoveredProviderPluginIds,
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
@@ -21,6 +24,54 @@ import {
|
||||
} from "./runtime/load-context.js";
|
||||
import type { ProviderPlugin } from "./types.js";
|
||||
|
||||
function dedupeSortedPluginIds(values: Iterable<string>): string[] {
|
||||
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveExplicitProviderOwnerPluginIds(params: {
|
||||
providerRefs: readonly string[];
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
return dedupeSortedPluginIds(
|
||||
params.providerRefs.flatMap((provider) => {
|
||||
const plannedPluginIds = resolveManifestActivationPluginIds({
|
||||
trigger: {
|
||||
kind: "provider",
|
||||
provider,
|
||||
},
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
if (plannedPluginIds.length > 0) {
|
||||
return plannedPluginIds;
|
||||
}
|
||||
// Keep legacy provider/CLI-backend ownership working until every owner is
|
||||
// expressible through activation descriptors.
|
||||
return (
|
||||
resolveOwningPluginIdsForProvider({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
}) ?? []
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function mergeExplicitOwnerPluginIds(
|
||||
providerPluginIds: readonly string[],
|
||||
explicitOwnerPluginIds: readonly string[],
|
||||
): string[] {
|
||||
if (explicitOwnerPluginIds.length === 0) {
|
||||
return [...providerPluginIds];
|
||||
}
|
||||
return dedupeSortedPluginIds([...providerPluginIds, ...explicitOwnerPluginIds]);
|
||||
}
|
||||
|
||||
function resolvePluginProviderLoadBase(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
@@ -32,19 +83,12 @@ function resolvePluginProviderLoadBase(params: {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const providerOwnedPluginIds = params.providerRefs?.length
|
||||
? [
|
||||
...new Set(
|
||||
params.providerRefs.flatMap(
|
||||
(provider) =>
|
||||
resolveOwningPluginIdsForProvider({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
}) ?? [],
|
||||
),
|
||||
),
|
||||
]
|
||||
? resolveExplicitProviderOwnerPluginIds({
|
||||
providerRefs: params.providerRefs,
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
})
|
||||
: [];
|
||||
const modelOwnedPluginIds = params.modelRefs?.length
|
||||
? resolveOwningPluginIdsForModelRefs({
|
||||
@@ -68,15 +112,16 @@ function resolvePluginProviderLoadBase(params: {
|
||||
]),
|
||||
].toSorted((left, right) => left.localeCompare(right))
|
||||
: undefined;
|
||||
const runtimeConfig = withActivatedPluginIds({
|
||||
config: params.config,
|
||||
pluginIds: [...providerOwnedPluginIds, ...modelOwnedPluginIds],
|
||||
});
|
||||
const explicitOwnerPluginIds = dedupeSortedPluginIds([
|
||||
...providerOwnedPluginIds,
|
||||
...modelOwnedPluginIds,
|
||||
]);
|
||||
return {
|
||||
env,
|
||||
workspaceDir,
|
||||
requestedPluginIds,
|
||||
runtimeConfig,
|
||||
explicitOwnerPluginIds,
|
||||
rawConfig: params.config,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,29 +130,38 @@ function resolveSetupProviderPluginLoadState(
|
||||
base: ReturnType<typeof resolvePluginProviderLoadBase>,
|
||||
) {
|
||||
const providerPluginIds = resolveDiscoveredProviderPluginIds({
|
||||
config: base.runtimeConfig,
|
||||
config: params.config,
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
onlyPluginIds: base.requestedPluginIds,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
});
|
||||
if (providerPluginIds.length === 0) {
|
||||
const explicitOwnerPluginIds = resolveDiscoverableProviderOwnerPluginIds({
|
||||
pluginIds: base.explicitOwnerPluginIds,
|
||||
config: params.config,
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
});
|
||||
const setupPluginIds = mergeExplicitOwnerPluginIds(providerPluginIds, explicitOwnerPluginIds);
|
||||
if (setupPluginIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const setupConfig = withActivatedPluginIds({
|
||||
config: base.rawConfig,
|
||||
pluginIds: setupPluginIds,
|
||||
});
|
||||
const loadOptions = buildPluginRuntimeLoadOptionsFromValues(
|
||||
{
|
||||
config: withActivatedPluginIds({
|
||||
config: base.runtimeConfig,
|
||||
pluginIds: providerPluginIds,
|
||||
}),
|
||||
activationSourceConfig: base.runtimeConfig,
|
||||
config: setupConfig,
|
||||
activationSourceConfig: setupConfig,
|
||||
autoEnabledReasons: {},
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
logger: createPluginRuntimeLoaderLogger(),
|
||||
},
|
||||
{
|
||||
onlyPluginIds: providerPluginIds,
|
||||
onlyPluginIds: setupPluginIds,
|
||||
pluginSdkResolution: params.pluginSdkResolution,
|
||||
cache: params.cache ?? false,
|
||||
activate: params.activate ?? false,
|
||||
@@ -120,11 +174,26 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
params: Parameters<typeof resolvePluginProviders>[0],
|
||||
base: ReturnType<typeof resolvePluginProviderLoadBase>,
|
||||
) {
|
||||
const explicitOwnerPluginIds = resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: base.explicitOwnerPluginIds,
|
||||
config: base.rawConfig,
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
});
|
||||
const runtimeRequestedPluginIds =
|
||||
base.requestedPluginIds !== undefined
|
||||
? dedupeSortedPluginIds([...(params.onlyPluginIds ?? []), ...explicitOwnerPluginIds])
|
||||
: undefined;
|
||||
const requestConfig = withActivatedPluginIds({
|
||||
config: base.rawConfig,
|
||||
pluginIds: explicitOwnerPluginIds,
|
||||
});
|
||||
const activation = resolveBundledPluginCompatibleActivationInputs({
|
||||
rawConfig: base.runtimeConfig,
|
||||
rawConfig: requestConfig,
|
||||
env: base.env,
|
||||
workspaceDir: base.workspaceDir,
|
||||
onlyPluginIds: base.requestedPluginIds,
|
||||
onlyPluginIds: runtimeRequestedPluginIds,
|
||||
applyAutoEnable: true,
|
||||
compatMode: {
|
||||
allowlist: params.bundledProviderAllowlistCompat,
|
||||
@@ -140,12 +209,15 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
env: base.env,
|
||||
})
|
||||
: activation.config;
|
||||
const providerPluginIds = resolveEnabledProviderPluginIds({
|
||||
config,
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
onlyPluginIds: base.requestedPluginIds,
|
||||
});
|
||||
const providerPluginIds = mergeExplicitOwnerPluginIds(
|
||||
resolveEnabledProviderPluginIds({
|
||||
config,
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
onlyPluginIds: runtimeRequestedPluginIds,
|
||||
}),
|
||||
explicitOwnerPluginIds,
|
||||
);
|
||||
const loadOptions = buildPluginRuntimeLoadOptionsFromValues(
|
||||
{
|
||||
config,
|
||||
|
||||
@@ -21,7 +21,9 @@ const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
|
||||
|
||||
let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
||||
let resolveOwningPluginIdsForModelRef: typeof import("./providers.js").resolveOwningPluginIdsForModelRef;
|
||||
let resolveActivatableProviderOwnerPluginIds: typeof import("./providers.js").resolveActivatableProviderOwnerPluginIds;
|
||||
let resolveEnabledProviderPluginIds: typeof import("./providers.js").resolveEnabledProviderPluginIds;
|
||||
let resolveDiscoverableProviderOwnerPluginIds: typeof import("./providers.js").resolveDiscoverableProviderOwnerPluginIds;
|
||||
let resolvePluginProviders: typeof import("./providers.runtime.js").resolvePluginProviders;
|
||||
let setActivePluginRegistry: SetActivePluginRegistry;
|
||||
|
||||
@@ -32,6 +34,8 @@ function createManifestProviderPlugin(params: {
|
||||
origin?: "bundled" | "workspace";
|
||||
enabledByDefault?: boolean;
|
||||
modelSupport?: { modelPrefixes?: string[]; modelPatterns?: string[] };
|
||||
activation?: PluginManifestRecord["activation"];
|
||||
setup?: PluginManifestRecord["setup"];
|
||||
}): PluginManifestRecord {
|
||||
return {
|
||||
id: params.id,
|
||||
@@ -40,6 +44,8 @@ function createManifestProviderPlugin(params: {
|
||||
providers: params.providerIds,
|
||||
cliBackends: params.cliBackends ?? [],
|
||||
modelSupport: params.modelSupport,
|
||||
activation: params.activation,
|
||||
setup: params.setup,
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: params.origin ?? "bundled",
|
||||
@@ -280,9 +286,11 @@ describe("resolvePluginProviders", () => {
|
||||
loadPluginManifestRegistryMock(...args),
|
||||
}));
|
||||
({
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveOwningPluginIdsForProvider,
|
||||
resolveOwningPluginIdsForModelRef,
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveDiscoverableProviderOwnerPluginIds,
|
||||
} = await import("./providers.js"));
|
||||
({ resolvePluginProviders } = await import("./providers.runtime.js"));
|
||||
({ setActivePluginRegistry } = await import("./runtime.js"));
|
||||
@@ -538,7 +546,7 @@ describe("resolvePluginProviders", () => {
|
||||
"workspace-provider",
|
||||
]),
|
||||
entries: expect.objectContaining({
|
||||
google: { enabled: true },
|
||||
google: { enabled: false },
|
||||
kilocode: { enabled: true },
|
||||
moonshot: { enabled: true },
|
||||
"workspace-provider": { enabled: true },
|
||||
@@ -709,6 +717,414 @@ describe("resolvePluginProviders", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses activation.onProviders to keep explicit provider owners on the runtime path", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "activation-owned-provider",
|
||||
providerIds: [],
|
||||
activation: {
|
||||
onProviders: ["activation-owned"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["activation-owned"],
|
||||
activate: true,
|
||||
});
|
||||
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["activation-owned-provider"],
|
||||
activate: true,
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["activation-owned-provider"],
|
||||
entries: {
|
||||
"activation-owned-provider": { enabled: true },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not activate explicit runtime owners when plugins are globally disabled", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "activation-owned-provider",
|
||||
providerIds: [],
|
||||
activation: {
|
||||
onProviders: ["activation-owned"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: ["activation-owned-provider"],
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not activate explicit runtime owners disabled in config", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "activation-owned-provider",
|
||||
providerIds: [],
|
||||
activation: {
|
||||
onProviders: ["activation-owned"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: ["activation-owned-provider"],
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"activation-owned-provider": { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not activate explicit runtime owners outside the allowlist", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "activation-owned-provider",
|
||||
providerIds: [],
|
||||
activation: {
|
||||
onProviders: ["activation-owned"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: ["activation-owned-provider"],
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["other-plugin"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("uses setup.providers to keep explicit provider owners on the setup path", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "setup-owned-provider",
|
||||
providerIds: [],
|
||||
setup: {
|
||||
providers: [{ id: "setup-owned" }],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["setup-owned"],
|
||||
activate: true,
|
||||
mode: "setup",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["setup-owned-provider"],
|
||||
activate: true,
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["setup-owned-provider"],
|
||||
entries: {
|
||||
"setup-owned-provider": { enabled: true },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not override global plugin disable during setup owner loading", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "setup-owned-provider",
|
||||
providerIds: [],
|
||||
setup: {
|
||||
providers: [{ id: "setup-owned" }],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
providerRefs: ["setup-owned"],
|
||||
activate: true,
|
||||
mode: "setup",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
enabled: false,
|
||||
allow: ["setup-owned-provider"],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not override explicitly disabled setup owners", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "setup-owned-provider",
|
||||
providerIds: [],
|
||||
setup: {
|
||||
providers: [{ id: "setup-owned" }],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
"setup-owned-provider": { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
providerRefs: ["setup-owned"],
|
||||
activate: true,
|
||||
mode: "setup",
|
||||
});
|
||||
|
||||
expect(loadOpenClawPluginsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["setup-owned-provider"],
|
||||
entries: {
|
||||
"setup-owned-provider": { enabled: false },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("filters explicit setup owners through the untrusted workspace discovery gate", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-activation-owner",
|
||||
providerIds: [],
|
||||
origin: "workspace",
|
||||
activation: {
|
||||
onProviders: ["workspace-activation"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["workspace-activation"],
|
||||
activate: true,
|
||||
mode: "setup",
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
});
|
||||
|
||||
expect(providers).toEqual([]);
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not auto-activate untrusted workspace runtime owners when requested", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-activation-owner",
|
||||
providerIds: [],
|
||||
origin: "workspace",
|
||||
activation: {
|
||||
onProviders: ["workspace-activation"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(createEmptyPluginRegistry());
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["workspace-activation"],
|
||||
activate: true,
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
});
|
||||
|
||||
expect(providers).toEqual([]);
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: {},
|
||||
onlyPluginIds: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not auto-activate workspace runtime owners by default", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-activation-owner",
|
||||
providerIds: [],
|
||||
origin: "workspace",
|
||||
activation: {
|
||||
onProviders: ["workspace-activation"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(createEmptyPluginRegistry());
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["workspace-activation"],
|
||||
activate: true,
|
||||
});
|
||||
|
||||
expect(providers).toEqual([]);
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: {},
|
||||
onlyPluginIds: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps explicit provider requests scoped when runtime owner activation resolves nothing", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "activation-owned-provider",
|
||||
providerIds: [],
|
||||
activation: {
|
||||
onProviders: ["activation-owned"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
resolveRuntimePluginRegistryMock.mockReturnValue(createEmptyPluginRegistry());
|
||||
|
||||
const providers = resolvePluginProviders({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["other-plugin"],
|
||||
},
|
||||
},
|
||||
providerRefs: ["activation-owned"],
|
||||
activate: true,
|
||||
});
|
||||
|
||||
expect(providers).toEqual([]);
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["other-plugin"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps explicitly trusted disabled workspace setup owners discoverable", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-activation-owner",
|
||||
providerIds: [],
|
||||
origin: "workspace",
|
||||
activation: {
|
||||
onProviders: ["workspace-activation"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveDiscoverableProviderOwnerPluginIds({
|
||||
pluginIds: ["workspace-activation-owner"],
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
allow: ["workspace-activation-owner"],
|
||||
entries: {
|
||||
"workspace-activation-owner": { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
}),
|
||||
).toEqual(["workspace-activation-owner"]);
|
||||
});
|
||||
|
||||
it("does not auto-activate explicitly disabled trusted workspace runtime owners", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "workspace-activation-owner",
|
||||
providerIds: [],
|
||||
origin: "workspace",
|
||||
activation: {
|
||||
onProviders: ["workspace-activation"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(
|
||||
resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: ["workspace-activation-owner"],
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["workspace-activation-owner"],
|
||||
entries: {
|
||||
"workspace-activation-owner": { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps legacy CLI backend ownership as the explicit provider fallback", () => {
|
||||
setOwningProviderManifestPlugins();
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {},
|
||||
providerRefs: ["claude-cli"],
|
||||
activate: true,
|
||||
});
|
||||
|
||||
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["anthropic"],
|
||||
activate: true,
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
allow: ["anthropic"],
|
||||
entries: {
|
||||
anthropic: { enabled: true },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
{
|
||||
provider: "minimax-portal",
|
||||
|
||||
@@ -89,33 +89,145 @@ export function resolveDiscoveredProviderPluginIds(params: {
|
||||
if (!(plugin.providers.length > 0 && (!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)))) {
|
||||
return false;
|
||||
}
|
||||
if (!shouldFilterUntrustedWorkspacePlugins || plugin.origin !== "workspace") {
|
||||
return true;
|
||||
}
|
||||
const activation = resolveEffectivePluginActivationState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
return isProviderPluginEligibleForSetupDiscovery({
|
||||
plugin,
|
||||
shouldFilterUntrustedWorkspacePlugins,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
});
|
||||
if (activation.activated) {
|
||||
return true;
|
||||
}
|
||||
const explicitlyTrustedButDisabled =
|
||||
normalizedConfig.enabled &&
|
||||
!normalizedConfig.deny.includes(plugin.id) &&
|
||||
normalizedConfig.allow.includes(plugin.id) &&
|
||||
normalizedConfig.entries[plugin.id]?.enabled === false;
|
||||
return explicitlyTrustedButDisabled;
|
||||
})
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function isProviderPluginEligibleForSetupDiscovery(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
shouldFilterUntrustedWorkspacePlugins: boolean;
|
||||
normalizedConfig: ReturnType<typeof normalizePluginsConfig>;
|
||||
rootConfig?: PluginLoadOptions["config"];
|
||||
}): boolean {
|
||||
if (!params.shouldFilterUntrustedWorkspacePlugins || params.plugin.origin !== "workspace") {
|
||||
return true;
|
||||
}
|
||||
const activation = resolveEffectivePluginActivationState({
|
||||
id: params.plugin.id,
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
});
|
||||
if (activation.activated) {
|
||||
return true;
|
||||
}
|
||||
const explicitlyTrustedButDisabled =
|
||||
params.normalizedConfig.enabled &&
|
||||
!params.normalizedConfig.deny.includes(params.plugin.id) &&
|
||||
params.normalizedConfig.allow.includes(params.plugin.id) &&
|
||||
params.normalizedConfig.entries[params.plugin.id]?.enabled === false;
|
||||
return explicitlyTrustedButDisabled;
|
||||
}
|
||||
|
||||
export function resolveDiscoverableProviderOwnerPluginIds(params: {
|
||||
pluginIds: readonly string[];
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
}): string[] {
|
||||
if (params.pluginIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const pluginIdSet = new Set(params.pluginIds);
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false;
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return registry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
pluginIdSet.has(plugin.id) &&
|
||||
isProviderPluginEligibleForSetupDiscovery({
|
||||
plugin,
|
||||
shouldFilterUntrustedWorkspacePlugins,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
}),
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function isProviderPluginEligibleForRuntimeOwnerActivation(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
normalizedConfig: ReturnType<typeof normalizePluginsConfig>;
|
||||
rootConfig?: PluginLoadOptions["config"];
|
||||
}): boolean {
|
||||
if (!params.normalizedConfig.enabled) {
|
||||
return false;
|
||||
}
|
||||
if (params.normalizedConfig.deny.includes(params.plugin.id)) {
|
||||
return false;
|
||||
}
|
||||
if (params.normalizedConfig.entries[params.plugin.id]?.enabled === false) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
params.normalizedConfig.allow.length > 0 &&
|
||||
!params.normalizedConfig.allow.includes(params.plugin.id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (params.plugin.origin !== "workspace") {
|
||||
return true;
|
||||
}
|
||||
return resolveEffectivePluginActivationState({
|
||||
id: params.plugin.id,
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
}).activated;
|
||||
}
|
||||
|
||||
export function resolveActivatableProviderOwnerPluginIds(params: {
|
||||
pluginIds: readonly string[];
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
}): string[] {
|
||||
if (params.pluginIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const pluginIdSet = new Set(params.pluginIds);
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return registry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
pluginIdSet.has(plugin.id) &&
|
||||
isProviderPluginEligibleForRuntimeOwnerActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
}),
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveEnabledProviderPluginIds,
|
||||
resolveDiscoveredProviderPluginIds,
|
||||
resolveDiscoverableProviderOwnerPluginIds,
|
||||
resolveBundledProviderCompatPluginIds,
|
||||
withBundledProviderVitestCompat,
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user