mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-15 11:11:09 +00:00
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { getChatChannelMeta, normalizeChatChannelId } from "../channels/registry.js";
|
|
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
|
import { isRecord, resolveConfigDir, resolveUserPath } from "../utils.js";
|
|
import type { OpenClawConfig } from "./config.js";
|
|
import type { PluginAutoEnableCandidate } from "./plugin-auto-enable.shared.js";
|
|
|
|
type ExternalCatalogChannelEntry = {
|
|
id: string;
|
|
preferOver: string[];
|
|
};
|
|
|
|
const ENV_CATALOG_PATHS = ["OPENCLAW_PLUGIN_CATALOG_PATHS", "OPENCLAW_MPM_CATALOG_PATHS"];
|
|
|
|
function splitEnvPaths(value: string): string[] {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return [];
|
|
}
|
|
return trimmed
|
|
.split(/[;,]/g)
|
|
.flatMap((chunk) => chunk.split(path.delimiter))
|
|
.map((entry) => entry.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function resolveExternalCatalogPaths(env: NodeJS.ProcessEnv): string[] {
|
|
for (const key of ENV_CATALOG_PATHS) {
|
|
const raw = env[key];
|
|
if (raw && raw.trim()) {
|
|
return splitEnvPaths(raw);
|
|
}
|
|
}
|
|
const configDir = resolveConfigDir(env);
|
|
return [
|
|
path.join(configDir, "mpm", "plugins.json"),
|
|
path.join(configDir, "mpm", "catalog.json"),
|
|
path.join(configDir, "plugins", "catalog.json"),
|
|
];
|
|
}
|
|
|
|
function parseExternalCatalogChannelEntries(raw: unknown): ExternalCatalogChannelEntry[] {
|
|
const list = (() => {
|
|
if (Array.isArray(raw)) {
|
|
return raw;
|
|
}
|
|
if (!isRecord(raw)) {
|
|
return [];
|
|
}
|
|
const entries = raw.entries ?? raw.packages ?? raw.plugins;
|
|
return Array.isArray(entries) ? entries : [];
|
|
})();
|
|
|
|
const channels: ExternalCatalogChannelEntry[] = [];
|
|
for (const entry of list) {
|
|
if (!isRecord(entry) || !isRecord(entry.openclaw) || !isRecord(entry.openclaw.channel)) {
|
|
continue;
|
|
}
|
|
const channel = entry.openclaw.channel;
|
|
const id = typeof channel.id === "string" ? channel.id.trim() : "";
|
|
if (!id) {
|
|
continue;
|
|
}
|
|
const preferOver = Array.isArray(channel.preferOver)
|
|
? channel.preferOver.filter((value): value is string => typeof value === "string")
|
|
: [];
|
|
channels.push({ id, preferOver });
|
|
}
|
|
return channels;
|
|
}
|
|
|
|
function resolveExternalCatalogPreferOver(channelId: string, env: NodeJS.ProcessEnv): string[] {
|
|
for (const rawPath of resolveExternalCatalogPaths(env)) {
|
|
const resolved = resolveUserPath(rawPath, env);
|
|
if (!fs.existsSync(resolved)) {
|
|
continue;
|
|
}
|
|
try {
|
|
const payload = JSON.parse(fs.readFileSync(resolved, "utf-8")) as unknown;
|
|
const channel = parseExternalCatalogChannelEntries(payload).find(
|
|
(entry) => entry.id === channelId,
|
|
);
|
|
if (channel) {
|
|
return channel.preferOver;
|
|
}
|
|
} catch {
|
|
// Ignore invalid catalog files.
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function resolvePreferredOverIds(
|
|
pluginId: string,
|
|
env: NodeJS.ProcessEnv,
|
|
registry: PluginManifestRegistry,
|
|
): string[] {
|
|
const normalized = normalizeChatChannelId(pluginId);
|
|
if (normalized) {
|
|
return [...(getChatChannelMeta(normalized).preferOver ?? [])];
|
|
}
|
|
const installedPlugin = registry.plugins.find((record) => record.id === pluginId);
|
|
const manifestChannelPreferOver = installedPlugin?.channelConfigs?.[pluginId]?.preferOver;
|
|
if (manifestChannelPreferOver?.length) {
|
|
return [...manifestChannelPreferOver];
|
|
}
|
|
const installedChannelMeta = installedPlugin?.channelCatalogMeta;
|
|
if (installedChannelMeta?.preferOver?.length) {
|
|
return [...installedChannelMeta.preferOver];
|
|
}
|
|
return resolveExternalCatalogPreferOver(pluginId, env);
|
|
}
|
|
|
|
export function shouldSkipPreferredPluginAutoEnable(params: {
|
|
config: OpenClawConfig;
|
|
entry: PluginAutoEnableCandidate;
|
|
configured: readonly PluginAutoEnableCandidate[];
|
|
env: NodeJS.ProcessEnv;
|
|
registry: PluginManifestRegistry;
|
|
isPluginDenied: (config: OpenClawConfig, pluginId: string) => boolean;
|
|
isPluginExplicitlyDisabled: (config: OpenClawConfig, pluginId: string) => boolean;
|
|
}): boolean {
|
|
for (const other of params.configured) {
|
|
if (other.pluginId === params.entry.pluginId) {
|
|
continue;
|
|
}
|
|
if (
|
|
params.isPluginDenied(params.config, other.pluginId) ||
|
|
params.isPluginExplicitlyDisabled(params.config, other.pluginId)
|
|
) {
|
|
continue;
|
|
}
|
|
if (
|
|
resolvePreferredOverIds(other.pluginId, params.env, params.registry).includes(
|
|
params.entry.pluginId,
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|