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