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