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