diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c41992268b4d..974e0912287e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "openhands-frontend", - "version": "0.15.0", + "version": "0.15.1", "dependencies": { "@monaco-editor/react": "^4.6.0", "@nextui-org/react": "^2.4.8", diff --git a/openhands/core/logger.py b/openhands/core/logger.py index 2e954fbe5a71..865cf553f450 100644 --- a/openhands/core/logger.py +++ b/openhands/core/logger.py @@ -5,7 +5,8 @@ import sys import traceback from datetime import datetime -from typing import Literal, Mapping +from types import TracebackType +from typing import Any, Literal, Mapping from termcolor import colored @@ -61,7 +62,8 @@ def format(self, record: logging.LogRecord) -> str: def strip_ansi(s: str) -> str: - """ + """Remove ANSI escape sequences (terminal color/formatting codes) from string. + Removes ANSI escape sequences from str, as defined by ECMA-048 in http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf # https://github.com/ewen-lbh/python-strip-ansi/blob/master/strip_ansi/__init__.py @@ -136,6 +138,7 @@ def write_immediately(self, line): def print_lines(self): """Display the last n log_lines in the console (not for file logging). + This will create the effect of a rolling display in the console. """ self.move_back() @@ -143,18 +146,14 @@ def print_lines(self): self.replace_current_line(line) def move_back(self, amount=-1): - """ - '\033[F' moves the cursor up one line. - """ + r"""'\033[F' moves the cursor up one line.""" if amount == -1: amount = self.max_lines self._write('\033[F' * (self.max_lines)) self._flush() def replace_current_line(self, line=''): - """ - '\033[2K\r' clears the line and moves the cursor to the beginning of the line. - """ + r"""'\033[2K\r' clears the line and moves the cursor to the beginning of the line.""" self._write('\033[2K' + line + '\n') self._flush() @@ -235,18 +234,21 @@ def get_file_handler(log_dir: str, log_level: int = logging.INFO): logging.basicConfig(level=logging.ERROR) -def log_uncaught_exceptions(ex_cls, ex, tb): +def log_uncaught_exceptions( + ex_cls: type[BaseException], ex: BaseException, tb: TracebackType | None +) -> Any: """Logs uncaught exceptions along with the traceback. Args: - ex_cls (type): The type of the exception. - ex (Exception): The exception instance. - tb (traceback): The traceback object. + ex_cls: The type of the exception. + ex: The exception instance. + tb: The traceback object. Returns: None """ - logging.error(''.join(traceback.format_tb(tb))) + if tb: # Add check since tb can be None + logging.error(''.join(traceback.format_tb(tb))) logging.error('{0}: {1}'.format(ex_cls, ex)) @@ -314,7 +316,7 @@ def clear_llm_logs(dir: str): class LlmFileHandler(logging.FileHandler): - """# LLM prompt and response logging""" + """LLM prompt and response logging.""" def __init__(self, filename, mode='a', encoding='utf-8', delay=False): """Initializes an instance of LlmFileHandler. diff --git a/openhands/resolver/README.md b/openhands/resolver/README.md index f3dc43d5917b..296d8b085c67 100644 --- a/openhands/resolver/README.md +++ b/openhands/resolver/README.md @@ -61,7 +61,7 @@ Follow these steps to use this workflow in your own repository: 2. Create a draft PR if successful, or push a branch if unsuccessful 3. Comment on the issue with the results -Need help? Feel free to [open an issue](https://github.com/all-hands-ai/openhands-resolver/issues) or email us at [contact@all-hands.dev](mailto:contact@all-hands.dev). +Need help? Feel free to [open an issue](https://github.com/all-hands-ai/openhands/issues) or email us at [contact@all-hands.dev](mailto:contact@all-hands.dev). ## Manual Installation @@ -111,7 +111,7 @@ python -m openhands.resolver.resolve_issue --repo [OWNER]/[REPO] --issue-number For instance, if you want to resolve issue #100 in this repo, you would run: ```bash -python -m openhands.resolver.resolve_issue --repo all-hands-ai/openhands-resolver --issue-number 100 +python -m openhands.resolver.resolve_issue --repo all-hands-ai/openhands --issue-number 100 ``` The output will be written to the `output/` directory. @@ -119,7 +119,7 @@ The output will be written to the `output/` directory. If you've installed the package from source using poetry, you can use: ```bash -poetry run python openhands/resolver/resolve_issue.py --repo all-hands-ai/openhands-resolver --issue-number 100 +poetry run python openhands/resolver/resolve_issue.py --repo all-hands-ai/openhands --issue-number 100 ``` For resolving multiple issues at once (e.g., in a batch process), you can use the `resolve_all_issues` command: @@ -131,7 +131,7 @@ python -m openhands.resolver.resolve_all_issues --repo [OWNER]/[REPO] --issue-nu For example: ```bash -python -m openhands.resolver.resolve_all_issues --repo all-hands-ai/openhands-resolver --issue-numbers 100,101,102 +python -m openhands.resolver.resolve_all_issues --repo all-hands-ai/openhands --issue-numbers 100,101,102 ``` ## Responding to PR Comments diff --git a/openhands/resolver/examples/openhands-resolver.yml b/openhands/resolver/examples/openhands-resolver.yml index a244af04c6a1..74bafe1c17e6 100644 --- a/openhands/resolver/examples/openhands-resolver.yml +++ b/openhands/resolver/examples/openhands-resolver.yml @@ -23,7 +23,7 @@ jobs: with: macro: ${{ vars.OPENHANDS_MACRO || '@openhands-agent' }} max_iterations: ${{ fromJson(vars.OPENHANDS_MAX_ITER || 50) }} - base_container_image: ${{ vars.OPENHANDS_BASE_CONTAINER_IMAGE || "" }} + base_container_image: ${{ vars.OPENHANDS_BASE_CONTAINER_IMAGE || '' }} secrets: PAT_TOKEN: ${{ secrets.PAT_TOKEN }} PAT_USERNAME: ${{ secrets.PAT_USERNAME }} diff --git a/openhands/runtime/builder/base.py b/openhands/runtime/builder/base.py index 4930b13d7ffd..df2ee99035c9 100644 --- a/openhands/runtime/builder/base.py +++ b/openhands/runtime/builder/base.py @@ -9,13 +9,13 @@ def build( tags: list[str], platform: str | None = None, ) -> str: - """ - Build the runtime image. + """Build the runtime image. Args: path (str): The path to the runtime image's build directory. tags (list[str]): The tags to apply to the runtime image (e.g., ["repo:my-repo", "sha:my-sha"]). platform (str, optional): The target platform for the build. Defaults to None. + Returns: str: The name:tag of the runtime image after build (e.g., "repo:sha"). This can be different from the tags input if the builder chooses to mutate the tags (e.g., adding a @@ -28,8 +28,7 @@ def build( @abc.abstractmethod def image_exists(self, image_name: str, pull_from_repo: bool = True) -> bool: - """ - Check if the runtime image exists. + """Check if the runtime image exists. Args: image_name (str): The name of the runtime image (e.g., "repo:sha"). diff --git a/openhands/runtime/builder/docker.py b/openhands/runtime/builder/docker.py index a3cb5af39f3d..9cdf0f998fcd 100644 --- a/openhands/runtime/builder/docker.py +++ b/openhands/runtime/builder/docker.py @@ -9,6 +9,7 @@ from openhands.core.logger import RollingLogger from openhands.core.logger import openhands_logger as logger from openhands.runtime.builder.base import RuntimeBuilder +from openhands.utils.term_color import TermColor, colorize class DockerRuntimeBuilder(RuntimeBuilder): @@ -187,7 +188,9 @@ def image_exists(self, image_name: str, pull_from_repo: bool = True) -> bool: return True except docker.errors.ImageNotFound: if not pull_from_repo: - logger.debug(f'Image {image_name} not found locally') + logger.debug( + f'Image {image_name} {colorize("not found", TermColor.WARNING)} locally' + ) return False try: logger.debug( @@ -214,7 +217,7 @@ def image_exists(self, image_name: str, pull_from_repo: bool = True) -> bool: logger.debug('Could not find image locally or in registry.') return False except Exception as e: - msg = 'Image could not be pulled: ' + msg = f'Image {colorize("could not be pulled", TermColor.ERROR)}: ' ex_msg = str(e) if 'Not Found' in ex_msg: msg += 'image not found in registry.' @@ -286,8 +289,7 @@ def _output_build_progress( logger.debug(current_line['status']) def _prune_old_cache_files(self, cache_dir: str, max_age_days: int = 7) -> None: - """ - Prune cache files older than the specified number of days. + """Prune cache files older than the specified number of days. Args: cache_dir (str): The path to the cache directory. @@ -311,8 +313,7 @@ def _prune_old_cache_files(self, cache_dir: str, max_age_days: int = 7) -> None: logger.warning(f'Error during build cache pruning: {e}') def _is_cache_usable(self, cache_dir: str) -> bool: - """ - Check if the cache directory is usable (exists and is writable). + """Check if the cache directory is usable (exists and is writable). Args: cache_dir (str): The path to the cache directory. diff --git a/openhands/utils/term_color.py b/openhands/utils/term_color.py new file mode 100644 index 000000000000..6938369da336 --- /dev/null +++ b/openhands/utils/term_color.py @@ -0,0 +1,25 @@ +from enum import Enum + +from termcolor import colored + + +class TermColor(Enum): + """Terminal color codes.""" + + WARNING = 'yellow' + SUCCESS = 'green' + ERROR = 'red' + INFO = 'blue' + + +def colorize(text: str, color: TermColor = TermColor.WARNING) -> str: + """Colorize text with specified color. + + Args: + text (str): Text to be colored + color (TermColor, optional): Color to use. Defaults to TermColor.WARNING + + Returns: + str: Colored text + """ + return colored(text, color.value)