|
import time
|
|
import threading
|
|
from pr.core.background_monitor import get_global_monitor
|
|
from pr.tools.interactive_control import list_active_sessions, get_session_status, read_session_output
|
|
|
|
class AutonomousInteractions:
|
|
def __init__(self, interaction_interval=10.0):
|
|
self.interaction_interval = interaction_interval
|
|
self.active = False
|
|
self.interaction_thread = None
|
|
self.llm_callback = None
|
|
self.last_check_time = 0
|
|
|
|
def start(self, llm_callback=None):
|
|
"""Start the autonomous interaction loop."""
|
|
self.llm_callback = llm_callback
|
|
if self.interaction_thread is None:
|
|
self.active = True
|
|
self.interaction_thread = threading.Thread(target=self._interaction_loop, daemon=True)
|
|
self.interaction_thread.start()
|
|
|
|
def stop(self):
|
|
"""Stop the autonomous interaction loop."""
|
|
self.active = False
|
|
if self.interaction_thread:
|
|
self.interaction_thread.join(timeout=2)
|
|
|
|
def _interaction_loop(self):
|
|
"""Main loop for autonomous interactions with background processes."""
|
|
while self.active:
|
|
try:
|
|
current_time = time.time()
|
|
if current_time - self.last_check_time >= self.interaction_interval:
|
|
self._check_sessions_and_notify()
|
|
self.last_check_time = current_time
|
|
|
|
time.sleep(1) # Check every second for shutdown
|
|
|
|
except Exception as e:
|
|
print(f"Error in autonomous interaction loop: {e}")
|
|
time.sleep(self.interaction_interval)
|
|
|
|
def _check_sessions_and_notify(self):
|
|
"""Check active sessions and determine if LLM notification is needed."""
|
|
try:
|
|
sessions = list_active_sessions()
|
|
|
|
if not sessions:
|
|
return # No active sessions
|
|
|
|
sessions_needing_attention = self._identify_sessions_needing_attention(sessions)
|
|
|
|
if sessions_needing_attention and self.llm_callback:
|
|
# Format session updates for LLM
|
|
updates = self._format_session_updates(sessions_needing_attention)
|
|
self.llm_callback(updates)
|
|
|
|
except Exception as e:
|
|
print(f"Error checking sessions: {e}")
|
|
|
|
def _identify_sessions_needing_attention(self, sessions):
|
|
"""Identify which sessions need LLM attention based on various criteria."""
|
|
needing_attention = []
|
|
|
|
for session_name, session_data in sessions.items():
|
|
metadata = session_data['metadata']
|
|
output_summary = session_data['output_summary']
|
|
|
|
# Criteria for needing attention:
|
|
|
|
# 1. Recent output activity
|
|
time_since_activity = time.time() - metadata.get('last_activity', 0)
|
|
if time_since_activity < 30: # Activity in last 30 seconds
|
|
needing_attention.append(session_name)
|
|
continue
|
|
|
|
# 2. High output volume (potential completion or error)
|
|
total_lines = output_summary['stdout_lines'] + output_summary['stderr_lines']
|
|
if total_lines > 50: # Arbitrary threshold
|
|
needing_attention.append(session_name)
|
|
continue
|
|
|
|
# 3. Long-running sessions that might need intervention
|
|
session_age = time.time() - metadata.get('start_time', 0)
|
|
if session_age > 300 and time_since_activity > 60: # 5+ minutes old, inactive for 1+ minute
|
|
needing_attention.append(session_name)
|
|
continue
|
|
|
|
# 4. Sessions that appear to be waiting for input
|
|
if self._session_looks_stuck(session_name, session_data):
|
|
needing_attention.append(session_name)
|
|
continue
|
|
|
|
return needing_attention
|
|
|
|
def _session_looks_stuck(self, session_name, session_data):
|
|
"""Determine if a session appears to be stuck waiting for input."""
|
|
metadata = session_data['metadata']
|
|
|
|
# Check if process is still running
|
|
status = get_session_status(session_name)
|
|
if not status or not status.get('is_active', False):
|
|
return False
|
|
|
|
time_since_activity = time.time() - metadata.get('last_activity', 0)
|
|
interaction_count = metadata.get('interaction_count', 0)
|
|
|
|
# If running for a while but no interactions, might be waiting
|
|
session_age = time.time() - metadata.get('start_time', 0)
|
|
if session_age > 60 and interaction_count == 0 and time_since_activity > 30:
|
|
return True
|
|
|
|
# If had interactions but been quiet for a while
|
|
if interaction_count > 0 and time_since_activity > 120: # 2 minutes
|
|
return True
|
|
|
|
return False
|
|
|
|
def _format_session_updates(self, session_names):
|
|
"""Format session information for LLM consumption."""
|
|
updates = {
|
|
'type': 'background_session_updates',
|
|
'timestamp': time.time(),
|
|
'sessions': {}
|
|
}
|
|
|
|
for session_name in session_names:
|
|
status = get_session_status(session_name)
|
|
if status:
|
|
# Get recent output (last 20 lines)
|
|
try:
|
|
recent_output = read_session_output(session_name, lines=20)
|
|
except:
|
|
recent_output = {'stdout': '', 'stderr': ''}
|
|
|
|
updates['sessions'][session_name] = {
|
|
'status': status,
|
|
'recent_output': recent_output,
|
|
'summary': self._create_session_summary(status, recent_output)
|
|
}
|
|
|
|
return updates
|
|
|
|
def _create_session_summary(self, status, recent_output):
|
|
"""Create a human-readable summary of session status."""
|
|
summary_parts = []
|
|
|
|
process_type = status.get('metadata', {}).get('process_type', 'unknown')
|
|
summary_parts.append(f"Type: {process_type}")
|
|
|
|
is_active = status.get('is_active', False)
|
|
summary_parts.append(f"Status: {'Active' if is_active else 'Inactive'}")
|
|
|
|
if is_active and 'pid' in status:
|
|
summary_parts.append(f"PID: {status['pid']}")
|
|
|
|
age = time.time() - status.get('metadata', {}).get('start_time', 0)
|
|
summary_parts.append(f"Age: {age:.1f}s")
|
|
|
|
output_lines = len(recent_output.get('stdout', '').split('\n')) + len(recent_output.get('stderr', '').split('\n'))
|
|
summary_parts.append(f"Recent output: {output_lines} lines")
|
|
|
|
interaction_count = status.get('metadata', {}).get('interaction_count', 0)
|
|
summary_parts.append(f"Interactions: {interaction_count}")
|
|
|
|
return " | ".join(summary_parts)
|
|
|
|
# Global autonomous interactions instance
|
|
_global_autonomous = None
|
|
|
|
def get_global_autonomous():
|
|
"""Get the global autonomous interactions instance."""
|
|
global _global_autonomous
|
|
return _global_autonomous
|
|
|
|
def start_global_autonomous(llm_callback=None):
|
|
"""Start global autonomous interactions."""
|
|
global _global_autonomous
|
|
if _global_autonomous is None:
|
|
_global_autonomous = AutonomousInteractions()
|
|
_global_autonomous.start(llm_callback)
|
|
return _global_autonomous
|
|
|
|
def stop_global_autonomous():
|
|
"""Stop global autonomous interactions."""
|
|
global _global_autonomous
|
|
if _global_autonomous:
|
|
_global_autonomous.stop()
|
|
_global_autonomous = None
|