1use agentzero_core::common::local_providers::is_local_provider;
2use anyhow::anyhow;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use tracing::warn;
6use url::Url;
7
8#[derive(Debug, Clone, Deserialize, Serialize, Default)]
9#[serde(default)]
10pub struct AgentZeroConfig {
11 pub provider: ProviderConfig,
12 pub memory: MemoryConfig,
13 pub agent: AgentSettings,
14 pub security: SecurityConfig,
15 pub autonomy: AutonomyConfig,
16 pub observability: ObservabilityConfig,
17 pub research: ResearchConfig,
18 pub runtime: RuntimeConfig,
19 pub browser: BrowserConfig,
20 pub http_request: HttpRequestConfig,
21 pub web_fetch: WebFetchConfig,
22 pub web_search: WebSearchConfig,
23 pub composio: ComposioConfig,
24 pub pushover: PushoverConfig,
25 pub cost: CostConfig,
26 pub identity: IdentityConfig,
27 pub multimodal: MultimodalConfig,
28 pub skills: SkillsConfig,
29 #[serde(alias = "provider_settings")]
30 pub provider_options: ProviderOptionsConfig,
31 pub gateway: GatewayConfig,
32 pub channels_config: ChannelsGlobalConfig,
33 pub query_classification: QueryClassificationConfig,
34 pub model_providers: HashMap<String, ModelProviderProfile>,
35 pub model_routes: Vec<ModelRoute>,
36 pub embedding_routes: Vec<EmbeddingRoute>,
37 pub agents: HashMap<String, DelegateAgentConfig>,
38 pub privacy: PrivacyConfig,
39}
40
41impl AgentZeroConfig {
42 pub fn validate(&self) -> anyhow::Result<()> {
43 if self.provider.kind.trim().is_empty() {
44 return Err(anyhow!("provider.kind must not be empty"));
45 }
46 if self.provider.base_url.trim().is_empty() {
47 return Err(anyhow!("provider.base_url must not be empty"));
48 }
49 let provider_url = Url::parse(&self.provider.base_url)
50 .map_err(|_| anyhow!("provider.base_url must be a valid URL"))?;
51 if !matches!(provider_url.scheme(), "http" | "https") {
52 return Err(anyhow!("provider.base_url scheme must be http or https"));
53 }
54 if is_local_provider(&self.provider.kind) {
55 let is_localhost = matches!(
56 provider_url.host_str(),
57 Some("localhost") | Some("127.0.0.1") | Some("0.0.0.0") | Some("::1")
58 );
59 if !is_localhost {
60 if self.privacy.mode == "local_only" || self.privacy.enforce_local_provider {
61 return Err(anyhow!(
62 "privacy mode '{}' requires localhost base_url for local provider '{}', \
63 but got '{}'. Use http://localhost:<port> or change your provider.",
64 self.privacy.mode,
65 self.provider.kind,
66 self.provider.base_url,
67 ));
68 }
69 tracing::warn!(
70 "provider '{}' is a local provider but base_url '{}' is not localhost \
71 — did you mean to use a different provider?",
72 self.provider.kind,
73 self.provider.base_url,
74 );
75 }
76 }
77 if self.provider.model.trim().is_empty() {
78 return Err(anyhow!("provider.model must not be empty"));
79 }
80 if !(0.0..=2.0).contains(&self.provider.default_temperature) {
81 return Err(anyhow!(
82 "provider.default_temperature must be between 0.0 and 2.0"
83 ));
84 }
85 if let Some(api) = &self.provider.provider_api {
86 if !matches!(api.as_str(), "openai-chat-completions" | "openai-responses") {
87 return Err(anyhow!(
88 "provider.provider_api must be 'openai-chat-completions' or 'openai-responses'"
89 ));
90 }
91 }
92
93 if self.memory.backend.trim().is_empty() {
94 return Err(anyhow!("memory.backend must not be empty"));
95 }
96 match self.memory.backend.as_str() {
97 "sqlite" | "turso" => {}
98 other => {
99 return Err(anyhow!(
100 "unsupported memory.backend `{other}`; expected `sqlite` or `turso`"
101 ));
102 }
103 }
104 if self.memory.sqlite_path.trim().is_empty() {
105 return Err(anyhow!("memory.sqlite_path must not be empty"));
106 }
107
108 if self.agent.max_tool_iterations == 0 {
109 return Err(anyhow!("agent.max_tool_iterations must be > 0"));
110 }
111 if self.agent.request_timeout_ms == 0 {
112 return Err(anyhow!("agent.request_timeout_ms must be > 0"));
113 }
114 if self.agent.memory_window_size == 0 {
115 return Err(anyhow!("agent.memory_window_size must be > 0"));
116 }
117 if self.agent.max_prompt_chars == 0 {
118 return Err(anyhow!("agent.max_prompt_chars must be > 0"));
119 }
120 if self.agent.hooks.timeout_ms == 0 {
121 return Err(anyhow!("agent.hooks.timeout_ms must be > 0"));
122 }
123 if !is_valid_hook_error_mode(&self.agent.hooks.on_error_default) {
124 return Err(anyhow!(
125 "agent.hooks.on_error_default must be one of: block, warn, ignore"
126 ));
127 }
128 if let Some(value) = &self.agent.hooks.on_error_low {
129 if !is_valid_hook_error_mode(value) {
130 return Err(anyhow!(
131 "agent.hooks.on_error_low must be one of: block, warn, ignore"
132 ));
133 }
134 }
135 if let Some(value) = &self.agent.hooks.on_error_medium {
136 if !is_valid_hook_error_mode(value) {
137 return Err(anyhow!(
138 "agent.hooks.on_error_medium must be one of: block, warn, ignore"
139 ));
140 }
141 }
142 if let Some(value) = &self.agent.hooks.on_error_high {
143 if !is_valid_hook_error_mode(value) {
144 return Err(anyhow!(
145 "agent.hooks.on_error_high must be one of: block, warn, ignore"
146 ));
147 }
148 }
149
150 if self.security.allowed_root.trim().is_empty() {
151 return Err(anyhow!("security.allowed_root must not be empty"));
152 }
153 if self.security.allowed_commands.is_empty() && !self.agent.is_dev_mode() {
154 return Err(anyhow!("security.allowed_commands must not be empty"));
155 }
156
157 if self.security.read_file.max_read_bytes == 0 {
158 return Err(anyhow!("security.read_file.max_read_bytes must be > 0"));
159 }
160 if self.security.write_file.max_write_bytes == 0 {
161 return Err(anyhow!("security.write_file.max_write_bytes must be > 0"));
162 }
163
164 if self.security.shell.max_args == 0 {
165 return Err(anyhow!("security.shell.max_args must be > 0"));
166 }
167 if self.security.shell.max_arg_length == 0 {
168 return Err(anyhow!("security.shell.max_arg_length must be > 0"));
169 }
170 if self.security.shell.max_output_bytes == 0 {
171 return Err(anyhow!("security.shell.max_output_bytes must be > 0"));
172 }
173 if self.security.shell.forbidden_chars.is_empty() {
174 return Err(anyhow!("security.shell.forbidden_chars must not be empty"));
175 }
176
177 if self.security.mcp.enabled && self.security.mcp.allowed_servers.is_empty() {
178 return Err(anyhow!(
179 "security.mcp.allowed_servers must not be empty when MCP is enabled"
180 ));
181 }
182
183 if self.security.audit.enabled && self.security.audit.path.trim().is_empty() {
184 return Err(anyhow!(
185 "security.audit.path must not be empty when audit is enabled"
186 ));
187 }
188
189 if self.gateway.host.trim().is_empty() {
191 return Err(anyhow!("gateway.host must not be empty"));
192 }
193 if self.gateway.port == 0 {
194 return Err(anyhow!("gateway.port must be > 0"));
195 }
196 if !self.gateway.allow_public_bind
197 && self.gateway.host != "127.0.0.1"
198 && self.gateway.host != "::1"
199 && self.gateway.host != "localhost"
200 {
201 return Err(anyhow!(
202 "gateway.host `{}` binds publicly but gateway.allow_public_bind is false",
203 self.gateway.host
204 ));
205 }
206
207 match self.autonomy.level.trim() {
209 "supervised" | "autonomous" | "semi" | "locked" => {}
210 other => {
211 return Err(anyhow!(
212 "autonomy.level must be one of: supervised, autonomous, semi, locked; got `{other}`"
213 ));
214 }
215 }
216 if self.autonomy.max_actions_per_hour == 0 {
217 return Err(anyhow!("autonomy.max_actions_per_hour must be > 0"));
218 }
219 if self.autonomy.max_cost_per_day_cents == 0 {
220 return Err(anyhow!("autonomy.max_cost_per_day_cents must be > 0"));
221 }
222
223 match self.privacy.mode.as_str() {
225 "off" | "local_only" | "encrypted" | "full" => {}
226 other => {
227 return Err(anyhow!(
228 "privacy.mode must be one of: off, local_only, encrypted, full; got `{other}`"
229 ));
230 }
231 }
232 if (self.privacy.mode == "local_only" || self.privacy.enforce_local_provider)
233 && !is_local_provider(&self.provider.kind)
234 {
235 return Err(anyhow!(
236 "privacy mode '{}' requires a local provider, but '{}' is a cloud provider; \
237 use ollama, llamacpp, lmstudio, vllm, sglang, or another local provider",
238 self.privacy.mode,
239 self.provider.kind
240 ));
241 }
242 if self.privacy.noise.session_timeout_secs == 0 {
243 return Err(anyhow!("privacy.noise.session_timeout_secs must be > 0"));
244 }
245 if self.privacy.noise.max_sessions == 0 {
246 return Err(anyhow!("privacy.noise.max_sessions must be > 0"));
247 }
248 if self.privacy.sealed_envelopes.max_envelope_bytes == 0 {
249 return Err(anyhow!(
250 "privacy.sealed_envelopes.max_envelope_bytes must be > 0"
251 ));
252 }
253 match self.privacy.noise.handshake_pattern.as_str() {
254 "XX" | "IK" => {}
255 other => {
256 return Err(anyhow!(
257 "privacy.noise.handshake_pattern must be XX or IK; got `{other}`"
258 ));
259 }
260 }
261 if self.privacy.mode == "encrypted" && !self.privacy.noise.enabled {
264 return Err(anyhow!(
265 "privacy.mode 'encrypted' requires privacy.noise.enabled = true; \
266 either enable Noise or change the privacy mode"
267 ));
268 }
269
270 let valid_boundaries = ["", "inherit", "local_only", "encrypted_only", "any"];
272 for (name, agent) in &self.agents {
273 if !agent.privacy_boundary.is_empty()
274 && !valid_boundaries.contains(&agent.privacy_boundary.as_str())
275 {
276 return Err(anyhow!(
277 "agents.{name}.privacy_boundary must be one of: inherit, local_only, \
278 encrypted_only, any; got '{}'",
279 agent.privacy_boundary
280 ));
281 }
282 let global_boundary = match self.privacy.mode.as_str() {
285 "local_only" => "local_only",
286 "encrypted" | "full" => "encrypted_only",
287 _ => "any",
288 };
289 if !agent.privacy_boundary.is_empty()
290 && agent.privacy_boundary != "inherit"
291 && global_boundary == "local_only"
292 && agent.privacy_boundary != "local_only"
293 {
294 return Err(anyhow!(
295 "agents.{name}.privacy_boundary '{}' is more permissive than \
296 global privacy mode '{}' (local_only)",
297 agent.privacy_boundary,
298 self.privacy.mode
299 ));
300 }
301 }
302
303 for (tool_name, boundary) in &self.security.tool_boundaries {
305 if !valid_boundaries.contains(&boundary.as_str()) {
306 return Err(anyhow!(
307 "security.tool_boundaries.{tool_name} must be one of: inherit, local_only, \
308 encrypted_only, any; got '{boundary}'"
309 ));
310 }
311 }
312
313 if self.query_classification.enabled && self.query_classification.rules.is_empty() {
315 warn!(
316 "query_classification is enabled but has no rules — classification will be a no-op"
317 );
318 }
319 for route in &self.embedding_routes {
320 if route.provider.trim().is_empty() {
321 warn!(hint = %route.hint, "embedding route has an empty provider field");
322 }
323 if route.model.trim().is_empty() {
324 warn!(hint = %route.hint, "embedding route has an empty model field");
325 }
326 }
327
328 Ok(())
329 }
330
331 pub fn masked(&self) -> Self {
333 let mut copy = self.clone();
334 let mask = |opt: &mut Option<String>| {
335 if opt.as_ref().is_some_and(|v| !v.is_empty()) {
336 *opt = Some("****".to_string());
337 }
338 };
339 mask(&mut copy.browser.computer_use.api_key);
340 mask(&mut copy.web_fetch.api_key);
341 mask(&mut copy.web_search.api_key);
342 mask(&mut copy.web_search.brave_api_key);
343 mask(&mut copy.web_search.perplexity_api_key);
344 mask(&mut copy.web_search.exa_api_key);
345 mask(&mut copy.web_search.jina_api_key);
346 mask(&mut copy.composio.api_key);
347 mask(&mut copy.skills.clawhub_token);
348 mask(&mut copy.gateway.node_control.auth_token);
349 for profile in copy.model_providers.values_mut() {
350 mask(&mut profile.api_key);
351 }
352 for route in &mut copy.model_routes {
353 mask(&mut route.api_key);
354 }
355 for route in &mut copy.embedding_routes {
356 mask(&mut route.api_key);
357 }
358 for agent in copy.agents.values_mut() {
359 mask(&mut agent.api_key);
360 }
361 copy
362 }
363}
364
365#[derive(Debug, Clone, Deserialize, Serialize)]
366#[serde(default)]
367pub struct ProviderConfig {
368 #[serde(alias = "name", alias = "default_provider")]
369 pub kind: String,
370 pub base_url: String,
371 pub model: String,
372 pub default_temperature: f64,
373 pub provider_api: Option<String>,
374 pub model_support_vision: Option<bool>,
375 #[serde(default)]
376 pub transport: TransportSettings,
377}
378
379#[derive(Debug, Clone, Deserialize, Serialize)]
381#[serde(default)]
382pub struct TransportSettings {
383 pub timeout_ms: u64,
384 pub max_retries: usize,
385 pub circuit_breaker_threshold: u32,
386 pub circuit_breaker_reset_ms: u64,
387}
388
389impl Default for TransportSettings {
390 fn default() -> Self {
391 Self {
392 timeout_ms: 30_000,
393 max_retries: 3,
394 circuit_breaker_threshold: 5,
395 circuit_breaker_reset_ms: 30_000,
396 }
397 }
398}
399
400impl Default for ProviderConfig {
401 fn default() -> Self {
402 Self {
403 kind: "openrouter".to_string(),
404 base_url: "https://openrouter.ai/api".to_string(),
405 model: "anthropic/claude-sonnet-4-6".to_string(),
406 default_temperature: 0.7,
407 provider_api: None,
408 model_support_vision: None,
409 transport: TransportSettings::default(),
410 }
411 }
412}
413
414#[derive(Debug, Clone, Deserialize, Serialize)]
415#[serde(default)]
416pub struct MemoryConfig {
417 pub backend: String,
418 #[serde(alias = "path")]
419 pub sqlite_path: String,
420}
421
422impl Default for MemoryConfig {
423 fn default() -> Self {
424 Self {
425 backend: "sqlite".to_string(),
426 sqlite_path: default_sqlite_path(),
427 }
428 }
429}
430
431fn default_sqlite_path() -> String {
432 agentzero_core::common::paths::default_sqlite_path()
433 .map(|path| path.to_string_lossy().to_string())
434 .unwrap_or_else(|| "./agentzero.db".to_string())
435}
436
437fn is_valid_hook_error_mode(value: &str) -> bool {
438 matches!(value.trim(), "block" | "warn" | "ignore")
439}
440
441#[derive(Debug, Clone, Deserialize, Serialize)]
442#[serde(default)]
443pub struct AgentSettings {
444 pub max_tool_iterations: usize,
445 pub request_timeout_ms: u64,
446 #[serde(alias = "max_history_messages")]
447 pub memory_window_size: usize,
448 pub max_prompt_chars: usize,
449 pub mode: String,
450 pub hooks: HookSettings,
451 pub parallel_tools: bool,
452 pub tool_dispatcher: String,
453 pub compact_context: bool,
454 pub loop_detection_no_progress_threshold: usize,
455 pub loop_detection_ping_pong_cycles: usize,
456 pub loop_detection_failure_streak: usize,
457 pub system_prompt: Option<String>,
459}
460
461impl Default for AgentSettings {
462 fn default() -> Self {
463 Self {
464 max_tool_iterations: 20,
465 request_timeout_ms: 30_000,
466 memory_window_size: 50,
467 max_prompt_chars: 8_000,
468 mode: "development".to_string(),
469 hooks: HookSettings::default(),
470 parallel_tools: false,
471 tool_dispatcher: "auto".to_string(),
472 compact_context: true,
473 loop_detection_no_progress_threshold: 3,
474 loop_detection_ping_pong_cycles: 2,
475 loop_detection_failure_streak: 3,
476 system_prompt: None,
477 }
478 }
479}
480
481impl AgentSettings {
482 pub fn is_dev_mode(&self) -> bool {
483 matches!(self.mode.trim(), "dev" | "development")
484 }
485}
486
487#[derive(Debug, Clone, Deserialize, Serialize)]
488#[serde(default)]
489pub struct HookSettings {
490 pub enabled: bool,
491 pub timeout_ms: u64,
492 pub fail_closed: bool,
493 pub on_error_default: String,
494 pub on_error_low: Option<String>,
495 pub on_error_medium: Option<String>,
496 pub on_error_high: Option<String>,
497}
498
499impl Default for HookSettings {
500 fn default() -> Self {
501 Self {
502 enabled: false,
503 timeout_ms: 250,
504 fail_closed: false,
505 on_error_default: "warn".to_string(),
506 on_error_low: None,
507 on_error_medium: None,
508 on_error_high: None,
509 }
510 }
511}
512
513#[derive(Debug, Clone, Deserialize, Serialize)]
514#[serde(default)]
515pub struct SecurityConfig {
516 pub allowed_root: String,
517 pub allowed_commands: Vec<String>,
518 pub read_file: ReadFileConfig,
519 pub write_file: WriteFileConfig,
520 pub shell: ShellConfig,
521 pub mcp: McpConfig,
522 pub plugin: PluginConfig,
523 pub audit: AuditConfig,
524 pub url_access: UrlAccessConfig,
525 pub otp: OtpConfig,
526 pub estop: EstopConfig,
527 pub outbound_leak_guard: OutboundLeakGuardConfig,
528 pub perplexity_filter: PerplexityFilterConfig,
529 pub syscall_anomaly: SyscallAnomalyConfig,
530 #[serde(default)]
533 pub tool_boundaries: std::collections::HashMap<String, String>,
534}
535
536impl Default for SecurityConfig {
537 fn default() -> Self {
538 Self {
539 allowed_root: ".".to_string(),
540 allowed_commands: vec![
541 "ls".to_string(),
542 "pwd".to_string(),
543 "cat".to_string(),
544 "echo".to_string(),
545 "grep".to_string(),
546 "find".to_string(),
547 "head".to_string(),
548 "tail".to_string(),
549 "wc".to_string(),
550 "sort".to_string(),
551 "uniq".to_string(),
552 "diff".to_string(),
553 "file".to_string(),
554 "which".to_string(),
555 "basename".to_string(),
556 "dirname".to_string(),
557 "mkdir".to_string(),
558 "cp".to_string(),
559 "mv".to_string(),
560 "rm".to_string(),
561 "touch".to_string(),
562 "date".to_string(),
563 "env".to_string(),
564 "test".to_string(),
565 "tr".to_string(),
566 "cut".to_string(),
567 "xargs".to_string(),
568 "sed".to_string(),
569 "awk".to_string(),
570 "git".to_string(),
571 "cargo".to_string(),
572 "rustc".to_string(),
573 "npm".to_string(),
574 "node".to_string(),
575 "python3".to_string(),
576 ],
577 read_file: ReadFileConfig::default(),
578 write_file: WriteFileConfig::default(),
579 shell: ShellConfig::default(),
580 mcp: McpConfig::default(),
581 plugin: PluginConfig::default(),
582 audit: AuditConfig::default(),
583 url_access: UrlAccessConfig::default(),
584 otp: OtpConfig::default(),
585 estop: EstopConfig::default(),
586 outbound_leak_guard: OutboundLeakGuardConfig::default(),
587 perplexity_filter: PerplexityFilterConfig::default(),
588 syscall_anomaly: SyscallAnomalyConfig::default(),
589 tool_boundaries: std::collections::HashMap::new(),
590 }
591 }
592}
593
594#[derive(Debug, Clone, Deserialize, Serialize)]
595#[serde(default)]
596pub struct ReadFileConfig {
597 pub max_read_bytes: u64,
598 pub allow_binary: bool,
599}
600
601impl Default for ReadFileConfig {
602 fn default() -> Self {
603 Self {
604 max_read_bytes: 256 * 1024,
605 allow_binary: false,
606 }
607 }
608}
609
610#[derive(Debug, Clone, Deserialize, Serialize)]
611#[serde(default)]
612pub struct WriteFileConfig {
613 pub enabled: bool,
614 pub max_write_bytes: u64,
615}
616
617impl Default for WriteFileConfig {
618 fn default() -> Self {
619 Self {
620 enabled: false,
621 max_write_bytes: 64 * 1024,
622 }
623 }
624}
625
626#[derive(Debug, Clone, Deserialize, Serialize)]
627#[serde(default)]
628pub struct ShellConfig {
629 pub max_args: usize,
630 pub max_arg_length: usize,
631 pub max_output_bytes: usize,
632 pub forbidden_chars: String,
633 pub context_aware_parsing: bool,
634}
635
636impl Default for ShellConfig {
637 fn default() -> Self {
638 Self {
639 max_args: 32,
640 max_arg_length: 4096,
641 max_output_bytes: 65536,
642 forbidden_chars: ";&|><$`\n\r".to_string(),
643 context_aware_parsing: true,
644 }
645 }
646}
647
648#[derive(Debug, Clone, Deserialize, Serialize, Default)]
649#[serde(default)]
650pub struct McpConfig {
651 pub enabled: bool,
652 pub allowed_servers: Vec<String>,
653}
654
655#[derive(Debug, Clone, Deserialize, Serialize, Default)]
656#[serde(default)]
657pub struct PluginConfig {
658 pub enabled: bool,
660 pub wasm_enabled: bool,
662 pub global_plugin_dir: Option<String>,
665 pub project_plugin_dir: Option<String>,
668 pub dev_plugin_dir: Option<String>,
671}
672
673#[derive(Debug, Clone, Deserialize, Serialize)]
674#[serde(default)]
675pub struct AuditConfig {
676 pub enabled: bool,
677 pub path: String,
678}
679
680impl Default for AuditConfig {
681 fn default() -> Self {
682 Self {
683 enabled: false,
684 path: "./agentzero-audit.log".to_string(),
685 }
686 }
687}
688
689#[derive(Debug, Clone, Deserialize, Serialize)]
692#[serde(default)]
693pub struct AutonomyConfig {
694 pub level: String,
695 pub workspace_only: bool,
696 pub forbidden_paths: Vec<String>,
697 pub allowed_roots: Vec<String>,
698 pub auto_approve: Vec<String>,
699 pub always_ask: Vec<String>,
700 pub allow_sensitive_file_reads: bool,
701 pub allow_sensitive_file_writes: bool,
702 pub non_cli_excluded_tools: Vec<String>,
703 pub non_cli_approval_approvers: Vec<String>,
704 pub non_cli_natural_language_approval_mode: String,
705 pub non_cli_natural_language_approval_mode_by_channel: HashMap<String, String>,
706 pub max_actions_per_hour: u32,
707 pub max_cost_per_day_cents: u32,
708 pub require_approval_for_medium_risk: bool,
709 pub block_high_risk_commands: bool,
710}
711
712impl Default for AutonomyConfig {
713 fn default() -> Self {
714 Self {
715 level: "supervised".to_string(),
716 workspace_only: true,
717 forbidden_paths: vec![
718 "/etc".to_string(),
719 "/root".to_string(),
720 "/proc".to_string(),
721 "/sys".to_string(),
722 "~/.ssh".to_string(),
723 "~/.gnupg".to_string(),
724 "~/.aws".to_string(),
725 ],
726 allowed_roots: Vec::new(),
727 auto_approve: Vec::new(),
728 always_ask: Vec::new(),
729 allow_sensitive_file_reads: false,
730 allow_sensitive_file_writes: false,
731 non_cli_excluded_tools: Vec::new(),
732 non_cli_approval_approvers: Vec::new(),
733 non_cli_natural_language_approval_mode: "direct".to_string(),
734 non_cli_natural_language_approval_mode_by_channel: HashMap::new(),
735 max_actions_per_hour: 200,
736 max_cost_per_day_cents: 2000,
737 require_approval_for_medium_risk: true,
738 block_high_risk_commands: true,
739 }
740 }
741}
742
743#[derive(Debug, Clone, Deserialize, Serialize)]
744#[serde(default)]
745pub struct ObservabilityConfig {
746 pub backend: String,
747 pub otel_endpoint: String,
748 pub otel_service_name: String,
749 pub runtime_trace_mode: String,
750 pub runtime_trace_path: String,
751 pub runtime_trace_max_entries: usize,
752}
753
754impl Default for ObservabilityConfig {
755 fn default() -> Self {
756 Self {
757 backend: "none".to_string(),
758 otel_endpoint: "http://localhost:4318".to_string(),
759 otel_service_name: "agentzero".to_string(),
760 runtime_trace_mode: "none".to_string(),
761 runtime_trace_path: "state/runtime-trace.jsonl".to_string(),
762 runtime_trace_max_entries: 200,
763 }
764 }
765}
766
767#[derive(Debug, Clone, Deserialize, Serialize)]
768#[serde(default)]
769pub struct ResearchConfig {
770 pub enabled: bool,
771 pub trigger: String,
772 pub keywords: Vec<String>,
773 pub min_message_length: usize,
774 pub max_iterations: usize,
775 pub show_progress: bool,
776}
777
778impl Default for ResearchConfig {
779 fn default() -> Self {
780 Self {
781 enabled: false,
782 trigger: "never".to_string(),
783 keywords: vec![
784 "find".to_string(),
785 "search".to_string(),
786 "check".to_string(),
787 "investigate".to_string(),
788 ],
789 min_message_length: 50,
790 max_iterations: 5,
791 show_progress: true,
792 }
793 }
794}
795
796#[derive(Debug, Clone, Deserialize, Serialize)]
797#[serde(default)]
798pub struct RuntimeConfig {
799 pub kind: String,
800 pub reasoning_enabled: Option<bool>,
801 pub wasm: WasmRuntimeConfig,
802}
803
804impl Default for RuntimeConfig {
805 fn default() -> Self {
806 Self {
807 kind: "native".to_string(),
808 reasoning_enabled: None,
809 wasm: WasmRuntimeConfig::default(),
810 }
811 }
812}
813
814#[derive(Debug, Clone, Deserialize, Serialize)]
815#[serde(default)]
816pub struct WasmRuntimeConfig {
817 pub tools_dir: String,
818 pub fuel_limit: u64,
819 pub memory_limit_mb: u64,
820 pub max_module_size_mb: u64,
821 pub allow_workspace_read: bool,
822 pub allow_workspace_write: bool,
823 pub allowed_hosts: Vec<String>,
824 pub security: WasmSecurityConfig,
825}
826
827impl Default for WasmRuntimeConfig {
828 fn default() -> Self {
829 Self {
830 tools_dir: "tools/wasm".to_string(),
831 fuel_limit: 1_000_000,
832 memory_limit_mb: 64,
833 max_module_size_mb: 50,
834 allow_workspace_read: false,
835 allow_workspace_write: false,
836 allowed_hosts: Vec::new(),
837 security: WasmSecurityConfig::default(),
838 }
839 }
840}
841
842#[derive(Debug, Clone, Deserialize, Serialize)]
843#[serde(default)]
844pub struct WasmSecurityConfig {
845 pub require_workspace_relative_tools_dir: bool,
846 pub reject_symlink_modules: bool,
847 pub reject_symlink_tools_dir: bool,
848 pub strict_host_validation: bool,
849 pub capability_escalation_mode: String,
850 pub module_hash_policy: String,
851 pub module_sha256: HashMap<String, String>,
852}
853
854impl Default for WasmSecurityConfig {
855 fn default() -> Self {
856 Self {
857 require_workspace_relative_tools_dir: true,
858 reject_symlink_modules: true,
859 reject_symlink_tools_dir: true,
860 strict_host_validation: true,
861 capability_escalation_mode: "deny".to_string(),
862 module_hash_policy: "warn".to_string(),
863 module_sha256: HashMap::new(),
864 }
865 }
866}
867
868#[derive(Debug, Clone, Deserialize, Serialize)]
869#[serde(default)]
870pub struct BrowserConfig {
871 pub enabled: bool,
872 pub allowed_domains: Vec<String>,
873 pub browser_open: String,
874 pub session_name: Option<String>,
875 pub backend: String,
876 pub auto_backend_priority: Vec<String>,
877 pub agent_browser_command: String,
878 pub agent_browser_extra_args: Vec<String>,
879 pub agent_browser_timeout_ms: u64,
880 pub native_headless: bool,
881 pub native_webdriver_url: String,
882 pub native_chrome_path: Option<String>,
883 pub computer_use: ComputerUseConfig,
884}
885
886impl Default for BrowserConfig {
887 fn default() -> Self {
888 Self {
889 enabled: false,
890 allowed_domains: Vec::new(),
891 browser_open: "default".to_string(),
892 session_name: None,
893 backend: "agent_browser".to_string(),
894 auto_backend_priority: Vec::new(),
895 agent_browser_command: "agent-browser".to_string(),
896 agent_browser_extra_args: Vec::new(),
897 agent_browser_timeout_ms: 30_000,
898 native_headless: true,
899 native_webdriver_url: "http://127.0.0.1:9515".to_string(),
900 native_chrome_path: None,
901 computer_use: ComputerUseConfig::default(),
902 }
903 }
904}
905
906#[derive(Debug, Clone, Deserialize, Serialize)]
907#[serde(default)]
908pub struct ComputerUseConfig {
909 pub endpoint: String,
910 pub api_key: Option<String>,
911 pub timeout_ms: u64,
912 pub allow_remote_endpoint: bool,
913 pub window_allowlist: Vec<String>,
914 pub max_coordinate_x: Option<u32>,
915 pub max_coordinate_y: Option<u32>,
916}
917
918impl Default for ComputerUseConfig {
919 fn default() -> Self {
920 Self {
921 endpoint: "http://127.0.0.1:8787/v1/actions".to_string(),
922 api_key: None,
923 timeout_ms: 15_000,
924 allow_remote_endpoint: false,
925 window_allowlist: Vec::new(),
926 max_coordinate_x: None,
927 max_coordinate_y: None,
928 }
929 }
930}
931
932#[derive(Debug, Clone, Deserialize, Serialize)]
933#[serde(default)]
934pub struct HttpRequestConfig {
935 pub enabled: bool,
936 pub allowed_domains: Vec<String>,
937 pub max_response_size: usize,
938 pub timeout_secs: u64,
939 pub user_agent: String,
940 pub credential_profiles: HashMap<String, CredentialProfile>,
941}
942
943impl Default for HttpRequestConfig {
944 fn default() -> Self {
945 Self {
946 enabled: false,
947 allowed_domains: Vec::new(),
948 max_response_size: 1_000_000,
949 timeout_secs: 30,
950 user_agent: "AgentZero/1.0".to_string(),
951 credential_profiles: HashMap::new(),
952 }
953 }
954}
955
956#[derive(Debug, Clone, Deserialize, Serialize, Default)]
957#[serde(default)]
958pub struct CredentialProfile {
959 pub header_name: String,
960 pub env_var: String,
961 #[serde(default)]
962 pub value_prefix: String,
963}
964
965#[derive(Debug, Clone, Deserialize, Serialize)]
966#[serde(default)]
967pub struct WebFetchConfig {
968 pub enabled: bool,
969 pub provider: String,
970 pub api_key: Option<String>,
971 pub api_url: Option<String>,
972 pub allowed_domains: Vec<String>,
973 pub blocked_domains: Vec<String>,
974 pub max_response_size: usize,
975 pub timeout_secs: u64,
976 pub user_agent: String,
977}
978
979impl Default for WebFetchConfig {
980 fn default() -> Self {
981 Self {
982 enabled: false,
983 provider: "fast_html2md".to_string(),
984 api_key: None,
985 api_url: None,
986 allowed_domains: vec!["*".to_string()],
987 blocked_domains: Vec::new(),
988 max_response_size: 500_000,
989 timeout_secs: 30,
990 user_agent: "AgentZero/1.0".to_string(),
991 }
992 }
993}
994
995#[derive(Debug, Clone, Deserialize, Serialize)]
996#[serde(default)]
997pub struct WebSearchConfig {
998 pub enabled: bool,
999 pub provider: String,
1000 pub fallback_providers: Vec<String>,
1001 pub retries_per_provider: u32,
1002 pub retry_backoff_ms: u64,
1003 pub api_key: Option<String>,
1004 pub api_url: Option<String>,
1005 pub brave_api_key: Option<String>,
1006 pub perplexity_api_key: Option<String>,
1007 pub exa_api_key: Option<String>,
1008 pub jina_api_key: Option<String>,
1009 pub max_results: usize,
1010 pub timeout_secs: u64,
1011 pub user_agent: String,
1012 pub domain_filter: Vec<String>,
1013 pub language_filter: Vec<String>,
1014 pub country: Option<String>,
1015 pub recency_filter: Option<String>,
1016}
1017
1018impl Default for WebSearchConfig {
1019 fn default() -> Self {
1020 Self {
1021 enabled: false,
1022 provider: "duckduckgo".to_string(),
1023 fallback_providers: Vec::new(),
1024 retries_per_provider: 0,
1025 retry_backoff_ms: 250,
1026 api_key: None,
1027 api_url: None,
1028 brave_api_key: None,
1029 perplexity_api_key: None,
1030 exa_api_key: None,
1031 jina_api_key: None,
1032 max_results: 5,
1033 timeout_secs: 15,
1034 user_agent: "AgentZero/1.0".to_string(),
1035 domain_filter: Vec::new(),
1036 language_filter: Vec::new(),
1037 country: None,
1038 recency_filter: None,
1039 }
1040 }
1041}
1042
1043#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1044#[serde(default)]
1045pub struct ComposioConfig {
1046 pub enabled: bool,
1047 pub api_key: Option<String>,
1048 pub entity_id: String,
1049}
1050
1051#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1052#[serde(default)]
1053pub struct PushoverConfig {
1054 pub enabled: bool,
1055}
1056
1057#[derive(Debug, Clone, Deserialize, Serialize)]
1058#[serde(default)]
1059pub struct CostConfig {
1060 pub enabled: bool,
1061 pub daily_limit_usd: f64,
1062 pub monthly_limit_usd: f64,
1063 pub warn_at_percent: u32,
1064 pub allow_override: bool,
1065}
1066
1067impl Default for CostConfig {
1068 fn default() -> Self {
1069 Self {
1070 enabled: false,
1071 daily_limit_usd: 10.0,
1072 monthly_limit_usd: 100.0,
1073 warn_at_percent: 80,
1074 allow_override: false,
1075 }
1076 }
1077}
1078
1079#[derive(Debug, Clone, Deserialize, Serialize)]
1080#[serde(default)]
1081pub struct IdentityConfig {
1082 pub format: String,
1083 pub aieos_path: Option<String>,
1084 pub aieos_inline: Option<String>,
1085}
1086
1087impl Default for IdentityConfig {
1088 fn default() -> Self {
1089 Self {
1090 format: "openclaw".to_string(),
1091 aieos_path: None,
1092 aieos_inline: None,
1093 }
1094 }
1095}
1096
1097#[derive(Debug, Clone, Deserialize, Serialize)]
1098#[serde(default)]
1099pub struct MultimodalConfig {
1100 pub max_images: usize,
1101 pub max_image_size_mb: usize,
1102 pub allow_remote_fetch: bool,
1103}
1104
1105impl Default for MultimodalConfig {
1106 fn default() -> Self {
1107 Self {
1108 max_images: 4,
1109 max_image_size_mb: 5,
1110 allow_remote_fetch: false,
1111 }
1112 }
1113}
1114
1115#[derive(Debug, Clone, Deserialize, Serialize)]
1116#[serde(default)]
1117pub struct SkillsConfig {
1118 pub open_skills_enabled: bool,
1119 pub open_skills_dir: Option<String>,
1120 pub prompt_injection_mode: String,
1121 pub clawhub_token: Option<String>,
1122}
1123
1124impl Default for SkillsConfig {
1125 fn default() -> Self {
1126 Self {
1127 open_skills_enabled: false,
1128 open_skills_dir: None,
1129 prompt_injection_mode: "full".to_string(),
1130 clawhub_token: None,
1131 }
1132 }
1133}
1134
1135#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1136#[serde(default)]
1137pub struct ProviderOptionsConfig {
1138 pub reasoning_level: Option<String>,
1139 pub transport: Option<String>,
1140}
1141
1142#[derive(Debug, Clone, Deserialize, Serialize)]
1143#[serde(default)]
1144pub struct GatewayConfig {
1145 pub host: String,
1146 pub port: u16,
1147 pub require_pairing: bool,
1148 pub allow_public_bind: bool,
1149 pub node_control: NodeControlConfig,
1150 pub relay_mode: bool,
1153 pub relay: RelayConfig,
1155}
1156
1157impl Default for GatewayConfig {
1158 fn default() -> Self {
1159 Self {
1160 host: "127.0.0.1".to_string(),
1161 port: 42617,
1162 require_pairing: true,
1163 allow_public_bind: false,
1164 node_control: NodeControlConfig::default(),
1165 relay_mode: false,
1166 relay: RelayConfig::default(),
1167 }
1168 }
1169}
1170
1171#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1172#[serde(default)]
1173pub struct NodeControlConfig {
1174 pub enabled: bool,
1175 pub auth_token: Option<String>,
1176 pub allowed_node_ids: Vec<String>,
1177}
1178
1179#[derive(Debug, Clone, Deserialize, Serialize)]
1180#[serde(default)]
1181pub struct ChannelsGlobalConfig {
1182 pub message_timeout_secs: u64,
1183 pub group_reply: HashMap<String, GroupReplyConfig>,
1184 pub ack_reaction: HashMap<String, AckReactionConfig>,
1185 pub stream_mode: String,
1186 pub draft_update_interval_ms: u64,
1187 pub interrupt_on_new_message: bool,
1188 pub default_privacy_boundary: String,
1191}
1192
1193impl Default for ChannelsGlobalConfig {
1194 fn default() -> Self {
1195 Self {
1196 message_timeout_secs: 300,
1197 group_reply: HashMap::new(),
1198 ack_reaction: HashMap::new(),
1199 stream_mode: "off".to_string(),
1200 draft_update_interval_ms: 500,
1201 interrupt_on_new_message: false,
1202 default_privacy_boundary: String::new(),
1203 }
1204 }
1205}
1206
1207#[derive(Debug, Clone, Deserialize, Serialize)]
1208#[serde(default)]
1209pub struct GroupReplyConfig {
1210 pub mode: String,
1211 pub allowed_sender_ids: Vec<String>,
1212 pub bot_name: Option<String>,
1213}
1214
1215impl Default for GroupReplyConfig {
1216 fn default() -> Self {
1217 Self {
1218 mode: "all_messages".to_string(),
1219 allowed_sender_ids: Vec::new(),
1220 bot_name: None,
1221 }
1222 }
1223}
1224
1225#[derive(Debug, Clone, Deserialize, Serialize)]
1226#[serde(default)]
1227pub struct AckReactionConfig {
1228 pub enabled: bool,
1229 pub emoji_pool: Vec<String>,
1230 pub strategy: String,
1231 pub sample_rate: f64,
1232 pub rules: Vec<AckReactionRule>,
1233}
1234
1235impl Default for AckReactionConfig {
1236 fn default() -> Self {
1237 Self {
1238 enabled: false,
1239 emoji_pool: vec!["👍".to_string(), "👀".to_string(), "🤔".to_string()],
1240 strategy: "random".to_string(),
1241 sample_rate: 1.0,
1242 rules: Vec::new(),
1243 }
1244 }
1245}
1246
1247#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1248#[serde(default)]
1249pub struct AckReactionRule {
1250 pub contains_any: Vec<String>,
1251 pub contains_all: Vec<String>,
1252 pub contains_none: Vec<String>,
1253 pub regex: Option<String>,
1254 pub sender_ids: Vec<String>,
1255 pub chat_ids: Vec<String>,
1256 pub emoji_override: Vec<String>,
1257}
1258
1259#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1262#[serde(default)]
1263pub struct ModelProviderProfile {
1264 pub name: Option<String>,
1265 pub base_url: Option<String>,
1266 pub wire_api: Option<String>,
1267 pub model: Option<String>,
1268 pub api_key: Option<String>,
1269 pub requires_openai_auth: bool,
1270}
1271
1272#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1275#[serde(default)]
1276pub struct ModelRoute {
1277 pub hint: String,
1278 pub provider: String,
1279 pub model: String,
1280 pub max_tokens: Option<usize>,
1281 pub api_key: Option<String>,
1282 pub transport: Option<String>,
1283}
1284
1285#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1286#[serde(default)]
1287pub struct EmbeddingRoute {
1288 pub hint: String,
1289 pub provider: String,
1290 pub model: String,
1291 pub dimensions: Option<usize>,
1292 pub api_key: Option<String>,
1293}
1294
1295#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1296#[serde(default)]
1297pub struct QueryClassificationConfig {
1298 pub enabled: bool,
1299 pub rules: Vec<QueryClassificationRule>,
1300}
1301
1302#[derive(Debug, Clone, Deserialize, Serialize, Default)]
1303#[serde(default)]
1304pub struct QueryClassificationRule {
1305 pub hint: String,
1306 #[serde(default)]
1307 pub keywords: Vec<String>,
1308 #[serde(default)]
1309 pub patterns: Vec<String>,
1310 pub min_length: Option<usize>,
1311 pub max_length: Option<usize>,
1312 #[serde(default)]
1313 pub priority: i32,
1314}
1315
1316#[derive(Debug, Clone, Deserialize, Serialize)]
1319#[serde(default)]
1320pub struct DelegateAgentConfig {
1321 pub provider: String,
1322 pub model: String,
1323 pub system_prompt: Option<String>,
1324 pub api_key: Option<String>,
1325 pub temperature: Option<f64>,
1326 pub max_depth: usize,
1327 pub agentic: bool,
1328 pub allowed_tools: Vec<String>,
1329 pub max_iterations: usize,
1330 #[serde(default)]
1332 pub privacy_boundary: String,
1333 #[serde(default)]
1335 pub allowed_providers: Vec<String>,
1336 #[serde(default)]
1338 pub blocked_providers: Vec<String>,
1339}
1340
1341impl Default for DelegateAgentConfig {
1342 fn default() -> Self {
1343 Self {
1344 provider: String::new(),
1345 model: String::new(),
1346 system_prompt: None,
1347 api_key: None,
1348 temperature: None,
1349 max_depth: 3,
1350 agentic: false,
1351 allowed_tools: Vec::new(),
1352 max_iterations: 10,
1353 privacy_boundary: String::new(),
1354 allowed_providers: Vec::new(),
1355 blocked_providers: Vec::new(),
1356 }
1357 }
1358}
1359
1360#[derive(Debug, Clone, Deserialize, Serialize)]
1363#[serde(default)]
1364pub struct UrlAccessConfig {
1365 pub block_private_ip: bool,
1366 pub allow_cidrs: Vec<String>,
1367 pub allow_domains: Vec<String>,
1368 pub allow_loopback: bool,
1369 pub require_first_visit_approval: bool,
1370 pub enforce_domain_allowlist: bool,
1371 pub domain_allowlist: Vec<String>,
1372 pub domain_blocklist: Vec<String>,
1373 pub approved_domains: Vec<String>,
1374}
1375
1376impl Default for UrlAccessConfig {
1377 fn default() -> Self {
1378 Self {
1379 block_private_ip: true,
1380 allow_cidrs: Vec::new(),
1381 allow_domains: Vec::new(),
1382 allow_loopback: false,
1383 require_first_visit_approval: false,
1384 enforce_domain_allowlist: false,
1385 domain_allowlist: Vec::new(),
1386 domain_blocklist: Vec::new(),
1387 approved_domains: Vec::new(),
1388 }
1389 }
1390}
1391
1392#[derive(Debug, Clone, Deserialize, Serialize)]
1393#[serde(default)]
1394pub struct OtpConfig {
1395 pub enabled: bool,
1396 pub method: String,
1397 pub token_ttl_secs: u64,
1398 pub cache_valid_secs: u64,
1399 pub gated_actions: Vec<String>,
1400 pub gated_domains: Vec<String>,
1401 pub gated_domain_categories: Vec<String>,
1402}
1403
1404impl Default for OtpConfig {
1405 fn default() -> Self {
1406 Self {
1407 enabled: false,
1408 method: "totp".to_string(),
1409 token_ttl_secs: 30,
1410 cache_valid_secs: 300,
1411 gated_actions: vec![
1412 "shell".to_string(),
1413 "file_write".to_string(),
1414 "browser_open".to_string(),
1415 "browser".to_string(),
1416 "memory_forget".to_string(),
1417 ],
1418 gated_domains: Vec::new(),
1419 gated_domain_categories: Vec::new(),
1420 }
1421 }
1422}
1423
1424#[derive(Debug, Clone, Deserialize, Serialize)]
1425#[serde(default)]
1426pub struct EstopConfig {
1427 pub enabled: bool,
1428 pub state_file: String,
1429 pub require_otp_to_resume: bool,
1430}
1431
1432impl Default for EstopConfig {
1433 fn default() -> Self {
1434 Self {
1435 enabled: false,
1436 state_file: "~/.agentzero/estop-state.json".to_string(),
1437 require_otp_to_resume: true,
1438 }
1439 }
1440}
1441
1442#[derive(Debug, Clone, Deserialize, Serialize)]
1443#[serde(default)]
1444pub struct OutboundLeakGuardConfig {
1445 pub enabled: bool,
1446 pub action: String,
1447 pub sensitivity: f64,
1448}
1449
1450impl Default for OutboundLeakGuardConfig {
1451 fn default() -> Self {
1452 Self {
1453 enabled: true,
1454 action: "redact".to_string(),
1455 sensitivity: 0.7,
1456 }
1457 }
1458}
1459
1460#[derive(Debug, Clone, Deserialize, Serialize)]
1461#[serde(default)]
1462pub struct PerplexityFilterConfig {
1463 pub enable_perplexity_filter: bool,
1464 pub perplexity_threshold: f64,
1465 pub suffix_window_chars: usize,
1466 pub min_prompt_chars: usize,
1467 pub symbol_ratio_threshold: f64,
1468}
1469
1470impl Default for PerplexityFilterConfig {
1471 fn default() -> Self {
1472 Self {
1473 enable_perplexity_filter: false,
1474 perplexity_threshold: 18.0,
1475 suffix_window_chars: 64,
1476 min_prompt_chars: 32,
1477 symbol_ratio_threshold: 0.20,
1478 }
1479 }
1480}
1481
1482#[derive(Debug, Clone, Deserialize, Serialize)]
1483#[serde(default)]
1484pub struct SyscallAnomalyConfig {
1485 pub enabled: bool,
1486 pub strict_mode: bool,
1487 pub alert_on_unknown_syscall: bool,
1488 pub max_denied_events_per_minute: u32,
1489 pub max_total_events_per_minute: u32,
1490 pub max_alerts_per_minute: u32,
1491 pub alert_cooldown_secs: u64,
1492 pub log_path: String,
1493 pub baseline_syscalls: Vec<String>,
1494}
1495
1496impl Default for SyscallAnomalyConfig {
1497 fn default() -> Self {
1498 Self {
1499 enabled: true,
1500 strict_mode: false,
1501 alert_on_unknown_syscall: true,
1502 max_denied_events_per_minute: 5,
1503 max_total_events_per_minute: 120,
1504 max_alerts_per_minute: 30,
1505 alert_cooldown_secs: 20,
1506 log_path: "syscall-anomalies.log".to_string(),
1507 baseline_syscalls: vec![
1508 "read".to_string(),
1509 "write".to_string(),
1510 "openat".to_string(),
1511 "close".to_string(),
1512 "execve".to_string(),
1513 "futex".to_string(),
1514 ],
1515 }
1516 }
1517}
1518
1519#[derive(Debug, Clone, Deserialize, Serialize)]
1522#[serde(default)]
1523pub struct PrivacyConfig {
1524 pub mode: String,
1534 pub enforce_local_provider: bool,
1536 pub block_cloud_providers: bool,
1539 pub noise: NoiseConfig,
1541 pub sealed_envelopes: SealedEnvelopeConfig,
1543 pub key_rotation: KeyRotationConfig,
1545}
1546
1547impl Default for PrivacyConfig {
1548 fn default() -> Self {
1549 Self {
1550 mode: "off".to_string(),
1551 enforce_local_provider: false,
1552 block_cloud_providers: false,
1553 noise: NoiseConfig::default(),
1554 sealed_envelopes: SealedEnvelopeConfig::default(),
1555 key_rotation: KeyRotationConfig::default(),
1556 }
1557 }
1558}
1559
1560#[derive(Debug, Clone, Deserialize, Serialize)]
1561#[serde(default)]
1562pub struct NoiseConfig {
1563 pub enabled: bool,
1564 pub handshake_pattern: String,
1566 pub session_timeout_secs: u64,
1568 pub max_sessions: usize,
1570}
1571
1572impl Default for NoiseConfig {
1573 fn default() -> Self {
1574 Self {
1575 enabled: false,
1576 handshake_pattern: "XX".to_string(),
1577 session_timeout_secs: 3600,
1578 max_sessions: 256,
1579 }
1580 }
1581}
1582
1583#[derive(Debug, Clone, Deserialize, Serialize)]
1584#[serde(default)]
1585pub struct SealedEnvelopeConfig {
1586 pub enabled: bool,
1587 pub default_ttl_secs: u32,
1589 pub max_envelope_bytes: usize,
1591 pub timing_jitter_enabled: bool,
1594 pub submit_jitter_min_ms: u32,
1596 pub submit_jitter_max_ms: u32,
1598 pub poll_jitter_min_ms: u32,
1600 pub poll_jitter_max_ms: u32,
1602}
1603
1604impl Default for SealedEnvelopeConfig {
1605 fn default() -> Self {
1606 Self {
1607 enabled: false,
1608 default_ttl_secs: 86400,
1609 max_envelope_bytes: 1_048_576,
1610 timing_jitter_enabled: false,
1611 submit_jitter_min_ms: 10,
1612 submit_jitter_max_ms: 100,
1613 poll_jitter_min_ms: 20,
1614 poll_jitter_max_ms: 200,
1615 }
1616 }
1617}
1618
1619#[derive(Debug, Clone, Deserialize, Serialize)]
1620#[serde(default)]
1621pub struct KeyRotationConfig {
1622 pub enabled: bool,
1623 pub rotation_interval_secs: u64,
1625 pub overlap_secs: u64,
1627 pub key_store_path: String,
1629}
1630
1631impl Default for KeyRotationConfig {
1632 fn default() -> Self {
1633 Self {
1634 enabled: false,
1635 rotation_interval_secs: 604_800,
1636 overlap_secs: 86_400,
1637 key_store_path: String::new(),
1638 }
1639 }
1640}
1641
1642#[derive(Debug, Clone, Deserialize, Serialize)]
1643#[serde(default)]
1644pub struct RelayConfig {
1645 pub timing_jitter_ms: u64,
1648 pub max_mailbox_size: usize,
1650 pub gc_interval_secs: u64,
1652}
1653
1654impl Default for RelayConfig {
1655 fn default() -> Self {
1656 Self {
1657 timing_jitter_ms: 500,
1658 max_mailbox_size: 1000,
1659 gc_interval_secs: 60,
1660 }
1661 }
1662}