fix: send SIGWINCH to docker exec after PTY resize
When resizing the PTY, docker exec needs to be notified so it can re-read the terminal size and propagate it to the container's PTY. Without this, the container shell stays at 80x24 regardless of what the frontend sends.
This commit is contained in:
@@ -148,10 +148,10 @@ async def _write_loop(session, websocket) -> None:
|
||||
msg_type = ctrl.get("type")
|
||||
|
||||
if msg_type == "resize":
|
||||
await session.resize(
|
||||
ctrl.get("cols", 80),
|
||||
ctrl.get("rows", 24),
|
||||
)
|
||||
cols = ctrl.get("cols", 80)
|
||||
rows = ctrl.get("rows", 24)
|
||||
logger.info(f"Received resize message for instance {instance_id}: {cols}x{rows}")
|
||||
await session.resize(cols, rows)
|
||||
elif msg_type == "reset":
|
||||
# Reset terminal session
|
||||
logger.info("Resetting terminal session for instance %s", session.instance_id)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Terminal session management for tool instances."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import pty
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import fcntl
|
||||
import time
|
||||
@@ -11,6 +13,8 @@ import uuid
|
||||
from collections import deque
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TerminalSession:
|
||||
"""Manages a single terminal session connected to a docker container.
|
||||
@@ -55,6 +59,7 @@ class TerminalSession:
|
||||
|
||||
# Set the terminal size initially
|
||||
self._set_terminal_size(self._cols, self._rows)
|
||||
logger.info(f"Starting terminal session {self.session_id} for container {self.container_id} with initial size {self._cols}x{self._rows}")
|
||||
|
||||
# Start docker exec with the slave fd as stdin/stdout/stderr
|
||||
# Using -it because the slave fd IS a TTY
|
||||
@@ -79,16 +84,27 @@ class TerminalSession:
|
||||
self.last_activity = time.time()
|
||||
|
||||
def _set_terminal_size(self, cols: int, rows: int) -> None:
|
||||
"""Set the terminal size using TIOCSWINSZ."""
|
||||
"""Set the terminal size using TIOCSWINSZ and signal docker exec."""
|
||||
if self._master_fd is None:
|
||||
logger.warning("Cannot resize: master_fd is None (session not started)")
|
||||
return
|
||||
# TIOCSWINSZ = 0x5414 on Linux
|
||||
TIOCSWINSZ = 0x5414
|
||||
size = struct.pack('HHHH', rows, cols, 0, 0)
|
||||
try:
|
||||
fcntl.ioctl(self._master_fd, TIOCSWINSZ, size)
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
logger.info(f"Resized PTY to {cols}x{rows} (fd={self._master_fd})")
|
||||
|
||||
# Send SIGWINCH to docker exec process so it re-reads terminal
|
||||
# size and propagates it to the container's PTY
|
||||
if self.process and self.process.pid:
|
||||
try:
|
||||
os.kill(self.process.pid, signal.SIGWINCH)
|
||||
logger.info(f"Sent SIGWINCH to docker exec pid={self.process.pid}")
|
||||
except (OSError, ProcessLookupError) as e:
|
||||
logger.warning(f"Failed to send SIGWINCH: {e}")
|
||||
except (OSError, IOError) as e:
|
||||
logger.error(f"Failed to resize PTY: {e}")
|
||||
|
||||
async def read_output(self) -> bytes:
|
||||
"""Read output from the PTY master and store in buffer."""
|
||||
@@ -134,9 +150,11 @@ class TerminalSession:
|
||||
async def resize(self, cols: int, rows: int) -> None:
|
||||
"""Resize the terminal."""
|
||||
if self._closed:
|
||||
logger.warning("Cannot resize: session is closed")
|
||||
return
|
||||
self._cols = cols
|
||||
self._rows = rows
|
||||
logger.info(f"resize() called for session {self.session_id}: {cols}x{rows}")
|
||||
self._set_terminal_size(cols, rows)
|
||||
|
||||
async def reset(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user