#!/usr/bin/env python3 """ Luzia Request Handler - Autonomous request approval orchestration Implements: /request-approver command Luzia's responsibility: monitor and process all pending infrastructure requests """ import json import subprocess import sys from pathlib import Path from datetime import datetime, timedelta class LuziaRequestApprover: """Luzia's autonomous request approval orchestrator""" def __init__(self): self.requests_file = Path("/opt/server-agents/state/pending-requests.json") self.log_file = Path("/opt/server-agents/logs/request-approvals.log") self.log_file.parent.mkdir(parents=True, exist_ok=True) # Auto-approve rules - Luzia's policy self.auto_approve = { 'service_restart': 24, # hours 'config_change': 24, 'subdomain_create': 48, } # Manual review required self.escalate = { 'support_request': True, 'service_deploy': True, 'ssl_certificate': True, } def log(self, message): """Log approval action""" timestamp = datetime.now().isoformat() log_entry = f"[{timestamp}] {message}\n" with open(self.log_file, 'a') as f: f.write(log_entry) print(message) def load_requests(self): """Load pending requests from state""" if not self.requests_file.exists(): return [] with open(self.requests_file, 'r') as f: data = json.load(f) return data.get('pending', []) def get_age_hours(self, request): """Calculate request age""" ts = request['timestamp'] if 'Z' in ts or '+' in ts: timestamp = datetime.fromisoformat(ts.replace('Z', '+00:00')) age = datetime.now(timestamp.tzinfo) - timestamp else: timestamp = datetime.fromisoformat(ts) age = datetime.now() - timestamp return age.total_seconds() / 3600 def approve_request(self, request_id, reason): """Approve via sarlo-admin MCP""" try: # Call sarlo-admin to approve result = subprocess.run( ['python3', '-c', f''' import sys sys.path.insert(0, "/opt/server-agents/mcp-servers/sarlo-admin") from server import approve_request approve_request("{request_id}", "{reason}") '''], capture_output=True, text=True, timeout=10 ) return result.returncode == 0 except Exception as e: self.log(f"âš ī¸ Error approving {request_id}: {e}") return False def escalate_request(self, request): """Escalate for manual review""" req_id = request['id'] req_type = request['type'] user = request.get('user', 'unknown') reason = request.get('reason', 'No reason provided')[:100] # Log escalation self.log(f"đŸ”ļ ESCALATE: {req_id} ({req_type}) from {user}: {reason}") # Send Telegram alert to admin try: subprocess.run([ 'python3', '-c', f''' import sys sys.path.insert(0, "/opt/server-agents/mcp-servers/sarlo-admin") from server import send_telegram_message msg = """đŸ”ļ *Request Escalation* ID: {req_id} Type: {req_type} User: {user} Reason: {reason} Review at: /opt/server-agents/state/pending-requests.json""" send_telegram_message(msg) ''' ], timeout=5) except: pass def process_requests(self): """Luzia's request processing loop""" requests = self.load_requests() if not requests: self.log("â„šī¸ No pending requests") return {'status': 'idle', 'count': 0} auto_approved = [] escalated = [] self.log(f"🔄 Processing {len(requests)} pending request(s)...") for req in requests: req_id = req['id'] req_type = req['type'] age_hours = self.get_age_hours(req) # Check auto-approve rules if req_type in self.auto_approve: max_age = self.auto_approve[req_type] if age_hours >= max_age: reason = f"Auto-approved by luzia (age: {age_hours:.1f}h >= {max_age}h)" if self.approve_request(req_id, reason): auto_approved.append(req_id) self.log(f"✅ APPROVED: {req_id} ({req_type})") continue # Check escalate rules if req_type in self.escalate or req_type not in self.auto_approve: self.escalate_request(req) escalated.append(req_id) result = { 'status': 'processed', 'auto_approved_count': len(auto_approved), 'escalated_count': len(escalated), 'auto_approved': auto_approved, 'escalated': escalated, 'total': len(requests) } self.log(f"✨ Cycle complete: {len(auto_approved)} approved, {len(escalated)} escalated") return result def run_background(self): """Run as luzia's background task""" self.log("🚀 Luzia Request Approver started (background)") result = self.process_requests() return result if __name__ == '__main__': approver = LuziaRequestApprover() if len(sys.argv) > 1 and sys.argv[1] == '--background': result = approver.run_background() else: result = approver.process_requests() print(json.dumps(result, indent=2))