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
45
46impl PermissionLevel {
47 pub fn auto_approve_actions(&self) -> Vec<&'static str> {
49 match self {
50 PermissionLevel::NotifyOnly => vec![],
51 PermissionLevel::LowRisk => vec![
52 "send_notification",
53 "calendar_read",
54 "email_read",
55 "web_search",
56 "package_track",
57 "weather_check",
58 "energy_read",
59 ],
60 PermissionLevel::MediumRisk => vec![
61 "send_notification",
62 "calendar_read",
63 "calendar_create",
64 "email_read",
65 "email_draft",
66 "document_create",
67 "web_search",
68 "package_track",
69 "weather_check",
70 "energy_read",
71 "reminder_create",
72 "task_create",
73 ],
74 PermissionLevel::HighAutonomy => vec!["*"], }
76 }
77
78 pub fn always_require_approval() -> Vec<&'static str> {
80 vec![
81 "bill_pay",
82 "email_send",
83 "slack_send",
84 "teams_send",
85 "discord_send",
86 "sms_send",
87 "expense_submit",
88 "purchase",
89 "transfer_money",
90 "delete_file",
91 "share_external",
92 "change_password",
93 ]
94 }
95
96 pub fn can_auto_approve(&self, action: &str) -> bool {
98 if Self::always_require_approval().contains(&action) {
100 return false;
101 }
102
103 let allowed = self.auto_approve_actions();
104 allowed.contains(&"*") || allowed.contains(&action)
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "lowercase")]
115pub enum ProblemSeverity {
116 Info,
118 Warning,
120 Urgent,
122 Critical,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
128#[serde(rename_all = "snake_case")]
129pub enum ProblemStatus {
130 #[default]
132 New,
133 Acknowledged,
135 InProgress,
137 Resolved,
139 Dismissed,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145#[serde(rename_all = "snake_case")]
146pub enum ProblemCategory {
147 BillDue {
149 amount: f64,
150 due_date: DateTime<Utc>,
151 vendor: String,
152 },
153 MaintenanceNeeded {
154 item: String,
155 urgency: String,
156 },
157 SupplyLow {
158 item: String,
159 current_quantity: u32,
160 reorder_threshold: u32,
161 },
162 EnergyAnomaly {
163 device: String,
164 expected_kwh: f64,
165 actual_kwh: f64,
166 },
167 DeviceOffline {
168 device_id: String,
169 device_name: String,
170 last_seen: DateTime<Utc>,
171 },
172 SecurityAlert {
173 alert_type: String,
174 location: String,
175 },
176 PackageDelayed {
177 tracking_id: String,
178 carrier: String,
179 expected_date: String,
180 },
181 AppointmentReminder {
182 title: String,
183 time: DateTime<Utc>,
184 location: Option<String>,
185 },
186 WeatherAlert {
187 alert_type: String,
188 severity: String,
189 },
190 SubscriptionRenewal {
191 service: String,
192 amount: f64,
193 renewal_date: DateTime<Utc>,
194 },
195
196 CalendarConflict {
198 event1: String,
199 event2: String,
200 overlap_minutes: u32,
201 },
202 DeadlineApproaching {
203 project: String,
204 deadline: DateTime<Utc>,
205 days_remaining: u32,
206 },
207 EmailUrgent {
208 from: String,
209 subject: String,
210 received_at: DateTime<Utc>,
211 },
212 MeetingPrepNeeded {
213 meeting: String,
214 time: DateTime<Utc>,
215 prep_items: Vec<String>,
216 },
217 FollowUpDue {
218 context: String,
219 person: String,
220 due_date: DateTime<Utc>,
221 },
222 ExpensePending {
223 amount: f64,
224 category: String,
225 days_pending: u32,
226 },
227 ProjectAtRisk {
228 project: String,
229 risk_factors: Vec<String>,
230 },
231 CompetitorNews {
232 competitor: String,
233 headline: String,
234 },
235 TeamBlocker {
236 team_member: String,
237 blocker: String,
238 },
239 ReportDue {
240 report_name: String,
241 due_date: DateTime<Utc>,
242 },
243
244 Custom {
246 category: String,
247 details: HashMap<String, serde_json::Value>,
248 },
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct DetectedProblem {
254 pub id: String,
256 pub agent_id: String,
258 pub category: ProblemCategory,
260 pub title: String,
262 pub description: String,
264 pub severity: ProblemSeverity,
266 pub detected_at: DateTime<Utc>,
268 pub source: String,
270 pub suggested_actions: Vec<ProactiveAction>,
272 pub status: ProblemStatus,
274 pub resolved_at: Option<DateTime<Utc>>,
276 pub metadata: HashMap<String, serde_json::Value>,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
286#[serde(rename_all = "lowercase")]
287pub enum ActionRisk {
288 Low,
289 Medium,
290 High,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
295#[serde(rename_all = "snake_case")]
296pub enum ActionStatus {
297 #[default]
299 Pending,
300 Approved,
302 Rejected,
304 Executed,
306 Cancelled,
308 Failed,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct ProactiveAction {
315 pub id: String,
317 pub agent_id: String,
319 pub problem_id: Option<String>,
321 pub action_type: String,
323 pub description: String,
325 pub reasoning: String,
327 pub estimated_impact: Option<String>,
329 pub risk_level: ActionRisk,
331 pub status: ActionStatus,
333 pub auto_approved: bool,
335 pub approved_at: Option<DateTime<Utc>>,
337 pub approved_by: Option<String>,
339 pub executed_at: Option<DateTime<Utc>>,
341 pub result: Option<String>,
343 pub error: Option<String>,
345 pub parameters: HashMap<String, serde_json::Value>,
347 pub created_at: DateTime<Utc>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ProactiveAgentConfig {
358 pub agent_id: String,
360 pub enabled: bool,
362 pub permission_level: PermissionLevel,
364 pub monitored_integrations: Vec<String>,
366 pub watch_categories: Vec<String>,
368 pub scan_interval_secs: u64,
370 pub quiet_hours: Option<QuietHours>,
372 pub max_pending_actions: u32,
374 pub custom_rules: Vec<ApprovalRule>,
376}
377
378impl Default for ProactiveAgentConfig {
379 fn default() -> Self {
380 Self {
381 agent_id: String::new(),
382 enabled: true,
383 permission_level: PermissionLevel::LowRisk,
384 monitored_integrations: Vec::new(),
385 watch_categories: Vec::new(),
386 scan_interval_secs: 300, quiet_hours: None,
388 max_pending_actions: 10,
389 custom_rules: Vec::new(),
390 }
391 }
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct QuietHours {
397 pub start: String,
399 pub end: String,
401 pub days: Vec<u8>,
403 pub allow_critical: bool,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct ApprovalRule {
410 pub name: String,
412 pub action_type: String,
414 pub conditions: Vec<RuleCondition>,
416 pub auto_approve: bool,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct RuleCondition {
423 pub field: String,
425 pub operator: ConditionOperator,
427 pub value: serde_json::Value,
429}
430
431#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
433#[serde(rename_all = "snake_case")]
434pub enum ConditionOperator {
435 Equals,
436 NotEquals,
437 GreaterThan,
438 LessThan,
439 Contains,
440 StartsWith,
441 EndsWith,
442 Matches,
443}
444
445pub fn household_agent_config() -> ProactiveAgentConfig {
451 ProactiveAgentConfig {
452 agent_id: "household".to_string(),
453 enabled: true,
454 permission_level: PermissionLevel::LowRisk,
455 monitored_integrations: vec![
456 "home_assistant".to_string(),
457 "google_calendar".to_string(),
458 "gmail".to_string(),
459 "todoist".to_string(),
460 "plaid".to_string(),
461 "amazon".to_string(),
462 "instacart".to_string(),
463 "hue".to_string(),
464 "nest".to_string(),
465 ],
466 watch_categories: vec![
467 "bill_due".to_string(),
468 "maintenance_needed".to_string(),
469 "supply_low".to_string(),
470 "energy_anomaly".to_string(),
471 "device_offline".to_string(),
472 "security_alert".to_string(),
473 "package_delayed".to_string(),
474 "appointment_reminder".to_string(),
475 "weather_alert".to_string(),
476 "subscription_renewal".to_string(),
477 ],
478 scan_interval_secs: 300,
479 quiet_hours: Some(QuietHours {
480 start: "22:00".to_string(),
481 end: "07:00".to_string(),
482 days: vec![0, 1, 2, 3, 4, 5, 6],
483 allow_critical: true,
484 }),
485 max_pending_actions: 10,
486 custom_rules: vec![ApprovalRule {
487 name: "auto_remind_low_supplies".to_string(),
488 action_type: "send_notification".to_string(),
489 conditions: vec![RuleCondition {
490 field: "category".to_string(),
491 operator: ConditionOperator::Equals,
492 value: serde_json::json!("supply_low"),
493 }],
494 auto_approve: true,
495 }],
496 }
497}
498
499pub fn business_agent_config() -> ProactiveAgentConfig {
505 ProactiveAgentConfig {
506 agent_id: "business".to_string(),
507 enabled: true,
508 permission_level: PermissionLevel::MediumRisk,
509 monitored_integrations: vec![
510 "google_calendar".to_string(),
511 "outlook".to_string(),
512 "gmail".to_string(),
513 "slack".to_string(),
514 "teams".to_string(),
515 "notion".to_string(),
516 "linear".to_string(),
517 "github".to_string(),
518 ],
519 watch_categories: vec![
520 "calendar_conflict".to_string(),
521 "deadline_approaching".to_string(),
522 "email_urgent".to_string(),
523 "meeting_prep_needed".to_string(),
524 "follow_up_due".to_string(),
525 "expense_pending".to_string(),
526 "project_at_risk".to_string(),
527 "competitor_news".to_string(),
528 "team_blocker".to_string(),
529 "report_due".to_string(),
530 ],
531 scan_interval_secs: 300,
532 quiet_hours: Some(QuietHours {
533 start: "20:00".to_string(),
534 end: "08:00".to_string(),
535 days: vec![0, 6], allow_critical: true,
537 }),
538 max_pending_actions: 15,
539 custom_rules: vec![
540 ApprovalRule {
541 name: "auto_prep_meeting_docs".to_string(),
542 action_type: "document_create".to_string(),
543 conditions: vec![RuleCondition {
544 field: "category".to_string(),
545 operator: ConditionOperator::Equals,
546 value: serde_json::json!("meeting_prep_needed"),
547 }],
548 auto_approve: true,
549 },
550 ApprovalRule {
551 name: "auto_flag_deadline".to_string(),
552 action_type: "send_notification".to_string(),
553 conditions: vec![RuleCondition {
554 field: "days_remaining".to_string(),
555 operator: ConditionOperator::LessThan,
556 value: serde_json::json!(3),
557 }],
558 auto_approve: true,
559 },
560 ],
561 }
562}
563
564#[async_trait::async_trait]
570pub trait ProactiveMonitor: Send + Sync {
571 async fn scan(&self) -> Result<Vec<DetectedProblem>, Box<dyn std::error::Error + Send + Sync>>;
573
574 async fn propose_actions(
576 &self,
577 problem: &DetectedProblem,
578 ) -> Result<Vec<ProactiveAction>, Box<dyn std::error::Error + Send + Sync>>;
579
580 async fn execute_action(
582 &self,
583 action: &ProactiveAction,
584 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
585
586 fn can_auto_approve(&self, action: &ProactiveAction) -> bool;
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_permission_levels() {
596 assert!(!PermissionLevel::NotifyOnly.can_auto_approve("send_notification"));
597 assert!(PermissionLevel::LowRisk.can_auto_approve("send_notification"));
598 assert!(PermissionLevel::LowRisk.can_auto_approve("calendar_read"));
599 assert!(!PermissionLevel::LowRisk.can_auto_approve("bill_pay"));
600 assert!(PermissionLevel::MediumRisk.can_auto_approve("calendar_create"));
601 assert!(!PermissionLevel::HighAutonomy.can_auto_approve("email_send")); }
603
604 #[test]
605 fn test_household_config() {
606 let config = household_agent_config();
607 assert_eq!(config.agent_id, "household");
608 assert!(config
609 .monitored_integrations
610 .contains(&"home_assistant".to_string()));
611 }
612
613 #[test]
614 fn test_business_config() {
615 let config = business_agent_config();
616 assert_eq!(config.agent_id, "business");
617 assert!(config
618 .watch_categories
619 .contains(&"calendar_conflict".to_string()));
620 }
621}