import { z } from 'zod'; const telegramSchema = z.object({ bot_token: z.string().min(1, 'Bot token is required'), allowed_chat_ids: z.array(z.number()).min(1, 'At least one chat ID required'), require_mention: z.boolean().default(true), }); const tailscaleSchema = z.object({ /** Enable Tailscale Serve to expose gateway on tailnet. */ serve: z.boolean().default(false), /** Custom hostname for Tailscale Serve. Defaults to machine hostname. */ hostname: z.string().optional(), /** Tailscale Serve HTTPS port. */ port: z.number().default(443), }).default({}); const pairingSchema = z.object({ /** Enable DM pairing codes for unknown senders. */ enabled: z.boolean().default(false), /** Pairing code time-to-live duration (e.g. '5m', '1h'). */ code_ttl: z.string().default('5m'), /** Length of generated pairing codes. */ code_length: z.number().default(6), }).default({}); const wsRateLimitSchema = z.object({ enabled: z.boolean().default(true), capacity: z.number().min(1).max(1000).default(30), refill_per_sec: z.number().min(1).max(1000).default(15), max_violations: z.number().min(1).max(100).default(8), violation_window_ms: z.number().min(1000).max(60000).default(10000), }).default({}); const laneQueueSchema = z.object({ /** Queue behavior for concurrent requests in the same session lane. */ mode: z.enum(['collect', 'followup', 'steer', 'steer_backlog', 'interrupt']).default('collect'), /** Max queued (pending) requests per lane. */ cap: z.number().min(1).max(1000).default(50), /** Overflow strategy when cap is reached. */ overflow: z.enum(['drop_old', 'drop_new']).default('drop_old'), /** Debounce window before starting next queued request (ms). */ debounce_ms: z.number().min(0).max(60_000).default(0), /** Include contextual summary details in overflow rejections. */ summarize_overflow: z.boolean().default(true), /** Optional per-channel/per-session queue policy overrides. */ overrides: z.object({ channels: z.record( z.string(), z.object({ mode: z.enum(['collect', 'followup', 'steer', 'steer_backlog', 'interrupt']).optional(), cap: z.number().min(1).max(1000).optional(), overflow: z.enum(['drop_old', 'drop_new']).optional(), debounce_ms: z.number().min(0).max(60_000).optional(), summarize_overflow: z.boolean().optional(), }), ).default({}), sessions: z.record( z.string(), z.object({ mode: z.enum(['collect', 'followup', 'steer', 'steer_backlog', 'interrupt']).optional(), cap: z.number().min(1).max(1000).optional(), overflow: z.enum(['drop_old', 'drop_new']).optional(), debounce_ms: z.number().min(0).max(60_000).optional(), summarize_overflow: z.boolean().optional(), }), ).default({}), }).default({}), }).default({}); const serverDiscoverySchema = z.object({ /** Enable local-network service discovery (mDNS/Bonjour advertisement). */ enabled: z.boolean().default(false), /** Service instance name advertised on LAN. */ service_name: z.string().min(1).default('flynn-gateway'), /** mDNS service type. */ service_type: z.string().min(1).default('_flynn._tcp'), /** Additional TXT metadata advertised with the service record. */ txt: z.record(z.string(), z.string()).default({}), }).default({}); const serverNodePolicySchema = z.object({ /** Enable node registration/capability RPC surface. */ enabled: z.boolean().default(false), /** Allowed node roles for node.register. */ allowed_roles: z.array(z.string().min(1)).default(['companion']), /** Optional feature gates exposed via system/node capability APIs. */ feature_gates: z.record(z.string(), z.boolean()).default({}), /** Node location access controls. */ location: z.object({ /** Enable node.location.set/get and system.location visibility. */ enabled: z.boolean().default(false), }).default({}), /** Node push registration controls (e.g. APNs for iOS companion). */ push: z.object({ /** Enable node.push_token.set and push summary visibility in system.nodes. */ enabled: z.boolean().default(false), }).default({}), }).default({}); const serverSchema = z.object({ tailscale: tailscaleSchema, localhost: z.boolean().default(true), port: z.number().default(18800), /** Static bearer token for gateway auth. If set, all connections must provide it. */ token: z.string().optional(), /** Trust Tailscale-User-Login header for identity. */ tailscale_identity: z.boolean().default(false), /** Apply token auth to HTTP requests too (not just WebSocket). Default: true when token is set. */ auth_http: z.boolean().default(true), /** Single-client gateway lock. When true, only one WebSocket client can be connected at a time. */ lock: z.boolean().default(false), /** Maximum size (bytes) for inbound HTTP request bodies (webhooks/Gmail push). */ max_request_body_bytes: z.number().min(1024).max(10 * 1024 * 1024).default(1_048_576), /** Per-connection WebSocket ingress rate limit settings. */ ws_rate_limit: wsRateLimitSchema, /** Per-session gateway lane queue behavior. */ queue: laneQueueSchema, /** Optional companion-node registration/capability settings. */ nodes: serverNodePolicySchema, /** Optional Bonjour/mDNS advertisement settings. */ discovery: serverDiscoverySchema, }); /** All supported model provider identifiers. Used by the config schema and TUI autocompletion. */ export const MODEL_PROVIDERS = ['anthropic', 'openai', 'gemini', 'ollama', 'llamacpp', 'openrouter', 'vercel', 'bedrock', 'github', 'zhipuai', 'xai', 'minimax', 'moonshot', 'synthetic'] as const; export type ModelProvider = (typeof MODEL_PROVIDERS)[number]; const modelConfigBaseSchema = z.object({ provider: z.enum(MODEL_PROVIDERS), model: z.string(), endpoint: z.string().optional(), api_key: z.string().optional(), auth_token: z.string().optional(), /** Credential selection strategy for this tier (provider-specific). */ auth_mode: z.enum(['auto', 'api_key', 'oauth']).optional(), /** Use OAuth credential flow (provider-specific). */ use_oauth: z.boolean().optional(), for: z.array(z.string()).optional(), num_gpu: z.number().optional(), context_window: z.number().optional(), supports_audio: z.boolean().optional(), }); const modelConfigSchema = modelConfigBaseSchema.extend({ fallback: modelConfigBaseSchema.optional(), }); const thinkingSchema = z.object({ anthropic: z.object({ budgetTokens: z.number().default(4096), }).default({}), openai: z.object({ reasoningEffort: z.enum(['low', 'medium', 'high']).default('medium'), }).default({}), gemini: z.object({ budgetTokens: z.number().default(4096), }).default({}), }).default({}); const modelsSchema = z.object({ local: modelConfigSchema.optional(), fast: modelConfigSchema.optional(), default: modelConfigSchema, complex: modelConfigSchema.optional(), fallback_chain: z.array(z.string()).default(['anthropic']), local_providers: z.record(z.string(), modelConfigSchema).optional(), thinking: thinkingSchema, }); const backendsSchema = z.object({ default: z.enum(['claude_code', 'opencode', 'codex', 'gemini']).optional(), claude_code: z.object({ enabled: z.boolean().default(false), path: z.string().optional(), args: z.array(z.string()).default([]), timeout_ms: z.number().min(1_000).max(600_000).default(120_000), }).default({ enabled: false }), opencode: z.object({ enabled: z.boolean().default(false), path: z.string().optional(), args: z.array(z.string()).default([]), timeout_ms: z.number().min(1_000).max(600_000).default(120_000), }).default({ enabled: false }), codex: z.object({ enabled: z.boolean().default(false), path: z.string().optional(), args: z.array(z.string()).default([]), timeout_ms: z.number().min(1_000).max(600_000).default(120_000), }).default({ enabled: false }), gemini: z.object({ enabled: z.boolean().default(false), path: z.string().optional(), args: z.array(z.string()).default([]), timeout_ms: z.number().min(1_000).max(600_000).default(120_000), }).default({ enabled: false }), native: z.object({ enabled: z.boolean().default(true), }).default({ enabled: true }), }).default({}); const hooksSchema = z.object({ confirm: z.array(z.string()).default([]), log: z.array(z.string()).default([]), silent: z.array(z.string()).default([]), }).default({}); const skillsLoadSchema = z.object({ /** Enable filesystem watcher for automatic skill reload detection. */ watch: z.boolean().default(false), /** Debounce window for batched watcher events. */ watch_debounce_ms: z.number().min(10).max(10_000).default(250), }).default({}); const skillsShellRunnerGovernanceSchema = z.object({ /** Responsible owner for shell-runner allowlist decisions. */ owner: z.string().min(1).optional(), /** Review cadence for allowlist + rollout status checks. */ review_cadence_days: z.number().min(1).max(90).default(7), /** Minimum success rate required before broader rollout. */ promotion_min_success_rate: z.number().min(0).max(1).default(0.9), }).default({}); const skillsSchema = z.object({ /** Registry catalog source for `flynn skills registry` and install-by-id (file path or HTTPS URL). */ registry_source: z.string().optional(), /** Directory for user-created workspace skills. */ workspace_dir: z.string().optional(), /** Directory for managed (installed) skills. Defaults to ~/.flynn/workspace/skills. */ managed_dir: z.string().optional(), /** Directory for bundled skills shipped with Flynn. */ bundled_dir: z.string().optional(), /** Global policy gate for installer command execution. */ installation_execution: z.enum(['disabled', 'enabled']).default('disabled'), /** Allow use of the shell runner for installer commands. */ allow_shell_runner: z.boolean().default(false), /** Allowlist patterns for shell runner commands (supports '*' wildcard). */ shell_runner_allowlist: z.array(z.string()).default([]), /** Governance controls for shell-runner rollout decisions. */ shell_runner_governance: skillsShellRunnerGovernanceSchema, /** Skills watcher settings. */ load: skillsLoadSchema, }).default({}); const mcpServerSchema = z.object({ name: z.string(), command: z.string(), args: z.array(z.string()).default([]), env: z.record(z.string(), z.string()).optional(), cwd: z.string().optional(), }); const mcpSchema = z.object({ servers: z.array(mcpServerSchema).default([]), }).default({ servers: [] }); const modelTierEnum = z.enum(['fast', 'default', 'complex', 'local']); const cronJobSchema = z.object({ name: z.string().min(1, 'Cron job name is required'), schedule: z.string().min(1, 'Cron schedule is required'), message: z.string().min(1, 'Cron message is required'), output: z.object({ channel: z.string().min(1), peer: z.string().min(1), }), enabled: z.boolean().default(true), timezone: z.string().optional(), model_tier: modelTierEnum.optional(), once_per_local_day: z.boolean().default(false), }); const webhookSchema = z.object({ name: z.string().min(1, 'Webhook name is required'), secret: z.string().optional(), message: z.string().default('{{body}}'), output: z.object({ channel: z.string().min(1), peer: z.string().min(1), }), enabled: z.boolean().default(true), }); const gmailSchema = z.object({ enabled: z.boolean().default(false), credentials_file: z.string().optional(), token_file: z.string().default('~/.config/flynn/gmail-token.json'), /** * Optional Google Cloud Pub/Sub topic for Gmail push notifications. * Format: projects//topics/ * If omitted, push notifications are disabled and Flynn will use polling. */ pubsub_topic: z.string().optional(), /** * Explicitly disable Gmail push watch registration even if pubsub_topic is set. * Useful for environments where Google cannot reach the gateway (e.g. tailnet-only). */ disable_push: z.boolean().default(false), /** * Optional Pub/Sub subscription for pull-based delivery (no inbound webhook required). * Format: projects//subscriptions/ */ pubsub_subscription_id: z.string().optional(), /** How often to pull messages from pubsub_subscription_id (e.g. '60s'). */ pubsub_pull_interval: z.string().default('60s'), /** Max messages to pull per cycle (1..100). */ pubsub_max_messages: z.number().min(1).max(100).default(10), watch_labels: z.array(z.string()).default(['INBOX']), poll_interval: z.string().default('300s'), history_start: z.string().optional(), // ISO date string — only process emails after this date output: z.object({ channel: z.string().min(1), peer: z.string().min(1), }), message: z.string().default('New email from {{from}}: {{subject}}\n\n{{snippet}}'), }).optional(); const heartbeatCheckSchema = z.enum(['gateway', 'model', 'channels', 'memory', 'disk', 'process_memory', 'backup', 'provider_errors']); const heartbeatSchema = z.object({ enabled: z.boolean().default(false), interval: z.string().default('5m'), notify_cooldown: z.string().default('30m'), checks: z.array(heartbeatCheckSchema).default(['gateway', 'model', 'channels', 'memory', 'disk', 'process_memory', 'backup', 'provider_errors']), notify: z.object({ channel: z.string().min(1), peer: z.string().min(1), }).optional(), failure_threshold: z.number().min(1).max(10).default(2), disk_threshold_mb: z.number().min(10).default(100), process_memory_threshold_mb: z.number().min(64).default(1500), backup_failure_threshold: z.number().min(1).max(10).default(1), provider_error_rate_threshold: z.number().min(0).max(1).default(0.5), provider_error_min_calls: z.number().min(1).default(5), }).default({}); const gcalSchema = z.object({ enabled: z.boolean().default(false), credentials_file: z.string().optional(), token_file: z.string().default('~/.config/flynn/gcal-token.json'), calendar_ids: z.array(z.string()).default(['primary']), }).optional(); const gdocsSchema = z.object({ enabled: z.boolean().default(false), credentials_file: z.string().optional(), token_file: z.string().default('~/.config/flynn/gdocs-token.json'), }).optional(); const gdriveSchema = z.object({ enabled: z.boolean().default(false), credentials_file: z.string().optional(), token_file: z.string().default('~/.config/flynn/gdrive-token.json'), }).optional(); const gtasksSchema = z.object({ enabled: z.boolean().default(false), credentials_file: z.string().optional(), token_file: z.string().default('~/.config/flynn/gtasks-token.json'), }).optional(); const dailyBriefingSchema = z.object({ enabled: z.boolean().default(false), name: z.string().min(1).default('daily-briefing'), schedule: z.string().min(1).default('0 8 * * *'), timezone: z.string().optional(), dedupe_per_local_day: z.boolean().default(true), output: z.object({ channel: z.string().min(1), peer: z.string().min(1), }).optional(), prompt: z.string().min(1).default( [ 'Create my daily briefing.', '', 'Use available tools to gather:', '- Today\'s calendar events (calendar.today or calendar.list)', '- Unread or recent important email (gmail.search/gmail.list)', '- Top pending tasks (tasks.list/tasks.lists)', '', 'Output format:', '1) Schedule', '2) Priorities', '3) Risks/Follow-ups', '4) Suggested first actions', '', 'Keep it concise and actionable.', ].join('\n'), ), model_tier: modelTierEnum.optional(), }).default({}); const minioSyncTaskSchema = z.object({ prefix: z.string().min(1, 'MinIO sync prefix is required'), bucket: z.string().optional(), namespace_base: z.string().min(1).default('global/knowledge/minio'), mode: z.enum(['append', 'replace']).default('append'), max_objects: z.number().min(1).max(500).default(20), max_chars_per_object: z.number().min(1).max(1_000_000).default(8_000), force: z.boolean().default(false), }); const minioSyncAutomationSchema = z.object({ enabled: z.boolean().default(false), interval: z.string().default('6h'), run_on_start: z.boolean().default(false), tasks: z.array(minioSyncTaskSchema).default([]), notify: z.object({ channel: z.string().min(1), peer: z.string().min(1), }).optional(), notify_on_success: z.boolean().default(false), }).default({}); const automationDeliveryModeSchema = z.enum(['shared_session', 'isolated_job']); const automationSchema = z.object({ /** Session strategy for automation-triggered runs (cron/webhooks/gmail). */ delivery_mode: automationDeliveryModeSchema.default('shared_session'), cron: z.array(cronJobSchema).default([]), webhooks: z.array(webhookSchema).default([]), gmail: gmailSchema, gcal: gcalSchema, gdocs: gdocsSchema, gdrive: gdriveSchema, gtasks: gtasksSchema, daily_briefing: dailyBriefingSchema, minio_sync: minioSyncAutomationSchema, heartbeat: heartbeatSchema, }).default({}); const truthfulnessModeSchema = z.enum(['strict', 'standard', 'relaxed']); const autonomyLevelSchema = z.enum(['conservative', 'standard', 'autonomous']); const agentsSchema = z.object({ primary_tier: z.enum(['fast', 'default', 'complex', 'local']).default('default'), delegation: z.object({ compaction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'), memory_extraction: z.enum(['fast', 'default', 'complex', 'local']).default('fast'), classification: z.enum(['fast', 'default', 'complex', 'local']).default('fast'), tool_summarisation: z.enum(['fast', 'default', 'complex', 'local']).default('fast'), complex_reasoning: z.enum(['fast', 'default', 'complex', 'local']).default('complex'), }).default({ compaction: 'fast', memory_extraction: 'fast', classification: 'fast', tool_summarisation: 'fast', complex_reasoning: 'complex', }), auto_escalate: z.boolean().default(false), max_delegation_depth: z.number().min(1).max(10).default(3), /** Maximum tool-loop iterations before the agent stops. */ max_iterations: z.number().min(1).max(200).default(200), /** Truthfulness enforcement level: strict | standard | relaxed. */ truthfulness_mode: truthfulnessModeSchema.default('standard'), /** Autonomy level for tool execution: conservative | standard | autonomous. */ autonomy_level: autonomyLevelSchema.default('standard'), }).default({}); const embeddingProviderSchema = z.enum(['openai', 'gemini', 'ollama', 'llamacpp', 'voyage']); const embeddingSchema = z.object({ enabled: z.boolean().default(false), provider: embeddingProviderSchema.default('openai'), model: z.string().default('text-embedding-3-small'), endpoint: z.string().optional(), api_key: z.string().optional(), dimensions: z.number().optional(), chunk_size: z.number().min(64).max(8192).default(512), chunk_overlap: z.number().min(0).max(1024).default(50), top_k: z.number().min(1).max(50).default(5), hybrid_weight: z.number().min(0).max(1).default(0.7), }).default({}); const qmdSchema = z.object({ /** Enable experimental QMD (query markdown database) memory search backend. */ enabled: z.boolean().default(false), /** Maximum number of QMD results returned by memory.search. */ top_k: z.number().min(1).max(50).default(8), /** Minimum relevance score (0-1) for QMD matches. */ min_score: z.number().min(0).max(1).default(0.15), }).default({}); const memorySchema = z.object({ enabled: z.boolean().default(true), dir: z.string().optional(), // Default: ~/.local/share/flynn/memory auto_extract: z.boolean().default(true), injection_strategy: z.enum(['all', 'recent', 'adaptive']).default('all'), max_injection_tokens: z.number().min(100).max(10000).default(2000), max_context_tokens: z.number().min(100).max(10000).default(2000), embedding: embeddingSchema, qmd: qmdSchema, }).default({}); const compactionSchema = z.object({ enabled: z.boolean().default(true), threshold_pct: z.number().min(10).max(100).default(80), keep_turns: z.number().min(1).max(50).default(4), summary_max_tokens: z.number().min(128).max(4096).default(1024), importance_threshold: z.number().min(0).max(1).default(1), proactive: z.object({ enabled: z.boolean().default(false), warn_pct: z.number().min(10).max(100).default(75), checkpoint_pct: z.number().min(10).max(100).default(85), auto_compact_pct: z.number().min(10).max(100).default(95), checkpoint_cooldown_ms: z.number().min(1000).max(86_400_000).default(300_000), memory_namespace: z.string().default('session/checkpoints'), }).default({}), }).default({}); const discordSchema = z.object({ bot_token: z.string().min(1, 'Bot token is required'), allowed_guild_ids: z.array(z.string()).default([]), allowed_channel_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), }).optional(); const slackSchema = z.object({ bot_token: z.string().min(1, 'Bot token is required'), app_token: z.string().min(1, 'App token is required'), signing_secret: z.string().min(1, 'Signing secret is required'), allowed_channel_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(false), }).optional(); const whatsappSchema = z.object({ allowed_numbers: z.array(z.string()).default([]), allowed_group_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), data_dir: z.string().optional(), no_sandbox: z.boolean().default(false), }).optional(); const matrixSchema = z.object({ homeserver_url: z.string().url('Homeserver URL is required'), access_token: z.string().min(1, 'Access token is required'), allowed_room_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), sync_timeout_ms: z.number().min(1000).max(120000).default(30000), display_name: z.string().optional(), }).optional(); const signalSchema = z.object({ account: z.string().min(1, 'Signal account is required'), signal_cli_path: z.string().default('signal-cli'), allowed_numbers: z.array(z.string()).default([]), allowed_group_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), poll_interval_ms: z.number().min(1000).max(60000).default(5000), send_timeout_ms: z.number().min(1000).max(60000).default(15000), }).optional(); const mattermostSchema = z.object({ server_url: z.string().url('Mattermost server_url must be a valid URL'), bot_token: z.string().min(1, 'Mattermost bot_token is required'), allowed_channel_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), poll_interval_ms: z.number().min(1000).max(60000).default(3000), }).optional(); const teamsSchema = z.object({ app_id: z.string().min(1, 'Teams app_id is required'), app_password: z.string().min(1, 'Teams app_password is required'), allowed_conversation_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), }).optional(); const googleChatSchema = z.object({ service_account_key_file: z.string().optional(), service_account_json: z.string().optional(), webhook_token: z.string().optional(), allowed_space_names: z.array(z.string()).default([]), require_mention: z.boolean().default(true), }).optional(); const bluebubblesSchema = z.object({ endpoint: z.string().url('BlueBubbles endpoint must be a valid URL'), api_key: z.string().min(1, 'BlueBubbles api_key is required'), webhook_token: z.string().optional(), allowed_chat_guids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), }).optional(); const lineSchema = z.object({ channel_access_token: z.string().min(1, 'LINE channel_access_token is required'), channel_secret: z.string().min(1, 'LINE channel_secret is required'), allowed_source_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), }).optional(); const feishuSchema = z.object({ app_id: z.string().min(1, 'Feishu app_id is required'), app_secret: z.string().min(1, 'Feishu app_secret is required'), webhook_token: z.string().optional(), allowed_chat_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), endpoint: z.string().url('Feishu endpoint must be a valid URL').optional(), }).optional(); const zaloSchema = z.object({ oa_access_token: z.string().min(1, 'Zalo oa_access_token is required'), endpoint: z.string().url('Zalo endpoint must be a valid URL').optional(), webhook_token: z.string().optional(), allowed_user_ids: z.array(z.string()).default([]), require_mention: z.boolean().default(true), mention_name: z.string().default('flynn'), }).optional(); const browserSchema = z.object({ enabled: z.boolean().default(false), executable_path: z.string().optional(), ws_endpoint: z.string().optional(), headless: z.boolean().default(true), max_pages: z.number().min(1).max(20).default(5), default_timeout: z.number().min(1000).max(120000).default(30000), }).default({}); const processSchema = z.object({ max_concurrent: z.number().min(1).max(50).default(10), max_runtime_minutes: z.number().min(1).max(1440).default(60), buffer_size: z.number().min(1024).max(1048576).default(65536), }).default({}); const k8sSchema = z.object({ enabled: z.boolean().default(false), kubectl_path: z.string().default('kubectl'), default_namespace: z.string().optional(), allowed_namespaces: z.array(z.string()).default([]), }).optional(); const retrySchema = z.object({ enabled: z.boolean().default(true), max_retries: z.number().min(0).max(10).default(3), initial_delay_ms: z.number().min(100).max(60000).default(1000), backoff_multiplier: z.number().min(1).max(5).default(2), max_delay_ms: z.number().min(1000).max(120000).default(30000), }).default({}); const webSearchSchema = z.object({ provider: z.enum(['brave', 'searxng']).default('brave'), api_key: z.string().optional(), endpoint: z.string().optional(), max_results: z.number().min(1).max(20).default(5), }).default({}); const audioProviderSchema = z.object({ type: z.enum(['openai', 'groq', 'ollama', 'llamacpp', 'custom']), endpoint: z.string().optional(), api_key: z.string().optional(), model: z.string().optional(), }); const talkModeSchema = z.object({ enabled: z.boolean().default(false), wake_phrase: z.string().default('hey flynn'), timeout_ms: z.number().min(1000).max(60 * 60 * 1000).default(120000), allow_manual_toggle: z.boolean().default(true), }).default({}); const audioSchema = z.object({ enabled: z.boolean().default(false), provider: audioProviderSchema.optional(), talk_mode: talkModeSchema, }).default({}); // ── Tool policy schemas ────────────────────────────────────────────── const toolProfileEnum = z.enum(['minimal', 'messaging', 'coding', 'full']); const toolOverrideSchema = z.object({ profile: toolProfileEnum.optional(), allow: z.array(z.string()).default([]), deny: z.array(z.string()).default([]), }).default({}); const toolsSchema = z.object({ profile: toolProfileEnum.default('full'), allow: z.array(z.string()).default([]), deny: z.array(z.string()).default([]), agents: z.record(z.string(), toolOverrideSchema).default({}), providers: z.record(z.string(), toolOverrideSchema).default({}), }).default({}); // ── Sandbox schemas ─────────────────────────────────────────────────── const sandboxSchema = z.object({ enabled: z.boolean().default(false), image: z.string().default('node:22-slim'), workspace_dir: z.string().default('/workspace'), network: z.enum(['none', 'bridge', 'host']).default('none'), memory_limit: z.string().default('512m'), cpu_limit: z.string().default('1.0'), timeout_seconds: z.number().min(10).max(3600).default(300), }).default({}); // ── Agent config + routing schemas ──────────────────────────────────── const agentConfigEntrySchema = z.object({ system_prompt: z.string().optional(), model_tier: modelTierEnum.optional(), backend: z.enum(['native', 'claude_code', 'opencode', 'codex', 'gemini']).optional(), tool_profile: toolProfileEnum.optional(), tool_overrides: toolOverrideSchema.optional(), sandbox: z.boolean().default(false), }); const agentConfigsSchema = z.record(z.string(), agentConfigEntrySchema).default({}); const routingSchema = z.object({ default_agent: z.string().optional(), channels: z.record(z.string(), z.string()).default({}), senders: z.record(z.string(), z.string()).default({}), }).default({}); const intentTargetTypeSchema = z.enum(['agent', 'skill']); const intentRuleSchema = z.object({ name: z.string().min(1), patterns: z.array(z.string().min(1)).min(1), target: z.object({ type: intentTargetTypeSchema, name: z.string().min(1), }), priority: z.number().default(0), enabled: z.boolean().default(true), }); const intentsSchema = z.object({ enabled: z.boolean().default(false), match_threshold: z.number().min(0).max(1).default(0.7), rules: z.array(intentRuleSchema).default([]), }).default({}); const routingPolicySchema = z.object({ enabled: z.boolean().default(false), fast_path_threshold: z.number().min(0).max(1).default(0.85), llm_threshold: z.number().min(0).max(1).default(0.5), default_path: z.enum(['fast', 'llm']).default('llm'), }).default({}); const contextLevelSchema = z.enum(['minimal', 'normal', 'detailed', 'debug']); const promptSchema = z.object({ /** Additional directories to search for prompt template files. */ search_dirs: z.array(z.string()).default([]), /** Extra named sections to include in the system prompt. */ extra_sections: z.array(z.object({ name: z.string(), content: z.string(), })).default([]), /** Prompt context depth control: minimal | normal | detailed | debug. */ context_level: contextLevelSchema.default('normal'), }).default({}); const sessionsSchema = z.object({ ttl: z.string().default('30d'), end_summary: z.object({ enabled: z.boolean().default(false), tier: modelTierEnum.default('fast'), max_messages: z.number().min(2).max(500).default(50), max_input_chars: z.number().min(500).max(200000).default(20000), max_tokens: z.number().min(64).max(4096).default(512), write_to_memory: z.boolean().default(true), memory_namespace: z.string().default('session/summaries'), }).default({}), }).default({}); const backupSchema = z.object({ enabled: z.boolean().default(false), schedule: z.string().optional(), interval: z.string().default('24h'), run_on_start: z.boolean().default(false), notify: z.object({ channel: z.string().min(1), peer: z.string().min(1), }).optional(), failure_threshold: z.number().min(1).max(10).default(1), notify_recovery: z.boolean().default(true), local_dir: z.string().default('~/.local/share/flynn/backups'), include_vectors: z.boolean().default(true), minio: z.object({ enabled: z.boolean().default(false), endpoint: z.string().optional(), access_key: z.string().optional(), secret_key: z.string().optional(), bucket: z.string().optional(), prefix: z.string().default('flynn'), secure: z.boolean().default(true), }).default({}), }).default({}); const historyIndexSchema = z.object({ enabled: z.boolean().default(false), max_keywords: z.number().min(1).max(20).default(8), search_limit: z.number().min(1).max(100).default(10), min_score: z.number().min(0).max(1).default(0.15), routing_boost: z.number().min(0).max(0.2).default(0.05), }).default({}); const logLevelSchema = z.enum(['debug', 'info', 'warn', 'error', 'silent']).default('info'); const auditLevelSchema = z.enum(['debug', 'info', 'warn', 'error']).default('debug'); const auditSchema = z.object({ enabled: z.boolean().default(true), path: z.string().default('~/.local/share/flynn/audit.log'), max_size_mb: z.number().min(1).max(1000).default(10), keep_days: z.number().min(1).max(365).default(30), levels: z.object({ tools: auditLevelSchema.default('debug'), sessions: auditLevelSchema.default('debug'), automation: auditLevelSchema.default('debug'), }).default({}), }).default({}); export const configSchema = z.object({ log_level: logLevelSchema, audit: auditSchema, telegram: telegramSchema.optional(), discord: discordSchema, slack: slackSchema, whatsapp: whatsappSchema, matrix: matrixSchema, signal: signalSchema, mattermost: mattermostSchema, teams: teamsSchema, google_chat: googleChatSchema, bluebubbles: bluebubblesSchema, line: lineSchema, feishu: feishuSchema, zalo: zaloSchema, server: serverSchema.default({}), models: modelsSchema, backends: backendsSchema.default({}), hooks: hooksSchema.default({}), skills: skillsSchema.default({}), mcp: mcpSchema.default({ servers: [] }), automation: automationSchema, agents: agentsSchema, compaction: compactionSchema, memory: memorySchema, process: processSchema, browser: browserSchema, k8s: k8sSchema, retry: retrySchema, web_search: webSearchSchema, audio: audioSchema, prompt: promptSchema, tools: toolsSchema, sandbox: sandboxSchema, agent_configs: agentConfigsSchema, routing: routingSchema, intents: intentsSchema, routing_policy: routingPolicySchema, history_index: historyIndexSchema, sessions: sessionsSchema, backup: backupSchema, pairing: pairingSchema, }); export type Config = z.infer; export type TelegramConfig = z.infer; export type ModelConfig = z.infer; export type CronJobConfig = z.infer; export type WebhookConfig = z.infer; export type GmailConfig = z.infer; export type AgentsConfig = z.infer; export type CompactionConfig = z.infer; export type MemoryConfig = z.infer; export type WebSearchConfig = z.infer; export type AudioConfig = z.infer; export type ProcessConfig = z.infer; export type BrowserConfig = z.infer; export type K8sConfig = z.infer; export type DiscordConfig = z.infer; export type SlackConfig = z.infer; export type WhatsAppConfig = z.infer; export type MatrixConfig = z.infer; export type SignalConfig = z.infer; export type MattermostConfig = z.infer; export type TeamsConfig = z.infer; export type GoogleChatConfig = z.infer; export type BlueBubblesConfig = z.infer; export type RetryPolicyConfig = z.infer; export type ContextLevel = z.infer; export type PromptConfig = z.infer; export type ToolProfile = z.infer; export type ToolOverrideConfig = z.infer; export type ToolsConfig = z.infer; export type SandboxConfig = z.infer; export type AgentConfigEntry = z.infer; export type RoutingConfig = z.infer; export type IntentTargetType = z.infer; export type IntentRuleConfig = z.infer; export type IntentsConfig = z.infer; export type RoutingPolicyConfig = z.infer; export type HistoryIndexConfig = z.infer; export type ServerConfig = z.infer; export type SessionsConfig = z.infer; export type BackupConfig = z.infer; export type ThinkingConfig = z.infer; export type HeartbeatConfig = z.infer; export type HeartbeatCheck = z.infer; export type EmbeddingConfig = z.infer; export type EmbeddingProvider = z.infer; export type QmdConfig = z.infer; export type GcalConfig = z.infer; export type GdocsConfig = z.infer; export type GdriveConfig = z.infer; export type GtasksConfig = z.infer; export type DailyBriefingConfig = z.infer; export type MinioSyncTaskConfig = z.infer; export type MinioSyncAutomationConfig = z.infer; export type AutomationDeliveryMode = z.infer; export type PairingCodeConfig = z.infer; export type LogLevel = z.infer; export type AuditConfig = z.infer; export type AuditLevel = z.infer; export type TruthfulnessMode = z.infer; export type AutonomyLevel = z.infer;