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", 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
"""
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,
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:
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
"""
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)}