mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 13:28:36 +00:00
perf(plugins,gateway): thread metadata snapshot + discovery through hot paths + plugin owner fixes (#84649)
* perf(plugins): thread metadata snapshot and discovery through hot paths With the snapshot memo now actually hitting, route the snapshot's manifestRegistry and discovery through the helper chains that already had fast paths for them. Eliminates redundant per-call rebuilds at two big amplifiers. - Provider resolve paths (resolvePluginProviders / isPluginProvidersLoadInFlight / resolveOwningPluginIdsForProvider / resolveExternalAuthProfilesWithPlugins) self-service a snapshot once at the public entry, then thread it as a separate required arg through resolvePluginProviderLoadBase, resolveExplicitProviderOwnerPluginIds, and the setup/runtime load state helpers. Inner reads change from 'params.pluginMetadataSnapshot?.x' to 'snapshot.x', no more enrichedParams clone. loadPluginManifestRegistryForInstalledIndex fires drop ~685 -> ~10 per cold start. - Bundled-channel / auto-enable chain accepts an optional PluginDiscoveryResult. discoverOpenClawPlugins is fired once during snapshot building (resolveInstalledPluginIndexRegistry already produced it internally; now bubbled up through loadInstalledPluginIndexWithDiscovery, PluginRegistrySnapshotResult, and onto PluginMetadataSnapshot.discovery). load-context reads metadataSnapshot.discovery and passes it through applyPluginAutoEnable, so the bundled-channel cascade (collectConfiguredChannelIds, listBundledChannelIdsWith*, listPotentialConfiguredChannelPresenceSignals) short-circuits instead of each leaf re-firing discovery. Persisted-cache path is unchanged: no discovery on the snapshot, downstream chain handles its own fallback (pre-PR behavior on that path). * test(plugins): isolate snapshot memo across tests that mock manifest registry The snapshot memo is now process-scoped and effective (~98% hit rate). Three test files were depending on cache misses (because the broken cache returned them) — each test would set up its own loadPluginManifestRegistry mock and expect a fresh derive. With the cache fixed, an earlier test's mocked registry now leaks into later tests in the same file. - io.write-config.test.ts: afterEach now clears the snapshot memo so the 'demo' plugin mocked in the first test does not survive into 'keeps shipped plugin install config records when index migration fails', which expects an empty registry to surface the 'plugin not found: demo' warning. - gateway/model-pricing-cache.ts: resetGatewayModelPricingCacheForTest also clears the memo. Tests in model-pricing-cache.test.ts assert loadPluginManifestRegistryForInstalledIndex was called; the memo hit otherwise skips the call. - providers.test.ts: vi.doMock loadPluginMetadataSnapshot to wrap the existing loadPluginManifestRegistryMock fixture. The plumbing commit added an auto-fetch fall-through in resolveOwningPluginIdsForProvider; without the mock, providers tests hit real disk reads and return empty registries (which is what surfaced as 9 unrelated-looking failures in the prior CI run). * fix(plugins): preserve setup.cliBackends owner matching in provider scan resolveOwningPluginIdsForProvider now also checks plugin.setup?.cliBackends. The pre-PR no-registry fallback used resolvePluginContributionOwners which includes both top-level cliBackends and setup.cliBackends; the PR's manifest scan replacement was missing the setup case. * fix(plugins): inherit active registry workspaceDir before loading metadata snapshot isPluginProvidersLoadInFlight and resolvePluginProviders now resolve env and workspaceDir once at the entry point (falling back to getActivePluginRegistryWorkspaceDir) and pass them into both loadPluginMetadataSnapshot and resolvePluginProviderLoadBase. Pre-fix the snapshot used params.workspaceDir raw while the load base inherited the active workspace, so workspace-scoped provider plugins could be absent from the snapshot manifest registry even though owner resolution expected them. Regression test asserts the snapshot mock receives the active workspaceDir when the caller omits it. * perf(gateway): thread discovery into applyPluginAutoEnable call sites Every gateway applyPluginAutoEnable call now passes the snapshot's PluginDiscoveryResult so the bundled-channel cascade (collectConfiguredChannelIds → listBundledChannelIdsWith* → listPotentialConfiguredChannelPresenceSignals) short-circuits instead of each leaf re-firing discovery. Startup-time sites pull discovery from the snapshot/lookup-table they already hold: - server-plugin-bootstrap.ts (pluginLookUpTable) - server-startup-plugins.ts (pluginMetadataSnapshot) - server-startup-config.ts (pluginMetadataSnapshot) - server-plugins.ts (pluginLookUpTable, both call sites) Per-RPC sites (server.impl getRuntimeConfig callback, server-methods/channels status + start handlers, server-methods/send) source discovery via getCurrentPluginMetadataSnapshot using the runtime config to validate compatibility. Falls through to the original slow path when the snapshot is absent or incompatible.
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { hasNonEmptyString } from "../infra/outbound/channel-target.js";
|
||||
import type { PluginDiscoveryResult } from "../plugins/discovery.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { listBundledChannelIds } from "./plugins/bundled-ids.js";
|
||||
@@ -15,6 +16,7 @@ const IGNORED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]);
|
||||
|
||||
type ChannelPresenceOptions = {
|
||||
channelIds?: readonly string[];
|
||||
discovery?: PluginDiscoveryResult;
|
||||
includePersistedAuthState?: boolean;
|
||||
persistedAuthStateProbe?: {
|
||||
listChannelIds: () => readonly string[];
|
||||
@@ -71,6 +73,9 @@ function listPersistedAuthStateChannelIds(options: ChannelPresenceOptions): read
|
||||
if (override) {
|
||||
return override;
|
||||
}
|
||||
if (options.discovery) {
|
||||
return listBundledChannelIdsWithPersistedAuthState(options.discovery);
|
||||
}
|
||||
if (persistedAuthStateChannelIds) {
|
||||
return persistedAuthStateChannelIds;
|
||||
}
|
||||
@@ -88,7 +93,12 @@ function hasPersistedAuthState(params: {
|
||||
if (override) {
|
||||
return override.hasState(params);
|
||||
}
|
||||
return hasBundledChannelPersistedAuthState(params);
|
||||
return hasBundledChannelPersistedAuthState({
|
||||
channelId: params.channelId,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
discovery: params.options.discovery,
|
||||
});
|
||||
}
|
||||
|
||||
export function listPotentialConfiguredChannelIds(
|
||||
@@ -121,7 +131,7 @@ export function listPotentialConfiguredChannelPresenceSignals(
|
||||
signals.push({ channelId, source });
|
||||
};
|
||||
const configuredChannelIds = new Set<string>();
|
||||
const channelIds = options.channelIds ?? listBundledChannelIds(env);
|
||||
const channelIds = options.channelIds ?? listBundledChannelIds(env, options.discovery);
|
||||
const channelEnvPrefixes = listChannelEnvPrefixes(channelIds);
|
||||
const channels = isRecord(cfg.channels) ? cfg.channels : null;
|
||||
if (channels) {
|
||||
@@ -165,7 +175,7 @@ function hasEnvConfiguredChannel(
|
||||
env: NodeJS.ProcessEnv,
|
||||
options: ChannelPresenceOptions = {},
|
||||
): boolean {
|
||||
const channelIds = options.channelIds ?? listBundledChannelIds(env);
|
||||
const channelIds = options.channelIds ?? listBundledChannelIds(env, options.discovery);
|
||||
const channelEnvPrefixes = listChannelEnvPrefixes(channelIds);
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (!hasNonEmptyString(value)) {
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { listChannelCatalogEntries } from "../../plugins/channel-catalog-registry.js";
|
||||
import type { PluginDiscoveryResult } from "../../plugins/discovery.js";
|
||||
import { resolveBundledChannelRootScope } from "./bundled-root.js";
|
||||
|
||||
export function listBundledChannelPluginIdsForRoot(
|
||||
_packageRoot: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listChannelCatalogEntries({ origin: "bundled", env })
|
||||
return listChannelCatalogEntries({
|
||||
origin: "bundled",
|
||||
env,
|
||||
discovery,
|
||||
})
|
||||
.map((entry) => entry.pluginId)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
@@ -13,17 +19,32 @@ export function listBundledChannelPluginIdsForRoot(
|
||||
export function listBundledChannelIdsForRoot(
|
||||
_packageRoot: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listChannelCatalogEntries({ origin: "bundled", env })
|
||||
return listChannelCatalogEntries({
|
||||
origin: "bundled",
|
||||
env,
|
||||
discovery,
|
||||
})
|
||||
.map((entry) => entry.channel.id)
|
||||
.filter((channelId): channelId is string => Boolean(channelId))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function listBundledChannelPluginIds(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
return listBundledChannelPluginIdsForRoot(resolveBundledChannelRootScope(env).cacheKey, env);
|
||||
export function listBundledChannelPluginIds(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listBundledChannelPluginIdsForRoot(
|
||||
resolveBundledChannelRootScope(env).cacheKey,
|
||||
env,
|
||||
discovery,
|
||||
);
|
||||
}
|
||||
|
||||
export function listBundledChannelIds(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
return listBundledChannelIdsForRoot(resolveBundledChannelRootScope(env).cacheKey, env);
|
||||
export function listBundledChannelIds(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listBundledChannelIdsForRoot(resolveBundledChannelRootScope(env).cacheKey, env, discovery);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { PluginDiscoveryResult } from "../../plugins/discovery.js";
|
||||
import {
|
||||
hasBundledChannelPackageState,
|
||||
listBundledChannelIdsForPackageState,
|
||||
} from "./package-state-probes.js";
|
||||
|
||||
export function listBundledChannelIdsWithConfiguredState(): string[] {
|
||||
return listBundledChannelIdsForPackageState("configuredState");
|
||||
export function listBundledChannelIdsWithConfiguredState(
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listBundledChannelIdsForPackageState("configuredState", discovery);
|
||||
}
|
||||
|
||||
export function hasBundledChannelConfiguredState(params: {
|
||||
channelId: string;
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): boolean {
|
||||
return hasBundledChannelPackageState({
|
||||
metadataKey: "configuredState",
|
||||
channelId: params.channelId,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
listChannelCatalogEntries,
|
||||
type PluginChannelCatalogEntry,
|
||||
} from "../../plugins/channel-catalog-registry.js";
|
||||
import type { PluginDiscoveryResult } from "../../plugins/discovery.js";
|
||||
import {
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
@@ -174,10 +175,12 @@ function resolveChannelPackageStateMetadata(
|
||||
|
||||
function listChannelPackageStateCatalog(
|
||||
metadataKey: ChannelPackageStateMetadataKey,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): PluginChannelCatalogEntry[] {
|
||||
return listChannelCatalogEntries({ origin: "bundled" }).filter((entry) =>
|
||||
Boolean(resolveChannelPackageStateMetadata(entry, metadataKey)),
|
||||
);
|
||||
return listChannelCatalogEntries({
|
||||
origin: "bundled",
|
||||
discovery,
|
||||
}).filter((entry) => Boolean(resolveChannelPackageStateMetadata(entry, metadataKey)));
|
||||
}
|
||||
|
||||
function resolveChannelPackageStateChecker(params: {
|
||||
@@ -235,8 +238,9 @@ function resolvePackageStateChannelId(entry: PluginChannelCatalogEntry): string
|
||||
|
||||
export function listBundledChannelIdsForPackageState(
|
||||
metadataKey: ChannelPackageStateMetadataKey,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listChannelPackageStateCatalog(metadataKey)
|
||||
return listChannelPackageStateCatalog(metadataKey, discovery)
|
||||
.map((entry) => resolvePackageStateChannelId(entry))
|
||||
.filter((channelId): channelId is string => Boolean(channelId))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
@@ -247,9 +251,10 @@ export function hasBundledChannelPackageState(params: {
|
||||
channelId: string;
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): boolean {
|
||||
const requestedChannelId = normalizeOptionalString(params.channelId);
|
||||
const entry = listChannelPackageStateCatalog(params.metadataKey).find(
|
||||
const entry = listChannelPackageStateCatalog(params.metadataKey, params.discovery).find(
|
||||
(candidate) => resolvePackageStateChannelId(candidate) === requestedChannelId,
|
||||
);
|
||||
if (!entry) {
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { PluginDiscoveryResult } from "../../plugins/discovery.js";
|
||||
import {
|
||||
hasBundledChannelPackageState,
|
||||
listBundledChannelIdsForPackageState,
|
||||
} from "./package-state-probes.js";
|
||||
|
||||
export function listBundledChannelIdsWithPersistedAuthState(): string[] {
|
||||
return listBundledChannelIdsForPackageState("persistedAuthState");
|
||||
export function listBundledChannelIdsWithPersistedAuthState(
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
return listBundledChannelIdsForPackageState("persistedAuthState", discovery);
|
||||
}
|
||||
|
||||
export function hasBundledChannelPersistedAuthState(params: {
|
||||
channelId: string;
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): boolean {
|
||||
return hasBundledChannelPackageState({
|
||||
metadataKey: "persistedAuthState",
|
||||
channelId: params.channelId,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PluginDiscoveryResult } from "../plugins/discovery.js";
|
||||
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import { detectPluginAutoEnableCandidates } from "./plugin-auto-enable.detect.js";
|
||||
import {
|
||||
@@ -44,6 +45,7 @@ export function applyPluginAutoEnable(params: {
|
||||
config?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): PluginAutoEnableResult {
|
||||
const candidates = detectPluginAutoEnableCandidates(params);
|
||||
return materializePluginAutoEnableCandidates({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PluginDiscoveryResult } from "../plugins/discovery.js";
|
||||
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import {
|
||||
resolveConfiguredPluginAutoEnableCandidates,
|
||||
@@ -11,10 +12,11 @@ export function detectPluginAutoEnableCandidates(params: {
|
||||
config?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): PluginAutoEnableCandidate[] {
|
||||
const env = params.env ?? process.env;
|
||||
const config = params.config ?? ({} as OpenClawConfig);
|
||||
const readiness = resolvePluginAutoEnableReadiness(config, env);
|
||||
const readiness = resolvePluginAutoEnableReadiness(config, env, params.discovery);
|
||||
if (!readiness.mayNeedAutoEnable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { getChatChannelMeta, normalizeChatChannelId } from "../channels/registry.js";
|
||||
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import type { PluginDiscoveryResult } from "../plugins/discovery.js";
|
||||
import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js";
|
||||
import {
|
||||
type PluginManifestRecord,
|
||||
@@ -265,10 +266,15 @@ function collectPluginIdsForConfiguredChannel(
|
||||
return [claims[0]?.plugin.id ?? builtInId ?? normalizedChannelId];
|
||||
}
|
||||
|
||||
function collectConfiguredChannelIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): string[] {
|
||||
const configuredStateChannelIds = new Set(listBundledChannelIdsWithConfiguredState());
|
||||
function collectConfiguredChannelIds(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): string[] {
|
||||
const configuredStateChannelIds = new Set(listBundledChannelIdsWithConfiguredState(discovery));
|
||||
return listPotentialConfiguredChannelPresenceSignals(cfg, env, {
|
||||
includePersistedAuthState: false,
|
||||
discovery,
|
||||
})
|
||||
.map((signal) => ({
|
||||
source: signal.source,
|
||||
@@ -281,6 +287,7 @@ function collectConfiguredChannelIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv
|
||||
channelId,
|
||||
source,
|
||||
configuredStateChannelIds,
|
||||
discovery,
|
||||
}),
|
||||
)
|
||||
.map(({ channelId }) => channelId);
|
||||
@@ -292,6 +299,7 @@ function isAutoEnableConfiguredChannelSignal(params: {
|
||||
channelId: string;
|
||||
source: ChannelPresenceSignalSource;
|
||||
configuredStateChannelIds: ReadonlySet<string>;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): boolean {
|
||||
if (
|
||||
params.source === "env" &&
|
||||
@@ -300,6 +308,7 @@ function isAutoEnableConfiguredChannelSignal(params: {
|
||||
channelId: params.channelId,
|
||||
cfg: params.cfg,
|
||||
env: params.env,
|
||||
discovery: params.discovery,
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
@@ -535,6 +544,7 @@ export function configMayNeedPluginAutoEnable(
|
||||
export function resolvePluginAutoEnableReadiness(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
discovery?: PluginDiscoveryResult,
|
||||
): { mayNeedAutoEnable: boolean; configuredChannelIds: string[] } {
|
||||
if (arePluginsGloballyDisabled(cfg)) {
|
||||
return { mayNeedAutoEnable: false, configuredChannelIds: [] };
|
||||
@@ -545,7 +555,7 @@ export function resolvePluginAutoEnableReadiness(
|
||||
if (hasConfiguredPluginConfigEntry(cfg)) {
|
||||
return { mayNeedAutoEnable: true, configuredChannelIds: [] };
|
||||
}
|
||||
const configuredChannelIds = collectConfiguredChannelIds(cfg, env);
|
||||
const configuredChannelIds = collectConfiguredChannelIds(cfg, env, discovery);
|
||||
if (configuredChannelIds.length > 0) {
|
||||
return { mayNeedAutoEnable: true, configuredChannelIds };
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ import type {
|
||||
PluginManifestModelPricingProvider,
|
||||
PluginManifestModelPricingSource,
|
||||
} from "../plugins/manifest.js";
|
||||
import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import {
|
||||
clearLoadPluginMetadataSnapshotMemo,
|
||||
resolvePluginMetadataSnapshot,
|
||||
} from "../plugins/plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
||||
import { normalizeOptionalString, resolvePrimaryStringValue } from "../shared/string-coerce.js";
|
||||
@@ -1396,6 +1399,7 @@ export function startGatewayModelPricingRefresh(
|
||||
|
||||
export function resetGatewayModelPricingCacheForTest(): void {
|
||||
clearGatewayModelPricingCacheState();
|
||||
clearLoadPluginMetadataSnapshotMemo();
|
||||
clearRefreshTimer();
|
||||
inFlightRefresh = null;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { getChannelActivity } from "../../infra/channel-activity.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
@@ -301,9 +302,16 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
const rawChannel = (params as { channel?: unknown }).channel;
|
||||
const requestedChannel =
|
||||
typeof rawChannel === "string" ? normalizeChannelId(rawChannel) : undefined;
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: context.getRuntimeConfig(),
|
||||
const runtimeConfig = context.getRuntimeConfig();
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
manifestRegistry: currentSnapshot?.manifestRegistry,
|
||||
discovery: currentSnapshot?.discovery,
|
||||
}).config;
|
||||
const runtime = context.getRuntimeSnapshot();
|
||||
const plugins = listChannelPlugins();
|
||||
@@ -579,9 +587,16 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: context.getRuntimeConfig(),
|
||||
const runtimeConfig = context.getRuntimeConfig();
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
manifestRegistry: currentSnapshot?.manifestRegistry,
|
||||
discovery: currentSnapshot?.discovery,
|
||||
}).config;
|
||||
const payload = await startChannelAccount({
|
||||
channelId,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { maybeResolveIdLikeTarget } from "../../infra/outbound/target-resolver.j
|
||||
import { resolveOutboundTarget } from "../../infra/outbound/targets.js";
|
||||
import { extractToolPayload } from "../../infra/outbound/tool-payload.js";
|
||||
import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { normalizePollInput } from "../../polls.js";
|
||||
import { parseThreadSessionSuffix } from "../../sessions/session-key-utils.js";
|
||||
import {
|
||||
@@ -131,9 +132,16 @@ async function resolveRequestedChannel(params: {
|
||||
error: errorShape(ErrorCodes.INVALID_REQUEST, params.unsupportedMessage(channelInput)),
|
||||
};
|
||||
}
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: params.context.getRuntimeConfig(),
|
||||
const runtimeConfig = params.context.getRuntimeConfig();
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
const cfg = applyPluginAutoEnable({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
manifestRegistry: currentSnapshot?.manifestRegistry,
|
||||
discovery: currentSnapshot?.discovery,
|
||||
}).config;
|
||||
let channel = normalizedChannel;
|
||||
if (!channel) {
|
||||
|
||||
@@ -83,6 +83,7 @@ export function prepareGatewayPluginLoad(params: GatewayPluginBootstrapParams) {
|
||||
...(params.pluginLookUpTable?.manifestRegistry
|
||||
? { manifestRegistry: params.pluginLookUpTable.manifestRegistry }
|
||||
: {}),
|
||||
discovery: params.pluginLookUpTable?.discovery,
|
||||
});
|
||||
const resolvedConfig =
|
||||
activationSourceConfig === params.cfg
|
||||
|
||||
@@ -636,6 +636,7 @@ export function loadGatewayPlugins(params: {
|
||||
...(params.pluginLookUpTable?.manifestRegistry
|
||||
? { manifestRegistry: params.pluginLookUpTable.manifestRegistry }
|
||||
: {}),
|
||||
discovery: params.pluginLookUpTable?.discovery,
|
||||
})
|
||||
: undefined;
|
||||
const autoEnableMs = performance.now() - started;
|
||||
@@ -659,6 +660,7 @@ export function loadGatewayPlugins(params: {
|
||||
...(params.pluginLookUpTable?.manifestRegistry
|
||||
? { manifestRegistry: params.pluginLookUpTable.manifestRegistry }
|
||||
: {}),
|
||||
discovery: params.pluginLookUpTable?.discovery,
|
||||
});
|
||||
const resolvedConfigMs = performance.now() - started;
|
||||
const resolvedConfig = autoEnabled.config;
|
||||
|
||||
@@ -125,6 +125,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
...(pluginMetadataSnapshot?.manifestRegistry
|
||||
? { manifestRegistry: pluginMetadataSnapshot.manifestRegistry }
|
||||
: {}),
|
||||
discovery: pluginMetadataSnapshot?.discovery,
|
||||
}),
|
||||
);
|
||||
if (autoEnable.changes.length === 0) {
|
||||
|
||||
@@ -87,6 +87,7 @@ export async function prepareGatewayPluginBootstrap(params: {
|
||||
...(params.pluginMetadataSnapshot?.manifestRegistry
|
||||
? { manifestRegistry: params.pluginMetadataSnapshot.manifestRegistry }
|
||||
: {}),
|
||||
discovery: params.pluginMetadataSnapshot?.discovery,
|
||||
}).config,
|
||||
});
|
||||
const pluginsGloballyDisabled = gatewayPluginConfig.plugins?.enabled === false;
|
||||
|
||||
@@ -41,6 +41,7 @@ import { startDiagnosticHeartbeat, stopDiagnosticHeartbeat } from "../logging/di
|
||||
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
|
||||
import {
|
||||
clearCurrentPluginMetadataSnapshot,
|
||||
getCurrentPluginMetadataSnapshot,
|
||||
setCurrentPluginMetadataSnapshot,
|
||||
} from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import type { PluginHookGatewayCronService } from "../plugins/hook-types.js";
|
||||
@@ -836,11 +837,19 @@ export async function startGatewayServer(
|
||||
});
|
||||
const { createChannelManager } = await import("./server-channels.js");
|
||||
const channelManager = createChannelManager({
|
||||
getRuntimeConfig: () =>
|
||||
applyPluginAutoEnable({
|
||||
config: getRuntimeConfig(),
|
||||
getRuntimeConfig: () => {
|
||||
const runtimeConfig = getRuntimeConfig();
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
}).config,
|
||||
});
|
||||
return applyPluginAutoEnable({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
manifestRegistry: currentSnapshot?.manifestRegistry,
|
||||
discovery: currentSnapshot?.discovery,
|
||||
}).config;
|
||||
},
|
||||
channelLogs,
|
||||
channelRuntimeEnvs,
|
||||
resolveChannelRuntime: getChannelRuntime,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type PluginActivationConfigSource,
|
||||
} from "./config-state.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
|
||||
import type { PluginDiscoveryResult } from "./discovery.js";
|
||||
|
||||
export type PluginActivationCompatConfig = {
|
||||
allowlistPluginIds?: readonly string[];
|
||||
@@ -68,6 +69,7 @@ type BundledPluginCompatibleActivationParams = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
onlyPluginIds?: readonly string[];
|
||||
}) => string[];
|
||||
discovery?: PluginDiscoveryResult;
|
||||
};
|
||||
|
||||
export function withActivatedPluginIds(params: {
|
||||
@@ -175,6 +177,7 @@ function applyPluginAutoEnableForActivation(params: {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}) {
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
@@ -196,7 +199,8 @@ function applyPluginAutoEnableForActivation(params: {
|
||||
return applyPluginAutoEnable({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
...(currentManifestRegistry ? { manifestRegistry: currentManifestRegistry } : {}),
|
||||
manifestRegistry: currentManifestRegistry,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,6 +211,7 @@ export function resolvePluginActivationSnapshot(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
applyAutoEnable?: boolean;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): PluginActivationSnapshot {
|
||||
const env = params.env ?? process.env;
|
||||
const rawConfig = params.rawConfig ?? params.resolvedConfig;
|
||||
@@ -218,6 +223,7 @@ export function resolvePluginActivationSnapshot(params: {
|
||||
config: rawConfig,
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
resolvedConfig = autoEnabled.config;
|
||||
autoEnabledReasons = autoEnabled.autoEnabledReasons;
|
||||
@@ -243,6 +249,7 @@ export function resolvePluginActivationInputs(params: {
|
||||
workspaceDir?: string;
|
||||
compat?: PluginActivationCompatConfig;
|
||||
applyAutoEnable?: boolean;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
}): PluginActivationInputs {
|
||||
const env = params.env ?? process.env;
|
||||
const snapshot = resolvePluginActivationSnapshot({
|
||||
@@ -252,6 +259,7 @@ export function resolvePluginActivationInputs(params: {
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
const config = applyPluginCompatibilityOverrides({
|
||||
config: snapshot.config,
|
||||
@@ -279,6 +287,7 @@ export function resolveBundledPluginCompatibleActivationInputs(
|
||||
env: params.env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
const allowlistCompatEnabled = params.compatMode.allowlist === true;
|
||||
const shouldResolveCompatPluginIds = shouldResolveBundledCompatPluginIds({
|
||||
@@ -304,6 +313,7 @@ export function resolveBundledPluginCompatibleActivationInputs(
|
||||
allowlistCompatEnabled,
|
||||
compatPluginIds,
|
||||
}),
|
||||
discovery: params.discovery,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -325,6 +335,7 @@ export function resolveBundledPluginCompatibleLoadValues(
|
||||
config: rawConfig,
|
||||
env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
discovery: params.discovery,
|
||||
});
|
||||
resolvedConfig = autoEnabled.config;
|
||||
autoEnabledReasons = autoEnabled.autoEnabledReasons;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { normalizePluginsConfig } from "./config-state.js";
|
||||
import { discoverOpenClawPlugins, type PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
discoverOpenClawPlugins,
|
||||
type PluginCandidate,
|
||||
type PluginDiscoveryResult,
|
||||
} from "./discovery.js";
|
||||
import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader.js";
|
||||
import type { LoadInstalledPluginIndexParams } from "./installed-plugin-index-types.js";
|
||||
import { loadPluginManifestRegistry, type PluginManifestRegistry } from "./manifest-registry.js";
|
||||
@@ -7,6 +11,7 @@ import { loadPluginManifestRegistry, type PluginManifestRegistry } from "./manif
|
||||
export function resolveInstalledPluginIndexRegistry(params: LoadInstalledPluginIndexParams): {
|
||||
registry: PluginManifestRegistry;
|
||||
candidates: readonly PluginCandidate[];
|
||||
discovery?: PluginDiscoveryResult;
|
||||
} {
|
||||
if (params.candidates) {
|
||||
return {
|
||||
@@ -35,6 +40,7 @@ export function resolveInstalledPluginIndexRegistry(params: LoadInstalledPluginI
|
||||
});
|
||||
return {
|
||||
candidates: discovery.candidates,
|
||||
discovery,
|
||||
registry: loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveCompatibilityHostVersion } from "../version.js";
|
||||
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import { isPluginEnabledByDefaultForPlatform } from "./default-enablement.js";
|
||||
import type { PluginDiscoveryResult } from "./discovery.js";
|
||||
import { normalizeInstallRecordMap } from "./installed-plugin-index-install-records.js";
|
||||
import {
|
||||
resolveCompatRegistryVersion,
|
||||
@@ -42,9 +43,9 @@ export { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-
|
||||
|
||||
function buildInstalledPluginIndex(
|
||||
params: LoadInstalledPluginIndexParams & { refreshReason?: InstalledPluginIndexRefreshReason },
|
||||
): InstalledPluginIndex {
|
||||
): { index: InstalledPluginIndex; discovery: PluginDiscoveryResult | undefined } {
|
||||
const env = params.env ?? process.env;
|
||||
const { candidates, registry } = resolveInstalledPluginIndexRegistry(params);
|
||||
const { candidates, registry, discovery } = resolveInstalledPluginIndexRegistry(params);
|
||||
const registryDiagnostics = registry.diagnostics ?? [];
|
||||
const diagnostics = [...registryDiagnostics];
|
||||
const generatedAtMs = (params.now?.() ?? new Date()).getTime();
|
||||
@@ -65,30 +66,39 @@ function buildInstalledPluginIndex(
|
||||
});
|
||||
|
||||
return {
|
||||
version: INSTALLED_PLUGIN_INDEX_VERSION,
|
||||
warning: INSTALLED_PLUGIN_INDEX_WARNING,
|
||||
hostContractVersion: resolveCompatibilityHostVersion(env),
|
||||
compatRegistryVersion: resolveCompatRegistryVersion(),
|
||||
migrationVersion: INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION,
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
|
||||
generatedAtMs,
|
||||
...(params.refreshReason ? { refreshReason: params.refreshReason } : {}),
|
||||
installRecords,
|
||||
plugins,
|
||||
diagnostics,
|
||||
index: {
|
||||
version: INSTALLED_PLUGIN_INDEX_VERSION,
|
||||
warning: INSTALLED_PLUGIN_INDEX_WARNING,
|
||||
hostContractVersion: resolveCompatibilityHostVersion(env),
|
||||
compatRegistryVersion: resolveCompatRegistryVersion(),
|
||||
migrationVersion: INSTALLED_PLUGIN_INDEX_MIGRATION_VERSION,
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
|
||||
generatedAtMs,
|
||||
...(params.refreshReason ? { refreshReason: params.refreshReason } : {}),
|
||||
installRecords,
|
||||
plugins,
|
||||
diagnostics,
|
||||
},
|
||||
discovery,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadInstalledPluginIndex(
|
||||
params: LoadInstalledPluginIndexParams = {},
|
||||
): InstalledPluginIndex {
|
||||
return buildInstalledPluginIndex(params).index;
|
||||
}
|
||||
|
||||
export function loadInstalledPluginIndexWithDiscovery(
|
||||
params: LoadInstalledPluginIndexParams = {},
|
||||
): { index: InstalledPluginIndex; discovery: PluginDiscoveryResult | undefined } {
|
||||
return buildInstalledPluginIndex(params);
|
||||
}
|
||||
|
||||
export function refreshInstalledPluginIndex(
|
||||
params: RefreshInstalledPluginIndexParams,
|
||||
): InstalledPluginIndex {
|
||||
return buildInstalledPluginIndex({ ...params, refreshReason: params.reason });
|
||||
return buildInstalledPluginIndex({ ...params, refreshReason: params.reason }).index;
|
||||
}
|
||||
|
||||
export function listInstalledPluginRecords(
|
||||
|
||||
@@ -681,6 +681,7 @@ function loadPluginMetadataSnapshotImpl(params: LoadPluginMetadataSnapshotParams
|
||||
indexPluginCount: index.plugins.length,
|
||||
manifestPluginCount: manifestRegistry.plugins.length,
|
||||
},
|
||||
discovery: registryResult.discovery,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginDiscoveryResult } from "./discovery.js";
|
||||
import type { InstalledPluginIndex } from "./installed-plugin-index-types.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginDiagnostic } from "./manifest-types.js";
|
||||
@@ -48,6 +49,7 @@ export type PluginMetadataSnapshot = {
|
||||
normalizePluginId: (pluginId: string) => string;
|
||||
owners: PluginMetadataSnapshotOwnerMaps;
|
||||
metrics: PluginMetadataSnapshotMetrics;
|
||||
discovery?: PluginDiscoveryResult;
|
||||
};
|
||||
|
||||
export type PluginMetadataRegistryView = Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
|
||||
import type { PluginDiscoveryResult } from "./discovery.js";
|
||||
import { fileSignatureMatches } from "./installed-plugin-index-hash.js";
|
||||
import { hasOptionalMissingPluginManifestFile } from "./installed-plugin-index-manifest.js";
|
||||
import { loadInstalledPluginIndexInstallRecordsSync } from "./installed-plugin-index-record-reader.js";
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
extractPluginInstallRecordsFromInstalledPluginIndex,
|
||||
isInstalledPluginEnabled,
|
||||
listInstalledPluginRecords,
|
||||
loadInstalledPluginIndex,
|
||||
loadInstalledPluginIndexWithDiscovery,
|
||||
resolveInstalledPluginIndexPolicyHash,
|
||||
type InstalledPluginIndex,
|
||||
type InstalledPluginIndexRecord,
|
||||
@@ -48,6 +49,7 @@ export type PluginRegistrySnapshotResult = {
|
||||
snapshot: PluginRegistrySnapshot;
|
||||
source: PluginRegistrySnapshotSource;
|
||||
diagnostics: readonly PluginRegistrySnapshotDiagnostic[];
|
||||
discovery?: PluginDiscoveryResult;
|
||||
};
|
||||
|
||||
export const DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV = "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY";
|
||||
@@ -347,11 +349,12 @@ export function loadPluginRegistrySnapshotWithMetadata(
|
||||
"Persisted plugin registry is missing recoverable managed npm plugins; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry.",
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
const persistedResult: PluginRegistrySnapshotResult = {
|
||||
snapshot: persistedIndex,
|
||||
source: "persisted",
|
||||
diagnostics,
|
||||
};
|
||||
return persistedResult;
|
||||
}
|
||||
} else if (persistedReadsEnabled) {
|
||||
diagnostics.push({
|
||||
@@ -370,15 +373,17 @@ export function loadPluginRegistrySnapshotWithMetadata(
|
||||
});
|
||||
}
|
||||
|
||||
const derived = loadInstalledPluginIndexWithDiscovery({
|
||||
...params,
|
||||
installRecords: persistedInstallRecordReadsEnabled
|
||||
? params.installRecords
|
||||
: (params.installRecords ?? {}),
|
||||
});
|
||||
return {
|
||||
snapshot: loadInstalledPluginIndex({
|
||||
...params,
|
||||
...(persistedInstallRecordReadsEnabled
|
||||
? {}
|
||||
: { installRecords: params.installRecords ?? {} }),
|
||||
}),
|
||||
snapshot: derived.index,
|
||||
source: "derived",
|
||||
diagnostics,
|
||||
discovery: derived.discovery,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import { sanitizeForLog } from "../terminal/ansi.js";
|
||||
import { normalizeProviderModelIdWithManifest } from "./manifest-model-id-normalization.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import { resolvePluginDiscoveryProvidersRuntime } from "./provider-discovery.runtime.js";
|
||||
import {
|
||||
prepareProviderExtraParams,
|
||||
@@ -925,10 +926,16 @@ export function resolveExternalAuthProfilesWithPlugins(params: {
|
||||
}): ProviderExternalAuthProfile[] {
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState();
|
||||
const env = params.env ?? process.env;
|
||||
const { manifestRegistry } = loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
const externalAuthPluginIds = resolveExternalAuthProfileProviderPluginIds({
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
manifestRegistry,
|
||||
});
|
||||
const declaredPluginIds = new Set(externalAuthPluginIds);
|
||||
const fallbackPluginIds = resolveExternalAuthProfileCompatFallbackPluginIds({
|
||||
@@ -936,6 +943,7 @@ export function resolveExternalAuthProfilesWithPlugins(params: {
|
||||
workspaceDir,
|
||||
env,
|
||||
declaredPluginIds,
|
||||
manifestRegistry,
|
||||
});
|
||||
const pluginIds = [...new Set([...externalAuthPluginIds, ...fallbackPluginIds])].toSorted(
|
||||
(left, right) => left.localeCompare(right),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
loadOpenClawPlugins,
|
||||
type PluginLoadOptions,
|
||||
} from "./loader.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
|
||||
import { hasExplicitPluginIdScope } from "./plugin-scope.js";
|
||||
import { resolveProviderConfigApiOwnerHint } from "./provider-config-owner.js";
|
||||
@@ -33,13 +34,15 @@ 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"];
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): string[] {
|
||||
function resolveExplicitProviderOwnerPluginIds(
|
||||
params: {
|
||||
providerRefs: readonly string[];
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
},
|
||||
snapshot: PluginMetadataRegistryView,
|
||||
): string[] {
|
||||
return dedupeSortedPluginIds(
|
||||
params.providerRefs.flatMap((provider) => {
|
||||
const plannedPluginIds = resolveManifestActivationPluginIds({
|
||||
@@ -50,7 +53,7 @@ function resolveExplicitProviderOwnerPluginIds(params: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRecords: params.pluginMetadataSnapshot?.manifestRegistry.plugins,
|
||||
manifestRecords: snapshot.manifestRegistry.plugins,
|
||||
});
|
||||
if (plannedPluginIds.length > 0) {
|
||||
return plannedPluginIds;
|
||||
@@ -68,7 +71,7 @@ function resolveExplicitProviderOwnerPluginIds(params: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRecords: params.pluginMetadataSnapshot?.manifestRegistry.plugins,
|
||||
manifestRecords: snapshot.manifestRegistry.plugins,
|
||||
});
|
||||
if (apiOwnerPluginIds.length > 0) {
|
||||
return apiOwnerPluginIds;
|
||||
@@ -78,7 +81,7 @@ function resolveExplicitProviderOwnerPluginIds(params: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
});
|
||||
if (legacyApiOwnerPluginIds?.length) {
|
||||
return legacyApiOwnerPluginIds;
|
||||
@@ -92,7 +95,7 @@ function resolveExplicitProviderOwnerPluginIds(params: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
}) ?? []
|
||||
);
|
||||
}),
|
||||
@@ -109,25 +112,29 @@ function mergeExplicitOwnerPluginIds(
|
||||
return dedupeSortedPluginIds([...providerPluginIds, ...explicitOwnerPluginIds]);
|
||||
}
|
||||
|
||||
function resolvePluginProviderLoadBase(params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
onlyPluginIds?: string[];
|
||||
providerRefs?: readonly string[];
|
||||
modelRefs?: readonly string[];
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}) {
|
||||
function resolvePluginProviderLoadBase(
|
||||
params: {
|
||||
config?: PluginLoadOptions["config"];
|
||||
workspaceDir?: string;
|
||||
env?: PluginLoadOptions["env"];
|
||||
onlyPluginIds?: string[];
|
||||
providerRefs?: readonly string[];
|
||||
modelRefs?: readonly string[];
|
||||
},
|
||||
snapshot: PluginMetadataRegistryView,
|
||||
) {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const providerOwnedPluginIds = params.providerRefs?.length
|
||||
? resolveExplicitProviderOwnerPluginIds({
|
||||
providerRefs: params.providerRefs,
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
pluginMetadataSnapshot: params.pluginMetadataSnapshot,
|
||||
})
|
||||
? resolveExplicitProviderOwnerPluginIds(
|
||||
{
|
||||
providerRefs: params.providerRefs,
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
},
|
||||
snapshot,
|
||||
)
|
||||
: [];
|
||||
const modelOwnedPluginIds = params.modelRefs?.length
|
||||
? resolveOwningPluginIdsForModelRefs({
|
||||
@@ -135,7 +142,7 @@ function resolvePluginProviderLoadBase(params: {
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
})
|
||||
: [];
|
||||
const requestedPluginIds =
|
||||
@@ -168,6 +175,7 @@ function resolvePluginProviderLoadBase(params: {
|
||||
function resolveSetupProviderPluginLoadState(
|
||||
params: Parameters<typeof resolvePluginProviders>[0],
|
||||
base: ReturnType<typeof resolvePluginProviderLoadBase>,
|
||||
snapshot: PluginMetadataRegistryView,
|
||||
) {
|
||||
const providerPluginIds = resolveDiscoveredProviderPluginIds({
|
||||
config: params.config,
|
||||
@@ -175,8 +183,8 @@ function resolveSetupProviderPluginLoadState(
|
||||
env: base.env,
|
||||
onlyPluginIds: base.requestedPluginIds,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
registry: params.pluginMetadataSnapshot?.index,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
registry: snapshot.index,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
});
|
||||
const explicitOwnerPluginIds = resolveDiscoverableProviderOwnerPluginIds({
|
||||
pluginIds: base.explicitOwnerPluginIds,
|
||||
@@ -184,8 +192,8 @@ function resolveSetupProviderPluginLoadState(
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
registry: params.pluginMetadataSnapshot?.index,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
registry: snapshot.index,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
});
|
||||
const setupPluginIds = mergeExplicitOwnerPluginIds(providerPluginIds, explicitOwnerPluginIds);
|
||||
if (setupPluginIds.length === 0) {
|
||||
@@ -203,9 +211,8 @@ function resolveSetupProviderPluginLoadState(
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
logger: createPluginRuntimeLoaderLogger(),
|
||||
installRecords: params.pluginMetadataSnapshot
|
||||
? extractPluginInstallRecordsFromInstalledPluginIndex(params.pluginMetadataSnapshot.index)
|
||||
: undefined,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
installRecords: extractPluginInstallRecordsFromInstalledPluginIndex(snapshot.index),
|
||||
},
|
||||
{
|
||||
onlyPluginIds: setupPluginIds,
|
||||
@@ -220,6 +227,7 @@ function resolveSetupProviderPluginLoadState(
|
||||
function resolveRuntimeProviderPluginLoadState(
|
||||
params: Parameters<typeof resolvePluginProviders>[0],
|
||||
base: ReturnType<typeof resolvePluginProviderLoadBase>,
|
||||
snapshot: PluginMetadataRegistryView,
|
||||
) {
|
||||
const explicitOwnerPluginIds = resolveActivatableProviderOwnerPluginIds({
|
||||
pluginIds: base.explicitOwnerPluginIds,
|
||||
@@ -227,8 +235,8 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
includeUntrustedWorkspacePlugins: params.includeUntrustedWorkspacePlugins,
|
||||
registry: params.pluginMetadataSnapshot?.index,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
registry: snapshot.index,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
});
|
||||
const runtimeRequestedPluginIds =
|
||||
base.requestedPluginIds !== undefined
|
||||
@@ -252,7 +260,7 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
resolveCompatPluginIds: (compatParams) =>
|
||||
resolveBundledProviderCompatPluginIds({
|
||||
...compatParams,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
}),
|
||||
});
|
||||
const config = params.bundledProviderVitestCompat
|
||||
@@ -268,8 +276,8 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
onlyPluginIds: runtimeRequestedPluginIds,
|
||||
registry: params.pluginMetadataSnapshot?.index,
|
||||
manifestRegistry: params.pluginMetadataSnapshot?.manifestRegistry,
|
||||
registry: snapshot.index,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
}),
|
||||
explicitOwnerPluginIds,
|
||||
);
|
||||
@@ -281,9 +289,8 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
workspaceDir: base.workspaceDir,
|
||||
env: base.env,
|
||||
logger: createPluginRuntimeLoaderLogger(),
|
||||
installRecords: params.pluginMetadataSnapshot
|
||||
? extractPluginInstallRecordsFromInstalledPluginIndex(params.pluginMetadataSnapshot.index)
|
||||
: undefined,
|
||||
manifestRegistry: snapshot.manifestRegistry,
|
||||
installRecords: extractPluginInstallRecordsFromInstalledPluginIndex(snapshot.index),
|
||||
},
|
||||
{
|
||||
onlyPluginIds: providerPluginIds,
|
||||
@@ -298,11 +305,20 @@ function resolveRuntimeProviderPluginLoadState(
|
||||
export function isPluginProvidersLoadInFlight(
|
||||
params: Parameters<typeof resolvePluginProviders>[0],
|
||||
): boolean {
|
||||
const base = resolvePluginProviderLoadBase(params);
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const snapshot =
|
||||
params.pluginMetadataSnapshot ??
|
||||
loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
const base = resolvePluginProviderLoadBase({ ...params, workspaceDir, env }, snapshot);
|
||||
const loadState =
|
||||
params.mode === "setup"
|
||||
? resolveSetupProviderPluginLoadState(params, base)
|
||||
: resolveRuntimeProviderPluginLoadState(params, base);
|
||||
? resolveSetupProviderPluginLoadState(params, base, snapshot)
|
||||
: resolveRuntimeProviderPluginLoadState(params, base, snapshot);
|
||||
if (!loadState) {
|
||||
return false;
|
||||
}
|
||||
@@ -327,9 +343,18 @@ export function resolvePluginProviders(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): ProviderPlugin[] {
|
||||
const base = resolvePluginProviderLoadBase(params);
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir();
|
||||
const snapshot =
|
||||
params.pluginMetadataSnapshot ??
|
||||
loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
workspaceDir,
|
||||
env,
|
||||
});
|
||||
const base = resolvePluginProviderLoadBase({ ...params, workspaceDir, env }, snapshot);
|
||||
if (params.mode === "setup") {
|
||||
const loadState = resolveSetupProviderPluginLoadState(params, base);
|
||||
const loadState = resolveSetupProviderPluginLoadState(params, base, snapshot);
|
||||
if (!loadState) {
|
||||
return [];
|
||||
}
|
||||
@@ -338,7 +363,7 @@ export function resolvePluginProviders(params: {
|
||||
Object.assign({}, entry.provider, { pluginId: entry.pluginId }),
|
||||
);
|
||||
}
|
||||
const loadState = resolveRuntimeProviderPluginLoadState(params, base);
|
||||
const loadState = resolveRuntimeProviderPluginLoadState(params, base, snapshot);
|
||||
const registry =
|
||||
loadState.loadOptions.onlyPluginIds?.length === 0
|
||||
? undefined
|
||||
|
||||
@@ -16,6 +16,8 @@ type LoadOpenClawPlugins = typeof import("./loader.js").loadOpenClawPlugins;
|
||||
type IsPluginRegistryLoadInFlight = typeof import("./loader.js").isPluginRegistryLoadInFlight;
|
||||
type LoadPluginManifestRegistry =
|
||||
typeof import("./manifest-registry.js").loadPluginManifestRegistry;
|
||||
type LoadPluginMetadataSnapshot =
|
||||
typeof import("./plugin-metadata-snapshot.js").loadPluginMetadataSnapshot;
|
||||
type ApplyPluginAutoEnable = typeof import("../config/plugin-auto-enable.js").applyPluginAutoEnable;
|
||||
type SetActivePluginRegistry = typeof import("./runtime.js").setActivePluginRegistry;
|
||||
|
||||
@@ -25,6 +27,7 @@ const resolveCompatibleRuntimePluginRegistryMock = vi.fn<ResolveCompatibleRuntim
|
||||
const loadOpenClawPluginsMock = vi.fn<LoadOpenClawPlugins>();
|
||||
const isPluginRegistryLoadInFlightMock = vi.fn<IsPluginRegistryLoadInFlight>((_) => false);
|
||||
const loadPluginManifestRegistryMock = vi.fn<LoadPluginManifestRegistry>();
|
||||
const loadPluginMetadataSnapshotMock = vi.fn<LoadPluginMetadataSnapshot>();
|
||||
const applyPluginAutoEnableMock = vi.fn<ApplyPluginAutoEnable>();
|
||||
|
||||
let resolveOwningPluginIdsForProvider: typeof import("./providers.js").resolveOwningPluginIdsForProvider;
|
||||
@@ -482,6 +485,15 @@ describe("resolvePluginProviders", () => {
|
||||
loadPluginManifestRegistry: (...args: Parameters<LoadPluginManifestRegistry>) =>
|
||||
loadPluginManifestRegistryMock(...args),
|
||||
}));
|
||||
vi.doMock("./plugin-metadata-snapshot.js", () => ({
|
||||
loadPluginMetadataSnapshot: (params: Parameters<LoadPluginMetadataSnapshot>[0]) => {
|
||||
loadPluginMetadataSnapshotMock(params);
|
||||
return {
|
||||
manifestRegistry: loadPluginManifestRegistryMock(),
|
||||
index: createProviderRegistrySnapshotFixture(),
|
||||
};
|
||||
},
|
||||
}));
|
||||
vi.doMock("./plugin-registry.js", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("./plugin-registry.js")>("./plugin-registry.js");
|
||||
@@ -521,6 +533,18 @@ describe("resolvePluginProviders", () => {
|
||||
expectOwningPluginIds("codex-cli");
|
||||
});
|
||||
|
||||
it("maps setup-only cli backend ids to owning plugin ids via manifests", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
id: "setup-only-backend-owner",
|
||||
providerIds: [],
|
||||
setup: { cliBackends: ["setup-only-cli"] },
|
||||
}),
|
||||
]);
|
||||
|
||||
expectOwningPluginIds("setup-only-cli", ["setup-only-backend-owner"]);
|
||||
});
|
||||
|
||||
it("reflects provider ownership manifest changes on the next lookup", () => {
|
||||
setManifestPlugins([
|
||||
createManifestProviderPlugin({
|
||||
@@ -548,6 +572,7 @@ describe("resolvePluginProviders", () => {
|
||||
loadOpenClawPluginsMock.mockReset();
|
||||
isPluginRegistryLoadInFlightMock.mockReset();
|
||||
isPluginRegistryLoadInFlightMock.mockReturnValue(false);
|
||||
loadPluginMetadataSnapshotMock.mockReset();
|
||||
const provider: ProviderPlugin = {
|
||||
id: "demo-provider",
|
||||
label: "Demo Provider",
|
||||
@@ -1154,6 +1179,28 @@ describe("resolvePluginProviders", () => {
|
||||
activate: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("inherits workspaceDir from the active registry when loading the metadata snapshot", () => {
|
||||
setActivePluginRegistry(
|
||||
createEmptyPluginRegistry(),
|
||||
undefined,
|
||||
"default",
|
||||
"/workspace/runtime",
|
||||
);
|
||||
|
||||
resolvePluginProviders({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["google"],
|
||||
},
|
||||
},
|
||||
onlyPluginIds: ["google"],
|
||||
});
|
||||
|
||||
expect(loadPluginMetadataSnapshotMock).toHaveBeenCalled();
|
||||
const snapshotCall = loadPluginMetadataSnapshotMock.mock.calls.at(-1)?.[0];
|
||||
expect(snapshotCall?.workspaceDir).toBe("/workspace/runtime");
|
||||
});
|
||||
it("activates owning plugins for explicit provider refs", () => {
|
||||
setOwningProviderManifestPlugins();
|
||||
|
||||
|
||||
@@ -10,11 +10,10 @@ import {
|
||||
} from "./manifest-owner-policy.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import {
|
||||
loadPluginRegistrySnapshot,
|
||||
normalizePluginsConfigWithRegistry,
|
||||
resolvePluginContributionOwners,
|
||||
resolveProviderOwners,
|
||||
type PluginRegistryRecord,
|
||||
type PluginRegistrySnapshot,
|
||||
} from "./plugin-registry.js";
|
||||
@@ -512,43 +511,30 @@ export function resolveOwningPluginIdsForProvider(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const env = params.env ?? process.env;
|
||||
const pluginIds = [
|
||||
...resolveProviderOwners({
|
||||
config: params.config,
|
||||
const manifestRegistry =
|
||||
params.manifestRegistry ??
|
||||
loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
providerId: normalizedProvider,
|
||||
includeDisabled: true,
|
||||
}),
|
||||
...resolvePluginContributionOwners({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
contribution: "cliBackends",
|
||||
matches: (backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
includeDisabled: true,
|
||||
}),
|
||||
];
|
||||
env: params.env ?? process.env,
|
||||
}).manifestRegistry;
|
||||
|
||||
const deduped = dedupeSortedPluginIds(pluginIds);
|
||||
return deduped.length > 0 ? deduped : undefined;
|
||||
const pluginIds = manifestRegistry.plugins
|
||||
.filter(
|
||||
(plugin) =>
|
||||
plugin.providers.some(
|
||||
(providerId) => normalizeProviderId(providerId) === normalizedProvider,
|
||||
) ||
|
||||
plugin.cliBackends.some(
|
||||
(backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
) ||
|
||||
(plugin.setup?.cliBackends ?? []).some(
|
||||
(backendId) => normalizeProviderId(backendId) === normalizedProvider,
|
||||
),
|
||||
)
|
||||
.map((plugin) => plugin.id);
|
||||
|
||||
return pluginIds.length > 0 ? pluginIds : undefined;
|
||||
}
|
||||
|
||||
export function resolveOwningPluginIdsForModelRef(params: {
|
||||
|
||||
@@ -87,6 +87,7 @@ export function resolvePluginRuntimeLoadContext(
|
||||
config: rawConfig,
|
||||
env,
|
||||
manifestRegistry,
|
||||
discovery: metadataSnapshot?.discovery,
|
||||
});
|
||||
const config = autoEnabled.config;
|
||||
const workspaceDir =
|
||||
@@ -111,7 +112,7 @@ export function resolvePluginRuntimeLoadContext(
|
||||
workspaceDir,
|
||||
env,
|
||||
logger: options?.logger ?? createPluginRuntimeLoaderLogger(),
|
||||
...(manifestRegistry ? { manifestRegistry } : {}),
|
||||
manifestRegistry,
|
||||
installRecords,
|
||||
};
|
||||
}
|
||||
@@ -134,7 +135,7 @@ export function buildPluginRuntimeLoadOptionsFromValues(
|
||||
workspaceDir: values.workspaceDir,
|
||||
env: values.env,
|
||||
logger: values.logger,
|
||||
...(values.manifestRegistry ? { manifestRegistry: values.manifestRegistry } : {}),
|
||||
manifestRegistry: values.manifestRegistry,
|
||||
installRecords: values.installRecords,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user