mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
feat(plugins): resolve provider owners from registry
This commit is contained in:
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/CLI: add `openclaw plugins registry` for explicit persisted-registry inspection and `--refresh` repair without making normal startup rescan plugin locations. Thanks @vincentkoc.
|
||||
- Plugins/CLI: make `openclaw plugins list` read the cold persisted registry snapshot by default, leaving module-aware diagnostics to `plugins doctor` and `plugins inspect`. Thanks @vincentkoc.
|
||||
- Plugins/startup: move gateway startup plugin planning onto the versioned cold registry index, with postinstall repair for older registry files that predate startup metadata. Thanks @vincentkoc.
|
||||
- Providers/plugins: resolve provider ownership, provider discovery scopes, and catalog-hook provider ids from the cold plugin registry instead of rescanning manifests on those paths. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: add bounded outbound message delivery lifecycle diagnostics and export them as low-cardinality delivery spans/metrics without message body, recipient, room, or media-path data. (#71471) Thanks @vincentkoc and @jlapenna.
|
||||
- Diagnostics/OTEL: emit bounded exec-process diagnostics and export them as `openclaw.exec` spans without exposing command text, working directories, or container identifiers. (#71451) Thanks @vincentkoc and @jlapenna.
|
||||
- Diagnostics/OTEL: support `OPENCLAW_OTEL_PRELOADED=1` so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#71450) Thanks @vincentkoc and @jlapenna.
|
||||
|
||||
@@ -311,6 +311,13 @@ describe("resolvePluginProviders", () => {
|
||||
resolveManifestContractPluginIds: (...args: Parameters<ResolveManifestContractPluginIds>) =>
|
||||
resolveManifestContractPluginIdsMock(...args),
|
||||
}));
|
||||
vi.doMock("./installed-plugin-index-store.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./installed-plugin-index-store.js")>();
|
||||
return {
|
||||
...actual,
|
||||
readPersistedInstalledPluginIndexSync: () => null,
|
||||
};
|
||||
});
|
||||
({
|
||||
resolveActivatableProviderOwnerPluginIds,
|
||||
resolveOwningPluginIdsForProvider,
|
||||
|
||||
@@ -8,10 +8,16 @@ import {
|
||||
} from "./manifest-owner-policy.js";
|
||||
import {
|
||||
loadPluginManifestRegistry,
|
||||
resolveManifestContractPluginIds,
|
||||
type PluginManifestRecord,
|
||||
type PluginManifestRegistry,
|
||||
} from "./manifest-registry.js";
|
||||
import {
|
||||
loadPluginRegistrySnapshot,
|
||||
resolvePluginContributionOwners,
|
||||
resolveProviderOwners,
|
||||
type PluginRegistryRecord,
|
||||
type PluginRegistrySnapshot,
|
||||
} from "./plugin-registry.js";
|
||||
import { createPluginIdScopeSet } from "./plugin-scope.js";
|
||||
|
||||
type ProviderManifestLoadParams = {
|
||||
@@ -20,6 +26,9 @@ type ProviderManifestLoadParams = {
|
||||
env?: PluginLoadOptions["env"];
|
||||
};
|
||||
type NormalizedPluginsConfig = ReturnType<typeof normalizePluginsConfig>;
|
||||
type ProviderRegistryLoadParams = ProviderManifestLoadParams & {
|
||||
onlyPluginIds?: readonly string[];
|
||||
};
|
||||
|
||||
function loadProviderManifestRegistry(params: ProviderManifestLoadParams): PluginManifestRegistry {
|
||||
return loadPluginManifestRegistry({
|
||||
@@ -29,33 +38,39 @@ function loadProviderManifestRegistry(params: ProviderManifestLoadParams): Plugi
|
||||
});
|
||||
}
|
||||
|
||||
function loadScopedProviderManifestRegistry(
|
||||
params: ProviderManifestLoadParams & { onlyPluginIds?: readonly string[] },
|
||||
): {
|
||||
registry: PluginManifestRegistry;
|
||||
function loadProviderRegistrySnapshot(params: ProviderManifestLoadParams): PluginRegistrySnapshot {
|
||||
return loadPluginRegistrySnapshot({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
}
|
||||
|
||||
function loadScopedProviderRegistry(params: ProviderRegistryLoadParams): {
|
||||
registry: PluginRegistrySnapshot;
|
||||
onlyPluginIdSet: ReturnType<typeof createPluginIdScopeSet>;
|
||||
} {
|
||||
return {
|
||||
registry: loadProviderManifestRegistry(params),
|
||||
registry: loadProviderRegistrySnapshot(params),
|
||||
onlyPluginIdSet: createPluginIdScopeSet(params.onlyPluginIds),
|
||||
};
|
||||
}
|
||||
|
||||
function listManifestPluginIds(
|
||||
registry: PluginManifestRegistry,
|
||||
predicate: (plugin: PluginManifestRecord) => boolean,
|
||||
function listRegistryPluginIds(
|
||||
registry: PluginRegistrySnapshot,
|
||||
predicate: (plugin: PluginRegistryRecord) => boolean,
|
||||
): string[] {
|
||||
return registry.plugins
|
||||
.filter(predicate)
|
||||
.map((plugin) => plugin.id)
|
||||
.map((plugin) => plugin.pluginId)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveProviderOwnerPluginIds(
|
||||
params: ProviderManifestLoadParams & {
|
||||
params: ProviderRegistryLoadParams & {
|
||||
pluginIds: readonly string[];
|
||||
isEligible: (
|
||||
plugin: PluginManifestRecord,
|
||||
plugin: PluginRegistryRecord,
|
||||
normalizedConfig: NormalizedPluginsConfig,
|
||||
) => boolean;
|
||||
},
|
||||
@@ -64,14 +79,40 @@ function resolveProviderOwnerPluginIds(
|
||||
return [];
|
||||
}
|
||||
const pluginIdSet = new Set(params.pluginIds);
|
||||
const registry = loadProviderManifestRegistry(params);
|
||||
const registry = loadProviderRegistrySnapshot(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(
|
||||
return listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) => pluginIdSet.has(plugin.id) && params.isEligible(plugin, normalizedConfig),
|
||||
(plugin) => pluginIdSet.has(plugin.pluginId) && params.isEligible(plugin, normalizedConfig),
|
||||
);
|
||||
}
|
||||
|
||||
function recordHasProviderSurface(plugin: PluginRegistryRecord): boolean {
|
||||
return plugin.contributions.providers.length > 0;
|
||||
}
|
||||
|
||||
function resolveEffectiveRegistryPluginActivation(params: {
|
||||
plugin: PluginRegistryRecord;
|
||||
normalizedConfig: NormalizedPluginsConfig;
|
||||
rootConfig?: PluginLoadOptions["config"];
|
||||
}) {
|
||||
return resolveEffectivePluginActivationState({
|
||||
id: params.plugin.pluginId,
|
||||
origin: params.plugin.origin,
|
||||
config: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
enabledByDefault: params.plugin.enabledByDefault,
|
||||
});
|
||||
}
|
||||
|
||||
function toManifestOwnerRecord(plugin: PluginRegistryRecord) {
|
||||
return {
|
||||
id: plugin.pluginId,
|
||||
origin: plugin.origin,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
};
|
||||
}
|
||||
|
||||
export function withBundledProviderVitestCompat(params: {
|
||||
config: PluginLoadOptions["config"];
|
||||
pluginIds: readonly string[];
|
||||
@@ -86,13 +127,13 @@ export function resolveBundledProviderCompatPluginIds(params: {
|
||||
env?: PluginLoadOptions["env"];
|
||||
onlyPluginIds?: readonly string[];
|
||||
}): string[] {
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderManifestRegistry(params);
|
||||
return listManifestPluginIds(
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
return listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.origin === "bundled" &&
|
||||
plugin.providers.length > 0 &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)),
|
||||
recordHasProviderSurface(plugin) &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.pluginId)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,19 +143,17 @@ export function resolveEnabledProviderPluginIds(params: {
|
||||
env?: PluginLoadOptions["env"];
|
||||
onlyPluginIds?: readonly string[];
|
||||
}): string[] {
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderManifestRegistry(params);
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(
|
||||
return listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.providers.length > 0 &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)) &&
|
||||
resolveEffectivePluginActivationState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
recordHasProviderSurface(plugin) &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.pluginId)) &&
|
||||
resolveEffectiveRegistryPluginActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
}).activated,
|
||||
);
|
||||
}
|
||||
@@ -124,11 +163,29 @@ export function resolveExternalAuthProfileProviderPluginIds(params: {
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
return resolveManifestContractPluginIds({
|
||||
return resolveRegistryManifestContractPluginIds({
|
||||
...params,
|
||||
contract: "externalAuthProviders",
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveRegistryManifestContractPluginIds(params: {
|
||||
contract: string;
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
origin?: PluginRegistryRecord["origin"];
|
||||
onlyPluginIds?: readonly string[];
|
||||
}): string[] {
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
return listRegistryPluginIds(registry, (plugin) => {
|
||||
if (params.origin && plugin.origin !== params.origin) {
|
||||
return false;
|
||||
}
|
||||
if (onlyPluginIdSet && !onlyPluginIdSet.has(plugin.pluginId)) {
|
||||
return false;
|
||||
}
|
||||
return plugin.contributions.contracts.includes(params.contract);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,14 +200,14 @@ export function resolveExternalAuthProfileCompatFallbackPluginIds(params: {
|
||||
// this with the warning path in provider-runtime after the migration window.
|
||||
const declaredPluginIds =
|
||||
params.declaredPluginIds ?? new Set(resolveExternalAuthProfileProviderPluginIds(params));
|
||||
const registry = loadProviderManifestRegistry(params);
|
||||
const registry = loadProviderRegistrySnapshot(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(
|
||||
return listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.origin !== "bundled" &&
|
||||
plugin.providers.length > 0 &&
|
||||
!declaredPluginIds.has(plugin.id) &&
|
||||
recordHasProviderSurface(plugin) &&
|
||||
!declaredPluginIds.has(plugin.pluginId) &&
|
||||
isProviderPluginEligibleForRuntimeOwnerActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
@@ -166,11 +223,16 @@ export function resolveDiscoveredProviderPluginIds(params: {
|
||||
onlyPluginIds?: readonly string[];
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
}): string[] {
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderManifestRegistry(params);
|
||||
const { registry, onlyPluginIdSet } = loadScopedProviderRegistry(params);
|
||||
const shouldFilterUntrustedWorkspacePlugins = params.includeUntrustedWorkspacePlugins === false;
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(registry, (plugin) => {
|
||||
if (!(plugin.providers.length > 0 && (!onlyPluginIdSet || onlyPluginIdSet.has(plugin.id)))) {
|
||||
return listRegistryPluginIds(registry, (plugin) => {
|
||||
if (
|
||||
!(
|
||||
recordHasProviderSurface(plugin) &&
|
||||
(!onlyPluginIdSet || onlyPluginIdSet.has(plugin.pluginId))
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return isProviderPluginEligibleForSetupDiscovery({
|
||||
@@ -183,7 +245,7 @@ export function resolveDiscoveredProviderPluginIds(params: {
|
||||
}
|
||||
|
||||
function isProviderPluginEligibleForSetupDiscovery(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
plugin: PluginRegistryRecord;
|
||||
shouldFilterUntrustedWorkspacePlugins: boolean;
|
||||
normalizedConfig: NormalizedPluginsConfig;
|
||||
rootConfig?: PluginLoadOptions["config"];
|
||||
@@ -193,14 +255,14 @@ function isProviderPluginEligibleForSetupDiscovery(params: {
|
||||
}
|
||||
if (
|
||||
!passesManifestOwnerBasePolicy({
|
||||
plugin: params.plugin,
|
||||
plugin: toManifestOwnerRecord(params.plugin),
|
||||
normalizedConfig: params.normalizedConfig,
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return isActivatedManifestOwner({
|
||||
plugin: params.plugin,
|
||||
plugin: toManifestOwnerRecord(params.plugin),
|
||||
normalizedConfig: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
});
|
||||
@@ -227,13 +289,13 @@ export function resolveDiscoverableProviderOwnerPluginIds(params: {
|
||||
}
|
||||
|
||||
function isProviderPluginEligibleForRuntimeOwnerActivation(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
plugin: PluginRegistryRecord;
|
||||
normalizedConfig: NormalizedPluginsConfig;
|
||||
rootConfig?: PluginLoadOptions["config"];
|
||||
}): boolean {
|
||||
if (
|
||||
!passesManifestOwnerBasePolicy({
|
||||
plugin: params.plugin,
|
||||
plugin: toManifestOwnerRecord(params.plugin),
|
||||
normalizedConfig: params.normalizedConfig,
|
||||
})
|
||||
) {
|
||||
@@ -243,7 +305,7 @@ function isProviderPluginEligibleForRuntimeOwnerActivation(params: {
|
||||
return true;
|
||||
}
|
||||
return isActivatedManifestOwner({
|
||||
plugin: params.plugin,
|
||||
plugin: toManifestOwnerRecord(params.plugin),
|
||||
normalizedConfig: params.normalizedConfig,
|
||||
rootConfig: params.rootConfig,
|
||||
});
|
||||
@@ -376,20 +438,42 @@ export function resolveOwningPluginIdsForProvider(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const registry = resolveManifestRegistry(params);
|
||||
const pluginIds = registry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
plugin.providers.some(
|
||||
(providerId) => normalizeProviderId(providerId) === normalizedProvider,
|
||||
) ||
|
||||
plugin.cliBackends.some(
|
||||
(backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
),
|
||||
)
|
||||
.map((plugin) => plugin.id);
|
||||
if (params.manifestRegistry) {
|
||||
const pluginIds = params.manifestRegistry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
plugin.providers.some(
|
||||
(providerId) => normalizeProviderId(providerId) === normalizedProvider,
|
||||
) ||
|
||||
plugin.cliBackends.some(
|
||||
(backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
),
|
||||
)
|
||||
.map((plugin) => plugin.id);
|
||||
|
||||
return pluginIds.length > 0 ? pluginIds : undefined;
|
||||
return pluginIds.length > 0 ? pluginIds : undefined;
|
||||
}
|
||||
|
||||
const pluginIds = [
|
||||
...resolveProviderOwners({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
providerId: normalizedProvider,
|
||||
includeDisabled: true,
|
||||
}),
|
||||
...resolvePluginContributionOwners({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
contribution: "cliBackends",
|
||||
matches: (backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
includeDisabled: true,
|
||||
}),
|
||||
];
|
||||
|
||||
const deduped = dedupeSortedPluginIds(pluginIds);
|
||||
return deduped.length > 0 ? deduped : undefined;
|
||||
}
|
||||
|
||||
export function resolveOwningPluginIdsForModelRef(params: {
|
||||
@@ -456,17 +540,16 @@ export function resolveNonBundledProviderPluginIds(params: {
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
const registry = loadProviderManifestRegistry(params);
|
||||
const registry = loadProviderRegistrySnapshot(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
return listManifestPluginIds(
|
||||
return listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.origin !== "bundled" &&
|
||||
plugin.providers.length > 0 &&
|
||||
resolveEffectivePluginActivationState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
recordHasProviderSurface(plugin) &&
|
||||
resolveEffectiveRegistryPluginActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
}).activated,
|
||||
);
|
||||
@@ -477,18 +560,16 @@ export function resolveCatalogHookProviderPluginIds(params: {
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
}): string[] {
|
||||
const registry = loadProviderManifestRegistry(params);
|
||||
const registry = loadProviderRegistrySnapshot(params);
|
||||
const normalizedConfig = normalizePluginsConfig(params.config?.plugins);
|
||||
const enabledProviderPluginIds = listManifestPluginIds(
|
||||
const enabledProviderPluginIds = listRegistryPluginIds(
|
||||
registry,
|
||||
(plugin) =>
|
||||
plugin.providers.length > 0 &&
|
||||
resolveEffectivePluginActivationState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
config: normalizedConfig,
|
||||
recordHasProviderSurface(plugin) &&
|
||||
resolveEffectiveRegistryPluginActivation({
|
||||
plugin,
|
||||
normalizedConfig,
|
||||
rootConfig: params.config,
|
||||
enabledByDefault: plugin.enabledByDefault,
|
||||
}).activated,
|
||||
);
|
||||
const bundledCompatPluginIds = resolveBundledProviderCompatPluginIds(params);
|
||||
|
||||
Reference in New Issue
Block a user