import type { OpenClawConfig } from "../config/config.js"; import type { PluginRecord } from "./registry.js"; import { defaultSlotIdForKey } from "./slots.js"; export type NormalizedPluginsConfig = { enabled: boolean; allow: string[]; deny: string[]; loadPaths: string[]; slots: { memory?: string | null; }; entries: Record; }; export const BUNDLED_ENABLED_BY_DEFAULT = new Set(); const normalizeList = (value: unknown): string[] => { if (!Array.isArray(value)) { return []; } return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean); }; const normalizeSlotValue = (value: unknown): string | null | undefined => { if (typeof value !== "string") { return undefined; } const trimmed = value.trim(); if (!trimmed) { return undefined; } if (trimmed.toLowerCase() === "none") { return null; } return trimmed; }; const normalizePluginEntries = (entries: unknown): NormalizedPluginsConfig["entries"] => { if (!entries || typeof entries !== "object" || Array.isArray(entries)) { return {}; } const normalized: NormalizedPluginsConfig["entries"] = {}; for (const [key, value] of Object.entries(entries)) { if (!key.trim()) { continue; } if (!value || typeof value !== "object" || Array.isArray(value)) { normalized[key] = {}; continue; } const entry = value as Record; normalized[key] = { enabled: typeof entry.enabled === "boolean" ? entry.enabled : undefined, config: "config" in entry ? entry.config : undefined, }; } return normalized; }; export const normalizePluginsConfig = ( config?: OpenClawConfig["plugins"], ): NormalizedPluginsConfig => { const memorySlot = normalizeSlotValue(config?.slots?.memory); return { enabled: config?.enabled !== false, allow: normalizeList(config?.allow), deny: normalizeList(config?.deny), loadPaths: normalizeList(config?.load?.paths), slots: { memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot, }, entries: normalizePluginEntries(config?.entries), }; }; const hasExplicitMemorySlot = (plugins?: OpenClawConfig["plugins"]) => Boolean(plugins?.slots && Object.prototype.hasOwnProperty.call(plugins.slots, "memory")); const hasExplicitMemoryEntry = (plugins?: OpenClawConfig["plugins"]) => Boolean(plugins?.entries && Object.prototype.hasOwnProperty.call(plugins.entries, "memory-core")); const hasExplicitPluginConfig = (plugins?: OpenClawConfig["plugins"]) => { if (!plugins) { return false; } if (typeof plugins.enabled === "boolean") { return true; } if (Array.isArray(plugins.allow) && plugins.allow.length > 0) { return true; } if (Array.isArray(plugins.deny) && plugins.deny.length > 0) { return true; } if (plugins.load?.paths && Array.isArray(plugins.load.paths) && plugins.load.paths.length > 0) { return true; } if (plugins.slots && Object.keys(plugins.slots).length > 0) { return true; } if (plugins.entries && Object.keys(plugins.entries).length > 0) { return true; } return false; }; export function applyTestPluginDefaults( cfg: OpenClawConfig, env: NodeJS.ProcessEnv = process.env, ): OpenClawConfig { if (!env.VITEST) { return cfg; } const plugins = cfg.plugins; const explicitConfig = hasExplicitPluginConfig(plugins); if (explicitConfig) { if (hasExplicitMemorySlot(plugins) || hasExplicitMemoryEntry(plugins)) { return cfg; } return { ...cfg, plugins: { ...plugins, slots: { ...plugins?.slots, memory: "none", }, }, }; } return { ...cfg, plugins: { ...plugins, enabled: false, slots: { ...plugins?.slots, memory: "none", }, }, }; } export function isTestDefaultMemorySlotDisabled( cfg: OpenClawConfig, env: NodeJS.ProcessEnv = process.env, ): boolean { if (!env.VITEST) { return false; } const plugins = cfg.plugins; if (hasExplicitMemorySlot(plugins) || hasExplicitMemoryEntry(plugins)) { return false; } return true; } export function resolveEnableState( id: string, origin: PluginRecord["origin"], config: NormalizedPluginsConfig, ): { enabled: boolean; reason?: string } { if (!config.enabled) { return { enabled: false, reason: "plugins disabled" }; } if (config.deny.includes(id)) { return { enabled: false, reason: "blocked by denylist" }; } if (config.allow.length > 0 && !config.allow.includes(id)) { return { enabled: false, reason: "not in allowlist" }; } if (config.slots.memory === id) { return { enabled: true }; } const entry = config.entries[id]; if (entry?.enabled === true) { return { enabled: true }; } if (entry?.enabled === false) { return { enabled: false, reason: "disabled in config" }; } if (origin === "bundled" && BUNDLED_ENABLED_BY_DEFAULT.has(id)) { return { enabled: true }; } if (origin === "bundled") { return { enabled: false, reason: "bundled (disabled by default)" }; } return { enabled: true }; } export function resolveMemorySlotDecision(params: { id: string; kind?: string; slot: string | null | undefined; selectedId: string | null; }): { enabled: boolean; reason?: string; selected?: boolean } { if (params.kind !== "memory") { return { enabled: true }; } if (params.slot === null) { return { enabled: false, reason: "memory slot disabled" }; } if (typeof params.slot === "string") { if (params.slot === params.id) { return { enabled: true, selected: true }; } return { enabled: false, reason: `memory slot set to "${params.slot}"`, }; } if (params.selectedId && params.selectedId !== params.id) { return { enabled: false, reason: `memory slot already filled by "${params.selectedId}"`, }; } return { enabled: true, selected: true }; }