Skip to content

Commit

Permalink
Refactor bash output capture to use file-based streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
openhands-agent committed Nov 21, 2024
1 parent e914055 commit c1467e6
Showing 1 changed file with 23 additions and 17 deletions.
40 changes: 23 additions & 17 deletions openhands/runtime/utils/bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
import uuid
from enum import Enum
from pathlib import Path

import bashlex
import libtmux
Expand Down Expand Up @@ -90,12 +91,18 @@ def __init__(
):
self.NO_CHANGE_TIMEOUT_SECONDS = no_change_timeout_seconds

# Create output directory
self.command_id = str(uuid.uuid4())
self.output_dir = Path('/openhands/commands')
self.output_dir.mkdir(parents=True, exist_ok=True)
self.output_file = self.output_dir / f'{self.command_id}.txt'

self.server = libtmux.Server()
window_command = '/bin/bash'
if username:
window_command = f'su {username}'

session_name = f'openhands-{username}-{uuid.uuid4()}'
session_name = f'openhands-{username}-{self.command_id}'
self.session = self.server.new_session(
session_name=session_name,
window_name='bash',
Expand All @@ -105,10 +112,6 @@ def __init__(
x=1000,
y=1000,
)
# https://unix.stackexchange.com/questions/43414/unlimited-history-in-tmux
_history_limit = 999999999
self.session.set_option('history-limit', str(_history_limit), _global=True)
self.session.history_limit = _history_limit

# We need to create a new pane because the initial pane's history limit is (default) 2000
_initial_window = self.session.attached_window
Expand All @@ -117,12 +120,13 @@ def __init__(
start_directory=work_dir,
)
self.pane = self.window.attached_pane
logger.debug(f'pane: {self.pane}; history_limit: {self.session.history_limit}')
_initial_window.kill_window()

# Configure bash to use simple PS1 and disable PS2
# Configure bash to use simple PS1, disable PS2, and redirect all output to file
self.pane.send_keys(
f'export PROMPT_COMMAND=\'export PS1="{self.PS1}"\'; export PS2=""'
f'export PROMPT_COMMAND=\'export PS1="{self.PS1}"\'; '
f'export PS2=""; '
f'exec > >(tee -a "{self.output_file}") 2>&1'
)
time.sleep(0.2) # Wait for command to take effect
self._clear_screen()
Expand All @@ -137,6 +141,10 @@ def __init__(

def close(self):
self.session.kill_session()
try:
self.output_file.unlink()
except FileNotFoundError:
pass

@property
def pwd(self):
Expand All @@ -158,16 +166,14 @@ def _get_pane_content(self, full: bool = False) -> str:
"""Get the current content of the tmux pane.
Args:
full: If True, capture the entire history of the pane.
full: If True, read the entire file. Otherwise read only the last part.
"""
# https://man7.org/linux/man-pages/man1/tmux.1.html
# -J preserves trailing spaces and joins any wrapped lines;
# -p direct output to stdout
# -S -: start from the start of history
if full:
# Capture the entire history of the pane
return '\n'.join(self.pane.cmd('capture-pane', '-J', '-pS', '-').stdout)
return '\n'.join(self.pane.cmd('capture-pane', '-J', '-p').stdout)
try:
with open(self.output_file, 'r') as f:
content = f.read()
return content
except FileNotFoundError:
return ""

def _get_command_output(
self,
Expand Down

0 comments on commit c1467e6

Please sign in to comment.