mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 15:20:23 +00:00
* refactor: share talk event metric extraction * refactor: reuse shared coercion helpers * refactor: reuse shared primitive guards * refactor: reuse shared record guard * refactor: reuse shared primitive helpers * refactor: reuse shared string guards * refactor: reuse shared non-empty string guard * refactor: share plugin primitive coercion helpers * refactor: reuse plugin coercion helpers * refactor: reuse plugin coercion helpers in more plugins * refactor: reuse channel coercion helpers * refactor: reuse monitor coercion helpers * refactor: reuse provider coercion helpers * refactor: reuse core coercion helpers * refactor: reuse runtime coercion helpers * refactor: reuse helper coercion in codex paths * refactor: reuse helper coercion in runtime paths * refactor: reuse codex app-server coercion helpers * refactor: reuse codex record helpers * refactor: reuse migration and qa record helpers * refactor: reuse feishu and core helper guards * refactor: reuse browser and policy coercion helpers * refactor: reuse memory wiki record helper * refactor: share boolean coercion helpers * refactor: reuse finite number coercion * refactor: reuse trimmed string list helpers * refactor: reuse string list normalization * refactor: reuse remaining string list helpers * refactor: reuse string entry normalizer * refactor: share sorted string helpers * refactor: share string list normalization * test: preserve command registry browser imports * refactor: reuse trimmed list helpers * refactor: reuse string dedupe helpers * refactor: reuse local dedupe helpers * refactor: reuse more string dedupe helpers * refactor: reuse command string dedupe helpers * refactor: dedupe memory path lists with helper * refactor: expose string dedupe helpers to plugins * refactor: reuse core string dedupe helpers * refactor: reuse shared unique value helpers * refactor: reuse unique helpers in agent utilities * refactor: reuse unique helpers in config plumbing * refactor: reuse unique helpers in extensions * refactor: reuse unique helpers in core utilities * refactor: reuse unique helpers in qa plugins * refactor: reuse unique helpers in memory plugins * refactor: reuse unique helpers in channel plugins * refactor: reuse unique helpers in core tails * refactor: reuse unique helper in comfy workflow * refactor: reuse unique helpers in test utilities * refactor: expose unique value helper to plugins * refactor: reuse unique helpers for numeric lists * refactor: replace index dedupe filters * refactor: reuse string entry normalization * refactor: reuse string normalization in plugin helpers * refactor: reuse string normalization in extension helpers * refactor: reuse string normalization in channel parsers * refactor: reuse string normalization in memory search * refactor: reuse string normalization in provider parsers * refactor: reuse string normalization in qa helpers * refactor: reuse string normalization in infra parsers * refactor: reuse string normalization in messaging parsers * refactor: reuse string normalization in core parsers * refactor: reuse string normalization in extension parsers * refactor: reuse string normalization in remaining parsers * refactor: reuse string normalization in final parser spots * refactor: reuse string normalization in qa media helpers * refactor: reuse normalization in provider and media lists * refactor: reuse normalization for remaining set filters * refactor: reuse normalization in policy allowlists * refactor: reuse normalization in session and owner lists * refactor: centralize primitive string lists * refactor: reuse lowercase entry helpers * refactor: reuse sorted string helpers * refactor: reuse unique trimmed helpers * refactor: reuse string normalization helpers * refactor: reuse catalog string helpers * refactor: reuse remaining string helpers * refactor: simplify remaining list normalization * refactor: reuse codex auth order normalization * chore: refresh plugin sdk api baseline * fix: make shared string sorting deterministic * chore: refresh plugin sdk api baseline * fix: align host env security ordering
284 lines
9.9 KiB
TypeScript
284 lines
9.9 KiB
TypeScript
import { normalizeChatChannelId } from "../channels/ids.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import {
|
|
normalizeOptionalLowercaseString,
|
|
normalizeOptionalString,
|
|
} from "../shared/string-coerce.js";
|
|
import { normalizeArrayBackedTrimmedStringList } from "../shared/string-normalization.js";
|
|
import { defaultSlotIdForKey } from "./slots.js";
|
|
|
|
export type NormalizedPluginsConfig = {
|
|
enabled: boolean;
|
|
allow: string[];
|
|
deny: string[];
|
|
loadPaths: string[];
|
|
slots: {
|
|
memory?: string | null;
|
|
contextEngine?: string | null;
|
|
};
|
|
entries: Record<
|
|
string,
|
|
{
|
|
enabled?: boolean;
|
|
hooks?: {
|
|
allowPromptInjection?: boolean;
|
|
allowConversationAccess?: boolean;
|
|
timeoutMs?: number;
|
|
timeouts?: Record<string, number>;
|
|
};
|
|
subagent?: {
|
|
allowModelOverride?: boolean;
|
|
allowedModels?: string[];
|
|
hasAllowedModelsConfig?: boolean;
|
|
};
|
|
llm?: {
|
|
allowModelOverride?: boolean;
|
|
allowedModels?: string[];
|
|
hasAllowedModelsConfig?: boolean;
|
|
allowAgentIdOverride?: boolean;
|
|
};
|
|
config?: unknown;
|
|
}
|
|
>;
|
|
};
|
|
|
|
export type NormalizePluginId = (id: string) => string;
|
|
|
|
export const identityNormalizePluginId: NormalizePluginId = (id) => id.trim();
|
|
|
|
function normalizeList(value: unknown, normalizePluginId: NormalizePluginId): string[] {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value
|
|
.map((entry) => (typeof entry === "string" ? normalizePluginId(entry) : ""))
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function normalizeSlotValue(value: unknown): string | null | undefined {
|
|
const trimmed = normalizeOptionalString(value);
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
if (normalizeOptionalLowercaseString(trimmed) === "none") {
|
|
return null;
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
function normalizeHookTimeoutMs(value: unknown): number | undefined {
|
|
if (
|
|
typeof value !== "number" ||
|
|
!Number.isInteger(value) ||
|
|
!Number.isFinite(value) ||
|
|
value <= 0 ||
|
|
value > 600_000
|
|
) {
|
|
return undefined;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function normalizeHookTimeouts(value: unknown): Record<string, number> | undefined {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return undefined;
|
|
}
|
|
const normalized: Record<string, number> = {};
|
|
for (const [hookName, timeoutMs] of Object.entries(value)) {
|
|
const normalizedTimeoutMs = normalizeHookTimeoutMs(timeoutMs);
|
|
if (normalizedTimeoutMs !== undefined) {
|
|
normalized[hookName] = normalizedTimeoutMs;
|
|
}
|
|
}
|
|
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
}
|
|
|
|
function normalizePluginEntries(
|
|
entries: unknown,
|
|
normalizePluginId: NormalizePluginId,
|
|
): NormalizedPluginsConfig["entries"] {
|
|
if (!entries || typeof entries !== "object" || Array.isArray(entries)) {
|
|
return {};
|
|
}
|
|
const normalized: NormalizedPluginsConfig["entries"] = {};
|
|
for (const [key, value] of Object.entries(entries)) {
|
|
const normalizedKey = normalizePluginId(key);
|
|
if (!normalizedKey) {
|
|
continue;
|
|
}
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
normalized[normalizedKey] = {};
|
|
continue;
|
|
}
|
|
const entry = value as Record<string, unknown>;
|
|
const hooksRaw = entry.hooks;
|
|
const hooks =
|
|
hooksRaw && typeof hooksRaw === "object" && !Array.isArray(hooksRaw)
|
|
? {
|
|
allowPromptInjection: (hooksRaw as { allowPromptInjection?: unknown })
|
|
.allowPromptInjection,
|
|
allowConversationAccess: (hooksRaw as { allowConversationAccess?: unknown })
|
|
.allowConversationAccess,
|
|
timeoutMs: normalizeHookTimeoutMs((hooksRaw as { timeoutMs?: unknown }).timeoutMs),
|
|
timeouts: normalizeHookTimeouts((hooksRaw as { timeouts?: unknown }).timeouts),
|
|
}
|
|
: undefined;
|
|
const normalizedHooks =
|
|
hooks &&
|
|
(typeof hooks.allowPromptInjection === "boolean" ||
|
|
typeof hooks.allowConversationAccess === "boolean" ||
|
|
hooks.timeoutMs !== undefined ||
|
|
hooks.timeouts !== undefined)
|
|
? {
|
|
...(typeof hooks.allowPromptInjection === "boolean"
|
|
? { allowPromptInjection: hooks.allowPromptInjection }
|
|
: {}),
|
|
...(typeof hooks.allowConversationAccess === "boolean"
|
|
? { allowConversationAccess: hooks.allowConversationAccess }
|
|
: {}),
|
|
...(hooks.timeoutMs !== undefined ? { timeoutMs: hooks.timeoutMs } : {}),
|
|
...(hooks.timeouts !== undefined ? { timeouts: hooks.timeouts } : {}),
|
|
}
|
|
: undefined;
|
|
const subagentRaw = entry.subagent;
|
|
const subagent =
|
|
subagentRaw && typeof subagentRaw === "object" && !Array.isArray(subagentRaw)
|
|
? {
|
|
allowModelOverride: (subagentRaw as { allowModelOverride?: unknown })
|
|
.allowModelOverride,
|
|
hasAllowedModelsConfig: Array.isArray(
|
|
(subagentRaw as { allowedModels?: unknown }).allowedModels,
|
|
),
|
|
allowedModels: Array.isArray((subagentRaw as { allowedModels?: unknown }).allowedModels)
|
|
? normalizeArrayBackedTrimmedStringList(
|
|
(subagentRaw as { allowedModels?: unknown }).allowedModels,
|
|
)
|
|
: undefined,
|
|
}
|
|
: undefined;
|
|
const normalizedSubagent =
|
|
subagent &&
|
|
(typeof subagent.allowModelOverride === "boolean" ||
|
|
subagent.hasAllowedModelsConfig ||
|
|
(Array.isArray(subagent.allowedModels) && subagent.allowedModels.length > 0))
|
|
? {
|
|
...(typeof subagent.allowModelOverride === "boolean"
|
|
? { allowModelOverride: subagent.allowModelOverride }
|
|
: {}),
|
|
...(subagent.hasAllowedModelsConfig ? { hasAllowedModelsConfig: true } : {}),
|
|
...(Array.isArray(subagent.allowedModels) && subagent.allowedModels.length > 0
|
|
? { allowedModels: subagent.allowedModels }
|
|
: {}),
|
|
}
|
|
: undefined;
|
|
const llmRaw = entry.llm;
|
|
const llm =
|
|
llmRaw && typeof llmRaw === "object" && !Array.isArray(llmRaw)
|
|
? {
|
|
allowModelOverride: (llmRaw as { allowModelOverride?: unknown }).allowModelOverride,
|
|
hasAllowedModelsConfig: Array.isArray(
|
|
(llmRaw as { allowedModels?: unknown }).allowedModels,
|
|
),
|
|
allowedModels: Array.isArray((llmRaw as { allowedModels?: unknown }).allowedModels)
|
|
? normalizeArrayBackedTrimmedStringList(
|
|
(llmRaw as { allowedModels?: unknown }).allowedModels,
|
|
)
|
|
: undefined,
|
|
allowAgentIdOverride: (llmRaw as { allowAgentIdOverride?: unknown })
|
|
.allowAgentIdOverride,
|
|
}
|
|
: undefined;
|
|
const normalizedLlm =
|
|
llm &&
|
|
(typeof llm.allowModelOverride === "boolean" ||
|
|
llm.hasAllowedModelsConfig ||
|
|
(Array.isArray(llm.allowedModels) && llm.allowedModels.length > 0) ||
|
|
typeof llm.allowAgentIdOverride === "boolean")
|
|
? {
|
|
...(typeof llm.allowModelOverride === "boolean"
|
|
? { allowModelOverride: llm.allowModelOverride }
|
|
: {}),
|
|
...(llm.hasAllowedModelsConfig ? { hasAllowedModelsConfig: true } : {}),
|
|
...(Array.isArray(llm.allowedModels) && llm.allowedModels.length > 0
|
|
? { allowedModels: llm.allowedModels }
|
|
: {}),
|
|
...(typeof llm.allowAgentIdOverride === "boolean"
|
|
? { allowAgentIdOverride: llm.allowAgentIdOverride }
|
|
: {}),
|
|
}
|
|
: undefined;
|
|
normalized[normalizedKey] = {
|
|
...normalized[normalizedKey],
|
|
enabled:
|
|
typeof entry.enabled === "boolean" ? entry.enabled : normalized[normalizedKey]?.enabled,
|
|
hooks: normalizedHooks ?? normalized[normalizedKey]?.hooks,
|
|
subagent: normalizedSubagent ?? normalized[normalizedKey]?.subagent,
|
|
llm: normalizedLlm ?? normalized[normalizedKey]?.llm,
|
|
config: "config" in entry ? entry.config : normalized[normalizedKey]?.config,
|
|
};
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function normalizePluginsConfigWithResolver(
|
|
config?: OpenClawConfig["plugins"],
|
|
normalizePluginId: NormalizePluginId = identityNormalizePluginId,
|
|
): NormalizedPluginsConfig {
|
|
const memorySlot = normalizeSlotValue(config?.slots?.memory);
|
|
return {
|
|
enabled: config?.enabled !== false,
|
|
allow: normalizeList(config?.allow, normalizePluginId),
|
|
deny: normalizeList(config?.deny, normalizePluginId),
|
|
loadPaths: normalizeList(config?.load?.paths, identityNormalizePluginId),
|
|
slots: {
|
|
memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
|
|
contextEngine: normalizeSlotValue(config?.slots?.contextEngine),
|
|
},
|
|
entries: normalizePluginEntries(config?.entries, normalizePluginId),
|
|
};
|
|
}
|
|
|
|
export function hasExplicitPluginConfig(plugins?: OpenClawConfig["plugins"]): boolean {
|
|
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 isBundledChannelEnabledByChannelConfig(
|
|
cfg: OpenClawConfig | undefined,
|
|
pluginId: string,
|
|
): boolean {
|
|
if (!cfg) {
|
|
return false;
|
|
}
|
|
const channelId = normalizeChatChannelId(pluginId);
|
|
if (!channelId) {
|
|
return false;
|
|
}
|
|
const channels = cfg.channels as Record<string, unknown> | undefined;
|
|
const entry = channels?.[channelId];
|
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
return false;
|
|
}
|
|
return (entry as Record<string, unknown>).enabled === true;
|
|
}
|