|
import queue
|
|
import threading
|
|
import time
|
|
|
|
from pr.multiplexer import get_all_multiplexer_states, get_multiplexer
|
|
|
|
|
|
class BackgroundMonitor:
|
|
def __init__(self, check_interval=5.0):
|
|
self.check_interval = check_interval
|
|
self.active = False
|
|
self.monitor_thread = None
|
|
self.event_queue = queue.Queue()
|
|
self.last_states = {}
|
|
self.event_callbacks = []
|
|
|
|
def start(self):
|
|
"""Start the background monitoring thread."""
|
|
if self.monitor_thread is None:
|
|
self.active = True
|
|
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
|
self.monitor_thread.start()
|
|
|
|
def stop(self):
|
|
"""Stop the background monitoring thread."""
|
|
self.active = False
|
|
if self.monitor_thread:
|
|
self.monitor_thread.join(timeout=2)
|
|
|
|
def get_pending_events(self):
|
|
"""Get all pending events from the queue."""
|
|
events = []
|
|
while not self.event_queue.empty():
|
|
try:
|
|
events.append(self.event_queue.get_nowait())
|
|
except queue.Empty:
|
|
break
|
|
return events
|
|
|
|
def _monitor_loop(self):
|
|
"""Main monitoring loop that checks for multiplexer activity."""
|
|
while self.active:
|
|
try:
|
|
current_states = get_all_multiplexer_states()
|
|
|
|
# Detect changes and events
|
|
events = self._detect_events(self.last_states, current_states)
|
|
|
|
# Queue events for processing
|
|
for event in events:
|
|
self.event_queue.put(event)
|
|
# Also call callbacks immediately
|
|
for callback in self.event_callbacks:
|
|
try:
|
|
callback(event)
|
|
except Exception as e:
|
|
print(f"Error in event callback: {e}")
|
|
|
|
self.last_states = current_states.copy()
|
|
time.sleep(self.check_interval)
|
|
|
|
except Exception as e:
|
|
print(f"Error in background monitor loop: {e}")
|
|
time.sleep(self.check_interval)
|
|
|
|
def _detect_events(self, old_states, new_states):
|
|
"""Detect significant events in multiplexer states."""
|
|
events = []
|
|
|
|
# Check for new sessions
|
|
for session_name in new_states:
|
|
if session_name not in old_states:
|
|
events.append(
|
|
{
|
|
"type": "session_started",
|
|
"session_name": session_name,
|
|
"metadata": new_states[session_name]["metadata"],
|
|
}
|
|
)
|
|
|
|
# Check for ended sessions
|
|
for session_name in old_states:
|
|
if session_name not in new_states:
|
|
events.append({"type": "session_ended", "session_name": session_name})
|
|
|
|
# Check for activity in existing sessions
|
|
for session_name, new_state in new_states.items():
|
|
if session_name in old_states:
|
|
old_state = old_states[session_name]
|
|
|
|
# Check for output changes
|
|
old_stdout_lines = old_state["output_summary"]["stdout_lines"]
|
|
new_stdout_lines = new_state["output_summary"]["stdout_lines"]
|
|
old_stderr_lines = old_state["output_summary"]["stderr_lines"]
|
|
new_stderr_lines = new_state["output_summary"]["stderr_lines"]
|
|
|
|
if new_stdout_lines > old_stdout_lines or new_stderr_lines > old_stderr_lines:
|
|
# Get the new output
|
|
mux = get_multiplexer(session_name)
|
|
if mux:
|
|
all_output = mux.get_all_output()
|
|
new_output = {
|
|
"stdout": all_output["stdout"].split("\n")[old_stdout_lines:],
|
|
"stderr": all_output["stderr"].split("\n")[old_stderr_lines:],
|
|
}
|
|
|
|
events.append(
|
|
{
|
|
"type": "output_received",
|
|
"session_name": session_name,
|
|
"new_output": new_output,
|
|
"total_lines": {
|
|
"stdout": new_stdout_lines,
|
|
"stderr": new_stderr_lines,
|
|
},
|
|
}
|
|
)
|
|
|
|
# Check for state changes
|
|
old_metadata = old_state["metadata"]
|
|
new_metadata = new_state["metadata"]
|
|
|
|
if old_metadata.get("state") != new_metadata.get("state"):
|
|
events.append(
|
|
{
|
|
"type": "state_changed",
|
|
"session_name": session_name,
|
|
"old_state": old_metadata.get("state"),
|
|
"new_state": new_metadata.get("state"),
|
|
}
|
|
)
|
|
|
|
# Check for process type identification
|
|
if (
|
|
old_metadata.get("process_type") == "unknown"
|
|
and new_metadata.get("process_type") != "unknown"
|
|
):
|
|
events.append(
|
|
{
|
|
"type": "process_identified",
|
|
"session_name": session_name,
|
|
"process_type": new_metadata.get("process_type"),
|
|
}
|
|
)
|
|
|
|
# Check for sessions needing attention (based on heuristics)
|
|
for session_name, state in new_states.items():
|
|
metadata = state["metadata"]
|
|
output_summary = state["output_summary"]
|
|
|
|
# Heuristic: High output volume might indicate completion or error
|
|
total_lines = output_summary["stdout_lines"] + output_summary["stderr_lines"]
|
|
if total_lines > 100: # Arbitrary threshold
|
|
events.append(
|
|
{
|
|
"type": "high_output_volume",
|
|
"session_name": session_name,
|
|
"total_lines": total_lines,
|
|
}
|
|
)
|
|
|
|
# Heuristic: Long-running session without recent activity
|
|
time_since_activity = time.time() - metadata.get("last_activity", 0)
|
|
if time_since_activity > 300: # 5 minutes
|
|
events.append(
|
|
{
|
|
"type": "inactive_session",
|
|
"session_name": session_name,
|
|
"inactive_seconds": time_since_activity,
|
|
}
|
|
)
|
|
|
|
# Heuristic: Sessions that might be waiting for input
|
|
# This would be enhanced with prompt detection in later phases
|
|
if self._might_be_waiting_for_input(session_name, state):
|
|
events.append({"type": "possible_input_needed", "session_name": session_name})
|
|
|
|
return events
|
|
|
|
def _might_be_waiting_for_input(self, session_name, state):
|
|
"""Heuristic to detect if a session might be waiting for input."""
|
|
metadata = state["metadata"]
|
|
metadata.get("process_type", "unknown")
|
|
|
|
# Simple heuristics based on process type and recent activity
|
|
time_since_activity = time.time() - metadata.get("last_activity", 0)
|
|
|
|
# If it's been more than 10 seconds since last activity, might be waiting
|
|
if time_since_activity > 10:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# Global monitor instance
|
|
_global_monitor = None
|
|
|
|
|
|
def get_global_monitor():
|
|
"""Get the global background monitor instance."""
|
|
global _global_monitor
|
|
if _global_monitor is None:
|
|
_global_monitor = BackgroundMonitor()
|
|
return _global_monitor
|
|
|
|
|
|
def start_global_monitor():
|
|
"""Start the global background monitor."""
|
|
monitor = get_global_monitor()
|
|
monitor.start()
|
|
|
|
|
|
def stop_global_monitor():
|
|
"""Stop the global background monitor."""
|
|
global _global_monitor
|
|
if _global_monitor:
|
|
_global_monitor.stop()
|