Files
clawdbot/src/config/schema.ts
2026-01-17 00:00:49 +00:00

565 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { VERSION } from "../version.js";
import { ClawdbotSchema } from "./zod-schema.js";
export type ConfigUiHint = {
label?: string;
help?: string;
group?: string;
order?: number;
advanced?: boolean;
sensitive?: boolean;
placeholder?: string;
itemTemplate?: unknown;
};
export type ConfigUiHints = Record<string, ConfigUiHint>;
export type ConfigSchema = ReturnType<typeof ClawdbotSchema.toJSONSchema>;
export type ConfigSchemaResponse = {
schema: ConfigSchema;
uiHints: ConfigUiHints;
version: string;
generatedAt: string;
};
export type PluginUiMetadata = {
id: string;
name?: string;
description?: string;
configUiHints?: Record<
string,
Pick<ConfigUiHint, "label" | "help" | "advanced" | "sensitive" | "placeholder">
>;
};
export type ChannelUiMetadata = {
id: string;
label?: string;
description?: string;
};
const GROUP_LABELS: Record<string, string> = {
wizard: "Wizard",
logging: "Logging",
gateway: "Gateway",
agents: "Agents",
tools: "Tools",
bindings: "Bindings",
audio: "Audio",
models: "Models",
messages: "Messages",
commands: "Commands",
session: "Session",
cron: "Cron",
hooks: "Hooks",
ui: "UI",
browser: "Browser",
talk: "Talk",
channels: "Messaging Channels",
skills: "Skills",
plugins: "Plugins",
discovery: "Discovery",
presence: "Presence",
voicewake: "Voice Wake",
};
const GROUP_ORDER: Record<string, number> = {
wizard: 20,
gateway: 30,
agents: 40,
tools: 50,
bindings: 55,
audio: 60,
models: 70,
messages: 80,
commands: 85,
session: 90,
cron: 100,
hooks: 110,
ui: 120,
browser: 130,
talk: 140,
channels: 150,
skills: 200,
plugins: 205,
discovery: 210,
presence: 220,
voicewake: 230,
logging: 900,
};
const FIELD_LABELS: Record<string, string> = {
"gateway.remote.url": "Remote Gateway URL",
"gateway.remote.sshTarget": "Remote Gateway SSH Target",
"gateway.remote.sshIdentity": "Remote Gateway SSH Identity",
"gateway.remote.token": "Remote Gateway Token",
"gateway.remote.password": "Remote Gateway Password",
"gateway.auth.token": "Gateway Token",
"gateway.auth.password": "Gateway Password",
"tools.audio.transcription.args": "Audio Transcription Args",
"tools.audio.transcription.timeoutSeconds": "Audio Transcription Timeout (sec)",
"tools.profile": "Tool Profile",
"agents.list[].tools.profile": "Agent Tool Profile",
"tools.byProvider": "Tool Policy by Provider",
"agents.list[].tools.byProvider": "Agent Tool Policy by Provider",
"tools.exec.applyPatch.enabled": "Enable apply_patch",
"tools.exec.applyPatch.allowModels": "apply_patch Model Allowlist",
"tools.web.search.enabled": "Enable Web Search Tool",
"tools.web.search.provider": "Web Search Provider",
"tools.web.search.apiKey": "Brave Search API Key",
"tools.web.search.maxResults": "Web Search Max Results",
"tools.web.search.timeoutSeconds": "Web Search Timeout (sec)",
"tools.web.search.cacheTtlMinutes": "Web Search Cache TTL (min)",
"tools.web.fetch.enabled": "Enable Web Fetch Tool",
"tools.web.fetch.maxChars": "Web Fetch Max Chars",
"tools.web.fetch.timeoutSeconds": "Web Fetch Timeout (sec)",
"tools.web.fetch.cacheTtlMinutes": "Web Fetch Cache TTL (min)",
"tools.web.fetch.userAgent": "Web Fetch User-Agent",
"gateway.controlUi.basePath": "Control UI Base Path",
"gateway.http.endpoints.chatCompletions.enabled": "OpenAI Chat Completions Endpoint",
"gateway.reload.mode": "Config Reload Mode",
"gateway.reload.debounceMs": "Config Reload Debounce (ms)",
"skills.load.watch": "Watch Skills",
"skills.load.watchDebounceMs": "Skills Watch Debounce (ms)",
"agents.defaults.workspace": "Workspace",
"agents.defaults.bootstrapMaxChars": "Bootstrap Max Chars",
"agents.defaults.memorySearch": "Memory Search",
"agents.defaults.memorySearch.enabled": "Enable Memory Search",
"agents.defaults.memorySearch.provider": "Memory Search Provider",
"agents.defaults.memorySearch.remote.baseUrl": "Remote Embedding Base URL",
"agents.defaults.memorySearch.remote.apiKey": "Remote Embedding API Key",
"agents.defaults.memorySearch.remote.headers": "Remote Embedding Headers",
"agents.defaults.memorySearch.model": "Memory Search Model",
"agents.defaults.memorySearch.fallback": "Memory Search Fallback",
"agents.defaults.memorySearch.local.modelPath": "Local Embedding Model Path",
"agents.defaults.memorySearch.store.path": "Memory Search Index Path",
"agents.defaults.memorySearch.chunking.tokens": "Memory Chunk Tokens",
"agents.defaults.memorySearch.chunking.overlap": "Memory Chunk Overlap Tokens",
"agents.defaults.memorySearch.sync.onSessionStart": "Index on Session Start",
"agents.defaults.memorySearch.sync.onSearch": "Index on Search (Lazy)",
"agents.defaults.memorySearch.sync.watch": "Watch Memory Files",
"agents.defaults.memorySearch.sync.watchDebounceMs": "Memory Watch Debounce (ms)",
"agents.defaults.memorySearch.query.maxResults": "Memory Search Max Results",
"agents.defaults.memorySearch.query.minScore": "Memory Search Min Score",
"auth.profiles": "Auth Profiles",
"auth.order": "Auth Profile Order",
"auth.cooldowns.billingBackoffHours": "Billing Backoff (hours)",
"auth.cooldowns.billingBackoffHoursByProvider": "Billing Backoff Overrides",
"auth.cooldowns.billingMaxHours": "Billing Backoff Cap (hours)",
"auth.cooldowns.failureWindowHours": "Failover Window (hours)",
"agents.defaults.models": "Models",
"agents.defaults.model.primary": "Primary Model",
"agents.defaults.model.fallbacks": "Model Fallbacks",
"agents.defaults.imageModel.primary": "Image Model",
"agents.defaults.imageModel.fallbacks": "Image Model Fallbacks",
"agents.defaults.humanDelay.mode": "Human Delay Mode",
"agents.defaults.humanDelay.minMs": "Human Delay Min (ms)",
"agents.defaults.humanDelay.maxMs": "Human Delay Max (ms)",
"agents.defaults.cliBackends": "CLI Backends",
"commands.native": "Native Commands",
"commands.nativeSkills": "Native Skill Commands",
"commands.text": "Text Commands",
"commands.bash": "Allow Bash Chat Command",
"commands.bashForegroundMs": "Bash Foreground Window (ms)",
"commands.config": "Allow /config",
"commands.debug": "Allow /debug",
"commands.restart": "Allow Restart",
"commands.useAccessGroups": "Use Access Groups",
"ui.seamColor": "Accent Color",
"browser.controlUrl": "Browser Control URL",
"browser.remoteCdpTimeoutMs": "Remote CDP Timeout (ms)",
"browser.remoteCdpHandshakeTimeoutMs": "Remote CDP Handshake Timeout (ms)",
"session.dmScope": "DM Session Scope",
"session.agentToAgent.maxPingPongTurns": "Agent-to-Agent Ping-Pong Turns",
"messages.ackReaction": "Ack Reaction Emoji",
"messages.ackReactionScope": "Ack Reaction Scope",
"messages.inbound.debounceMs": "Inbound Message Debounce (ms)",
"talk.apiKey": "Talk API Key",
"channels.whatsapp": "WhatsApp",
"channels.telegram": "Telegram",
"channels.telegram.customCommands": "Telegram Custom Commands",
"channels.discord": "Discord",
"channels.slack": "Slack",
"channels.signal": "Signal",
"channels.imessage": "iMessage",
"channels.msteams": "MS Teams",
"channels.telegram.botToken": "Telegram Bot Token",
"channels.telegram.dmPolicy": "Telegram DM Policy",
"channels.telegram.streamMode": "Telegram Draft Stream Mode",
"channels.telegram.draftChunk.minChars": "Telegram Draft Chunk Min Chars",
"channels.telegram.draftChunk.maxChars": "Telegram Draft Chunk Max Chars",
"channels.telegram.draftChunk.breakPreference": "Telegram Draft Chunk Break Preference",
"channels.telegram.retry.attempts": "Telegram Retry Attempts",
"channels.telegram.retry.minDelayMs": "Telegram Retry Min Delay (ms)",
"channels.telegram.retry.maxDelayMs": "Telegram Retry Max Delay (ms)",
"channels.telegram.retry.jitter": "Telegram Retry Jitter",
"channels.telegram.timeoutSeconds": "Telegram API Timeout (seconds)",
"channels.telegram.capabilities.inlineButtons": "Telegram Inline Buttons",
"channels.whatsapp.dmPolicy": "WhatsApp DM Policy",
"channels.whatsapp.selfChatMode": "WhatsApp Self-Phone Mode",
"channels.whatsapp.debounceMs": "WhatsApp Message Debounce (ms)",
"channels.signal.dmPolicy": "Signal DM Policy",
"channels.imessage.dmPolicy": "iMessage DM Policy",
"channels.discord.dm.policy": "Discord DM Policy",
"channels.discord.retry.attempts": "Discord Retry Attempts",
"channels.discord.retry.minDelayMs": "Discord Retry Min Delay (ms)",
"channels.discord.retry.maxDelayMs": "Discord Retry Max Delay (ms)",
"channels.discord.retry.jitter": "Discord Retry Jitter",
"channels.discord.maxLinesPerMessage": "Discord Max Lines Per Message",
"channels.slack.dm.policy": "Slack DM Policy",
"channels.slack.allowBots": "Slack Allow Bot Messages",
"channels.discord.token": "Discord Bot Token",
"channels.slack.botToken": "Slack Bot Token",
"channels.slack.appToken": "Slack App Token",
"channels.slack.userToken": "Slack User Token",
"channels.slack.userTokenReadOnly": "Slack User Token Read Only",
"channels.slack.thread.historyScope": "Slack Thread History Scope",
"channels.slack.thread.inheritParent": "Slack Thread Parent Inheritance",
"channels.signal.account": "Signal Account",
"channels.imessage.cliPath": "iMessage CLI Path",
"plugins.enabled": "Enable Plugins",
"plugins.allow": "Plugin Allowlist",
"plugins.deny": "Plugin Denylist",
"plugins.load.paths": "Plugin Load Paths",
"plugins.entries": "Plugin Entries",
"plugins.entries.*.enabled": "Plugin Enabled",
"plugins.entries.*.config": "Plugin Config",
"plugins.installs": "Plugin Install Records",
"plugins.installs.*.source": "Plugin Install Source",
"plugins.installs.*.spec": "Plugin Install Spec",
"plugins.installs.*.sourcePath": "Plugin Install Source Path",
"plugins.installs.*.installPath": "Plugin Install Path",
"plugins.installs.*.version": "Plugin Install Version",
"plugins.installs.*.installedAt": "Plugin Install Time",
};
const FIELD_HELP: Record<string, string> = {
"gateway.remote.url": "Remote Gateway WebSocket URL (ws:// or wss://).",
"gateway.remote.sshTarget":
"Remote gateway over SSH (tunnels the gateway port to localhost). Format: user@host or user@host:port.",
"gateway.remote.sshIdentity": "Optional SSH identity file path (passed to ssh -i).",
"gateway.auth.token": "Recommended for all gateways; required for non-loopback binds.",
"gateway.auth.password": "Required for Tailscale funnel.",
"gateway.controlUi.basePath":
"Optional URL prefix where the Control UI is served (e.g. /clawdbot).",
"gateway.http.endpoints.chatCompletions.enabled":
"Enable the OpenAI-compatible `POST /v1/chat/completions` endpoint (default: false).",
"gateway.reload.mode": 'Hot reload strategy for config changes ("hybrid" recommended).',
"gateway.reload.debounceMs": "Debounce window (ms) before applying config changes.",
"tools.exec.applyPatch.enabled":
"Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.",
"tools.exec.applyPatch.allowModels":
'Optional allowlist of model ids (e.g. "gpt-5.2" or "openai/gpt-5.2").',
"tools.web.search.enabled": "Enable the web_search tool (requires Brave API key).",
"tools.web.search.provider": 'Search provider (only "brave" supported today).',
"tools.web.search.apiKey": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
"tools.web.search.maxResults": "Default number of results to return (1-10).",
"tools.web.search.timeoutSeconds": "Timeout in seconds for web_search requests.",
"tools.web.search.cacheTtlMinutes": "Cache TTL in minutes for web_search results.",
"tools.web.fetch.enabled": "Enable the web_fetch tool (lightweight HTTP fetch).",
"tools.web.fetch.maxChars": "Max characters returned by web_fetch (truncated).",
"tools.web.fetch.timeoutSeconds": "Timeout in seconds for web_fetch requests.",
"tools.web.fetch.cacheTtlMinutes": "Cache TTL in minutes for web_fetch results.",
"tools.web.fetch.userAgent": "Override User-Agent header for web_fetch requests.",
"tools.web.fetch.readability":
"Use Readability to extract main content from HTML (fallbacks to basic HTML cleanup).",
"tools.web.fetch.firecrawl.enabled": "Enable Firecrawl fallback for web_fetch (if configured).",
"tools.web.fetch.firecrawl.apiKey":
"Firecrawl API key (fallback: FIRECRAWL_API_KEY env var).",
"tools.web.fetch.firecrawl.baseUrl":
"Firecrawl base URL (e.g. https://api.firecrawl.dev or custom endpoint).",
"tools.web.fetch.firecrawl.onlyMainContent":
"When true, Firecrawl returns only the main content (default: true).",
"tools.web.fetch.firecrawl.maxAgeMs":
"Firecrawl maxAge (ms) for cached results when supported by the API.",
"tools.web.fetch.firecrawl.timeoutSeconds":
"Timeout in seconds for Firecrawl requests.",
"channels.slack.allowBots":
"Allow bot-authored messages to trigger Slack replies (default: false).",
"channels.slack.thread.historyScope":
'Scope for Slack thread history context ("thread" isolates per thread; "channel" reuses channel history).',
"channels.slack.thread.inheritParent":
"If true, Slack thread sessions inherit the parent channel transcript (default: false).",
"auth.profiles": "Named auth profiles (provider + mode + optional email).",
"auth.order": "Ordered auth profile IDs per provider (used for automatic failover).",
"auth.cooldowns.billingBackoffHours":
"Base backoff (hours) when a profile fails due to billing/insufficient credits (default: 5).",
"auth.cooldowns.billingBackoffHoursByProvider":
"Optional per-provider overrides for billing backoff (hours).",
"auth.cooldowns.billingMaxHours": "Cap (hours) for billing backoff (default: 24).",
"auth.cooldowns.failureWindowHours": "Failure window (hours) for backoff counters (default: 24).",
"agents.defaults.bootstrapMaxChars":
"Max characters of each workspace bootstrap file injected into the system prompt before truncation (default: 20000).",
"agents.defaults.models": "Configured model catalog (keys are full provider/model IDs).",
"agents.defaults.memorySearch":
"Vector search over MEMORY.md and memory/*.md (per-agent overrides supported).",
"agents.defaults.memorySearch.provider": 'Embedding provider ("openai" or "local").',
"agents.defaults.memorySearch.remote.baseUrl":
"Custom OpenAI-compatible base URL (e.g. for Gemini/OpenRouter proxies).",
"agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",
"agents.defaults.memorySearch.remote.headers":
"Extra headers for remote embeddings (merged; remote overrides OpenAI headers).",
"agents.defaults.memorySearch.local.modelPath":
"Local GGUF model path or hf: URI (node-llama-cpp).",
"agents.defaults.memorySearch.fallback":
'Fallback to OpenAI when local embeddings fail ("openai" or "none").',
"agents.defaults.memorySearch.store.path":
"SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).",
"agents.defaults.memorySearch.sync.onSearch":
"Lazy sync: reindex on first search after a change.",
"agents.defaults.memorySearch.sync.watch": "Watch memory files for changes (chokidar).",
"plugins.enabled": "Enable plugin/extension loading (default: true).",
"plugins.allow": "Optional allowlist of plugin ids; when set, only listed plugins load.",
"plugins.deny": "Optional denylist of plugin ids; deny wins over allowlist.",
"plugins.load.paths": "Additional plugin files or directories to load.",
"plugins.entries": "Per-plugin settings keyed by plugin id (enable/disable + config payloads).",
"plugins.entries.*.enabled": "Overrides plugin enable/disable for this entry (restart required).",
"plugins.entries.*.config": "Plugin-defined config payload (schema is provided by the plugin).",
"plugins.installs":
"CLI-managed install metadata (used by `clawdbot plugins update` to locate install sources).",
"plugins.installs.*.source": 'Install source ("npm", "archive", or "path").',
"plugins.installs.*.spec": "Original npm spec used for install (if source is npm).",
"plugins.installs.*.sourcePath": "Original archive/path used for install (if any).",
"plugins.installs.*.installPath":
"Resolved install directory (usually ~/.clawdbot/extensions/<id>).",
"plugins.installs.*.version": "Version recorded at install time (if available).",
"plugins.installs.*.installedAt": "ISO timestamp of last install/update.",
"agents.defaults.model.primary": "Primary model (provider/model).",
"agents.defaults.model.fallbacks":
"Ordered fallback models (provider/model). Used when the primary model fails.",
"agents.defaults.imageModel.primary":
"Optional image model (provider/model) used when the primary model lacks image input.",
"agents.defaults.imageModel.fallbacks": "Ordered fallback image models (provider/model).",
"agents.defaults.cliBackends": "Optional CLI backends for text-only fallback (claude-cli, etc.).",
"agents.defaults.humanDelay.mode": 'Delay style for block replies ("off", "natural", "custom").',
"agents.defaults.humanDelay.minMs": "Minimum delay in ms for custom humanDelay (default: 800).",
"agents.defaults.humanDelay.maxMs": "Maximum delay in ms for custom humanDelay (default: 2500).",
"commands.native":
"Register native commands with channels that support it (Discord/Slack/Telegram).",
"commands.nativeSkills":
"Register native skill commands (user-invocable skills) with channels that support it.",
"commands.text": "Allow text command parsing (slash commands only).",
"commands.bash":
"Allow bash chat command (`!`; `/bash` alias) to run host shell commands (default: false; requires tools.elevated).",
"commands.bashForegroundMs":
"How long bash waits before backgrounding (default: 2000; 0 backgrounds immediately).",
"commands.config": "Allow /config chat command to read/write config on disk (default: false).",
"commands.debug": "Allow /debug chat command for runtime-only overrides (default: false).",
"commands.restart": "Allow /restart and gateway restart tool actions (default: false).",
"commands.useAccessGroups": "Enforce access-group allowlists/policies for commands.",
"session.dmScope":
'DM session scoping: "main" keeps continuity; "per-peer" or "per-channel-peer" isolates DM history (recommended for shared inboxes).',
"session.identityLinks":
"Map canonical identities to provider-prefixed peer IDs for DM session linking (example: telegram:123456).",
"channels.telegram.configWrites":
"Allow Telegram to write config in response to channel events/commands (default: true).",
"channels.slack.configWrites":
"Allow Slack to write config in response to channel events/commands (default: true).",
"channels.discord.configWrites":
"Allow Discord to write config in response to channel events/commands (default: true).",
"channels.whatsapp.configWrites":
"Allow WhatsApp to write config in response to channel events/commands (default: true).",
"channels.signal.configWrites":
"Allow Signal to write config in response to channel events/commands (default: true).",
"channels.imessage.configWrites":
"Allow iMessage to write config in response to channel events/commands (default: true).",
"channels.msteams.configWrites":
"Allow Microsoft Teams to write config in response to channel events/commands (default: true).",
"channels.discord.commands.native": 'Override native commands for Discord (bool or "auto").',
"channels.discord.commands.nativeSkills":
'Override native skill commands for Discord (bool or "auto").',
"channels.telegram.commands.native": 'Override native commands for Telegram (bool or "auto").',
"channels.telegram.commands.nativeSkills":
'Override native skill commands for Telegram (bool or "auto").',
"channels.slack.commands.native": 'Override native commands for Slack (bool or "auto").',
"channels.slack.commands.nativeSkills":
'Override native skill commands for Slack (bool or "auto").',
"session.agentToAgent.maxPingPongTurns":
"Max reply-back turns between requester and target (05).",
"channels.telegram.customCommands":
"Additional Telegram bot menu commands (merged with native; conflicts ignored).",
"messages.ackReaction": "Emoji reaction used to acknowledge inbound messages (empty disables).",
"messages.ackReactionScope":
'When to send ack reactions ("group-mentions", "group-all", "direct", "all").',
"messages.inbound.debounceMs":
"Debounce window (ms) for batching rapid inbound messages from the same sender (0 to disable).",
"channels.telegram.dmPolicy":
'Direct message access control ("pairing" recommended). "open" requires channels.telegram.allowFrom=["*"].',
"channels.telegram.streamMode":
"Draft streaming mode for Telegram replies (off | partial | block). Separate from block streaming; requires private topics + sendMessageDraft.",
"channels.telegram.draftChunk.minChars":
'Minimum chars before emitting a Telegram draft update when channels.telegram.streamMode="block" (default: 200).',
"channels.telegram.draftChunk.maxChars":
'Target max size for a Telegram draft update chunk when channels.telegram.streamMode="block" (default: 800; clamped to channels.telegram.textChunkLimit).',
"channels.telegram.draftChunk.breakPreference":
"Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence). Default: paragraph.",
"channels.telegram.retry.attempts":
"Max retry attempts for outbound Telegram API calls (default: 3).",
"channels.telegram.retry.minDelayMs": "Minimum retry delay in ms for Telegram outbound calls.",
"channels.telegram.retry.maxDelayMs":
"Maximum retry delay cap in ms for Telegram outbound calls.",
"channels.telegram.retry.jitter": "Jitter factor (0-1) applied to Telegram retry delays.",
"channels.telegram.timeoutSeconds":
"Max seconds before Telegram API requests are aborted (default: 500 per grammY).",
"channels.whatsapp.dmPolicy":
'Direct message access control ("pairing" recommended). "open" requires channels.whatsapp.allowFrom=["*"].',
"channels.whatsapp.selfChatMode": "Same-phone setup (bot uses your personal WhatsApp number).",
"channels.whatsapp.debounceMs":
"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable).",
"channels.signal.dmPolicy":
'Direct message access control ("pairing" recommended). "open" requires channels.signal.allowFrom=["*"].',
"channels.imessage.dmPolicy":
'Direct message access control ("pairing" recommended). "open" requires channels.imessage.allowFrom=["*"].',
"channels.discord.dm.policy":
'Direct message access control ("pairing" recommended). "open" requires channels.discord.dm.allowFrom=["*"].',
"channels.discord.retry.attempts":
"Max retry attempts for outbound Discord API calls (default: 3).",
"channels.discord.retry.minDelayMs": "Minimum retry delay in ms for Discord outbound calls.",
"channels.discord.retry.maxDelayMs": "Maximum retry delay cap in ms for Discord outbound calls.",
"channels.discord.retry.jitter": "Jitter factor (0-1) applied to Discord retry delays.",
"channels.discord.maxLinesPerMessage": "Soft max line count per Discord message (default: 17).",
"channels.slack.dm.policy":
'Direct message access control ("pairing" recommended). "open" requires channels.slack.dm.allowFrom=["*"].',
};
const FIELD_PLACEHOLDERS: Record<string, string> = {
"gateway.remote.url": "ws://host:18789",
"gateway.remote.sshTarget": "user@host",
"gateway.controlUi.basePath": "/clawdbot",
};
const SENSITIVE_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i];
function isSensitivePath(path: string): boolean {
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(path));
}
function buildBaseHints(): ConfigUiHints {
const hints: ConfigUiHints = {};
for (const [group, label] of Object.entries(GROUP_LABELS)) {
hints[group] = {
label,
group: label,
order: GROUP_ORDER[group],
};
}
for (const [path, label] of Object.entries(FIELD_LABELS)) {
const current = hints[path];
hints[path] = current ? { ...current, label } : { label };
}
for (const [path, help] of Object.entries(FIELD_HELP)) {
const current = hints[path];
hints[path] = current ? { ...current, help } : { help };
}
for (const [path, placeholder] of Object.entries(FIELD_PLACEHOLDERS)) {
const current = hints[path];
hints[path] = current ? { ...current, placeholder } : { placeholder };
}
return hints;
}
function applySensitiveHints(hints: ConfigUiHints): ConfigUiHints {
const next = { ...hints };
for (const key of Object.keys(next)) {
if (isSensitivePath(key)) {
next[key] = { ...next[key], sensitive: true };
}
}
return next;
}
function applyPluginHints(hints: ConfigUiHints, plugins: PluginUiMetadata[]): ConfigUiHints {
const next: ConfigUiHints = { ...hints };
for (const plugin of plugins) {
const id = plugin.id.trim();
if (!id) continue;
const name = (plugin.name ?? id).trim() || id;
const basePath = `plugins.entries.${id}`;
next[basePath] = {
...next[basePath],
label: name,
help: plugin.description
? `${plugin.description} (plugin: ${id})`
: `Plugin entry for ${id}.`,
};
next[`${basePath}.enabled`] = {
...next[`${basePath}.enabled`],
label: `Enable ${name}`,
};
next[`${basePath}.config`] = {
...next[`${basePath}.config`],
label: `${name} Config`,
help: `Plugin-defined config payload for ${id}.`,
};
const uiHints = plugin.configUiHints ?? {};
for (const [relPathRaw, hint] of Object.entries(uiHints)) {
const relPath = relPathRaw.trim().replace(/^\./, "");
if (!relPath) continue;
const key = `${basePath}.config.${relPath}`;
next[key] = {
...next[key],
...hint,
};
}
}
return next;
}
function applyChannelHints(hints: ConfigUiHints, channels: ChannelUiMetadata[]): ConfigUiHints {
const next: ConfigUiHints = { ...hints };
for (const channel of channels) {
const id = channel.id.trim();
if (!id) continue;
const basePath = `channels.${id}`;
const current = next[basePath] ?? {};
const label = channel.label?.trim();
const help = channel.description?.trim();
next[basePath] = {
...current,
...(label ? { label } : {}),
...(help ? { help } : {}),
};
}
return next;
}
let cachedBase: ConfigSchemaResponse | null = null;
function buildBaseConfigSchema(): ConfigSchemaResponse {
if (cachedBase) return cachedBase;
const schema = ClawdbotSchema.toJSONSchema({
target: "draft-07",
unrepresentable: "any",
});
schema.title = "ClawdbotConfig";
const hints = applySensitiveHints(buildBaseHints());
const next = {
schema,
uiHints: hints,
version: VERSION,
generatedAt: new Date().toISOString(),
};
cachedBase = next;
return next;
}
export function buildConfigSchema(params?: {
plugins?: PluginUiMetadata[];
channels?: ChannelUiMetadata[];
}): ConfigSchemaResponse {
const base = buildBaseConfigSchema();
const plugins = params?.plugins ?? [];
const channels = params?.channels ?? [];
if (plugins.length === 0 && channels.length === 0) return base;
const merged = applySensitiveHints(
applyChannelHints(applyPluginHints(base.uiHints, plugins), channels),
);
return {
...base,
uiHints: merged,
};
}