#![allow(dead_code)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum PermissionLevel {
NotifyOnly,
#[default]
LowRisk,
MediumRisk,
HighAutonomy,
}
impl PermissionLevel {
pub fn auto_approve_actions(&self) -> Vec<&'static str> {
match self {
PermissionLevel::NotifyOnly => vec![],
PermissionLevel::LowRisk => vec![
"send_notification",
"calendar_read",
"email_read",
"web_search",
"package_track",
"weather_check",
"energy_read",
],
PermissionLevel::MediumRisk => vec![
"send_notification",
"calendar_read",
"calendar_create",
"email_read",
"email_draft",
"document_create",
"web_search",
"package_track",
"weather_check",
"energy_read",
"reminder_create",
"task_create",
],
PermissionLevel::HighAutonomy => vec!["*"], }
}
pub fn always_require_approval() -> Vec<&'static str> {
vec![
"bill_pay",
"email_send",
"slack_send",
"teams_send",
"discord_send",
"sms_send",
"expense_submit",
"purchase",
"transfer_money",
"delete_file",
"share_external",
"change_password",
]
}
pub fn can_auto_approve(&self, action: &str) -> bool {
if Self::always_require_approval().contains(&action) {
return false;
}
let allowed = self.auto_approve_actions();
allowed.contains(&"*") || allowed.contains(&action)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProblemSeverity {
Info,
Warning,
Urgent,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProblemStatus {
#[default]
New,
Acknowledged,
InProgress,
Resolved,
Dismissed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProblemCategory {
BillDue {
amount: f64,
due_date: DateTime<Utc>,
vendor: String,
},
MaintenanceNeeded {
item: String,
urgency: String,
},
SupplyLow {
item: String,
current_quantity: u32,
reorder_threshold: u32,
},
EnergyAnomaly {
device: String,
expected_kwh: f64,
actual_kwh: f64,
},
DeviceOffline {
device_id: String,
device_name: String,
last_seen: DateTime<Utc>,
},
SecurityAlert {
alert_type: String,
location: String,
},
PackageDelayed {
tracking_id: String,
carrier: String,
expected_date: String,
},
AppointmentReminder {
title: String,
time: DateTime<Utc>,
location: Option<String>,
},
WeatherAlert {
alert_type: String,
severity: String,
},
SubscriptionRenewal {
service: String,
amount: f64,
renewal_date: DateTime<Utc>,
},
CalendarConflict {
event1: String,
event2: String,
overlap_minutes: u32,
},
DeadlineApproaching {
project: String,
deadline: DateTime<Utc>,
days_remaining: u32,
},
EmailUrgent {
from: String,
subject: String,
received_at: DateTime<Utc>,
},
MeetingPrepNeeded {
meeting: String,
time: DateTime<Utc>,
prep_items: Vec<String>,
},
FollowUpDue {
context: String,
person: String,
due_date: DateTime<Utc>,
},
ExpensePending {
amount: f64,
category: String,
days_pending: u32,
},
ProjectAtRisk {
project: String,
risk_factors: Vec<String>,
},
CompetitorNews {
competitor: String,
headline: String,
},
TeamBlocker {
team_member: String,
blocker: String,
},
ReportDue {
report_name: String,
due_date: DateTime<Utc>,
},
Custom {
category: String,
details: HashMap<String, serde_json::Value>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectedProblem {
pub id: String,
pub agent_id: String,
pub category: ProblemCategory,
pub title: String,
pub description: String,
pub severity: ProblemSeverity,
pub detected_at: DateTime<Utc>,
pub source: String,
pub suggested_actions: Vec<ProactiveAction>,
pub status: ProblemStatus,
pub resolved_at: Option<DateTime<Utc>>,
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ActionRisk {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ActionStatus {
#[default]
Pending,
Approved,
Rejected,
Executed,
Cancelled,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProactiveAction {
pub id: String,
pub agent_id: String,
pub problem_id: Option<String>,
pub action_type: String,
pub description: String,
pub reasoning: String,
pub estimated_impact: Option<String>,
pub risk_level: ActionRisk,
pub status: ActionStatus,
pub auto_approved: bool,
pub approved_at: Option<DateTime<Utc>>,
pub approved_by: Option<String>,
pub executed_at: Option<DateTime<Utc>>,
pub result: Option<String>,
pub error: Option<String>,
pub parameters: HashMap<String, serde_json::Value>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProactiveAgentConfig {
pub agent_id: String,
pub enabled: bool,
pub permission_level: PermissionLevel,
pub monitored_integrations: Vec<String>,
pub watch_categories: Vec<String>,
pub scan_interval_secs: u64,
pub quiet_hours: Option<QuietHours>,
pub max_pending_actions: u32,
pub custom_rules: Vec<ApprovalRule>,
}
impl Default for ProactiveAgentConfig {
fn default() -> Self {
Self {
agent_id: String::new(),
enabled: true,
permission_level: PermissionLevel::LowRisk,
monitored_integrations: Vec::new(),
watch_categories: Vec::new(),
scan_interval_secs: 300, quiet_hours: None,
max_pending_actions: 10,
custom_rules: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuietHours {
pub start: String,
pub end: String,
pub days: Vec<u8>,
pub allow_critical: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApprovalRule {
pub name: String,
pub action_type: String,
pub conditions: Vec<RuleCondition>,
pub auto_approve: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleCondition {
pub field: String,
pub operator: ConditionOperator,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConditionOperator {
Equals,
NotEquals,
GreaterThan,
LessThan,
Contains,
StartsWith,
EndsWith,
Matches,
}
pub fn household_agent_config() -> ProactiveAgentConfig {
ProactiveAgentConfig {
agent_id: "household".to_string(),
enabled: true,
permission_level: PermissionLevel::LowRisk,
monitored_integrations: vec![
"home_assistant".to_string(),
"google_calendar".to_string(),
"gmail".to_string(),
"todoist".to_string(),
"plaid".to_string(),
"amazon".to_string(),
"instacart".to_string(),
"hue".to_string(),
"nest".to_string(),
],
watch_categories: vec![
"bill_due".to_string(),
"maintenance_needed".to_string(),
"supply_low".to_string(),
"energy_anomaly".to_string(),
"device_offline".to_string(),
"security_alert".to_string(),
"package_delayed".to_string(),
"appointment_reminder".to_string(),
"weather_alert".to_string(),
"subscription_renewal".to_string(),
],
scan_interval_secs: 300,
quiet_hours: Some(QuietHours {
start: "22:00".to_string(),
end: "07:00".to_string(),
days: vec![0, 1, 2, 3, 4, 5, 6],
allow_critical: true,
}),
max_pending_actions: 10,
custom_rules: vec![ApprovalRule {
name: "auto_remind_low_supplies".to_string(),
action_type: "send_notification".to_string(),
conditions: vec![RuleCondition {
field: "category".to_string(),
operator: ConditionOperator::Equals,
value: serde_json::json!("supply_low"),
}],
auto_approve: true,
}],
}
}
pub fn business_agent_config() -> ProactiveAgentConfig {
ProactiveAgentConfig {
agent_id: "business".to_string(),
enabled: true,
permission_level: PermissionLevel::MediumRisk,
monitored_integrations: vec![
"google_calendar".to_string(),
"outlook".to_string(),
"gmail".to_string(),
"slack".to_string(),
"teams".to_string(),
"notion".to_string(),
"linear".to_string(),
"github".to_string(),
],
watch_categories: vec![
"calendar_conflict".to_string(),
"deadline_approaching".to_string(),
"email_urgent".to_string(),
"meeting_prep_needed".to_string(),
"follow_up_due".to_string(),
"expense_pending".to_string(),
"project_at_risk".to_string(),
"competitor_news".to_string(),
"team_blocker".to_string(),
"report_due".to_string(),
],
scan_interval_secs: 300,
quiet_hours: Some(QuietHours {
start: "20:00".to_string(),
end: "08:00".to_string(),
days: vec![0, 6], allow_critical: true,
}),
max_pending_actions: 15,
custom_rules: vec![
ApprovalRule {
name: "auto_prep_meeting_docs".to_string(),
action_type: "document_create".to_string(),
conditions: vec![RuleCondition {
field: "category".to_string(),
operator: ConditionOperator::Equals,
value: serde_json::json!("meeting_prep_needed"),
}],
auto_approve: true,
},
ApprovalRule {
name: "auto_flag_deadline".to_string(),
action_type: "send_notification".to_string(),
conditions: vec![RuleCondition {
field: "days_remaining".to_string(),
operator: ConditionOperator::LessThan,
value: serde_json::json!(3),
}],
auto_approve: true,
},
],
}
}
#[async_trait::async_trait]
pub trait ProactiveMonitor: Send + Sync {
async fn scan(&self) -> Result<Vec<DetectedProblem>, Box<dyn std::error::Error + Send + Sync>>;
async fn propose_actions(
&self,
problem: &DetectedProblem,
) -> Result<Vec<ProactiveAction>, Box<dyn std::error::Error + Send + Sync>>;
async fn execute_action(
&self,
action: &ProactiveAction,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
fn can_auto_approve(&self, action: &ProactiveAction) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_levels() {
assert!(!PermissionLevel::NotifyOnly.can_auto_approve("send_notification"));
assert!(PermissionLevel::LowRisk.can_auto_approve("send_notification"));
assert!(PermissionLevel::LowRisk.can_auto_approve("calendar_read"));
assert!(!PermissionLevel::LowRisk.can_auto_approve("bill_pay"));
assert!(PermissionLevel::MediumRisk.can_auto_approve("calendar_create"));
assert!(!PermissionLevel::HighAutonomy.can_auto_approve("email_send")); }
#[test]
fn test_household_config() {
let config = household_agent_config();
assert_eq!(config.agent_id, "household");
assert!(config
.monitored_integrations
.contains(&"home_assistant".to_string()));
}
#[test]
fn test_business_config() {
let config = business_agent_config();
assert_eq!(config.agent_id, "business");
assert!(config
.watch_categories
.contains(&"calendar_conflict".to_string()));
}
}