import subprocess import threading from pr.multiplexer import ( close_multiplexer, create_multiplexer, get_all_multiplexer_states, get_multiplexer, ) def start_interactive_session(command, session_name=None, process_type="generic"): """ 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.) Returns: session_name: The name of the created session """ name, mux = 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, ) mux.process = process mux.update_metadata("pid", process.pid) # Set process type and handler 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), daemon=True ) stderr_thread = threading.Thread( target=_read_output, args=(process.stderr, mux.write_stderr), daemon=True ) stdout_thread.start() stderr_thread.start() mux.stdout_thread = stdout_thread mux.stderr_thread = stderr_thread return name except Exception as e: close_multiplexer(name) raise e def _read_output(stream, write_func): """Read from a stream and write to multiplexer buffer.""" 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 """ mux = 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): """ 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 = 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 """ return 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 """ mux = 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: mux = get_multiplexer(session_name) if mux: mux.process.kill() close_multiplexer(session_name) return {"status": "success"} except Exception as e: return {"status": "error", "error": str(e)}