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>
This commit is contained in:
admin
2026-01-14 10:42:16 -03:00
commit ec33ac1936
265 changed files with 92011 additions and 0 deletions

171
lib/request_handler.py Executable file
View File

@@ -0,0 +1,171 @@
#!/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))