196 lines
5.7 KiB
Python
Raw Normal View History

2025-11-04 07:52:36 +01:00
import subprocess
import threading
import importlib
2025-11-04 07:52:36 +01:00
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,
}
2025-11-04 08:09:12 +01:00
def start_interactive_session(command, session_name=None, process_type="generic", cwd=None):
2025-11-04 07:52:36 +01:00
"""
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
2025-11-04 07:52:36 +01:00
Returns:
session_name: The name of the created session
"""
funcs = _get_multiplexer_functions()
name, mux = funcs["create_multiplexer"](session_name)
2025-11-04 08:09:12 +01:00
mux.update_metadata("process_type", process_type)
2025-11-04 07:52:36 +01:00
# 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,
2025-11-04 08:09:12 +01:00
bufsize=1,
cwd=cwd,
2025-11-04 07:52:36 +01:00
)
mux.process = process
2025-11-04 08:09:12 +01:00
mux.update_metadata("pid", process.pid)
2025-11-04 07:52:36 +01:00
# Set process type and handler
from pr.tools.process_handlers import detect_process_type
2025-11-04 07:52:36 +01:00
detected_type = detect_process_type(command)
mux.set_process_type(detected_type)
# Start output readers
2025-11-04 08:09:12 +01:00
stdout_thread = threading.Thread(
target=_read_output, args=(process.stdout, mux.write_stdout, detected_type), daemon=True
2025-11-04 08:09:12 +01:00
)
stderr_thread = threading.Thread(
target=_read_output, args=(process.stderr, mux.write_stderr, detected_type), daemon=True
2025-11-04 08:09:12 +01:00
)
2025-11-04 07:52:36 +01:00
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)
2025-11-04 07:52:36 +01:00
raise e
2025-11-04 08:09:12 +01:00
def _read_output(stream, write_func, process_type):
2025-11-04 07:52:36 +01:00
"""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}")
2025-11-04 07:52:36 +01:00
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
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)
2025-11-04 07:52:36 +01:00
if not mux:
raise ValueError(f"Session {session_name} not found")
2025-11-04 08:09:12 +01:00
if not hasattr(mux, "process") or mux.process.poll() is not None:
2025-11-04 07:52:36 +01:00
raise ValueError(f"Session {session_name} is not active")
try:
2025-11-04 08:09:12 +01:00
mux.process.stdin.write(input_data + "\n")
2025-11-04 07:52:36 +01:00
mux.process.stdin.flush()
except Exception as e:
raise RuntimeError(f"Failed to send input to session {session_name}: {e}")
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
def read_session_output(session_name, lines=None):
funcs = _get_multiplexer_functions()
2025-11-04 07:52:36 +01:00
"""
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)
2025-11-04 07:52:36 +01:00
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
2025-11-04 08:09:12 +01:00
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)}
2025-11-04 07:52:36 +01:00
return output
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
def list_active_sessions():
"""
List all active interactive sessions.
Returns:
dict: Session states
"""
funcs = _get_multiplexer_functions()
return funcs["get_all_multiplexer_states"]()
2025-11-04 07:52:36 +01:00
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
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)
2025-11-04 07:52:36 +01:00
if not mux:
return None
status = mux.get_metadata()
2025-11-04 08:09:12 +01:00
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),
2025-11-04 07:52:36 +01:00
}
return status
2025-11-04 08:09:12 +01:00
2025-11-04 07:52:36 +01:00
def close_interactive_session(session_name):
"""
Close an interactive session.
"""
try:
funcs = _get_multiplexer_functions()
mux = funcs["get_multiplexer"](session_name)
2025-11-04 07:52:36 +01:00
if mux:
mux.process.kill()
funcs["close_multiplexer"](session_name)
2025-11-04 07:52:36 +01:00
return {"status": "success"}
except Exception as e:
return {"status": "error", "error": str(e)}