from pr.multiplexer import get_multiplexer from pr.tools.interactive_control import ( close_interactive_session, get_session_status, list_active_sessions, read_session_output, send_input_to_session, ) from pr.tools.prompt_detection import get_global_detector from pr.ui import Colors def show_sessions(args=None): """Show all active multiplexer sessions.""" sessions = list_active_sessions() if not sessions: print(f"{Colors.YELLOW}No active sessions.{Colors.RESET}") return print(f"{Colors.BOLD}Active Sessions:{Colors.RESET}") print("-" * 80) for session_name, session_data in sessions.items(): metadata = session_data["metadata"] output_summary = session_data["output_summary"] status = get_session_status(session_name) is_active = status.get("is_active", False) if status else False status_color = Colors.GREEN if is_active else Colors.RED print( f"{Colors.CYAN}{session_name}{Colors.RESET}: {status_color}{metadata.get('process_type', 'unknown')}{Colors.RESET}" ) if status and "pid" in status: print(f" PID: {status['pid']}") print(f" Age: {metadata.get('start_time', 0):.1f}s") print( f" Output: {output_summary['stdout_lines']} stdout, {output_summary['stderr_lines']} stderr lines" ) print(f" Interactions: {metadata.get('interaction_count', 0)}") print(f" State: {metadata.get('state', 'unknown')}") print() def attach_session(args): """Attach to a session (show its output and allow interaction).""" if not args or len(args) < 1: print(f"{Colors.RED}Usage: attach_session {Colors.RESET}") return session_name = args[0] status = get_session_status(session_name) if not status: print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}") return print(f"{Colors.BOLD}Attaching to session: {session_name}{Colors.RESET}") print(f"Process type: {status.get('metadata', {}).get('process_type', 'unknown')}") print("-" * 50) # Show recent output try: output = read_session_output(session_name, lines=20) if output["stdout"]: print(f"{Colors.GRAY}Recent stdout:{Colors.RESET}") for line in output["stdout"].split("\n"): if line.strip(): print(f" {line}") if output["stderr"]: print(f"{Colors.YELLOW}Recent stderr:{Colors.RESET}") for line in output["stderr"].split("\n"): if line.strip(): print(f" {line}") except Exception as e: print(f"{Colors.RED}Error reading output: {e}{Colors.RESET}") print( f"\n{Colors.CYAN}Session is {'active' if status.get('is_active') else 'inactive'}{Colors.RESET}" ) def detach_session(args): """Detach from a session (stop showing its output but keep it running).""" if not args or len(args) < 1: print(f"{Colors.RED}Usage: detach_session {Colors.RESET}") return session_name = args[0] mux = get_multiplexer(session_name) if not mux: print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}") return # In this implementation, detaching just means we stop displaying output # The session continues to run in the background mux.show_output = False print( f"{Colors.GREEN}Detached from session '{session_name}'. It continues running in background.{Colors.RESET}" ) def kill_session(args): """Kill a session forcefully.""" if not args or len(args) < 1: print(f"{Colors.RED}Usage: kill_session {Colors.RESET}") return session_name = args[0] try: close_interactive_session(session_name) print(f"{Colors.GREEN}Session '{session_name}' terminated.{Colors.RESET}") except Exception as e: print(f"{Colors.RED}Error terminating session '{session_name}': {e}{Colors.RESET}") def send_command(args): """Send a command to a session.""" if not args or len(args) < 2: print(f"{Colors.RED}Usage: send_command {Colors.RESET}") return session_name = args[0] command = " ".join(args[1:]) try: send_input_to_session(session_name, command) print(f"{Colors.GREEN}Sent command to '{session_name}': {command}{Colors.RESET}") except Exception as e: print(f"{Colors.RED}Error sending command to '{session_name}': {e}{Colors.RESET}") def show_session_log(args): """Show the full log/output of a session.""" if not args or len(args) < 1: print(f"{Colors.RED}Usage: show_session_log {Colors.RESET}") return session_name = args[0] try: output = read_session_output(session_name) # Get all output print(f"{Colors.BOLD}Full log for session: {session_name}{Colors.RESET}") print("=" * 80) if output["stdout"]: print(f"{Colors.GRAY}STDOUT:{Colors.RESET}") print(output["stdout"]) print() if output["stderr"]: print(f"{Colors.YELLOW}STDERR:{Colors.RESET}") print(output["stderr"]) print() except Exception as e: print(f"{Colors.RED}Error reading log for '{session_name}': {e}{Colors.RESET}") def show_session_status(args): """Show detailed status of a session.""" if not args or len(args) < 1: print(f"{Colors.RED}Usage: show_session_status {Colors.RESET}") return session_name = args[0] status = get_session_status(session_name) if not status: print(f"{Colors.RED}Session '{session_name}' not found.{Colors.RESET}") return print(f"{Colors.BOLD}Status for session: {session_name}{Colors.RESET}") print("-" * 50) metadata = status.get("metadata", {}) print(f"Process type: {metadata.get('process_type', 'unknown')}") print(f"Active: {status.get('is_active', False)}") if "pid" in status: print(f"PID: {status['pid']}") print(f"Start time: {metadata.get('start_time', 0):.1f}") print(f"Last activity: {metadata.get('last_activity', 0):.1f}") print(f"Interaction count: {metadata.get('interaction_count', 0)}") print(f"State: {metadata.get('state', 'unknown')}") output_summary = status.get("output_summary", {}) print( f"Output lines: {output_summary.get('stdout_lines', 0)} stdout, {output_summary.get('stderr_lines', 0)} stderr" ) # Show prompt detection info detector = get_global_detector() session_info = detector.get_session_info(session_name) if session_info: print(f"Current state: {session_info['current_state']}") print(f"Is waiting for input: {session_info['is_waiting']}") def list_waiting_sessions(args=None): """List sessions that appear to be waiting for input.""" sessions = list_active_sessions() detector = get_global_detector() waiting_sessions = [] for session_name in sessions: if detector.is_waiting_for_input(session_name): waiting_sessions.append(session_name) if not waiting_sessions: print(f"{Colors.GREEN}No sessions are currently waiting for input.{Colors.RESET}") return print(f"{Colors.BOLD}Sessions waiting for input:{Colors.RESET}") for session_name in waiting_sessions: status = get_session_status(session_name) if status: process_type = status.get("metadata", {}).get("process_type", "unknown") print(f" {Colors.CYAN}{session_name}{Colors.RESET} ({process_type})") # Show suggestions session_info = detector.get_session_info(session_name) if session_info: suggestions = detector.get_response_suggestions({}, process_type) if suggestions: print(f" Suggested inputs: {', '.join(suggestions[:3])}") # Show first 3 print() # Command registry for the multiplexer commands MULTIPLEXER_COMMANDS = { "show_sessions": show_sessions, "attach_session": attach_session, "detach_session": detach_session, "kill_session": kill_session, "send_command": send_command, "show_session_log": show_session_log, "show_session_status": show_session_status, "list_waiting_sessions": list_waiting_sessions, }