mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
2 Commits
fix-basic-
...
0.47.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c39b93f7e | ||
|
|
197e1a3612 |
@@ -159,7 +159,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.46-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.47-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
@@ -62,17 +62,17 @@ system requirements and more information.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.46-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.47-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -64,7 +64,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -73,7 +73,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ To run OpenHands in Headless mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -42,7 +42,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
```
|
||||
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -67,17 +67,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
### Start the App
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.46
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.47
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.46.0",
|
||||
"version": "0.47.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.46.0",
|
||||
"version": "0.47.0",
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.0-beta.9",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.46.0",
|
||||
"version": "0.47.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -70,6 +70,13 @@ try:
|
||||
_THIRD_PARTY_RUNTIME_CLASSES[runtime_name] = runtime_class
|
||||
|
||||
except ImportError:
|
||||
# ImportError means the library is not installed (expected for optional dependencies)
|
||||
pass
|
||||
except Exception as e:
|
||||
# Other exceptions mean the library is present but broken, which should be logged
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
logger.warning(f'Failed to import third-party runtime {module_path}: {e}')
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
|
||||
@@ -40,7 +40,7 @@ Two configuration options are required to use the Kubernetes runtime:
|
||||
2. **Runtime Container Image**: Specify the container image to use for the runtime environment
|
||||
```toml
|
||||
[sandbox]
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.46-nikolaik"
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik"
|
||||
```
|
||||
|
||||
#### Additional Kubernetes Options
|
||||
|
||||
@@ -6,7 +6,7 @@ requires = [
|
||||
|
||||
[tool.poetry]
|
||||
name = "openhands-ai"
|
||||
version = "0.46.0"
|
||||
version = "0.47.0"
|
||||
description = "OpenHands: Code Less, Make More"
|
||||
authors = [ "OpenHands" ]
|
||||
license = "MIT"
|
||||
|
||||
160
tests/unit/test_runtime_import_robustness.py
Normal file
160
tests/unit/test_runtime_import_robustness.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Test that the runtime import system is robust against broken third-party dependencies.
|
||||
|
||||
This test specifically addresses the issue where broken third-party runtime dependencies
|
||||
(like runloop-api-client with incompatible httpx_aiohttp versions) would break the entire
|
||||
OpenHands CLI and system.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_cli_import_with_broken_third_party_runtime():
|
||||
"""Test that CLI can be imported even with broken third-party runtime dependencies."""
|
||||
|
||||
# Clear any cached modules to ensure fresh import
|
||||
modules_to_clear = [
|
||||
k for k in sys.modules.keys() if 'openhands' in k or 'third_party' in k
|
||||
]
|
||||
for module in modules_to_clear:
|
||||
del sys.modules[module]
|
||||
|
||||
# This should not raise an exception even if third-party runtimes have broken dependencies
|
||||
try:
|
||||
import openhands.cli.main # noqa: F401
|
||||
|
||||
assert True
|
||||
except Exception as e:
|
||||
pytest.fail(f'CLI import failed: {e}')
|
||||
|
||||
|
||||
def test_runtime_import_robustness():
|
||||
"""Test that runtime import system is robust against broken dependencies."""
|
||||
|
||||
# Clear any cached runtime modules
|
||||
modules_to_clear = [k for k in sys.modules.keys() if 'openhands.runtime' in k]
|
||||
for module in modules_to_clear:
|
||||
del sys.modules[module]
|
||||
|
||||
# Import the runtime module - should succeed even with broken third-party runtimes
|
||||
try:
|
||||
import openhands.runtime # noqa: F401
|
||||
|
||||
assert True
|
||||
except Exception as e:
|
||||
pytest.fail(f'Runtime import failed: {e}')
|
||||
|
||||
|
||||
def test_get_runtime_cls_works():
|
||||
"""Test that get_runtime_cls works even when third-party runtimes are broken."""
|
||||
|
||||
# Import the runtime module
|
||||
import openhands.runtime
|
||||
|
||||
# Test that we can still get core runtime classes
|
||||
docker_runtime = openhands.runtime.get_runtime_cls('docker')
|
||||
assert docker_runtime is not None
|
||||
|
||||
local_runtime = openhands.runtime.get_runtime_cls('local')
|
||||
assert local_runtime is not None
|
||||
|
||||
# Test that requesting a non-existent runtime raises appropriate error
|
||||
with pytest.raises(ValueError, match='Runtime nonexistent not supported'):
|
||||
openhands.runtime.get_runtime_cls('nonexistent')
|
||||
|
||||
|
||||
def test_runtime_exception_handling():
|
||||
"""Test that the runtime discovery code properly handles exceptions."""
|
||||
|
||||
# This test verifies that the fix in openhands/runtime/__init__.py
|
||||
# properly catches all exceptions (not just ImportError) during
|
||||
# third-party runtime discovery
|
||||
|
||||
import openhands.runtime
|
||||
|
||||
# The fact that we can import this module successfully means
|
||||
# the exception handling is working correctly, even if there
|
||||
# are broken third-party runtime dependencies
|
||||
assert hasattr(openhands.runtime, 'get_runtime_cls')
|
||||
assert hasattr(openhands.runtime, '_THIRD_PARTY_RUNTIME_CLASSES')
|
||||
|
||||
|
||||
def test_runtime_import_exception_handling_behavior():
|
||||
"""Test that runtime import handles ImportError silently but logs other exceptions."""
|
||||
|
||||
# Test the exception handling logic by simulating the exact code from runtime init
|
||||
from io import StringIO
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
# Create a string buffer to capture log output
|
||||
log_capture = StringIO()
|
||||
handler = logging.StreamHandler(log_capture)
|
||||
handler.setLevel(logging.WARNING)
|
||||
|
||||
# Add our test handler to the OpenHands logger
|
||||
logger.addHandler(handler)
|
||||
original_level = logger.level
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
# Test 1: ImportError should be handled silently (no logging)
|
||||
module_path = 'third_party.runtime.impl.missing.missing_runtime'
|
||||
try:
|
||||
raise ImportError("No module named 'missing_library'")
|
||||
except ImportError:
|
||||
# This is the exact code from runtime init: just pass, no logging
|
||||
pass
|
||||
|
||||
# Test 2: Other exceptions should be logged
|
||||
module_path = 'third_party.runtime.impl.runloop.runloop_runtime'
|
||||
try:
|
||||
raise AttributeError(
|
||||
"module 'httpx_aiohttp' has no attribute 'HttpxAiohttpClient'"
|
||||
)
|
||||
except ImportError:
|
||||
# ImportError means the library is not installed (expected for optional dependencies)
|
||||
pass
|
||||
except Exception as e:
|
||||
# Other exceptions mean the library is present but broken, which should be logged
|
||||
# This is the exact code from runtime init
|
||||
logger.warning(f'Failed to import third-party runtime {module_path}: {e}')
|
||||
|
||||
# Check the captured log output
|
||||
log_output = log_capture.getvalue()
|
||||
|
||||
# Should contain the AttributeError warning
|
||||
assert 'Failed to import third-party runtime' in log_output
|
||||
assert 'HttpxAiohttpClient' in log_output
|
||||
# Should NOT contain the ImportError message
|
||||
assert 'missing_library' not in log_output
|
||||
|
||||
finally:
|
||||
logger.removeHandler(handler)
|
||||
logger.setLevel(original_level)
|
||||
|
||||
|
||||
def test_import_error_handled_silently(caplog):
|
||||
"""Test that ImportError is handled silently (no logging) as it means library is not installed."""
|
||||
|
||||
# Simulate the exact code path for ImportError
|
||||
logging.getLogger('openhands.runtime')
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
# Simulate ImportError handling - this should NOT log anything
|
||||
try:
|
||||
raise ImportError("No module named 'optional_runtime_library'")
|
||||
except ImportError:
|
||||
# This is the exact code from runtime init: just pass, no logging
|
||||
pass
|
||||
|
||||
# Check that NO warning was logged for ImportError
|
||||
warning_records = [
|
||||
record for record in caplog.records if record.levelname == 'WARNING'
|
||||
]
|
||||
assert len(warning_records) == 0, (
|
||||
f'ImportError should not generate warnings, but got: {warning_records}'
|
||||
)
|
||||
Reference in New Issue
Block a user