Files
luzia/lib/request_handler.py
admin ec33ac1936 Refactor cockpit to use DockerTmuxController pattern
Based on claude-code-tools TmuxCLIController, this refactor:

- Added DockerTmuxController class for robust tmux session management
- Implements send_keys() with configurable delay_enter
- Implements capture_pane() for output retrieval
- Implements wait_for_prompt() for pattern-based completion detection
- Implements wait_for_idle() for content-hash-based idle detection
- Implements wait_for_shell_prompt() for shell prompt detection

Also includes workflow improvements:
- Pre-task git snapshot before agent execution
- Post-task commit protocol in agent guidelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 10:42:16 -03:00

172 lines
5.5 KiB
Python
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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))