1#![allow(dead_code)]
20
21use chrono::{DateTime, Utc};
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum PermissionLevel {
33 NotifyOnly,
35 LowRisk,
37 MediumRisk,
39 HighAutonomy,
41}
42
43impl Default for PermissionLevel {
44 fn default() -> Self {
45 Self::LowRisk
46 }
47}
48
49impl PermissionLevel {
50 pub fn auto_approve_actions(&self) -> Vec<&'static str> {
52 match self {
53 PermissionLevel::NotifyOnly => vec![],
54 PermissionLevel::LowRisk => vec![
55 "send_notification",
56 "calendar_read",
57 "email_read",
58 "web_search",
59 "package_track",
60 "weather_check",
61 "energy_read",
62 ],
63 PermissionLevel::MediumRisk => vec![
64 "send_notification",
65 "calendar_read",
66 "calendar_create",
67 "email_read",
68 "email_draft",
69 "document_create",
70 "web_search",
71 "package_track",
72 "weather_check",
73 "energy_read",
74 "reminder_create",
75 "task_create",
76 ],
77 PermissionLevel::HighAutonomy => vec!["*"], }
79 }
80
81 pub fn always_require_approval() -> Vec<&'static str> {
83 vec![
84 "bill_pay",
85 "email_send",
86 "slack_send",
87 "teams_send",
88 "discord_send",
89 "sms_send",
90 "expense_submit",
91 "purchase",
92 "transfer_money",
93 "delete_file",
94 "share_external",
95 "change_password",
96 ]
97 }
98
99 pub fn can_auto_approve(&self, action: &str) -> bool {
101 if Self::always_require_approval().contains(&action) {
103 return false;
104 }
105
106 let allowed = self.auto_approve_actions();
107 allowed.contains(&"*") || allowed.contains(&action)
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "lowercase")]
118pub enum ProblemSeverity {
119 Info,
121 Warning,
123 Urgent,
125 Critical,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
131#[serde(rename_all = "snake_case")]
132pub enum ProblemStatus {
133 #[default]
135 New,
136 Acknowledged,
138 InProgress,
140 Resolved,
142 Dismissed,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(rename_all = "snake_case")]
149pub enum ProblemCategory {
150 BillDue {
152 amount: f64,
153 due_date: DateTime<Utc>,
154 vendor: String,
155 },
156 MaintenanceNeeded {
157 item: String,
158 urgency: String,
159 },
160 SupplyLow {
161 item: String,
162 current_quantity: u32,
163 reorder_threshold: u32,
164 },
165 EnergyAnomaly {
166 device: String,
167 expected_kwh: f64,
168 actual_kwh: f64,
169 },
170 DeviceOffline {
171 device_id: String,
172 device_name: String,
173 last_seen: DateTime<Utc>,
174 },
175 SecurityAlert {
176 alert_type: String,
177 location: String,
178 },
179 PackageDelayed {
180 tracking_id: String,
181 carrier: String,
182 expected_date: String,
183 },
184 AppointmentReminder {
185 title: String,
186 time: DateTime<Utc>,
187 location: Option<String>,
188 },
189 WeatherAlert {
190 alert_type: String,
191 severity: String,
192 },
193 SubscriptionRenewal {
194 service: String,
195 amount: f64,
196 renewal_date: DateTime<Utc>,
197 },
198
199 CalendarConflict {
201 event1: String,
202 event2: String,
203 overlap_minutes: u32,
204 },
205 DeadlineApproaching {
206 project: String,
207 deadline: DateTime<Utc>,
208 days_remaining: u32,
209 },
210 EmailUrgent {
211 from: String,
212 subject: String,
213 received_at: DateTime<Utc>,
214 },
215 MeetingPrepNeeded {
216 meeting: String,
217 time: DateTime<Utc>,
218 prep_items: Vec<String>,
219 },
220 FollowUpDue {
221 context: String,
222 person: String,
223 due_date: DateTime<Utc>,
224 },
225 ExpensePending {
226 amount: f64,
227 category: String,
228 days_pending: u32,
229 },
230 ProjectAtRisk {
231 project: String,
232 risk_factors: Vec<String>,
233 },
234 CompetitorNews {
235 competitor: String,
236 headline: String,
237 },
238 TeamBlocker {
239 team_member: String,
240 blocker: String,
241 },
242 ReportDue {
243 report_name: String,
244 due_date: DateTime<Utc>,
245 },
246
247 Custom {
249 category: String,
250 details: HashMap<String, serde_json::Value>,
251 },
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct DetectedProblem {
257 pub id: String,
259 pub agent_id: String,
261 pub category: ProblemCategory,
263 pub title: String,
265 pub description: String,
267 pub severity: ProblemSeverity,
269 pub detected_at: DateTime<Utc>,
271 pub source: String,
273 pub suggested_actions: Vec<ProactiveAction>,
275 pub status: ProblemStatus,
277 pub resolved_at: Option<DateTime<Utc>>,
279 pub metadata: HashMap<String, serde_json::Value>,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
289#[serde(rename_all = "lowercase")]
290pub enum ActionRisk {
291 Low,
292 Medium,
293 High,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
298#[serde(rename_all = "snake_case")]
299pub enum ActionStatus {
300 #[default]
302 Pending,
303 Approved,
305 Rejected,
307 Executed,
309 Cancelled,
311 Failed,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct ProactiveAction {
318 pub id: String,
320 pub agent_id: String,
322 pub problem_id: Option<String>,
324 pub action_type: String,
326 pub description: String,
328 pub reasoning: String,
330 pub estimated_impact: Option<String>,
332 pub risk_level: ActionRisk,
334 pub status: ActionStatus,
336 pub auto_approved: bool,
338 pub approved_at: Option<DateTime<Utc>>,
340 pub approved_by: Option<String>,
342 pub executed_at: Option<DateTime<Utc>>,
344 pub result: Option<String>,
346 pub error: Option<String>,
348 pub parameters: HashMap<String, serde_json::Value>,
350 pub created_at: DateTime<Utc>,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct ProactiveAgentConfig {
361 pub agent_id: String,
363 pub enabled: bool,
365 pub permission_level: PermissionLevel,
367 pub monitored_integrations: Vec<String>,
369 pub watch_categories: Vec<String>,
371 pub scan_interval_secs: u64,
373 pub quiet_hours: Option<QuietHours>,
375 pub max_pending_actions: u32,
377 pub custom_rules: Vec<ApprovalRule>,
379}
380
381impl Default for ProactiveAgentConfig {
382 fn default() -> Self {
383 Self {
384 agent_id: String::new(),
385 enabled: true,
386 permission_level: PermissionLevel::LowRisk,
387 monitored_integrations: Vec::new(),
388 watch_categories: Vec::new(),
389 scan_interval_secs: 300, quiet_hours: None,
391 max_pending_actions: 10,
392 custom_rules: Vec::new(),
393 }
394 }
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct QuietHours {
400 pub start: String,
402 pub end: String,
404 pub days: Vec<u8>,
406 pub allow_critical: bool,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ApprovalRule {
413 pub name: String,
415 pub action_type: String,
417 pub conditions: Vec<RuleCondition>,
419 pub auto_approve: bool,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct RuleCondition {
426 pub field: String,
428 pub operator: ConditionOperator,
430 pub value: serde_json::Value,
432}
433
434#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
436#[serde(rename_all = "snake_case")]
437pub enum ConditionOperator {
438 Equals,
439 NotEquals,
440 GreaterThan,
441 LessThan,
442 Contains,
443 StartsWith,
444 EndsWith,
445 Matches,
446}
447
448pub fn household_agent_config() -> ProactiveAgentConfig {
454 ProactiveAgentConfig {
455 agent_id: "household".to_string(),
456 enabled: true,
457 permission_level: PermissionLevel::LowRisk,
458 monitored_integrations: vec![
459 "home_assistant".to_string(),
460 "google_calendar".to_string(),
461 "gmail".to_string(),
462 "todoist".to_string(),
463 "plaid".to_string(),
464 "amazon".to_string(),
465 "instacart".to_string(),
466 "hue".to_string(),
467 "nest".to_string(),
468 ],
469 watch_categories: vec![
470 "bill_due".to_string(),
471 "maintenance_needed".to_string(),
472 "supply_low".to_string(),
473 "energy_anomaly".to_string(),
474 "device_offline".to_string(),
475 "security_alert".to_string(),
476 "package_delayed".to_string(),
477 "appointment_reminder".to_string(),
478 "weather_alert".to_string(),
479 "subscription_renewal".to_string(),
480 ],
481 scan_interval_secs: 300,
482 quiet_hours: Some(QuietHours {
483 start: "22:00".to_string(),
484 end: "07:00".to_string(),
485 days: vec![0, 1, 2, 3, 4, 5, 6],
486 allow_critical: true,
487 }),
488 max_pending_actions: 10,
489 custom_rules: vec![ApprovalRule {
490 name: "auto_remind_low_supplies".to_string(),
491 action_type: "send_notification".to_string(),
492 conditions: vec![RuleCondition {
493 field: "category".to_string(),
494 operator: ConditionOperator::Equals,
495 value: serde_json::json!("supply_low"),
496 }],
497 auto_approve: true,
498 }],
499 }
500}
501
502pub fn business_agent_config() -> ProactiveAgentConfig {
508 ProactiveAgentConfig {
509 agent_id: "business".to_string(),
510 enabled: true,
511 permission_level: PermissionLevel::MediumRisk,
512 monitored_integrations: vec![
513 "google_calendar".to_string(),
514 "outlook".to_string(),
515 "gmail".to_string(),
516 "slack".to_string(),
517 "teams".to_string(),
518 "notion".to_string(),
519 "linear".to_string(),
520 "github".to_string(),
521 ],
522 watch_categories: vec![
523 "calendar_conflict".to_string(),
524 "deadline_approaching".to_string(),
525 "email_urgent".to_string(),
526 "meeting_prep_needed".to_string(),
527 "follow_up_due".to_string(),
528 "expense_pending".to_string(),
529 "project_at_risk".to_string(),
530 "competitor_news".to_string(),
531 "team_blocker".to_string(),
532 "report_due".to_string(),
533 ],
534 scan_interval_secs: 300,
535 quiet_hours: Some(QuietHours {
536 start: "20:00".to_string(),
537 end: "08:00".to_string(),
538 days: vec![0, 6], allow_critical: true,
540 }),
541 max_pending_actions: 15,
542 custom_rules: vec![
543 ApprovalRule {
544 name: "auto_prep_meeting_docs".to_string(),
545 action_type: "document_create".to_string(),
546 conditions: vec![RuleCondition {
547 field: "category".to_string(),
548 operator: ConditionOperator::Equals,
549 value: serde_json::json!("meeting_prep_needed"),
550 }],
551 auto_approve: true,
552 },
553 ApprovalRule {
554 name: "auto_flag_deadline".to_string(),
555 action_type: "send_notification".to_string(),
556 conditions: vec![RuleCondition {
557 field: "days_remaining".to_string(),
558 operator: ConditionOperator::LessThan,
559 value: serde_json::json!(3),
560 }],
561 auto_approve: true,
562 },
563 ],
564 }
565}
566
567#[async_trait::async_trait]
573pub trait ProactiveMonitor: Send + Sync {
574 async fn scan(&self) -> Result<Vec<DetectedProblem>, Box<dyn std::error::Error + Send + Sync>>;
576
577 async fn propose_actions(
579 &self,
580 problem: &DetectedProblem,
581 ) -> Result<Vec<ProactiveAction>, Box<dyn std::error::Error + Send + Sync>>;
582
583 async fn execute_action(
585 &self,
586 action: &ProactiveAction,
587 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
588
589 fn can_auto_approve(&self, action: &ProactiveAction) -> bool;
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn test_permission_levels() {
599 assert!(!PermissionLevel::NotifyOnly.can_auto_approve("send_notification"));
600 assert!(PermissionLevel::LowRisk.can_auto_approve("send_notification"));
601 assert!(PermissionLevel::LowRisk.can_auto_approve("calendar_read"));
602 assert!(!PermissionLevel::LowRisk.can_auto_approve("bill_pay"));
603 assert!(PermissionLevel::MediumRisk.can_auto_approve("calendar_create"));
604 assert!(!PermissionLevel::HighAutonomy.can_auto_approve("email_send")); }
606
607 #[test]
608 fn test_household_config() {
609 let config = household_agent_config();
610 assert_eq!(config.agent_id, "household");
611 assert!(config
612 .monitored_integrations
613 .contains(&"home_assistant".to_string()));
614 }
615
616 #[test]
617 fn test_business_config() {
618 let config = business_agent_config();
619 assert_eq!(config.agent_id, "business");
620 assert!(config
621 .watch_categories
622 .contains(&"calendar_conflict".to_string()));
623 }
624}