Skip to main content

openrustclaw_core/
config.rs

1//! Configuration structs for OpenRustClaw.
2//!
3//! Maps to `config/default.toml` and environment variable overrides.
4//! Uses the `config` crate for layered configuration loading.
5
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9/// Root configuration for the entire OpenRustClaw system.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AppConfig {
12    pub gateway: GatewayConfig,
13    pub database: DatabaseConfig,
14    pub providers: ProvidersConfig,
15    #[serde(default)]
16    pub external_backends: ExternalBackendsConfig,
17    pub memory: MemoryConfig,
18    pub session_routing: SessionRoutingConfig,
19    pub scheduler: SchedulerConfig,
20    pub security: SecurityConfig,
21    pub sidecar: SidecarConfig,
22    pub observability: ObservabilityConfig,
23    pub channels: ChannelsConfig,
24    pub voice: VoiceConfig,
25    pub skills: Option<SkillsConfig>,
26}
27
28/// Session-routing policy configuration.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SessionRoutingConfig {
31    #[serde(default = "default_direct_strategy")]
32    pub direct_strategy: String,
33    #[serde(default = "default_group_strategy")]
34    pub group_strategy: String,
35    #[serde(default = "default_thread_overrides_channel")]
36    pub thread_overrides_channel: bool,
37    #[serde(default = "default_pairing_approval_required")]
38    pub pairing_approval_required: bool,
39    #[serde(default = "default_group_activation_mode")]
40    pub default_group_activation: String,
41    #[serde(default = "default_send_mode")]
42    pub default_send_mode: String,
43    #[serde(default = "default_chunk_chars")]
44    pub default_chunk_chars: usize,
45    #[serde(default)]
46    pub default_chunk_delay_ms: u64,
47}
48
49fn default_direct_strategy() -> String {
50    "shared_main".to_string()
51}
52
53fn default_group_strategy() -> String {
54    "isolated".to_string()
55}
56
57fn default_thread_overrides_channel() -> bool {
58    true
59}
60
61fn default_pairing_approval_required() -> bool {
62    false
63}
64
65fn default_group_activation_mode() -> String {
66    "mention".to_string()
67}
68
69fn default_send_mode() -> String {
70    "blocks".to_string()
71}
72
73fn default_chunk_chars() -> usize {
74    1600
75}
76
77fn default_true() -> bool {
78    true
79}
80
81/// Gateway (WebSocket server) configuration.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct GatewayConfig {
84    #[serde(default = "default_gateway_network_mode")]
85    pub network_mode: String,
86    pub host: String,
87    pub port: u16,
88    pub allowed_origins: Vec<String>,
89}
90
91fn default_gateway_network_mode() -> String {
92    "loopback".to_string()
93}
94
95/// Database configuration.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct DatabaseConfig {
98    pub url: String,
99    pub wal_mode: bool,
100    pub max_connections: u32,
101}
102
103/// Provider configuration container.
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ProvidersConfig {
106    pub default_provider: String,
107    pub fallback_chain: Vec<String>,
108    #[serde(default)]
109    pub control_plane_provider: Option<String>,
110    #[serde(default)]
111    pub control_plane_fallback_chain: Vec<String>,
112    pub anthropic: AnthropicConfig,
113    pub openai: OpenAiConfig,
114    pub openrouter: OpenRouterConfig,
115    pub ollama: OllamaConfig,
116    #[serde(default)]
117    pub gemini: GeminiConfig,
118}
119
120/// Anthropic provider configuration.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AnthropicConfig {
123    pub model: String,
124    #[serde(default)]
125    pub api_key_env: Option<String>,
126    pub api_version: String,
127    pub strict_tools: bool,
128    pub streaming_tool_deltas: bool,
129}
130
131/// OpenAI provider configuration.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct OpenAiConfig {
134    pub model: String,
135    #[serde(default = "default_codex_model")]
136    pub codex_model: String,
137    #[serde(default)]
138    pub api_key_env: Option<String>,
139    pub use_responses_api: bool,
140    pub strict_tools: bool,
141}
142
143fn default_codex_model() -> String {
144    "gpt-5.3-codex".to_string()
145}
146
147/// OpenRouter provider configuration.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct OpenRouterConfig {
150    pub model: String,
151    #[serde(default)]
152    pub api_key_env: Option<String>,
153    pub route_strategy: String,
154}
155
156/// Ollama (local model) configuration.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct OllamaConfig {
159    pub base_url: String,
160    pub model: String,
161}
162
163/// Gemini provider configuration.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct GeminiConfig {
166    pub model: String,
167    #[serde(default)]
168    pub api_key_env: Option<String>,
169    #[serde(default)]
170    pub base_url: Option<String>,
171}
172
173impl Default for GeminiConfig {
174    fn default() -> Self {
175        Self {
176            model: "gemini-1.5-pro".to_string(),
177            api_key_env: Some("GEMINI_API_KEY".to_string()),
178            base_url: None,
179        }
180    }
181}
182
183/// Governance policy for optional external execution backends.
184#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185pub struct ExternalBackendsConfig {
186    #[serde(default = "default_allowed_external_backends")]
187    pub allowed_backends: Vec<String>,
188    #[serde(default = "default_true")]
189    pub allow_local_cli_wrappers: bool,
190    #[serde(default)]
191    pub allow_cloud_agent_execution: bool,
192    #[serde(default = "default_external_backend_audit_log_path")]
193    pub audit_log_path: String,
194    #[serde(default = "default_external_backend_env_allowlist")]
195    pub command_env_allowlist: Vec<String>,
196}
197
198fn default_allowed_external_backends() -> Vec<String> {
199    vec![
200        "agent_browser_cli".to_string(),
201        "claude_code".to_string(),
202        "codex".to_string(),
203        "gemini_cli".to_string(),
204    ]
205}
206
207fn default_external_backend_audit_log_path() -> String {
208    ".claw/control/external-backends-audit.jsonl".to_string()
209}
210
211fn default_external_backend_env_allowlist() -> Vec<String> {
212    vec![
213        "PATH".to_string(),
214        "HOME".to_string(),
215        "USER".to_string(),
216        "USERNAME".to_string(),
217        "TMPDIR".to_string(),
218        "TMP".to_string(),
219        "TEMP".to_string(),
220        "LANG".to_string(),
221        "LC_ALL".to_string(),
222        "SSL_CERT_FILE".to_string(),
223        "SSL_CERT_DIR".to_string(),
224        "XDG_RUNTIME_DIR".to_string(),
225        "XDG_CACHE_HOME".to_string(),
226        "XDG_CONFIG_HOME".to_string(),
227    ]
228}
229
230/// Memory system configuration.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct MemoryConfig {
233    pub core_memory_max_tokens: usize,
234    pub core_memory_max_entries: usize,
235    pub embedding_concurrency: usize,
236    pub dedupe_cosine_threshold: f32,
237    pub decay_half_life_days: f64,
238    pub ttl: MemoryTtlConfig,
239    pub consolidation: ConsolidationConfig,
240}
241
242/// Memory TTL (time-to-live) configuration.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct MemoryTtlConfig {
245    /// Days until episodic memories expire (0 = never).
246    pub episodic_days: u64,
247    /// Days until semantic memories expire (0 = never).
248    pub semantic_days: u64,
249    /// Days until procedural memories expire (0 = never).
250    pub procedural_days: u64,
251}
252
253/// Memory consolidation configuration.
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct ConsolidationConfig {
256    pub enabled: bool,
257    pub threshold_entries: usize,
258    pub schedule_interval_hours: u64,
259}
260
261/// Scheduler configuration.
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct SchedulerConfig {
264    pub poll_interval_ms: u64,
265    pub lease_duration_secs: u64,
266    pub max_retries: u32,
267    pub base_retry_delay_secs: u64,
268    pub max_retry_delay_secs: u64,
269}
270
271/// Security configuration.
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct SecurityConfig {
274    pub require_auth: bool,
275    pub origin_validation: bool,
276    #[serde(default)]
277    pub control_api_token_env: Option<String>,
278    #[serde(default)]
279    pub trusted_proxy_token_env: Option<String>,
280    pub prompt_injection_defense: bool,
281    pub skill_signature_required: bool,
282    pub skill_verifying_key: Option<String>,
283}
284
285/// Python sidecar configuration.
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct SidecarConfig {
288    pub grpc_port: u16,
289    pub python_path: String,
290    #[serde(default)]
291    pub role: SidecarRole,
292    pub auto_start: bool,
293    pub restart_on_crash: bool,
294}
295
296impl SidecarConfig {
297    pub fn supports_compat_dispatch(&self) -> bool {
298        matches!(self.role, SidecarRole::Compatibility)
299    }
300
301    pub fn supports_experimental_lane(&self) -> bool {
302        matches!(self.role, SidecarRole::Experimental)
303    }
304
305    pub fn is_disabled(&self) -> bool {
306        matches!(self.role, SidecarRole::Disabled)
307    }
308}
309
310/// The allowed role of the optional Python sidecar.
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
312#[serde(rename_all = "snake_case")]
313pub enum SidecarRole {
314    /// Optional bounded compatibility bridge for legacy workflow execution only.
315    #[default]
316    Compatibility,
317    /// Experimental LangGraph lane for prototyping and evaluation only.
318    Experimental,
319    /// Fully disabled; the sidecar should not be started or used for dispatch.
320    Disabled,
321}
322
323/// Observability configuration.
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct ObservabilityConfig {
326    pub langsmith_enabled: bool,
327    pub tracing_enabled: bool,
328    pub metrics_enabled: bool,
329    pub metrics_port: u16,
330}
331
332/// Voice runtime configuration.
333#[derive(Debug, Clone, Serialize, Deserialize, Default)]
334pub struct VoiceConfig {
335    #[serde(default)]
336    pub enabled: bool,
337    #[serde(default)]
338    pub wake_word: VoiceWakeWordConfig,
339    #[serde(default)]
340    pub stt: VoiceSttRuntimeConfig,
341    #[serde(default)]
342    pub tts: VoiceTtsRuntimeConfig,
343    #[serde(default)]
344    pub talk_mode: VoiceTalkModeRuntimeConfig,
345}
346
347/// Wake-word configuration for voice flows.
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct VoiceWakeWordConfig {
350    #[serde(default = "default_true")]
351    pub enabled: bool,
352    #[serde(default)]
353    pub model_path: Option<String>,
354    #[serde(default = "default_wake_word_sensitivity")]
355    pub sensitivity: f32,
356}
357
358impl Default for VoiceWakeWordConfig {
359    fn default() -> Self {
360        Self {
361            enabled: true,
362            model_path: None,
363            sensitivity: default_wake_word_sensitivity(),
364        }
365    }
366}
367
368fn default_wake_word_sensitivity() -> f32 {
369    0.7
370}
371
372/// Speech-to-text runtime configuration.
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct VoiceSttRuntimeConfig {
375    #[serde(default = "default_voice_stt_provider")]
376    pub provider: String,
377    #[serde(default = "default_voice_stt_model")]
378    pub model: String,
379    #[serde(default = "default_voice_language")]
380    pub language: String,
381    #[serde(default)]
382    pub api_base_url: Option<String>,
383    #[serde(default)]
384    pub api_key_env: Option<String>,
385    #[serde(default)]
386    pub prompt: Option<String>,
387    #[serde(default)]
388    pub transcribe_inbound_notes: bool,
389    #[serde(default)]
390    pub download_dir: Option<String>,
391    #[serde(default = "default_voice_max_audio_bytes")]
392    pub max_audio_bytes: usize,
393    #[serde(default = "default_voice_timeout_secs")]
394    pub timeout_secs: u64,
395}
396
397impl Default for VoiceSttRuntimeConfig {
398    fn default() -> Self {
399        Self {
400            provider: default_voice_stt_provider(),
401            model: default_voice_stt_model(),
402            language: default_voice_language(),
403            api_base_url: None,
404            api_key_env: None,
405            prompt: None,
406            transcribe_inbound_notes: false,
407            download_dir: None,
408            max_audio_bytes: default_voice_max_audio_bytes(),
409            timeout_secs: default_voice_timeout_secs(),
410        }
411    }
412}
413
414fn default_voice_stt_provider() -> String {
415    "openai".to_string()
416}
417
418fn default_voice_stt_model() -> String {
419    "whisper-1".to_string()
420}
421
422fn default_voice_language() -> String {
423    "auto".to_string()
424}
425
426fn default_voice_max_audio_bytes() -> usize {
427    25 * 1024 * 1024
428}
429
430fn default_voice_timeout_secs() -> u64 {
431    60
432}
433
434/// Text-to-speech runtime configuration.
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct VoiceTtsRuntimeConfig {
437    #[serde(default = "default_voice_tts_provider")]
438    pub provider: String,
439    #[serde(default = "default_voice_tts_model")]
440    pub model: String,
441    #[serde(default = "default_voice_tts_voice")]
442    pub voice: String,
443    #[serde(default)]
444    pub api_base_url: Option<String>,
445    #[serde(default)]
446    pub api_key_env: Option<String>,
447}
448
449impl Default for VoiceTtsRuntimeConfig {
450    fn default() -> Self {
451        Self {
452            provider: default_voice_tts_provider(),
453            model: default_voice_tts_model(),
454            voice: default_voice_tts_voice(),
455            api_base_url: None,
456            api_key_env: None,
457        }
458    }
459}
460
461fn default_voice_tts_provider() -> String {
462    "openai".to_string()
463}
464
465fn default_voice_tts_model() -> String {
466    "tts-1".to_string()
467}
468
469fn default_voice_tts_voice() -> String {
470    "alloy".to_string()
471}
472
473/// Continuous talk-mode configuration.
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct VoiceTalkModeRuntimeConfig {
476    #[serde(default = "default_true")]
477    pub enabled: bool,
478    #[serde(default = "default_talk_timeout_secs")]
479    pub timeout_secs: u64,
480}
481
482impl Default for VoiceTalkModeRuntimeConfig {
483    fn default() -> Self {
484        Self {
485            enabled: true,
486            timeout_secs: default_talk_timeout_secs(),
487        }
488    }
489}
490
491fn default_talk_timeout_secs() -> u64 {
492    30
493}
494
495/// Channel runtime resilience configuration.
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct ChannelRuntimeConfig {
498    #[serde(default = "default_true")]
499    pub health_monitor_enabled: bool,
500    #[serde(default = "default_channel_probe_interval_secs")]
501    pub probe_interval_secs: u64,
502    #[serde(default)]
503    pub auto_restart_on_failure: bool,
504    #[serde(default = "default_channel_failure_threshold")]
505    pub failure_threshold: usize,
506}
507
508impl Default for ChannelRuntimeConfig {
509    fn default() -> Self {
510        Self {
511            health_monitor_enabled: true,
512            probe_interval_secs: default_channel_probe_interval_secs(),
513            auto_restart_on_failure: false,
514            failure_threshold: default_channel_failure_threshold(),
515        }
516    }
517}
518
519fn default_channel_probe_interval_secs() -> u64 {
520    300
521}
522
523fn default_channel_failure_threshold() -> usize {
524    3
525}
526
527/// Channel integrations configuration.
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct ChannelsConfig {
530    #[serde(default)]
531    pub runtime: ChannelRuntimeConfig,
532    pub telegram: TelegramConfig,
533    pub discord: DiscordConfig,
534    pub slack: SlackConfig,
535    pub whatsapp: WhatsAppConfig,
536    pub teams: TeamsConfig,
537    pub mattermost: MattermostConfig,
538    pub google_chat: GoogleChatConfig,
539    pub google_meet: GoogleMeetConfig,
540    pub gmail_pubsub: GmailPubSubConfig,
541    pub signal: SignalConfig,
542    pub matrix: MatrixConfig,
543    pub x: XConfig,
544    pub twilio: TwilioConfig,
545    pub meta: MetaConfig,
546    pub imessage: IMessageConfig,
547    pub line: LineConfig,
548    pub viber: ViberConfig,
549    pub wechat: WeChatConfig,
550}
551
552/// Mattermost bot / slash-command configuration.
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct MattermostConfig {
555    pub enabled: bool,
556    /// Mattermost server base URL (e.g. "<https://chat.example.com>")
557    pub server_url: String,
558    /// Personal access token or bot token for REST API calls
559    pub bot_token: String,
560    /// Webhook path for incoming slash commands / outgoing webhooks
561    pub webhook_path: String,
562    /// Optional shared token used to validate incoming slash-command / webhook payloads
563    pub webhook_token: Option<String>,
564    /// Optional bot username used for mention detection
565    pub bot_username: Option<String>,
566    /// Allowed Mattermost user IDs or usernames
567    pub allowlist: Vec<String>,
568    /// Allowed Mattermost channel IDs (empty = all)
569    pub allowed_channels: Vec<String>,
570    /// Rate limit for outgoing REST API requests per second
571    pub rate_limit_requests_per_second: u32,
572}
573
574/// Skills/Plugins configuration.
575#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct SkillsConfig {
577    /// ClawHub registry URL
578    pub registry_url: Option<String>,
579    /// Auto-update skills on startup
580    pub auto_update: Option<bool>,
581    /// Additional skill directories
582    pub skill_dirs: Option<Vec<String>>,
583}
584
585/// Telegram bot configuration.
586#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct TelegramConfig {
588    pub enabled: bool,
589    pub token: String,
590    #[serde(default)]
591    pub api_base_url: Option<String>,
592    pub mode: TelegramMode,
593    pub webhook_url: Option<String>,
594    pub webhook_port: Option<u16>,
595    pub allowed_users: Vec<String>,
596    pub rate_limit_per_second: u32,
597}
598
599/// Telegram connection mode.
600#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(rename_all = "snake_case")]
602pub enum TelegramMode {
603    Polling,
604    Webhook,
605}
606
607/// Discord bot configuration.
608#[derive(Debug, Clone, Serialize, Deserialize)]
609pub struct DiscordConfig {
610    pub enabled: bool,
611    pub token: String,
612    pub application_id: String,
613    #[serde(default)]
614    pub interaction_public_key: Option<String>,
615    #[serde(default)]
616    pub api_base_url: Option<String>,
617    #[serde(default)]
618    pub attachment_download_dir: Option<String>,
619    pub rate_limit_requests_per_second: u32,
620    pub allowed_guilds: Vec<String>,
621    pub allowed_channels: Vec<String>,
622    pub dm_enabled: bool,
623}
624
625/// Slack app configuration.
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct SlackConfig {
628    pub enabled: bool,
629    pub token: String,
630    #[serde(default)]
631    pub api_base_url: Option<String>,
632    pub app_token: Option<String>,
633    pub signing_secret: Option<String>,
634    pub mode: SlackMode,
635    pub socket_mode: bool,
636    pub rate_limit_requests_per_second: u32,
637    pub allowed_workspaces: Vec<String>,
638    pub app_home_enabled: bool,
639}
640
641/// Slack connection mode.
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
643#[serde(rename_all = "snake_case")]
644pub enum SlackMode {
645    Http,
646    SocketMode,
647}
648
649/// WhatsApp Web configuration.
650#[derive(Debug, Clone, Serialize, Deserialize)]
651pub struct WhatsAppConfig {
652    pub enabled: bool,
653    /// Path to store session credentials
654    pub session_path: String,
655    /// Use pairing code instead of QR code
656    pub pairing_mode: bool,
657    /// Allowed phone numbers for DMs (empty = all allowed)
658    pub allowlist: Vec<String>,
659    /// Optional webhook URL for notifications
660    pub webhook_url: Option<String>,
661    /// Path to the Baileys bridge script
662    pub bridge_path: String,
663    /// Rate limit for outgoing messages per second
664    pub rate_limit_per_second: u32,
665    /// Maximum reconnection attempts
666    pub max_reconnect_attempts: u32,
667    /// Initial reconnection delay in seconds (increases with backoff)
668    pub reconnect_delay_secs: u64,
669}
670
671/// Microsoft Teams bot configuration.
672#[derive(Debug, Clone, Serialize, Deserialize)]
673pub struct TeamsConfig {
674    pub enabled: bool,
675    /// Microsoft App ID (from Azure Bot registration)
676    pub app_id: String,
677    /// Microsoft App Password (client secret)
678    pub app_password: String,
679    /// Tenant ID for single-tenant apps (None for multi-tenant)
680    pub tenant_id: Option<String>,
681    /// Webhook path for incoming messages
682    pub webhook_path: String,
683    /// List of allowed user emails or AAD object IDs
684    pub allowlist: Vec<String>,
685    /// Group policy for mentions
686    pub group_policy: TeamsGroupPolicy,
687    /// Rate limit for sending messages
688    pub rate_limit_requests_per_second: u32,
689    /// Enable Adaptive Cards support
690    pub adaptive_cards_enabled: bool,
691    /// Optional local download root for inbound Teams attachments
692    #[serde(default)]
693    pub attachment_download_dir: Option<String>,
694}
695
696/// Group policy for Microsoft Teams mentions.
697#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
698#[serde(rename_all = "snake_case")]
699pub enum TeamsGroupPolicy {
700    /// Bot responds only when @mentioned
701    Mention,
702    /// Bot responds to all messages in the channel
703    Open,
704}
705
706/// Google Chat bot configuration.
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct GoogleChatConfig {
709    pub enabled: bool,
710    /// Path to service account JSON key file
711    pub service_account_key: String,
712    /// Google Cloud project ID
713    pub project_id: String,
714    /// Webhook URL for receiving messages (if using HTTP push)
715    pub webhook_url: Option<String>,
716    /// Pub/Sub subscription name (if using Pub/Sub)
717    pub pubsub_subscription: Option<String>,
718    /// List of allowed user emails or Google Workspace user IDs
719    pub allowlist: Vec<String>,
720    /// List of allowed space IDs (empty = all spaces)
721    pub allowed_spaces: Vec<String>,
722    /// Rate limit for sending messages
723    pub rate_limit_requests_per_second: u32,
724    /// Enable Card-based responses
725    pub cards_enabled: bool,
726    /// Optional local download root for inbound Google Chat attachments
727    #[serde(default)]
728    pub attachment_download_dir: Option<String>,
729    /// Response mode for the bot
730    pub response_mode: GoogleChatResponseMode,
731}
732
733/// Response mode for Google Chat bot.
734#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
735#[serde(rename_all = "snake_case")]
736pub enum GoogleChatResponseMode {
737    /// Bot responds only when @mentioned
738    Mention,
739    /// Bot responds to all messages in the space
740    Open,
741    /// Bot only responds to slash commands
742    SlashCommands,
743}
744
745/// Google Meet integration configuration.
746#[derive(Debug, Clone, Serialize, Deserialize)]
747pub struct GoogleMeetConfig {
748    pub enabled: bool,
749    /// Path to service account JSON key file, or `token:<value>`, or `env:VAR`
750    pub service_account_key_path: String,
751    /// Delegated Google Workspace user email for Meet API access
752    pub delegated_user_email: String,
753    /// Webhook path for Google Workspace Events / Pub/Sub push delivery
754    pub webhook_path: String,
755    /// Optional space allowlist (empty = all spaces)
756    pub allowed_spaces: Vec<String>,
757    /// Rate limit for Meet API requests per second
758    pub rate_limit_requests_per_second: u32,
759    /// Optional override for the Google Meet API base URL
760    pub api_base_url: Option<String>,
761    /// Optional override for the Google OAuth token URL
762    pub oauth_token_url: Option<String>,
763    /// Additional OAuth scopes to request beyond the built-in Meet defaults
764    pub additional_scopes: Vec<String>,
765    /// Whether transcript events should hydrate transcript entries eagerly
766    pub hydrate_transcript_events: bool,
767}
768
769/// Gmail Pub/Sub configuration.
770#[derive(Debug, Clone, Serialize, Deserialize)]
771pub struct GmailPubSubConfig {
772    pub enabled: bool,
773    /// Google Cloud project ID
774    pub project_id: String,
775    /// Pub/Sub subscription name
776    pub subscription_name: String,
777    /// Optional fully qualified Pub/Sub topic name for Gmail watch notifications
778    pub topic_name: Option<String>,
779    /// Path to service account JSON key file
780    pub service_account_key_path: String,
781    /// Gmail user email address
782    pub user_email: String,
783    /// Label filters (e.g., ["INBOX", "UNREAD"])
784    pub label_filters: Vec<String>,
785    /// Optional query filter (e.g., "from:github.com")
786    pub query_filter: Option<String>,
787    /// Enable auto-reply functionality
788    pub auto_reply: bool,
789    /// Maximum number of history records to fetch per notification
790    pub max_history_fetch: u32,
791    /// Rate limit for Gmail API requests per second
792    pub rate_limit_requests_per_second: u32,
793    /// Optional override for the Gmail API base URL
794    pub api_base_url: Option<String>,
795    /// Optional override for the Google OAuth token URL
796    pub oauth_token_url: Option<String>,
797}
798
799/// Signal messenger configuration.
800#[derive(Debug, Clone, Serialize, Deserialize)]
801pub struct SignalConfig {
802    pub enabled: bool,
803    /// Bot's phone number (E.164 format, e.g., +1234567890)
804    pub phone_number: String,
805    /// Path to signal-cli data directory
806    pub data_dir: PathBuf,
807    /// Allowed phone numbers (empty = allow all)
808    pub allowlist: Vec<String>,
809    /// Allowed group IDs (empty = allow all)
810    pub allowed_groups: Vec<String>,
811    /// Path to signal-cli binary (None = use system PATH)
812    pub signal_cli_path: Option<PathBuf>,
813    /// Use native libsignal-client instead of signal-cli
814    pub use_libsignal: bool,
815    /// Rate limit for outgoing messages per minute
816    pub rate_limit_per_minute: u32,
817    /// Require allowlist for security
818    pub require_allowlist: bool,
819}
820
821/// Matrix protocol configuration.
822#[derive(Debug, Clone, Serialize, Deserialize)]
823pub struct MatrixConfig {
824    pub enabled: bool,
825    /// Matrix homeserver URL (e.g., "<https://matrix.org>")
826    pub homeserver: String,
827    /// Matrix user ID (e.g., "@bot:matrix.org")
828    pub user_id: String,
829    /// Access token for authentication (preferred over password)
830    pub access_token: Option<String>,
831    /// Password for authentication (if access_token not provided)
832    pub password: Option<String>,
833    /// Device ID for the session
834    pub device_id: Option<String>,
835    /// Directory to store Matrix client data (encryption keys, sync tokens)
836    pub data_dir: String,
837    /// List of allowed MXIDs (empty = allow all)
838    pub allowlist: Vec<String>,
839    /// List of allowed room IDs (empty = allow all)
840    pub room_allowlist: Vec<String>,
841    /// Automatically join rooms when invited
842    pub auto_join_rooms: bool,
843    /// Enable end-to-end encryption
844    pub enable_encryption: bool,
845    /// Rate limit for sending messages per second
846    pub rate_limit_per_second: u32,
847}
848
849/// X (Twitter) API v2 configuration.
850#[derive(Debug, Clone, Serialize, Deserialize)]
851pub struct XConfig {
852    pub enabled: bool,
853    /// Bearer token for X API v2
854    pub bearer_token: String,
855    /// API key (for user context endpoints)
856    pub api_key: String,
857    /// API secret (for user context endpoints)
858    pub api_secret: String,
859    /// Access token (for user context endpoints)
860    pub access_token: String,
861    /// Access token secret (for user context endpoints)
862    pub access_token_secret: String,
863    /// Bot user ID (numeric string)
864    pub bot_user_id: String,
865    /// List of allowed usernames (without @, empty = all allowed)
866    pub allowlist: Vec<String>,
867    /// Respond to mentions
868    pub respond_to_mentions: bool,
869    /// Respond to DMs
870    pub respond_to_dms: bool,
871    /// Maximum tweet length (default: 280)
872    pub max_tweet_length: usize,
873    /// Polling interval for mentions in seconds
874    pub mention_poll_interval_secs: u64,
875    /// Polling interval for DMs in seconds
876    pub dm_poll_interval_secs: u64,
877    /// Rate limit for sending tweets per minute
878    pub rate_limit_per_minute: u32,
879}
880
881/// Twilio SMS/MMS configuration.
882#[derive(Debug, Clone, Serialize, Deserialize)]
883pub struct TwilioConfig {
884    pub enabled: bool,
885    /// Twilio Account SID
886    pub account_sid: String,
887    /// Twilio Auth Token
888    pub auth_token: String,
889    /// Twilio phone number (E.164 format, e.g., +1234567890)
890    pub phone_number: String,
891    /// Optional webhook URL for receiving messages
892    pub webhook_url: Option<String>,
893    /// List of allowed phone numbers (empty = all allowed)
894    pub allowlist: Vec<String>,
895    /// Maximum message length (default 1600, Twilio's limit)
896    pub max_message_length: usize,
897    /// Enable MMS support
898    pub enable_mms: bool,
899    /// Rate limit for sending messages per second
900    pub rate_limit_per_second: u32,
901}
902
903/// LINE Messaging API configuration.
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct LineConfig {
906    pub enabled: bool,
907    /// LINE Channel Access Token
908    pub channel_access_token: String,
909    /// LINE Channel Secret (for webhook signature verification)
910    pub channel_secret: String,
911    /// Webhook path for receiving events
912    pub webhook_path: String,
913    /// List of allowed user IDs (empty = all allowed)
914    pub allowlist: Vec<String>,
915    /// Rate limit for sending messages per second (default: 1000)
916    pub rate_limit_per_second: u32,
917    /// Enable rich menu support
918    pub enable_rich_menu: bool,
919    /// Enable quick replies
920    pub enable_quick_replies: bool,
921}
922
923/// Viber Bot API configuration.
924#[derive(Debug, Clone, Serialize, Deserialize)]
925pub struct ViberConfig {
926    pub enabled: bool,
927    /// Viber Bot Authentication Token
928    pub auth_token: String,
929    /// Webhook URL for receiving callbacks (optional)
930    pub webhook_url: Option<String>,
931    /// Webhook path for receiving callbacks
932    pub webhook_path: String,
933    /// List of allowed user IDs (empty = all allowed)
934    pub allowlist: Vec<String>,
935    /// Rate limit for sending messages per minute (default: 300)
936    pub rate_limit_per_minute: u32,
937    /// Allow broadcast messages (admin only)
938    pub allow_broadcast: bool,
939    /// Welcome message for new conversations
940    pub welcome_message: Option<String>,
941    /// Enable keyboard support
942    pub enable_keyboards: bool,
943}
944
945/// WeChat configuration (supports both Work and Official Accounts).
946#[derive(Debug, Clone, Serialize, Deserialize)]
947pub struct WeChatConfig {
948    pub enabled: bool,
949    /// App type: "work" for WeChat Work, "official_account" for Official Accounts
950    pub app_type: String,
951    // WeChat Work fields
952    /// WeChat Work Corp ID
953    pub corp_id: String,
954    /// WeChat Work Corp Secret
955    pub corp_secret: String,
956    /// WeChat Work Agent ID
957    pub agent_id: String,
958    // Official Account fields
959    /// WeChat Official Account App ID
960    pub app_id: String,
961    /// WeChat Official Account App Secret
962    pub app_secret: String,
963    /// Token for webhook signature verification (Official Accounts)
964    pub token: String,
965    /// Encoding AES key for message encryption (optional)
966    pub encoding_aes_key: Option<String>,
967    /// Webhook path for receiving messages
968    pub webhook_path: String,
969    /// List of allowed user IDs (empty = all allowed)
970    pub allowlist: Vec<String>,
971    /// Rate limit for API requests per second (default: 20)
972    pub rate_limit_per_second: u32,
973    /// Enable message encryption
974    pub enable_encryption: bool,
975}
976
977/// Meta (Messenger & Instagram) configuration.
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct MetaConfig {
980    pub enabled: bool,
981    /// Meta App ID
982    pub app_id: String,
983    /// Meta App Secret
984    pub app_secret: String,
985    /// Page Access Token for Messenger
986    pub page_access_token: String,
987    /// Webhook verify token
988    pub verify_token: String,
989    /// Webhook path for receiving messages
990    pub webhook_path: String,
991    /// Facebook Page ID
992    pub page_id: String,
993    /// Instagram Account ID (optional, for Instagram Direct)
994    pub instagram_account_id: Option<String>,
995    /// List of allowed sender IDs (empty = all allowed)
996    pub allowlist: Vec<String>,
997    /// Respond to Messenger messages
998    pub respond_to_messenger: bool,
999    /// Respond to Instagram Direct messages
1000    pub respond_to_instagram: bool,
1001    /// Show typing indicator while processing
1002    pub show_typing_indicator: bool,
1003    /// Rate limit for sending messages per second
1004    pub rate_limit_per_second: u32,
1005}
1006
1007/// iMessage configuration.
1008#[derive(Debug, Clone, Serialize, Deserialize)]
1009pub struct IMessageConfig {
1010    pub enabled: bool,
1011    /// Bridge mode for iMessage integration
1012    pub bridge_mode: IMessageBridgeMode,
1013    /// List of allowed phone numbers or Apple IDs (empty = all allowed)
1014    pub allowlist: Vec<String>,
1015    /// Enable tapback/reaction support
1016    pub enable_tapbacks: bool,
1017    /// Enable typing indicator support
1018    pub enable_typing_indicator: bool,
1019}
1020
1021/// Bridge mode for iMessage integration.
1022#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1023#[serde(rename_all = "snake_case", tag = "mode")]
1024pub enum IMessageBridgeMode {
1025    /// BlueBubbles server (works remotely)
1026    #[serde(rename = "bluebubbles", alias = "blue_bubbles")]
1027    BlueBubbles {
1028        /// BlueBubbles server URL
1029        server_url: String,
1030        /// BlueBubbles API password
1031        password: String,
1032    },
1033    /// Direct macOS AppleScript (local only, requires macOS)
1034    #[default]
1035    #[serde(rename = "macos_direct", alias = "mac_o_s_direct")]
1036    MacOSDirect,
1037    /// macOS Messages.app private API (advanced)
1038    PrivateApi,
1039}
1040
1041impl AppConfig {
1042    /// Load configuration from default.toml, then overlay environment variables.
1043    ///
1044    /// Environment variables use the prefix `OPENRUSTCLAW_` with `__` as separator.
1045    /// Example: `OPENRUSTCLAW_GATEWAY__PORT=8080`
1046    pub fn load() -> Result<Self, config::ConfigError> {
1047        let config = config::Config::builder()
1048            .add_source(config::File::with_name("config/default").required(false))
1049            .add_source(
1050                config::Environment::with_prefix("OPENRUSTCLAW")
1051                    .separator("__")
1052                    .try_parsing(true),
1053            )
1054            .build()?;
1055
1056        config.try_deserialize()
1057    }
1058
1059    /// Load from a specific config file path.
1060    pub fn load_from(path: &str) -> Result<Self, config::ConfigError> {
1061        let config = config::Config::builder()
1062            .add_source(config::File::with_name(path))
1063            .add_source(
1064                config::Environment::with_prefix("OPENRUSTCLAW")
1065                    .separator("__")
1066                    .try_parsing(true),
1067            )
1068            .build()?;
1069
1070        config.try_deserialize()
1071    }
1072}
1073
1074impl Default for AppConfig {
1075    fn default() -> Self {
1076        Self {
1077            gateway: GatewayConfig {
1078                network_mode: default_gateway_network_mode(),
1079                host: "127.0.0.1".to_string(),
1080                port: 18789,
1081                allowed_origins: vec![
1082                    "http://localhost:3000".to_string(),
1083                    "http://127.0.0.1:3000".to_string(),
1084                ],
1085            },
1086            database: DatabaseConfig {
1087                url: "sqlite://data/openrustclaw.db".to_string(),
1088                wal_mode: true,
1089                max_connections: 10,
1090            },
1091            providers: ProvidersConfig {
1092                default_provider: "anthropic".to_string(),
1093                fallback_chain: vec![
1094                    "anthropic".to_string(),
1095                    "openai".to_string(),
1096                    "openrouter".to_string(),
1097                ],
1098                control_plane_provider: Some("openrouter".to_string()),
1099                control_plane_fallback_chain: vec!["ollama".to_string(), "anthropic".to_string()],
1100                anthropic: AnthropicConfig {
1101                    model: "claude-sonnet-4-20250514".to_string(),
1102                    api_key_env: None,
1103                    api_version: "2023-06-01".to_string(),
1104                    strict_tools: true,
1105                    streaming_tool_deltas: true,
1106                },
1107                openai: OpenAiConfig {
1108                    model: "gpt-4o".to_string(),
1109                    codex_model: default_codex_model(),
1110                    api_key_env: None,
1111                    use_responses_api: true,
1112                    strict_tools: true,
1113                },
1114                openrouter: OpenRouterConfig {
1115                    model: "anthropic/claude-sonnet-4".to_string(),
1116                    api_key_env: None,
1117                    route_strategy: "quality".to_string(),
1118                },
1119                ollama: OllamaConfig {
1120                    base_url: "http://localhost:11434".to_string(),
1121                    model: "llama3.1".to_string(),
1122                },
1123                gemini: GeminiConfig::default(),
1124            },
1125            external_backends: ExternalBackendsConfig {
1126                allowed_backends: default_allowed_external_backends(),
1127                allow_local_cli_wrappers: true,
1128                allow_cloud_agent_execution: false,
1129                audit_log_path: default_external_backend_audit_log_path(),
1130                command_env_allowlist: default_external_backend_env_allowlist(),
1131            },
1132            memory: MemoryConfig {
1133                core_memory_max_tokens: 500,
1134                core_memory_max_entries: 20,
1135                embedding_concurrency: 4,
1136                dedupe_cosine_threshold: 0.92,
1137                decay_half_life_days: 30.0,
1138                ttl: MemoryTtlConfig {
1139                    episodic_days: 90,
1140                    semantic_days: 0,
1141                    procedural_days: 0,
1142                },
1143                consolidation: ConsolidationConfig {
1144                    enabled: true,
1145                    threshold_entries: 1000,
1146                    schedule_interval_hours: 24,
1147                },
1148            },
1149            session_routing: SessionRoutingConfig {
1150                direct_strategy: "shared_main".to_string(),
1151                group_strategy: "isolated".to_string(),
1152                thread_overrides_channel: true,
1153                pairing_approval_required: false,
1154                default_group_activation: "mention".to_string(),
1155                default_send_mode: "blocks".to_string(),
1156                default_chunk_chars: 1600,
1157                default_chunk_delay_ms: 250,
1158            },
1159            scheduler: SchedulerConfig {
1160                poll_interval_ms: 1000,
1161                lease_duration_secs: 60,
1162                max_retries: 3,
1163                base_retry_delay_secs: 5,
1164                max_retry_delay_secs: 300,
1165            },
1166            security: SecurityConfig {
1167                require_auth: true,
1168                origin_validation: true,
1169                control_api_token_env: None,
1170                trusted_proxy_token_env: None,
1171                prompt_injection_defense: true,
1172                skill_signature_required: false,
1173                skill_verifying_key: None,
1174            },
1175            sidecar: SidecarConfig {
1176                grpc_port: 50051,
1177                python_path: "python3".to_string(),
1178                role: SidecarRole::Compatibility,
1179                auto_start: false,
1180                restart_on_crash: true,
1181            },
1182            observability: ObservabilityConfig {
1183                langsmith_enabled: false,
1184                tracing_enabled: true,
1185                metrics_enabled: true,
1186                metrics_port: 9090,
1187            },
1188            voice: VoiceConfig::default(),
1189            channels: ChannelsConfig {
1190                runtime: ChannelRuntimeConfig::default(),
1191                telegram: TelegramConfig {
1192                    enabled: false,
1193                    token: String::new(),
1194                    api_base_url: None,
1195                    mode: TelegramMode::Polling,
1196                    webhook_url: None,
1197                    webhook_port: None,
1198                    allowed_users: Vec::new(),
1199                    rate_limit_per_second: 30,
1200                },
1201                discord: DiscordConfig {
1202                    enabled: false,
1203                    token: String::new(),
1204                    application_id: String::new(),
1205                    interaction_public_key: None,
1206                    api_base_url: None,
1207                    attachment_download_dir: None,
1208                    rate_limit_requests_per_second: 5,
1209                    allowed_guilds: Vec::new(),
1210                    allowed_channels: Vec::new(),
1211                    dm_enabled: true,
1212                },
1213                slack: SlackConfig {
1214                    enabled: false,
1215                    token: String::new(),
1216                    api_base_url: None,
1217                    app_token: None,
1218                    signing_secret: None,
1219                    mode: SlackMode::SocketMode,
1220                    socket_mode: true,
1221                    rate_limit_requests_per_second: 10,
1222                    allowed_workspaces: Vec::new(),
1223                    app_home_enabled: true,
1224                },
1225                whatsapp: WhatsAppConfig {
1226                    enabled: false,
1227                    session_path: "./data/whatsapp-session".to_string(),
1228                    pairing_mode: false,
1229                    allowlist: Vec::new(),
1230                    webhook_url: None,
1231                    bridge_path: "./crates/channels/baileys-bridge/index.js".to_string(),
1232                    rate_limit_per_second: 10,
1233                    max_reconnect_attempts: 10,
1234                    reconnect_delay_secs: 5,
1235                },
1236                teams: TeamsConfig {
1237                    enabled: false,
1238                    app_id: String::new(),
1239                    app_password: String::new(),
1240                    tenant_id: None,
1241                    webhook_path: "/webhooks/teams".to_string(),
1242                    allowlist: Vec::new(),
1243                    group_policy: TeamsGroupPolicy::Mention,
1244                    rate_limit_requests_per_second: 10,
1245                    adaptive_cards_enabled: true,
1246                    attachment_download_dir: None,
1247                },
1248                mattermost: MattermostConfig {
1249                    enabled: false,
1250                    server_url: String::new(),
1251                    bot_token: String::new(),
1252                    webhook_path: "/webhooks/mattermost".to_string(),
1253                    webhook_token: None,
1254                    bot_username: None,
1255                    allowlist: Vec::new(),
1256                    allowed_channels: Vec::new(),
1257                    rate_limit_requests_per_second: 10,
1258                },
1259                google_chat: GoogleChatConfig {
1260                    enabled: false,
1261                    service_account_key: String::new(),
1262                    project_id: String::new(),
1263                    webhook_url: None,
1264                    pubsub_subscription: None,
1265                    allowlist: Vec::new(),
1266                    allowed_spaces: Vec::new(),
1267                    rate_limit_requests_per_second: 10,
1268                    cards_enabled: true,
1269                    attachment_download_dir: None,
1270                    response_mode: GoogleChatResponseMode::Mention,
1271                },
1272                google_meet: GoogleMeetConfig {
1273                    enabled: false,
1274                    service_account_key_path: String::new(),
1275                    delegated_user_email: String::new(),
1276                    webhook_path: "/webhooks/google-meet/events".to_string(),
1277                    allowed_spaces: Vec::new(),
1278                    rate_limit_requests_per_second: 5,
1279                    api_base_url: None,
1280                    oauth_token_url: None,
1281                    additional_scopes: Vec::new(),
1282                    hydrate_transcript_events: true,
1283                },
1284                gmail_pubsub: GmailPubSubConfig {
1285                    enabled: false,
1286                    project_id: String::new(),
1287                    subscription_name: String::new(),
1288                    topic_name: None,
1289                    service_account_key_path: String::new(),
1290                    user_email: String::new(),
1291                    label_filters: vec!["INBOX".to_string(), "UNREAD".to_string()],
1292                    query_filter: None,
1293                    auto_reply: false,
1294                    max_history_fetch: 100,
1295                    rate_limit_requests_per_second: 10,
1296                    api_base_url: None,
1297                    oauth_token_url: None,
1298                },
1299                signal: SignalConfig {
1300                    enabled: false,
1301                    phone_number: String::new(),
1302                    data_dir: std::path::PathBuf::from("./data/signal"),
1303                    allowlist: Vec::new(),
1304                    allowed_groups: Vec::new(),
1305                    signal_cli_path: None,
1306                    use_libsignal: false,
1307                    rate_limit_per_minute: 20,
1308                    require_allowlist: true,
1309                },
1310                matrix: MatrixConfig {
1311                    enabled: false,
1312                    homeserver: String::new(),
1313                    user_id: String::new(),
1314                    access_token: None,
1315                    password: None,
1316                    device_id: None,
1317                    data_dir: "./data/matrix".to_string(),
1318                    allowlist: Vec::new(),
1319                    room_allowlist: Vec::new(),
1320                    auto_join_rooms: true,
1321                    enable_encryption: true,
1322                    rate_limit_per_second: 10,
1323                },
1324                x: XConfig {
1325                    enabled: false,
1326                    bearer_token: String::new(),
1327                    api_key: String::new(),
1328                    api_secret: String::new(),
1329                    access_token: String::new(),
1330                    access_token_secret: String::new(),
1331                    bot_user_id: String::new(),
1332                    allowlist: Vec::new(),
1333                    respond_to_mentions: true,
1334                    respond_to_dms: true,
1335                    max_tweet_length: 280,
1336                    mention_poll_interval_secs: 60,
1337                    dm_poll_interval_secs: 120,
1338                    rate_limit_per_minute: 10,
1339                },
1340                twilio: TwilioConfig {
1341                    enabled: false,
1342                    account_sid: String::new(),
1343                    auth_token: String::new(),
1344                    phone_number: String::new(),
1345                    webhook_url: None,
1346                    allowlist: Vec::new(),
1347                    max_message_length: 1600,
1348                    enable_mms: true,
1349                    rate_limit_per_second: 10,
1350                },
1351                meta: MetaConfig {
1352                    enabled: false,
1353                    app_id: String::new(),
1354                    app_secret: String::new(),
1355                    page_access_token: String::new(),
1356                    verify_token: String::new(),
1357                    webhook_path: "/webhooks/meta".to_string(),
1358                    page_id: String::new(),
1359                    instagram_account_id: None,
1360                    allowlist: Vec::new(),
1361                    respond_to_messenger: true,
1362                    respond_to_instagram: true,
1363                    show_typing_indicator: true,
1364                    rate_limit_per_second: 10,
1365                },
1366                imessage: IMessageConfig {
1367                    enabled: false,
1368                    bridge_mode: IMessageBridgeMode::MacOSDirect,
1369                    allowlist: Vec::new(),
1370                    enable_tapbacks: true,
1371                    enable_typing_indicator: false,
1372                },
1373                line: LineConfig {
1374                    enabled: false,
1375                    channel_access_token: String::new(),
1376                    channel_secret: String::new(),
1377                    webhook_path: "/webhook/line".to_string(),
1378                    allowlist: Vec::new(),
1379                    rate_limit_per_second: 1000,
1380                    enable_rich_menu: true,
1381                    enable_quick_replies: true,
1382                },
1383                viber: ViberConfig {
1384                    enabled: false,
1385                    auth_token: String::new(),
1386                    webhook_url: None,
1387                    webhook_path: "/webhook/viber".to_string(),
1388                    allowlist: Vec::new(),
1389                    rate_limit_per_minute: 300,
1390                    allow_broadcast: false,
1391                    welcome_message: None,
1392                    enable_keyboards: true,
1393                },
1394                wechat: WeChatConfig {
1395                    enabled: false,
1396                    app_type: "work".to_string(),
1397                    corp_id: String::new(),
1398                    corp_secret: String::new(),
1399                    agent_id: String::new(),
1400                    app_id: String::new(),
1401                    app_secret: String::new(),
1402                    token: String::new(),
1403                    encoding_aes_key: None,
1404                    webhook_path: "/webhook/wechat".to_string(),
1405                    allowlist: Vec::new(),
1406                    rate_limit_per_second: 20,
1407                    enable_encryption: false,
1408                },
1409            },
1410            skills: None,
1411        }
1412    }
1413}