mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 03:10:42 +00:00
321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
import { normalizeProviderId } from "../agents/provider-id.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import {
|
|
normalizePluginsConfigWithResolver,
|
|
type NormalizedPluginsConfig,
|
|
} from "./config-normalization-shared.js";
|
|
import {
|
|
readPersistedInstalledPluginIndexSync,
|
|
type InstalledPluginIndexStoreInspection,
|
|
type InstalledPluginIndexStoreOptions,
|
|
} from "./installed-plugin-index-store.js";
|
|
import {
|
|
getInstalledPluginRecord,
|
|
isInstalledPluginEnabled,
|
|
listInstalledPluginContributionIds,
|
|
listInstalledPluginRecords,
|
|
loadInstalledPluginIndex,
|
|
resolveInstalledPluginContributionOwners,
|
|
resolveInstalledPluginIndexPolicyHash,
|
|
type InstalledPluginContributionKey,
|
|
type InstalledPluginIndex,
|
|
type InstalledPluginIndexRecord,
|
|
type LoadInstalledPluginIndexParams,
|
|
type RefreshInstalledPluginIndexParams,
|
|
} from "./installed-plugin-index.js";
|
|
|
|
export type PluginRegistrySnapshot = InstalledPluginIndex;
|
|
export type PluginRegistryRecord = InstalledPluginIndexRecord;
|
|
export type PluginRegistryInspection = InstalledPluginIndexStoreInspection;
|
|
export type PluginRegistrySnapshotSource = "provided" | "persisted" | "derived";
|
|
export type PluginRegistrySnapshotDiagnosticCode =
|
|
| "persisted-registry-disabled"
|
|
| "persisted-registry-missing"
|
|
| "persisted-registry-stale-policy";
|
|
|
|
export type PluginRegistrySnapshotDiagnostic = {
|
|
level: "info" | "warn";
|
|
code: PluginRegistrySnapshotDiagnosticCode;
|
|
message: string;
|
|
};
|
|
|
|
export type PluginRegistrySnapshotResult = {
|
|
snapshot: PluginRegistrySnapshot;
|
|
source: PluginRegistrySnapshotSource;
|
|
diagnostics: readonly PluginRegistrySnapshotDiagnostic[];
|
|
};
|
|
|
|
export const DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV = "OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY";
|
|
|
|
function formatDeprecatedPersistedRegistryDisableWarning(): string {
|
|
return `${DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV} is a deprecated break-glass compatibility switch; use \`openclaw plugins registry --refresh\` or \`openclaw doctor --fix\` to repair registry state.`;
|
|
}
|
|
|
|
export type LoadPluginRegistryParams = LoadInstalledPluginIndexParams &
|
|
InstalledPluginIndexStoreOptions & {
|
|
index?: PluginRegistrySnapshot;
|
|
preferPersisted?: boolean;
|
|
};
|
|
|
|
export type PluginRegistryContributionOptions = LoadPluginRegistryParams & {
|
|
includeDisabled?: boolean;
|
|
};
|
|
|
|
export type GetPluginRecordParams = LoadPluginRegistryParams & {
|
|
pluginId: string;
|
|
};
|
|
|
|
export type ResolvePluginContributionOwnersParams = PluginRegistryContributionOptions & {
|
|
contribution: InstalledPluginContributionKey;
|
|
matches: string | ((contributionId: string) => boolean);
|
|
};
|
|
|
|
export type ListPluginContributionIdsParams = PluginRegistryContributionOptions & {
|
|
contribution: InstalledPluginContributionKey;
|
|
};
|
|
|
|
export type ResolveProviderOwnersParams = PluginRegistryContributionOptions & {
|
|
providerId: string;
|
|
};
|
|
|
|
export type ResolveChannelOwnersParams = PluginRegistryContributionOptions & {
|
|
channelId: string;
|
|
};
|
|
|
|
export type ResolveCliBackendOwnersParams = PluginRegistryContributionOptions & {
|
|
cliBackendId: string;
|
|
};
|
|
|
|
export type ResolveSetupProviderOwnersParams = PluginRegistryContributionOptions & {
|
|
setupProviderId: string;
|
|
};
|
|
|
|
function normalizeContributionId(value: string): string {
|
|
return value.trim();
|
|
}
|
|
|
|
function normalizePluginRegistryAlias(value: string): string {
|
|
return value.trim();
|
|
}
|
|
|
|
function normalizePluginRegistryAliasKey(value: string): string {
|
|
return normalizePluginRegistryAlias(value).toLowerCase();
|
|
}
|
|
|
|
export function createPluginRegistryIdNormalizer(
|
|
index: PluginRegistrySnapshot,
|
|
): (pluginId: string) => string {
|
|
const aliases = new Map<string, string>();
|
|
for (const plugin of [...index.plugins].toSorted((left, right) =>
|
|
left.pluginId.localeCompare(right.pluginId),
|
|
)) {
|
|
const pluginId = normalizePluginRegistryAlias(plugin.pluginId);
|
|
if (!pluginId) {
|
|
continue;
|
|
}
|
|
aliases.set(normalizePluginRegistryAliasKey(pluginId), pluginId);
|
|
for (const alias of [
|
|
...plugin.contributions.providers,
|
|
...plugin.contributions.channels,
|
|
...plugin.contributions.setupProviders,
|
|
...plugin.contributions.cliBackends,
|
|
...plugin.contributions.modelCatalogProviders,
|
|
]) {
|
|
const normalizedAlias = normalizePluginRegistryAlias(alias);
|
|
const normalizedAliasKey = normalizePluginRegistryAliasKey(alias);
|
|
if (normalizedAlias && !aliases.has(normalizedAliasKey)) {
|
|
aliases.set(normalizedAliasKey, pluginId);
|
|
}
|
|
}
|
|
}
|
|
return (pluginId: string) => {
|
|
const trimmed = normalizePluginRegistryAlias(pluginId);
|
|
return aliases.get(normalizePluginRegistryAliasKey(trimmed)) ?? trimmed;
|
|
};
|
|
}
|
|
|
|
export function normalizePluginsConfigWithRegistry(
|
|
config: OpenClawConfig["plugins"] | undefined,
|
|
index: PluginRegistrySnapshot,
|
|
): NormalizedPluginsConfig {
|
|
return normalizePluginsConfigWithResolver(config, createPluginRegistryIdNormalizer(index));
|
|
}
|
|
|
|
function hasEnvFlag(env: NodeJS.ProcessEnv, name: string): boolean {
|
|
const value = env[name]?.trim().toLowerCase();
|
|
return Boolean(value && value !== "0" && value !== "false" && value !== "no");
|
|
}
|
|
|
|
export function loadPluginRegistrySnapshotWithMetadata(
|
|
params: LoadPluginRegistryParams = {},
|
|
): PluginRegistrySnapshotResult {
|
|
if (params.index) {
|
|
return {
|
|
snapshot: params.index,
|
|
source: "provided",
|
|
diagnostics: [],
|
|
};
|
|
}
|
|
|
|
const env = params.env ?? process.env;
|
|
const diagnostics: PluginRegistrySnapshotDiagnostic[] = [];
|
|
const disabledByCaller = params.preferPersisted === false;
|
|
const disabledByEnv = hasEnvFlag(env, DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV);
|
|
const persistedReadsEnabled = !disabledByCaller && !disabledByEnv;
|
|
if (persistedReadsEnabled) {
|
|
const persisted = readPersistedInstalledPluginIndexSync(params);
|
|
if (persisted) {
|
|
if (
|
|
params.config &&
|
|
persisted.policyHash !== resolveInstalledPluginIndexPolicyHash(params.config)
|
|
) {
|
|
diagnostics.push({
|
|
level: "warn",
|
|
code: "persisted-registry-stale-policy",
|
|
message:
|
|
"Persisted plugin registry policy does not match current config; using derived plugin index. Run `openclaw plugins registry --refresh` to update the persisted registry.",
|
|
});
|
|
} else {
|
|
return {
|
|
snapshot: persisted,
|
|
source: "persisted",
|
|
diagnostics,
|
|
};
|
|
}
|
|
} else {
|
|
diagnostics.push({
|
|
level: "info",
|
|
code: "persisted-registry-missing",
|
|
message: "Persisted plugin registry is missing or invalid; using derived plugin index.",
|
|
});
|
|
}
|
|
} else {
|
|
diagnostics.push({
|
|
level: "warn",
|
|
code: "persisted-registry-disabled",
|
|
message: disabledByEnv
|
|
? `${formatDeprecatedPersistedRegistryDisableWarning()} Using legacy derived plugin index.`
|
|
: "Persisted plugin registry reads are disabled by the caller; using derived plugin index.",
|
|
});
|
|
}
|
|
|
|
return {
|
|
snapshot: loadInstalledPluginIndex(params),
|
|
source: "derived",
|
|
diagnostics,
|
|
};
|
|
}
|
|
|
|
function resolveSnapshot(params: LoadPluginRegistryParams = {}): PluginRegistrySnapshot {
|
|
return loadPluginRegistrySnapshotWithMetadata(params).snapshot;
|
|
}
|
|
|
|
export function loadPluginRegistrySnapshot(
|
|
params: LoadPluginRegistryParams = {},
|
|
): PluginRegistrySnapshot {
|
|
return resolveSnapshot(params);
|
|
}
|
|
|
|
export function listPluginRecords(
|
|
params: LoadPluginRegistryParams = {},
|
|
): readonly PluginRegistryRecord[] {
|
|
return listInstalledPluginRecords(resolveSnapshot(params));
|
|
}
|
|
|
|
export function getPluginRecord(params: GetPluginRecordParams): PluginRegistryRecord | undefined {
|
|
return getInstalledPluginRecord(resolveSnapshot(params), params.pluginId);
|
|
}
|
|
|
|
export function isPluginEnabled(params: GetPluginRecordParams): boolean {
|
|
return isInstalledPluginEnabled(resolveSnapshot(params), params.pluginId, params.config);
|
|
}
|
|
|
|
export function listPluginContributionIds(
|
|
params: ListPluginContributionIdsParams,
|
|
): readonly string[] {
|
|
return listInstalledPluginContributionIds(resolveSnapshot(params), params.contribution, {
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
});
|
|
}
|
|
|
|
export function resolvePluginContributionOwners(
|
|
params: ResolvePluginContributionOwnersParams,
|
|
): readonly string[] {
|
|
return resolveInstalledPluginContributionOwners(
|
|
resolveSnapshot(params),
|
|
params.contribution,
|
|
params.matches,
|
|
{
|
|
includeDisabled: params.includeDisabled,
|
|
config: params.config,
|
|
},
|
|
);
|
|
}
|
|
|
|
export function resolveProviderOwners(params: ResolveProviderOwnersParams): readonly string[] {
|
|
const providerId = normalizeProviderId(params.providerId);
|
|
if (!providerId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "providers",
|
|
matches: (contributionId) => normalizeProviderId(contributionId) === providerId,
|
|
});
|
|
}
|
|
|
|
export function resolveChannelOwners(params: ResolveChannelOwnersParams): readonly string[] {
|
|
const channelId = normalizeContributionId(params.channelId);
|
|
if (!channelId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "channels",
|
|
matches: channelId,
|
|
});
|
|
}
|
|
|
|
export function resolveCliBackendOwners(params: ResolveCliBackendOwnersParams): readonly string[] {
|
|
const cliBackendId = normalizeContributionId(params.cliBackendId);
|
|
if (!cliBackendId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "cliBackends",
|
|
matches: cliBackendId,
|
|
});
|
|
}
|
|
|
|
export function resolveSetupProviderOwners(
|
|
params: ResolveSetupProviderOwnersParams,
|
|
): readonly string[] {
|
|
const setupProviderId = normalizeContributionId(params.setupProviderId);
|
|
if (!setupProviderId) {
|
|
return [];
|
|
}
|
|
return resolvePluginContributionOwners({
|
|
...params,
|
|
contribution: "setupProviders",
|
|
matches: setupProviderId,
|
|
});
|
|
}
|
|
|
|
export function inspectPluginRegistry(
|
|
params: LoadInstalledPluginIndexParams & InstalledPluginIndexStoreOptions = {},
|
|
): Promise<PluginRegistryInspection> {
|
|
return import("./installed-plugin-index-store.js").then((store) =>
|
|
store.inspectPersistedInstalledPluginIndex(params),
|
|
);
|
|
}
|
|
|
|
export function refreshPluginRegistry(
|
|
params: RefreshInstalledPluginIndexParams & InstalledPluginIndexStoreOptions,
|
|
): Promise<PluginRegistrySnapshot> {
|
|
return import("./installed-plugin-index-store.js").then((store) =>
|
|
store.refreshPersistedInstalledPluginIndex(params),
|
|
);
|
|
}
|