import subprocess import threading import importlib def _get_multiplexer_functions(): """Lazy import multiplexer functions to avoid circular imports.""" multiplexer = importlib.import_module("pr.multiplexer") return { "create_multiplexer": multiplexer.create_multiplexer, "get_multiplexer": multiplexer.get_multiplexer, "close_multiplexer": multiplexer.close_multiplexer, "get_all_multiplexer_states": multiplexer.get_all_multiplexer_states, } def start_interactive_session(command, session_name=None, process_type="generic", cwd=None): """ Start an interactive session in a dedicated multiplexer. Args: command: The command to run (list or string) session_name: Optional name for the session process_type: Type of process (ssh, vim, apt, etc.) cwd: Current working directory for the command Returns: session_name: The name of the created session """ funcs = _get_multiplexer_functions() name, mux = funcs["create_multiplexer"](session_name) mux.update_metadata("process_type", process_type) # Start the process if isinstance(command, str): command = command.split() try: process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, cwd=cwd, ) mux.process = process mux.update_metadata("pid", process.pid) # Set process type and handler from pr.tools.process_handlers import detect_process_type detected_type = detect_process_type(command) mux.set_process_type(detected_type) # Start output readers stdout_thread = threading.Thread( target=_read_output, args=(process.stdout, mux.write_stdout, detected_type), daemon=True ) stderr_thread = threading.Thread( target=_read_output, args=(process.stderr, mux.write_stderr, detected_type), daemon=True ) stdout_thread.start() stderr_thread.start() mux.stdout_thread = stdout_thread mux.stderr_thread = stderr_thread return name except Exception as e: funcs["close_multiplexer"](name) raise e def _read_output(stream, write_func, process_type): """Read from a stream and write to multiplexer buffer.""" if process_type in ["vim", "ssh"]: try: while True: char = stream.read(1) if not char: break write_func(char) except Exception as e: print(f"Error reading output: {e}") else: try: for line in iter(stream.readline, ""): if line: write_func(line.rstrip("\n")) except Exception as e: print(f"Error reading output: {e}") def send_input_to_session(session_name, input_data): """ Send input to an interactive session. Args: session_name: Name of the session input_data: Input string to send """ funcs = _get_multiplexer_functions() mux = funcs["get_multiplexer"](session_name) if not mux: raise ValueError(f"Session {session_name} not found") if not hasattr(mux, "process") or mux.process.poll() is not None: raise ValueError(f"Session {session_name} is not active") try: mux.process.stdin.write(input_data + "\n") mux.process.stdin.flush() except Exception as e: raise RuntimeError(f"Failed to send input to session {session_name}: {e}") def read_session_output(session_name, lines=None): funcs = _get_multiplexer_functions() """ Read output from a session. Args: session_name: Name of the session lines: Number of recent lines to return (None for all) Returns: dict: {'stdout': str, 'stderr': str} """ mux = funcs["get_multiplexer"](session_name) if not mux: raise ValueError(f"Session {session_name} not found") output = mux.get_all_output() if lines is not None: # Return last N lines stdout_lines = output["stdout"].split("\n")[-lines:] if output["stdout"] else [] stderr_lines = output["stderr"].split("\n")[-lines:] if output["stderr"] else [] output = {"stdout": "\n".join(stdout_lines), "stderr": "\n".join(stderr_lines)} return output def list_active_sessions(): """ List all active interactive sessions. Returns: dict: Session states """ funcs = _get_multiplexer_functions() return funcs["get_all_multiplexer_states"]() def get_session_status(session_name): """ Get detailed status of a session. Args: session_name: Name of the session Returns: dict: Session metadata and status """ funcs = _get_multiplexer_functions() mux = funcs["get_multiplexer"](session_name) if not mux: return None status = mux.get_metadata() status["is_active"] = hasattr(mux, "process") and mux.process.poll() is None if status["is_active"]: status["pid"] = mux.process.pid status["output_summary"] = { "stdout_lines": len(mux.stdout_buffer), "stderr_lines": len(mux.stderr_buffer), } return status def close_interactive_session(session_name): """ Close an interactive session. """ try: funcs = _get_multiplexer_functions() mux = funcs["get_multiplexer"](session_name) if mux: mux.process.kill() funcs["close_multiplexer"](session_name) return {"status": "success"} except Exception as e: return {"status": "error", "error": str(e)}