import threading
import time
from rp.tools.interactive_control import (
get_session_status,
list_active_sessions,
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)
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
sessions_needing_attention = self._identify_sessions_needing_attention(sessions)
if sessions_needing_attention and self.llm_callback:
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"]
time_since_activity = time.time() - metadata.get("last_activity", 0)
if time_since_activity < 30:
needing_attention.append(session_name)
continue
total_lines = output_summary["stdout_lines"] + output_summary["stderr_lines"]
if total_lines > 50:
needing_attention.append(session_name)
continue
session_age = time.time() - metadata.get("start_time", 0)
if session_age > 300 and time_since_activity > 60:
needing_attention.append(session_name)
continue
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"]
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)
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 interaction_count > 0 and time_since_activity > 120:
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:
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 = 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