Add OpenHands app support on windows without WSL (#8674)

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Boxuan Li <liboxuan@connect.hku.hk>
This commit is contained in:
Graham Neubig
2025-05-31 02:15:35 -04:00
committed by GitHub
parent 277b87413b
commit 59858dc73b
12 changed files with 471 additions and 143 deletions

View File

@@ -5,6 +5,7 @@
- MacOS with [Docker Desktop support](https://docs.docker.com/desktop/setup/install/mac-install/#system-requirements)
- Linux
- Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) and [Docker Desktop support](https://docs.docker.com/desktop/setup/install/windows-install/#system-requirements)
- Windows without WSL (see [Windows Without WSL Guide](./windows-without-wsl))
A system with a modern processor and a minimum of **4GB RAM** is recommended to run OpenHands.
@@ -51,6 +52,10 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
The docker command below to start the app must be run inside the WSL terminal.
:::
**Alternative: Windows without WSL**
If you prefer to run OpenHands on Windows without WSL or Docker, see our [Windows Without WSL Guide](./windows-without-wsl).
</details>
## Start the App

View File

@@ -0,0 +1,195 @@
# Running OpenHands GUI on Windows Without WSL
This guide provides step-by-step instructions for running OpenHands on a Windows machine without using WSL or Docker.
## Prerequisites
1. **Windows 10/11** - A modern Windows operating system
2. **PowerShell 7+** - While Windows PowerShell comes pre-installed on Windows 10/11, PowerShell 7+ is strongly recommended to avoid compatibility issues (see Troubleshooting section for "System.Management.Automation" errors)
3. **.NET Core Runtime** - Required for the PowerShell integration via pythonnet
4. **Python 3.12 or 3.13** - Python 3.12 or 3.13 is required (Python 3.14 is not supported due to pythonnet compatibility)
5. **Git** - For cloning the repository and version control
6. **Node.js and npm** - For running the frontend
## Step 1: Install Required Software
1. **Install Python 3.12 or 3.13**
- Download Python 3.12.x or 3.13.x from [python.org](https://www.python.org/downloads/)
- During installation, check "Add Python to PATH"
- Verify installation by opening PowerShell and running:
```powershell
python --version
```
2. **Install PowerShell 7**
- Download and install PowerShell 7 from the [official PowerShell GitHub repository](https://github.com/PowerShell/PowerShell/releases)
- Choose the MSI installer appropriate for your system (x64 for most modern computers)
- Run the installer with default options
- Verify installation by opening a new terminal and running:
```powershell
pwsh --version
```
- Using PowerShell 7 (pwsh) instead of Windows PowerShell will help avoid "System.Management.Automation" errors
3. **Install .NET Core Runtime**
- Download and install the .NET Core Runtime from [Microsoft's .NET download page](https://dotnet.microsoft.com/download)
- Choose the latest .NET Core Runtime (not SDK)
- Verify installation by opening PowerShell and running:
```powershell
dotnet --info
```
- This step is required for the PowerShell integration via pythonnet. Without it, OpenHands will fall back to a more limited PowerShell implementation.
4. **Install Git**
- Download Git from [git-scm.com](https://git-scm.com/download/win)
- Use default installation options
- Verify installation:
```powershell
git --version
```
5. **Install Node.js and npm**
- Download Node.js from [nodejs.org](https://nodejs.org/) (LTS version recommended)
- During installation, accept the default options which will install npm as well
- Verify installation:
```powershell
node --version
npm --version
```
6. **Install Poetry**
- Open PowerShell as Administrator and run:
```powershell
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
```
- Add Poetry to your PATH:
```powershell
$env:Path += ";$env:APPDATA\Python\Scripts"
```
- Verify installation:
```powershell
poetry --version
```
## Step 2: Clone and Set Up OpenHands
1. **Clone the Repository**
```powershell
git clone https://github.com/All-Hands-AI/OpenHands.git
cd OpenHands
```
2. **Install Dependencies**
```powershell
poetry install
```
This will install all required dependencies, including:
- pythonnet - Required for Windows PowerShell integration
- All other OpenHands dependencies
## Step 3: Run OpenHands
1. **Build the Frontend**
```powershell
cd frontend
npm install
npm run build
cd ..
```
This will build the frontend files that the backend will serve.
2. **Start the Backend**
```powershell
# Make sure to use PowerShell 7 (pwsh) instead of Windows PowerShell
pwsh
$env:RUNTIME="local"; poetry run uvicorn openhands.server.listen:app --host 0.0.0.0 --port 3000 --reload --reload-exclude "./workspace"
```
This will start the OpenHands app using the local runtime with PowerShell integration, available at `localhost:3000`.
> **Note**: If you encounter a `RuntimeError: Directory './frontend/build' does not exist` error, make sure you've built the frontend first using the command above.
> **Important**: Using PowerShell 7 (pwsh) instead of Windows PowerShell is recommended to avoid "System.Management.Automation" errors. If you encounter this error, see the Troubleshooting section below.
3. **Alternatively, Run the Frontend in Development Mode (in a separate PowerShell window)**
```powershell
cd frontend
npm run dev
```
4. **Access the OpenHands GUI**
Open your browser and navigate to:
```
http://localhost:3000
```
> **Note**: If you're running the frontend in development mode (using `npm run dev`), use port 3001 instead: `http://localhost:3001`
## Limitations on Windows
When running OpenHands on Windows without WSL or Docker, be aware of the following limitations:
1. **Browser Tool Not Supported**: The browser tool is not currently supported on Windows.
2. **.NET Core Requirement**: The PowerShell integration requires .NET Core Runtime to be installed. If .NET Core is not available, OpenHands will automatically fall back to a more limited PowerShell implementation with reduced functionality.
3. **Interactive Shell Commands**: Some interactive shell commands may not work as expected. The PowerShell session implementation has limitations compared to the bash session used on Linux/macOS.
4. **Path Handling**: Windows uses backslashes (`\`) in paths, which may require adjustments when working with code examples designed for Unix-like systems.
## Troubleshooting
### "System.Management.Automation" Not Found Error
If you encounter an error message stating that "System.Management.Automation" was not found, this typically indicates that you have a minimal version of PowerShell installed or that the .NET components required for PowerShell integration are missing.
> **IMPORTANT**: This error is most commonly caused by using the built-in Windows PowerShell (powershell.exe) instead of PowerShell 7 (pwsh.exe). Even if you installed PowerShell 7 during the prerequisites, you may still be using the older Windows PowerShell by default.
To resolve this issue:
1. **Install the latest version of PowerShell 7** from the official Microsoft repository:
- Visit [https://github.com/PowerShell/PowerShell/releases](https://github.com/PowerShell/PowerShell/releases)
- Download and install the latest MSI package for your system architecture (x64 for most systems)
- During installation, ensure you select the following options:
- "Add PowerShell to PATH environment variable"
- "Register Windows PowerShell 7 as the default shell"
- "Enable PowerShell remoting"
- The installer will place PowerShell 7 in `C:\Program Files\PowerShell\7` by default
2. **Restart your terminal or command prompt** to ensure the new PowerShell is available
3. **Verify the installation** by running:
```powershell
pwsh --version
```
You should see output indicating PowerShell 7.x.x
4. **Run OpenHands using PowerShell 7** instead of Windows PowerShell:
```powershell
pwsh
cd path\to\openhands
$env:RUNTIME="local"; poetry run uvicorn openhands.server.listen:app --host 0.0.0.0 --port 3000 --reload --reload-exclude "./workspace"
```
> **Note**: Make sure you're explicitly using `pwsh` (PowerShell 7) and not `powershell` (Windows PowerShell). The command prompt or terminal title should say "PowerShell 7" rather than just "Windows PowerShell".
5. **If the issue persists**, ensure that you have the .NET Runtime installed:
- Download and install the latest .NET Runtime from [Microsoft's .NET download page](https://dotnet.microsoft.com/download)
- Choose ".NET Runtime" (not SDK) version 6.0 or later
- After installation, verify it's properly installed by running:
```powershell
dotnet --info
```
- Restart your computer after installation
- Try running OpenHands again
6. **Ensure that the .NET Framework is properly installed** on your system:
- Go to Control Panel > Programs > Programs and Features > Turn Windows features on or off
- Make sure ".NET Framework 4.8 Advanced Services" is enabled
- Click OK and restart if prompted
This error occurs because OpenHands uses the pythonnet package to interact with PowerShell, which requires the System.Management.Automation assembly from the .NET framework. A minimal PowerShell installation or older Windows PowerShell (rather than PowerShell 7+) might not include all the necessary components for this integration.

View File

@@ -50,6 +50,15 @@ def convert_mcp_clients_to_tools(mcp_clients: list[MCPClient] | None) -> list[di
async def create_mcp_clients(
sse_servers: list[MCPSSEServerConfig], conversation_id: str | None = None
) -> list[MCPClient]:
import sys
# Skip MCP clients on Windows
if sys.platform == 'win32':
logger.info(
'MCP functionality is disabled on Windows, skipping client creation'
)
return []
mcp_clients: list[MCPClient] = []
# Initialize SSE connections
if sse_servers:
@@ -89,6 +98,13 @@ async def fetch_mcp_tools_from_config(mcp_config: MCPConfig) -> list[dict]:
Returns:
A list of tool dictionaries. Returns an empty list if no connections could be established.
"""
import sys
# Skip MCP tools on Windows
if sys.platform == 'win32':
logger.info('MCP functionality is disabled on Windows, skipping tool fetching')
return []
mcp_clients = []
mcp_tools = []
try:
@@ -131,6 +147,15 @@ async def call_tool_mcp(mcp_clients: list[MCPClient], action: MCPAction) -> Obse
Returns:
The observation from the MCP server
"""
import sys
from openhands.events.observation import ErrorObservation
# Skip MCP tools on Windows
if sys.platform == 'win32':
logger.info('MCP functionality is disabled on Windows')
return ErrorObservation('MCP functionality is not available on Windows')
if not mcp_clients:
raise ValueError('No MCP clients found')
@@ -169,6 +194,13 @@ async def add_mcp_tools_to_agent(
"""
Add MCP tools to an agent.
"""
import sys
# Skip MCP tools on Windows
if sys.platform == 'win32':
logger.info('MCP functionality is disabled on Windows, skipping MCP tools')
agent.set_mcp_tools([])
return
assert runtime.runtime_initialized, (
'Runtime must be initialized before adding MCP tools'

View File

@@ -677,44 +677,53 @@ if __name__ == '__main__':
await client.ainit()
logger.info('ActionExecutor initialized.')
# Initialize and mount MCP Router
logger.info('Initializing MCP Router...')
mcp_router = MCPRouter(
profile_path=MCP_ROUTER_PROFILE_PATH,
router_config=RouterConfig(
api_key=SESSION_API_KEY,
auth_enabled=bool(SESSION_API_KEY),
),
)
allowed_origins = ['*']
sse_app = await mcp_router.get_sse_server_app(
allow_origins=allowed_origins, include_lifespan=False
)
# Check if we're on Windows
is_windows = sys.platform == 'win32'
# Check for route conflicts before mounting
main_app_routes = {route.path for route in app.routes}
sse_app_routes = {route.path for route in sse_app.routes}
conflicting_routes = main_app_routes.intersection(sse_app_routes)
if conflicting_routes:
logger.error(f'Route conflicts detected: {conflicting_routes}')
raise RuntimeError(
f'Cannot mount SSE app - conflicting routes found: {conflicting_routes}'
# Initialize and mount MCP Router (skip on Windows)
if is_windows:
logger.info('Skipping MCP Router initialization on Windows')
mcp_router = None
else:
logger.info('Initializing MCP Router...')
mcp_router = MCPRouter(
profile_path=MCP_ROUTER_PROFILE_PATH,
router_config=RouterConfig(
api_key=SESSION_API_KEY,
auth_enabled=bool(SESSION_API_KEY),
),
)
allowed_origins = ['*']
sse_app = await mcp_router.get_sse_server_app(
allow_origins=allowed_origins, include_lifespan=False
)
app.mount('/', sse_app)
logger.info(
f'Mounted MCP Router SSE app at root path with allowed origins: {allowed_origins}'
)
# Only mount SSE app if MCP Router is initialized (not on Windows)
if mcp_router is not None:
# Check for route conflicts before mounting
main_app_routes = {route.path for route in app.routes}
sse_app_routes = {route.path for route in sse_app.routes}
conflicting_routes = main_app_routes.intersection(sse_app_routes)
# Additional debug logging
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Main app routes:')
for route in main_app_routes:
logger.debug(f' {route}')
logger.debug('MCP SSE server app routes:')
for route in sse_app_routes:
logger.debug(f' {route}')
if conflicting_routes:
logger.error(f'Route conflicts detected: {conflicting_routes}')
raise RuntimeError(
f'Cannot mount SSE app - conflicting routes found: {conflicting_routes}'
)
app.mount('/', sse_app)
logger.info(
f'Mounted MCP Router SSE app at root path with allowed origins: {allowed_origins}'
)
# Additional debug logging
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Main app routes:')
for route in main_app_routes:
logger.debug(f' {route}')
logger.debug('MCP SSE server app routes:')
for route in sse_app_routes:
logger.debug(f' {route}')
yield
@@ -816,6 +825,23 @@ if __name__ == '__main__':
@app.post('/update_mcp_server')
async def update_mcp_server(request: Request):
# Check if we're on Windows
is_windows = sys.platform == 'win32'
if is_windows:
# On Windows, just return a success response without doing anything
logger.info(
'MCP server update request received on Windows - skipping as MCP is disabled'
)
return JSONResponse(
status_code=200,
content={
'detail': 'MCP server update skipped (MCP is disabled on Windows)',
'router_error_log': '',
},
)
# Non-Windows implementation
assert mcp_router is not None
assert os.path.exists(MCP_ROUTER_PROFILE_PATH)

View File

@@ -218,35 +218,66 @@ class Runtime(FileEditRuntimeMixin):
# Note: we don't log the vars values, they're leaking info
logger.debug('Added env vars to IPython')
# Add env vars to the Bash shell and .bashrc for persistence
cmd = ''
bashrc_cmd = ''
for key, value in env_vars.items():
# Note: json.dumps gives us nice escaping for free
cmd += f'export {key}={json.dumps(value)}; '
# Add to .bashrc if not already present
bashrc_cmd += f'grep -q "^export {key}=" ~/.bashrc || echo "export {key}={json.dumps(value)}" >> ~/.bashrc; '
if not cmd:
return
cmd = cmd.strip()
logger.debug(
'Adding env vars to bash'
) # don't log the vars values, they're leaking info
# Check if we're on Windows
import os
import sys
obs = self.run(CmdRunAction(cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(
f'Failed to add env vars [{env_vars.keys()}] to environment: {obs.content}'
)
is_windows = os.name == 'nt' or sys.platform == 'win32'
# Add to .bashrc for persistence
bashrc_cmd = bashrc_cmd.strip()
logger.debug(f'Adding env var to .bashrc: {env_vars.keys()}')
obs = self.run(CmdRunAction(bashrc_cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(
f'Failed to add env vars [{env_vars.keys()}] to .bashrc: {obs.content}'
)
if is_windows:
# Add env vars using PowerShell commands for Windows
cmd = ''
for key, value in env_vars.items():
# Use PowerShell's $env: syntax for environment variables
# Note: json.dumps gives us nice escaping for free
cmd += f'$env:{key} = {json.dumps(value)}; '
if not cmd:
return
cmd = cmd.strip()
logger.debug('Adding env vars to PowerShell') # don't log the values
obs = self.run(CmdRunAction(cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(
f'Failed to add env vars [{env_vars.keys()}] to environment: {obs.content}'
)
# We don't add to profile persistence on Windows as it's more complex
# and varies between PowerShell versions
logger.debug(f'Added env vars to PowerShell session: {env_vars.keys()}')
else:
# Original bash implementation for Unix systems
cmd = ''
bashrc_cmd = ''
for key, value in env_vars.items():
# Note: json.dumps gives us nice escaping for free
cmd += f'export {key}={json.dumps(value)}; '
# Add to .bashrc if not already present
bashrc_cmd += f'grep -q "^export {key}=" ~/.bashrc || echo "export {key}={json.dumps(value)}" >> ~/.bashrc; '
if not cmd:
return
cmd = cmd.strip()
logger.debug('Adding env vars to bash') # don't log the values
obs = self.run(CmdRunAction(cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(
f'Failed to add env vars [{env_vars.keys()}] to environment: {obs.content}'
)
# Add to .bashrc for persistence
bashrc_cmd = bashrc_cmd.strip()
logger.debug(f'Adding env var to .bashrc: {env_vars.keys()}')
obs = self.run(CmdRunAction(bashrc_cmd))
if not isinstance(obs, CmdOutputObservation) or obs.exit_code != 0:
raise RuntimeError(
f'Failed to add env vars [{env_vars.keys()}] to .bashrc: {obs.content}'
)
def on_event(self, event: Event) -> None:
if isinstance(event, Action):
@@ -406,9 +437,13 @@ class Runtime(FileEditRuntimeMixin):
else f'git checkout -b {openhands_workspace_branch}'
)
action = CmdRunAction(
command=f'{clone_command} ; cd {dir_name} ; {checkout_command}',
clone_action = CmdRunAction(command=clone_command)
self.run_action(clone_action)
cd_checkout_action = CmdRunAction(
command=f'cd {dir_name} && {checkout_command}'
)
action = cd_checkout_action
self.log('info', f'Cloning repo: {selected_repository}')
self.run_action(action)
return dir_name
@@ -649,16 +684,13 @@ fi
# Get authenticated URL and do a shallow clone (--depth 1) for efficiency
remote_url = self._get_authenticated_git_url(org_openhands_repo)
clone_cmd = f"git clone --depth 1 {remote_url} {org_repo_dir} 2>/dev/null || echo 'Org repo not found'"
clone_cmd = f'git clone --depth 1 {remote_url} {org_repo_dir}'
action = CmdRunAction(command=clone_cmd)
obs = self.run_action(action)
if (
isinstance(obs, CmdOutputObservation)
and obs.exit_code == 0
and 'Org repo not found' not in obs.content
):
if isinstance(obs, CmdOutputObservation) and obs.exit_code == 0:
self.log(
'info',
f'Successfully cloned org-level microagents from {org_openhands_repo}',

View File

@@ -354,6 +354,14 @@ class ActionExecutionClient(Runtime):
def get_mcp_config(
self, extra_stdio_servers: list[MCPStdioServerConfig] | None = None
) -> MCPConfig:
import sys
# Check if we're on Windows - MCP is disabled on Windows
if sys.platform == 'win32':
# Return empty MCP config on Windows
self.log('debug', 'MCP is disabled on Windows, returning empty config')
return MCPConfig(sse_servers=[], stdio_servers=[])
# Add the runtime as another MCP server
updated_mcp_config = self.config.mcp.model_copy()
@@ -436,6 +444,15 @@ class ActionExecutionClient(Runtime):
return updated_mcp_config
async def call_tool_mcp(self, action: MCPAction) -> Observation:
import sys
from openhands.events.observation import ErrorObservation
# Check if we're on Windows - MCP is disabled on Windows
if sys.platform == 'win32':
self.log('info', 'MCP functionality is disabled on Windows')
return ErrorObservation('MCP functionality is not available on Windows')
# Import here to avoid circular imports
from openhands.mcp.utils import call_tool_mcp as call_tool_mcp_handler
from openhands.mcp.utils import create_mcp_clients

View File

@@ -238,6 +238,7 @@ class LocalRuntime(ActionExecutionClient):
env['PYTHONPATH'] = os.pathsep.join([code_repo_path, env.get('PYTHONPATH', '')])
env['OPENHANDS_REPO_PATH'] = code_repo_path
env['LOCAL_RUNTIME_MODE'] = '1'
env['VSCODE_PORT'] = str(self._vscode_port)
# Derive environment paths using sys.executable
interpreter_path = sys.executable

View File

@@ -1,6 +1,7 @@
import asyncio
import os
import shutil
import sys
import uuid
from dataclasses import dataclass
from pathlib import Path
@@ -26,6 +27,15 @@ class VSCodePlugin(Plugin):
gateway_process: asyncio.subprocess.Process
async def initialize(self, username: str) -> None:
# Check if we're on Windows - VSCode plugin is not supported on Windows
if os.name == 'nt' or sys.platform == 'win32':
self.vscode_port = None
self.vscode_connection_token = None
logger.warning(
'VSCode plugin is not supported on Windows. Plugin will be disabled.'
)
return
if username not in ['root', 'openhands']:
self.vscode_port = None
self.vscode_connection_token = None
@@ -38,9 +48,20 @@ class VSCodePlugin(Plugin):
# Set up VSCode settings.json
self._setup_vscode_settings()
self.vscode_port = int(os.environ['VSCODE_PORT'])
try:
self.vscode_port = int(os.environ['VSCODE_PORT'])
except (KeyError, ValueError):
logger.warning(
'VSCODE_PORT environment variable not set or invalid. VSCode plugin will be disabled.'
)
return
self.vscode_connection_token = str(uuid.uuid4())
assert check_port_available(self.vscode_port)
if not check_port_available(self.vscode_port):
logger.warning(
f'Port {self.vscode_port} is not available. VSCode plugin will be disabled.'
)
return
cmd = (
f"su - {username} -s /bin/bash << 'EOF'\n"
f'sudo chown -R {username}:{username} /openhands/.openvscode-server\n'

View File

@@ -4,8 +4,8 @@ This is primarily used to localize the most relevant chunks in a file
for a given query (e.g. edit draft produced by the agent).
"""
import pylcs
from pydantic import BaseModel
from rapidfuzz.distance import LCSseq
from tree_sitter_languages import get_parser
from openhands.core.logger import openhands_logger as logger
@@ -65,7 +65,9 @@ def normalized_lcs(chunk: str, query: str) -> float:
"""
if len(chunk) == 0:
return 0.0
_score = pylcs.lcs_sequence_length(chunk, query)
_score = LCSseq.similarity(chunk, query)
return _score / len(chunk)

45
poetry.lock generated
View File

@@ -3011,7 +3011,7 @@ grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_versi
grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}
proto-plus = [
{version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""},
{version = ">=1.22.3,<2.0.0"},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
requests = ">=2.18.0,<3.0.0"
@@ -3230,7 +3230,7 @@ google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0"
grpc-google-iam-v1 = ">=0.14.0,<1.0.0"
proto-plus = [
{version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0"},
{version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""},
]
protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
@@ -5403,11 +5403,8 @@ files = [
{file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"},
{file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"},
{file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"},
{file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"},
{file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"},
{file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"},
@@ -7593,21 +7590,6 @@ files = [
[package.dependencies]
pyasn1 = ">=0.6.1,<0.7.0"
[[package]]
name = "pybind11"
version = "2.13.6"
description = "Seamless operability between C++11 and Python"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "pybind11-2.13.6-py3-none-any.whl", hash = "sha256:237c41e29157b962835d356b370ededd57594a26d5894a795960f0047cb5caf5"},
{file = "pybind11-2.13.6.tar.gz", hash = "sha256:ba6af10348c12b24e92fa086b39cfba0eff619b61ac77c406167d813b096d39a"},
]
[package.extras]
global = ["pybind11-global (==2.13.6)"]
[[package]]
name = "pycodestyle"
version = "2.13.0"
@@ -7959,27 +7941,6 @@ files = [
{file = "pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3"},
]
[[package]]
name = "pylcs"
version = "0.1.1"
description = "super fast cpp implementation of longest common subsequence"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "pylcs-0.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b8adea6b41dff27332c967533ec3c42a5e94171be778d6f01f0c5cee82e7604"},
{file = "pylcs-0.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ff06e037c54056cb67d6ef5ad946c0360afeff7d43be67ce09e55201ecc15cc"},
{file = "pylcs-0.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:d2ebf340aa180d841939d9ec1168dfd072992dda1d48148ceb07b65b1ab62ffa"},
{file = "pylcs-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b6c43b63e20048f8fec7e122fbc08c238940a0ee5302bf84a70db22c7f8cc836"},
{file = "pylcs-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:db52d55cfdf813af974bcc164aedbd29274da83086877bf05778aa7fbf777f7f"},
{file = "pylcs-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:954495f1c164ccb722b835e7028783f8a38d85ed5f6ff7b9d50143896c6cff9b"},
{file = "pylcs-0.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:0f4c82fad8c0429abef9e98fb98904459c4f5f9fb9b6ce20e0df0841a6a48a54"},
{file = "pylcs-0.1.1.tar.gz", hash = "sha256:632c69235d77cda0ba524d82796878801d2f46131fc59e730c98767fc4ce1307"},
]
[package.dependencies]
pybind11 = ">=2.2"
[[package]]
name = "pynacl"
version = "1.5.0"
@@ -12193,4 +12154,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.1"
python-versions = "^3.12,<3.14"
content-hash = "509493c2d53d5923054d7521079a212c6e67323389b75886b6e1dccc9869127c"
content-hash = "071b19070a00bfbae9cab2332a58ce9e0f31fcf594bc4ad32fd647783611396c"

View File

@@ -20,12 +20,12 @@ packages = [
[tool.poetry.dependencies]
python = "^3.12,<3.14"
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
google-generativeai = "*" # To use litellm with Gemini Pro API
google-api-python-client = "^2.164.0" # For Google Sheets API
google-auth-httplib2 = "*" # For Google Sheets authentication
google-auth-oauthlib = "*" # For Google Sheets OAuth
litellm = "^1.60.0, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
google-generativeai = "*" # To use litellm with Gemini Pro API
google-api-python-client = "^2.164.0" # For Google Sheets API
google-auth-httplib2 = "*" # For Google Sheets authentication
google-auth-oauthlib = "*" # For Google Sheets OAuth
termcolor = "*"
docker = "*"
fastapi = "*"
@@ -34,7 +34,7 @@ uvicorn = "*"
types-toml = "*"
numpy = "*"
json-repair = "*"
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface
browsergym-core = "0.13.3" # integrate browsergym-core as the browsing interface
html2text = "*"
e2b = ">=1.0.5,<1.4.0"
pexpect = "*"
@@ -58,9 +58,9 @@ python-pptx = "*"
pylatexenc = "*"
tornado = "*"
python-dotenv = "*"
pylcs = "^0.1.1"
rapidfuzz = "^3.9.0"
whatthepatch = "^1.0.6"
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
protobuf = "^4.21.6,<5.0.0" # chromadb currently fails on 5.0+
opentelemetry-api = "1.25.0"
opentelemetry-exporter-otlp-proto-grpc = "1.25.0"
modal = ">=0.66.26,<0.78.0"

View File

@@ -327,8 +327,22 @@ async def test_clone_or_init_repo_github_with_token(temp_dir, monkeypatch):
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
cmd = runtime.run_action_calls[0].command
assert f'git clone https://{github_token}@github.com/owner/repo.git repo' in cmd
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
# Check that the first command is the git clone with the correct URL format with token
clone_cmd = runtime.run_action_calls[0].command
assert (
f'git clone https://{github_token}@github.com/owner/repo.git repo' in clone_cmd
)
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
assert result == 'repo'
@@ -346,15 +360,20 @@ async def test_clone_or_init_repo_github_no_token(temp_dir, monkeypatch):
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
result = await runtime.clone_or_init_repo(None, 'owner/repo', None)
# Verify that git clone was called with the public URL
assert len(runtime.run_action_calls) == 1
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
# Check that the first command is the git clone with the correct URL format without token
clone_cmd = runtime.run_action_calls[0].command
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
# Check that the command contains the correct URL format without token
cmd = runtime.run_action_calls[0].command
assert 'git clone https://github.com/owner/repo.git repo' in cmd
assert 'cd repo' in cmd
assert 'git checkout -b openhands-workspace-' in cmd
assert result == 'repo'
@@ -381,10 +400,23 @@ async def test_clone_or_init_repo_gitlab_with_token(temp_dir, monkeypatch):
result = await runtime.clone_or_init_repo(git_provider_tokens, 'owner/repo', None)
cmd = runtime.run_action_calls[0].command
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
# Check that the first command is the git clone with the correct URL format with token
clone_cmd = runtime.run_action_calls[0].command
assert (
f'git clone https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git repo' in cmd
f'git clone https://oauth2:{gitlab_token}@gitlab.com/owner/repo.git repo'
in clone_cmd
)
# Check that the second command is the checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'cd repo' in checkout_cmd
assert 'git checkout -b openhands-workspace-' in checkout_cmd
assert result == 'repo'
@@ -402,14 +434,18 @@ async def test_clone_or_init_repo_with_branch(temp_dir, monkeypatch):
mock_repo_and_patch(monkeypatch, provider=ProviderType.GITHUB)
result = await runtime.clone_or_init_repo(None, 'owner/repo', 'feature-branch')
# Verify that git clone was called with the correct branch checkout
assert len(runtime.run_action_calls) == 1
# Verify that git clone and checkout were called as separate commands
assert len(runtime.run_action_calls) == 2
assert isinstance(runtime.run_action_calls[0], CmdRunAction)
assert isinstance(runtime.run_action_calls[1], CmdRunAction)
# Check that the command contains the correct branch checkout
cmd = runtime.run_action_calls[0].command
assert 'git clone https://github.com/owner/repo.git repo' in cmd
assert 'cd repo' in cmd
assert 'git checkout feature-branch' in cmd
assert 'git checkout -b' not in cmd # Should not create a new branch
# Check that the first command is the git clone
clone_cmd = runtime.run_action_calls[0].command
# Check that the second command contains the correct branch checkout
checkout_cmd = runtime.run_action_calls[1].command
assert 'git clone https://github.com/owner/repo.git repo' in clone_cmd
assert 'cd repo' in checkout_cmd
assert 'git checkout feature-branch' in checkout_cmd
assert 'git checkout -b' not in checkout_cmd # Should not create a new branch
assert result == 'repo'