|
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
|
|
_autonomous_lock = threading.Lock()
|
|
|
|
|
|
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
|
|
with _autonomous_lock:
|
|
if _global_autonomous is None:
|
|
_global_autonomous = AutonomousInteractions()
|
|
_global_autonomous.start(llm_callback)
|
|
elif not _global_autonomous.active:
|
|
_global_autonomous.start(llm_callback)
|
|
return _global_autonomous
|
|
|
|
|
|
def stop_global_autonomous():
|
|
"""Stop global autonomous interactions."""
|
|
global _global_autonomous
|
|
with _autonomous_lock:
|
|
if _global_autonomous:
|
|
_global_autonomous.stop()
|
|
_global_autonomous = None
|