Compare commits

..

2 Commits

Author SHA1 Message Date
openhands 944530a72d Fix PyInstaller --target-arch issue with spec files
PyInstaller doesn't allow --target-arch flag when using .spec files.
Instead, modify the spec file directly to set target_arch parameter.

- Dynamically modify spec file to set target_arch for macOS builds
- Restore original spec file after build completes
- Remove --target-arch command line argument usage

This fixes the CI build failure for macOS ARM64 architecture.
2025-10-10 16:54:49 +00:00
openhands 09de78d426 Add multi-architecture support for CLI binary builds
- Expand CI matrix to build for x86_64 and ARM64 on both Linux and macOS
- Add --target-arch parameter to build.py for macOS cross-compilation
- Update artifact naming to include platform and architecture
- Enhance release asset preparation for multi-arch binaries

Fixes #11320

Co-authored-by: openhands <openhands@all-hands.dev>
2025-10-10 16:48:06 +00:00
42 changed files with 170 additions and 311 deletions
@@ -21,19 +21,27 @@ concurrency:
cancel-in-progress: true
jobs:
build-binary:
name: Build binary executable
build-and-test-binary:
name: Build and test binary executable
strategy:
matrix:
include:
# Build on Ubuntu 22.04 for maximum GLIBC compatibility (GLIBC 2.31)
- os: ubuntu-22.04
# Linux x86_64
- os: ubuntu-latest
arch: x86_64
platform: linux
artifact_name: openhands-cli-linux
# Build on macOS for macOS users
- os: macos-15
# Linux ARM64
- os: ubuntu-latest-arm64
arch: arm64
platform: linux
# macOS x86_64 (Intel)
- os: macos-13
arch: x86_64
platform: macos
# macOS ARM64 (Apple Silicon)
- os: macos-latest
arch: arm64
platform: macos
artifact_name: openhands-cli-macos
runs-on: ${{ matrix.os }}
steps:
@@ -60,7 +68,14 @@ jobs:
- name: Build binary executable
working-directory: openhands-cli
run: |
./build.sh --install-pyinstaller | tee output.log
# Set build arguments based on platform and architecture
BUILD_ARGS="--install-pyinstaller"
if [ "${{ matrix.platform }}" = "macos" ]; then
BUILD_ARGS="$BUILD_ARGS --target-arch ${{ matrix.arch }}"
fi
echo "🔨 Building with args: $BUILD_ARGS"
./build.sh $BUILD_ARGS | tee output.log
echo "Full output:"
cat output.log
@@ -71,17 +86,18 @@ jobs:
echo "✅ Build & test finished without ❌ markers"
- name: Upload binary artifact
- name: Upload binary artifact (for releases only)
if: startsWith(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
name: openhands-cli-${{ matrix.platform }}-${{ matrix.arch }}
path: openhands-cli/dist/openhands*
retention-days: 30
create-github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: build-binary
needs: build-and-test-binary
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout repository
@@ -95,13 +111,24 @@ jobs:
- name: Prepare release assets
run: |
mkdir -p release-assets
# Copy binaries with appropriate names for release
if [ -f artifacts/openhands-cli-linux/openhands ]; then
cp artifacts/openhands-cli-linux/openhands release-assets/openhands-linux
fi
if [ -f artifacts/openhands-cli-macos/openhands ]; then
cp artifacts/openhands-cli-macos/openhands release-assets/openhands-macos
fi
# Rename binaries to include platform and architecture in filename
for artifact_dir in artifacts/openhands-cli-*; do
if [ -d "$artifact_dir" ]; then
# Extract platform and arch from directory name
# Format: openhands-cli-{platform}-{arch}
dir_name=$(basename "$artifact_dir")
platform_arch=${dir_name#openhands-cli-}
if [ -f "$artifact_dir/openhands" ]; then
cp "$artifact_dir/openhands" "release-assets/openhands-$platform_arch"
echo "✅ Copied $artifact_dir/openhands to release-assets/openhands-$platform_arch"
else
echo "⚠️ No openhands binary found in $artifact_dir"
fi
fi
done
echo "📁 Release assets prepared:"
ls -la release-assets/
- name: Create GitHub Release
@@ -1,52 +0,0 @@
name: Enterprise Check Migrations
on:
pull_request:
paths:
- 'enterprise/migrations/**'
jobs:
check-sync:
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Fetch base branch
run: git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Check if base branch is ancestor of PR
id: check_up_to_date
shell: bash
run: |
BASE="origin/${{ github.event.pull_request.base.ref }}"
HEAD="${{ github.event.pull_request.head.sha }}"
if git merge-base --is-ancestor "$BASE" "$HEAD"; then
echo "We're up to date with base $BASE"
exit 0
else
echo "NOT up to date with base $BASE"
exit 1
fi
- name: Find Comment
uses: peter-evans/find-comment@v3
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: |
⚠️ This PR contains **migrations**
- name: Comment warning on PR
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
⚠️ This PR contains **migrations**. Please synchronize before merging to prevent conflicts.
+1 -1
View File
@@ -71,7 +71,7 @@ jobs:
run: pip install pre-commit==4.2.0
- name: Run pre-commit hooks
working-directory: ./enterprise
run: pre-commit run --all-files --show-diff-on-failure --config ./dev_config/python/.pre-commit-config.yaml
run: pre-commit run --all-files --config ./dev_config/python/.pre-commit-config.yaml
lint-cli-python:
name: Lint CLI python
+1 -1
View File
@@ -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.59-nikolaik`
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.58-nikolaik`
## Develop inside Docker container
+3 -3
View File
@@ -76,17 +76,17 @@ You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)
You can also run OpenHands directly with Docker:
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.59-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.58-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.59-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.58-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.59
docker.all-hands.dev/all-hands-ai/openhands:0.58
```
</details>
+1 -1
View File
@@ -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.59-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.58-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:
+1 -1
View File
@@ -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.59-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.58-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:
-5
View File
@@ -1,5 +0,0 @@
# Enterprise Migrations
## Migration conflicts
OpenHands PRs can fall out of sync with `main` quickly. When adding a migration, it's safest to sync the PR with main before merging to ensure you are caught up to any others that have been added.
@@ -16,10 +16,6 @@ At the end, you must test your code rigorously using the tools provided, and do
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
## Issue Description
{{ instance.problem_statement }}
# Workflow
## High-Level Problem Solving Strategy
@@ -77,7 +73,6 @@ Carefully read the issue and think hard about a plan to solve it before coding.
## 8. Final Reflection and Additional Testing
- Reflect carefully on the original intent of the user and the problem statement.
- Compare your changes with the base commit {{ instance.base_commit }} to ensure minimal and focused modifications.
- Think about potential edge cases or scenarios that may not be covered by existing tests.
- Write additional tests that would need to pass to fully validate the correctness of your solution.
- Run these new tests and ensure they all pass.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "openhands-frontend",
"version": "0.59.0",
"version": "0.58.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openhands-frontend",
"version": "0.59.0",
"version": "0.58.0",
"dependencies": {
"@heroui/react": "^2.8.4",
"@heroui/use-infinite-scroll": "^2.2.11",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openhands-frontend",
"version": "0.59.0",
"version": "0.58.0",
"private": true,
"type": "module",
"engines": {
@@ -121,7 +121,7 @@ class ConversationService {
reason?: string;
}>(url);
return data;
} catch {
} catch (error) {
// Error checking if feedback exists
return { exists: false };
}
@@ -120,7 +120,7 @@ export function InteractiveChatBox({
// Step 5: Handle failed results
handleFailedFiles(fileResults, imageResults);
} catch {
} catch (error) {
// Clear loading states and show error
clearLoadingStates(validFiles, validImages);
displayErrorToast("An unexpected error occurred while processing files");
@@ -90,7 +90,7 @@ export function ConversationCard({
}
}
// VS Code URL not available
} catch {
} catch (error) {
// Failed to fetch VS Code URL
}
}
@@ -23,7 +23,7 @@ export function useUrlSearch(inputValue: string, provider: Provider) {
);
setUrlSearchResults(repositories);
} catch {
} catch (error) {
setUrlSearchResults([]);
} finally {
setIsUrlSearchLoading(false);
@@ -28,7 +28,7 @@ export function CancelSubscriptionModal({
await cancelSubscriptionMutation.mutateAsync();
displaySuccessToast(t(I18nKey.PAYMENT$SUBSCRIPTION_CANCELLED));
onClose();
} catch {
} catch (error) {
displayErrorToast(t(I18nKey.ERROR$GENERIC));
}
};
@@ -39,7 +39,7 @@ export function CreateApiKeyModal({
onKeyCreated(newKey);
displaySuccessToast(t(I18nKey.SETTINGS$API_KEY_CREATED));
setNewKeyName("");
} catch {
} catch (error) {
displayErrorToast(t(I18nKey.ERROR$GENERIC));
}
};
@@ -32,7 +32,7 @@ export function DeleteApiKeyModal({
await deleteApiKeyMutation.mutateAsync(keyToDelete.id);
displaySuccessToast(t(I18nKey.SETTINGS$API_KEY_DELETED));
onClose();
} catch {
} catch (error) {
displayErrorToast(t(I18nKey.ERROR$GENERIC));
}
};
@@ -275,7 +275,7 @@ export function ConversationSubscriptionsProvider({
setActiveConversationIds((prev) =>
prev.includes(conversationId) ? prev : [...prev, conversationId],
);
} catch {
} catch (error) {
// Clean up the event handler if there was an error
delete eventHandlersRef.current[conversationId];
}
+1 -1
View File
@@ -25,7 +25,7 @@ function PosthogInit() {
try {
const config = await OptionService.getConfig();
setPosthogClientKey(config.POSTHOG_CLIENT_KEY);
} catch {
} catch (error) {
displayErrorToast("Error fetching PostHog client key");
}
})();
@@ -140,7 +140,7 @@ export function useConversationNameContextMenu({
}
}
// VS Code URL not available
} catch {
} catch (error) {
// Failed to fetch VS Code URL
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ export function transformVSCodeUrl(vsCodeUrl: string | null): string | null {
}
return vsCodeUrl;
} catch {
} catch (error) {
// Silently handle the error and return the original URL
return vsCodeUrl;
}
+35 -3
View File
@@ -72,6 +72,7 @@ def check_pyinstaller() -> bool:
def build_executable(
spec_file: str = 'openhands.spec',
clean: bool = True,
target_arch: str = None,
) -> bool:
"""Build the executable using PyInstaller."""
if clean:
@@ -83,8 +84,27 @@ def build_executable(
print(f'🔨 Building executable using {spec_file}...')
# Handle target architecture for macOS by modifying the spec file
original_spec_content = None
if target_arch and sys.platform == 'darwin':
print(f'🎯 Building for macOS target architecture: {target_arch}')
# Read the original spec file
with open(spec_file, 'r') as f:
original_spec_content = f.read()
# Replace target_arch=None with target_arch='<arch>'
modified_spec_content = original_spec_content.replace(
'target_arch=None',
f"target_arch='{target_arch}'"
)
# Write the modified spec file
with open(spec_file, 'w') as f:
f.write(modified_spec_content)
try:
# Run PyInstaller with uv
# Run PyInstaller with uv (no --target-arch flag needed with spec file)
cmd = ['uv', 'run', 'pyinstaller', spec_file, '--clean']
print(f'Running: {" ".join(cmd)}')
@@ -113,6 +133,12 @@ def build_executable(
if e.stderr:
print('STDERR:', e.stderr)
return False
finally:
# Restore the original spec file if we modified it
if original_spec_content is not None:
with open(spec_file, 'w') as f:
f.write(original_spec_content)
print(f'🔄 Restored original {spec_file}')
# =================================================
@@ -164,7 +190,7 @@ def test_executable() -> bool:
)
# --- Wait for welcome ---
deadline = boot_start + 60
deadline = boot_start + 30
saw_welcome = False
captured = []
@@ -254,6 +280,10 @@ def main() -> int:
action='store_true',
help='Install PyInstaller using uv before building',
)
parser.add_argument(
'--target-arch',
help='Target architecture for macOS builds (x86_64, arm64, universal2)',
)
parser.add_argument(
'--no-build', action='store_true', help='Skip testing the built executable'
@@ -270,7 +300,9 @@ def main() -> int:
return 1
# Build the executable
if not args.no_build and not build_executable(args.spec, clean=not args.no_clean):
if not args.no_build and not build_executable(
args.spec, clean=not args.no_clean, target_arch=args.target_arch
):
return 1
# Test the executable
+2 -7
View File
@@ -1,8 +1,3 @@
"""OpenHands package."""
"""OpenHands CLI package."""
from importlib.metadata import version, PackageNotFoundError
try:
__version__ = version("openhands")
except PackageNotFoundError:
__version__ = "0.0.0"
__version__ = '0.1.0'
@@ -54,7 +54,6 @@ def _print_exit_hint(conversation_id: str) -> None:
)
def run_cli_entry(resume_conversation_id: str | None = None) -> None:
"""Run the agent chat session using the agent SDK.
+10 -1
View File
@@ -113,12 +113,21 @@ def launch_gui_server(mount_cwd: bool = False, gpu: bool = False) -> None:
pull_cmd = ['docker', 'pull', runtime_image]
print_formatted_text(HTML(_format_docker_command_for_logging(pull_cmd)))
try:
subprocess.run(pull_cmd, check=True)
subprocess.run(
pull_cmd,
check=True,
timeout=300, # 5 minutes timeout
)
except subprocess.CalledProcessError:
print_formatted_text(
HTML('<ansired>❌ Failed to pull runtime image.</ansired>')
)
sys.exit(1)
except subprocess.TimeoutExpired:
print_formatted_text(
HTML('<ansired>❌ Timeout while pulling runtime image.</ansired>')
)
sys.exit(1)
print_formatted_text('')
print_formatted_text(
+2
View File
@@ -57,6 +57,8 @@ def display_banner(conversation_id: str, resume: bool = False) -> None:
style=DEFAULT_STYLE,
)
print_formatted_text(HTML(f'<grey>OpenHands CLI v{__version__}</grey>'))
print_formatted_text('')
if not resume:
print_formatted_text(
@@ -1,4 +1,3 @@
import html
from prompt_toolkit import HTML, print_formatted_text
from openhands.sdk.security.confirmation_policy import (
@@ -38,7 +37,7 @@ def ask_user_confirmation(
or '[unknown action]'
)
print_formatted_text(
HTML(f'<grey> {i}. {tool_name}: {html.escape(action_content)}...</grey>')
HTML(f'<grey> {i}. {tool_name}: {action_content}...</grey>')
)
question = 'Choose an option:'
@@ -123,15 +123,9 @@ def prompt_api_key(
validator = NonEmptyValueValidator()
question = helper_text + step_counter.next_step(question)
user_input = cli_text_input(
return cli_text_input(
question, escapable=escapable, validator=validator, is_password=True
)
# If user pressed ENTER with existing key (empty input), return the existing key
if existing_api_key and not user_input.strip():
return existing_api_key.get_secret_value()
return user_input
# Advanced settings functions
+16 -12
View File
@@ -1,14 +1,14 @@
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling>=1.25"]
requires = [ "hatchling>=1.25" ]
[project]
name = "openhands"
version = "1.0.1"
version = "1.0.0"
description = "OpenHands CLI - Terminal User Interface for OpenHands AI Agent"
readme = "README.md"
license = { text = "MIT" }
authors = [{ name = "OpenHands Team", email = "contact@all-hands.dev" }]
authors = [ { name = "OpenHands Team", email = "contact@all-hands.dev" } ]
requires-python = ">=3.12"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
@@ -16,13 +16,14 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"openhands-sdk @ git+https://github.com/All-Hands-AI/agent-sdk.git@50b094a92817e448ec4352d2950df4f19edd5a9f#subdirectory=openhands/sdk",
"openhands-tools @ git+https://github.com/All-Hands-AI/agent-sdk.git@50b094a92817e448ec4352d2950df4f19edd5a9f#subdirectory=openhands/tools",
"openhands-sdk",
"openhands-tools",
"prompt-toolkit>=3",
"typer>=0.17.4",
]
scripts = { openhands = "openhands_cli.simple_main:main" }
# Dev-only tools with uv groups: `uv sync --group dev`
scripts.openhands = "openhands_cli.simple_main:main"
[dependency-groups]
# Hatchling wheel target: include the package directory
@@ -42,13 +43,13 @@ dev = [
]
[tool.hatch.build.targets.wheel]
packages = ["openhands_cli"]
packages = [ "openhands_cli" ]
# uv source pins for internal packages
[tool.black]
line-length = 88
target-version = ["py312"]
target-version = [ "py312" ]
[tool.ruff]
target-version = "py312"
@@ -79,10 +80,13 @@ line_length = 88
[tool.coverage.run]
relative_files = true
omit = ["tests/*", "**/test_*"]
omit = [ "tests/*", "**/test_*" ]
[tool.coverage.paths]
source = ["openhands_cli/", "openhands-cli/openhands_cli/"]
source = [
"openhands_cli/",
"openhands-cli/openhands_cli/",
]
[tool.mypy]
python_version = "3.12"
@@ -92,5 +96,5 @@ disallow_untyped_defs = true
ignore_missing_imports = true
[tool.uv.sources]
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/sdk", rev = "50b094a92817e448ec4352d2950df4f19edd5a9f" }
openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/tools", rev = "50b094a92817e448ec4352d2950df4f19edd5a9f" }
openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/sdk", rev = "189979a5013751aa86852ab41afe9a79555e62ac" }
openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/tools", rev = "189979a5013751aa86852ab41afe9a79555e62ac" }
@@ -1,56 +0,0 @@
"""Test for API key preservation bug when updating settings."""
from unittest.mock import patch
import pytest
from pydantic import SecretStr
from openhands_cli.user_actions.settings_action import prompt_api_key
from openhands_cli.tui.utils import StepCounter
def test_api_key_preservation_when_user_presses_enter():
"""Test that API key is preserved when user presses ENTER to keep current key.
This test replicates the bug where API keys disappear when updating settings.
When a user presses ENTER to keep the current API key, the function should
return the existing API key, not an empty string.
"""
step_counter = StepCounter(1)
existing_api_key = SecretStr("sk-existing-key-123")
# Mock cli_text_input to return empty string (simulating user pressing ENTER)
with patch('openhands_cli.user_actions.settings_action.cli_text_input', return_value=''):
result = prompt_api_key(
step_counter=step_counter,
provider='openai',
existing_api_key=existing_api_key,
escapable=True
)
# The bug: result is empty string instead of the existing key
# This test will fail initially, demonstrating the bug
assert result == existing_api_key.get_secret_value(), (
f"Expected existing API key '{existing_api_key.get_secret_value()}' "
f"but got '{result}'. API key should be preserved when user presses ENTER."
)
def test_api_key_update_when_user_enters_new_key():
"""Test that API key is updated when user enters a new key."""
step_counter = StepCounter(1)
existing_api_key = SecretStr("sk-existing-key-123")
new_api_key = "sk-new-key-456"
# Mock cli_text_input to return new API key
with patch('openhands_cli.user_actions.settings_action.cli_text_input', return_value=new_api_key):
result = prompt_api_key(
step_counter=step_counter,
provider='openai',
existing_api_key=existing_api_key,
escapable=True
)
# Should return the new API key
assert result == new_api_key
+2
View File
@@ -111,6 +111,8 @@ class TestLaunchGuiServer:
[
# Docker pull failure
(subprocess.CalledProcessError(1, 'docker pull'), None, 1, False, False),
# Docker pull timeout
(subprocess.TimeoutExpired('docker pull', 300), None, 1, False, False),
# Docker run failure
(MagicMock(returncode=0), subprocess.CalledProcessError(1, 'docker run'), 1, False, False),
# KeyboardInterrupt during run
+21 -32
View File
@@ -660,32 +660,18 @@ wheels = [
[[package]]
name = "fastuuid"
version = "0.13.5"
version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/80/3c16a1edad2e6cd82fbd15ac998cc1b881f478bf1f80ca717d941c441874/fastuuid-0.13.5.tar.gz", hash = "sha256:d4976821ab424d41542e1ea39bc828a9d454c3f8a04067c06fca123c5b95a1a1", size = 18255, upload-time = "2025-09-26T09:05:38.281Z" }
sdist = { url = "https://files.pythonhosted.org/packages/19/17/13146a1e916bd2971d0a58db5e0a4ad23efdd49f78f33ac871c161f8007b/fastuuid-0.12.0.tar.gz", hash = "sha256:d0bd4e5b35aad2826403f4411937c89e7c88857b1513fe10f696544c03e9bd8e", size = 19180, upload-time = "2025-01-27T18:04:14.387Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/36/434f137c5970cac19e57834e1f7680e85301619d49891618c00666700c61/fastuuid-0.13.5-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:35fe8045e866bc6846f8de6fa05acb1de0c32478048484a995e96d31e21dff2a", size = 494638, upload-time = "2025-09-26T09:14:58.695Z" },
{ url = "https://files.pythonhosted.org/packages/ca/3c/083de2ac007b2b305523b9c006dba5051e5afd87a626ef1a39f76e2c6b82/fastuuid-0.13.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:02a460333f52d731a006d18a52ef6fcb2d295a1f5b1a5938d30744191b2f77b7", size = 253138, upload-time = "2025-09-26T09:13:33.283Z" },
{ url = "https://files.pythonhosted.org/packages/73/5e/630cffa1c8775db526e39e9e4c5c7db0c27be0786bb21ba82c912ae19f63/fastuuid-0.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:74b0e4f8c307b9f477a5d7284db4431ce53a3c1e3f4173db7a97db18564a6202", size = 244521, upload-time = "2025-09-26T09:14:40.682Z" },
{ url = "https://files.pythonhosted.org/packages/4d/51/55d78705f4fbdadf88fb40f382f508d6c7a4941ceddd7825fafebb4cc778/fastuuid-0.13.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6955a99ef455c2986f3851f4e0ccc35dec56ac1a7720f2b92e88a75d6684512e", size = 271557, upload-time = "2025-09-26T09:15:09.75Z" },
{ url = "https://files.pythonhosted.org/packages/6a/2b/1b89e90a8635e5587ccdbbeb169c590672ce7637880f2c047482a0359950/fastuuid-0.13.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f10c77b826738c1a27dcdaa92ea4dc1ec9d869748a99e1fde54f1379553d4854", size = 272334, upload-time = "2025-09-26T09:07:48.865Z" },
{ url = "https://files.pythonhosted.org/packages/0c/06/4c8207894eeb30414999e5c3f66ac039bc4003437eb4060d8a1bceb4cc6f/fastuuid-0.13.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb25dccbeb249d16d5e664f65f17ebec05136821d5ef462c4110e3f76b86fb86", size = 290594, upload-time = "2025-09-26T09:12:54.124Z" },
{ url = "https://files.pythonhosted.org/packages/50/69/96d221931a31d77a47cc2487bdfacfb3091edfc2e7a04b1795df1aec05df/fastuuid-0.13.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a5becc646a3eeafb76ce0a6783ba190cd182e3790a8b2c78ca9db2b5e87af952", size = 452835, upload-time = "2025-09-26T09:14:00.994Z" },
{ url = "https://files.pythonhosted.org/packages/25/ef/bf045f0a47dcec96247497ef3f7a31d86ebc074330e2dccc34b8dbc0468a/fastuuid-0.13.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:69b34363752d06e9bb0dbdf02ae391ec56ac948c6f2eb00be90dad68e80774b9", size = 468225, upload-time = "2025-09-26T09:13:38.585Z" },
{ url = "https://files.pythonhosted.org/packages/30/46/4817ab5a3778927155a4bde92540d4c4fa996161ec8b8e080c8928b0984e/fastuuid-0.13.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57d0768afcad0eab8770c9b8cf904716bd3c547e8b9a4e755ee8a673b060a3a3", size = 444907, upload-time = "2025-09-26T09:14:30.163Z" },
{ url = "https://files.pythonhosted.org/packages/80/27/ab284117ce4dc9b356a7196bdbf220510285f201d27f1f078592cdc8187b/fastuuid-0.13.5-cp312-cp312-win32.whl", hash = "sha256:8ac6c6f5129d52eaa6ef9ea4b6e2f7c69468a053f3ab8e439661186b9c06bb85", size = 145415, upload-time = "2025-09-26T09:08:59.494Z" },
{ url = "https://files.pythonhosted.org/packages/f4/0c/f970a4222773b248931819f8940800b760283216ca3dda173ed027e94bdd/fastuuid-0.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:ad630e97715beefef07ec37c9c162336e500400774e2c1cbe1a0df6f80d15b9a", size = 150840, upload-time = "2025-09-26T09:13:46.115Z" },
{ url = "https://files.pythonhosted.org/packages/4f/62/74fc53f6e04a4dc5b36c34e4e679f85a4c14eec800dcdb0f2c14b5442217/fastuuid-0.13.5-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ea17dfd35e0e91920a35d91e65e5f9c9d1985db55ac4ff2f1667a0f61189cefa", size = 494678, upload-time = "2025-09-26T09:14:30.908Z" },
{ url = "https://files.pythonhosted.org/packages/09/ba/f28b9b7045738a8bfccfb9cd6aff4b91fce2669e6b383a48b0694ee9b3ff/fastuuid-0.13.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:be6ad91e5fefbcc2a4b478858a2715e386d405834ea3ae337c3b6b95cc0e47d6", size = 253162, upload-time = "2025-09-26T09:13:35.879Z" },
{ url = "https://files.pythonhosted.org/packages/b1/18/13fac89cb4c9f0cd7e81a9154a77ecebcc95d2b03477aa91d4d50f7227ee/fastuuid-0.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ea6df13a306aab3e0439d58c312ff1e6f4f07f09f667579679239b4a6121f64a", size = 244546, upload-time = "2025-09-26T09:14:58.13Z" },
{ url = "https://files.pythonhosted.org/packages/04/bf/9691167804d59411cc4269841df949f6dd5e76452ab10dcfcd1dbe04c5bc/fastuuid-0.13.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2354c1996d3cf12dc2ba3752e2c4d6edc46e1a38c63893146777b1939f3062d4", size = 271528, upload-time = "2025-09-26T09:14:48.996Z" },
{ url = "https://files.pythonhosted.org/packages/a9/b5/7a75a03d1c7aa0b6d573032fcca39391f0aef7f2caabeeb45a672bc0bd3c/fastuuid-0.13.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6cf9b7469fc26d1f9b1c43ac4b192e219e85b88fdf81d71aa755a6c08c8a817", size = 272292, upload-time = "2025-09-26T09:14:42.82Z" },
{ url = "https://files.pythonhosted.org/packages/c0/db/fa0f16cbf76e6880599533af4ef01bb586949c5320612e9d884eff13e603/fastuuid-0.13.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92ba539170097b9047551375f1ca09d8d2b4aefcc79eeae3e1c43fe49b42072e", size = 290466, upload-time = "2025-09-26T09:08:33.161Z" },
{ url = "https://files.pythonhosted.org/packages/1e/02/6b8c45bfbc8500994dd94edba7f59555f9683c4d8c9a164ae1d25d03c7c7/fastuuid-0.13.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:dbb81d05617bc2970765c1ad82db7e8716f6a2b7a361a14b83de5b9240ade448", size = 452838, upload-time = "2025-09-26T09:13:44.747Z" },
{ url = "https://files.pythonhosted.org/packages/27/12/85d95a84f265b888e8eb9f9e2b5aaf331e8be60c0a7060146364b3544b6a/fastuuid-0.13.5-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:d973bd6bf9d754d3cca874714ac0a6b22a47f239fb3d3c8687569db05aac3471", size = 468149, upload-time = "2025-09-26T09:13:18.712Z" },
{ url = "https://files.pythonhosted.org/packages/ad/da/dd9a137e9ea707e883c92470113a432233482ec9ad3e9b99c4defc4904e6/fastuuid-0.13.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e725ceef79486423f05ee657634d4b4c1ca5fb2c8a94e0708f5d6356a83f2a83", size = 444933, upload-time = "2025-09-26T09:14:09.494Z" },
{ url = "https://files.pythonhosted.org/packages/12/f4/ab363d7f4ac3989691e2dc5ae2d8391cfb0b4169e52ef7fa0ac363e936f0/fastuuid-0.13.5-cp313-cp313-win32.whl", hash = "sha256:a1c430a332ead0b2674f1ef71b17f43b8139ec5a4201182766a21f131a31e021", size = 145462, upload-time = "2025-09-26T09:14:15.105Z" },
{ url = "https://files.pythonhosted.org/packages/aa/8a/52eb77d9c294a54caa0d2d8cc9f906207aa6d916a22de963687ab6db8b86/fastuuid-0.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:241fdd362fd96e6b337db62a65dd7cb3dfac20adf854573247a47510e192db6f", size = 150923, upload-time = "2025-09-26T09:13:03.923Z" },
{ url = "https://files.pythonhosted.org/packages/f6/28/442e79d6219b90208cb243ac01db05d89cc4fdf8ecd563fb89476baf7122/fastuuid-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:328694a573fe9dce556b0b70c9d03776786801e028d82f0b6d9db1cb0521b4d1", size = 247372, upload-time = "2025-01-27T18:03:40.967Z" },
{ url = "https://files.pythonhosted.org/packages/40/eb/e0fd56890970ca7a9ec0d116844580988b692b1a749ac38e0c39e1dbdf23/fastuuid-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02acaea2c955bb2035a7d8e7b3fba8bd623b03746ae278e5fa932ef54c702f9f", size = 258200, upload-time = "2025-01-27T18:04:12.138Z" },
{ url = "https://files.pythonhosted.org/packages/f5/3c/4b30e376e65597a51a3dc929461a0dec77c8aec5d41d930f482b8f43e781/fastuuid-0.12.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ed9f449cba8cf16cced252521aee06e633d50ec48c807683f21cc1d89e193eb0", size = 278446, upload-time = "2025-01-27T18:04:15.877Z" },
{ url = "https://files.pythonhosted.org/packages/fe/96/cc5975fd23d2197b3e29f650a7a9beddce8993eaf934fa4ac595b77bb71f/fastuuid-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:0df2ea4c9db96fd8f4fa38d0e88e309b3e56f8fd03675a2f6958a5b082a0c1e4", size = 157185, upload-time = "2025-01-27T18:06:19.21Z" },
{ url = "https://files.pythonhosted.org/packages/a9/e8/d2bb4f19e5ee15f6f8e3192a54a897678314151aa17d0fb766d2c2cbc03d/fastuuid-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7fe2407316a04ee8f06d3dbc7eae396d0a86591d92bafe2ca32fce23b1145786", size = 247512, upload-time = "2025-01-27T18:04:08.115Z" },
{ url = "https://files.pythonhosted.org/packages/bc/53/25e811d92fd60f5c65e098c3b68bd8f1a35e4abb6b77a153025115b680de/fastuuid-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b31dd488d0778c36f8279b306dc92a42f16904cba54acca71e107d65b60b0c", size = 258257, upload-time = "2025-01-27T18:03:56.408Z" },
{ url = "https://files.pythonhosted.org/packages/10/23/73618e7793ea0b619caae2accd9e93e60da38dd78dd425002d319152ef2f/fastuuid-0.12.0-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:b19361ee649365eefc717ec08005972d3d1eb9ee39908022d98e3bfa9da59e37", size = 278559, upload-time = "2025-01-27T18:03:58.661Z" },
{ url = "https://files.pythonhosted.org/packages/e4/41/6317ecfc4757d5f2a604e5d3993f353ba7aee85fa75ad8b86fce6fc2fa40/fastuuid-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:8fc66b11423e6f3e1937385f655bedd67aebe56a3dcec0cb835351cfe7d358c9", size = 157276, upload-time = "2025-01-27T18:06:39.245Z" },
]
[[package]]
@@ -1280,8 +1266,8 @@ wheels = [
[[package]]
name = "litellm"
version = "1.77.7"
source = { git = "https://github.com/BerriAI/litellm.git?rev=v1.77.7.dev9#763d2f8ccdd8412dbe6d4ac0e136d9ac34dcd4c0" }
version = "1.76.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
{ name = "click" },
@@ -1296,6 +1282,10 @@ dependencies = [
{ name = "tiktoken" },
{ name = "tokenizers" },
]
sdist = { url = "https://files.pythonhosted.org/packages/75/a3/f7c00c660972eed1ba5ed53771ac9b4235e7fb1dc410e91d35aff2778ae7/litellm-1.76.2.tar.gz", hash = "sha256:fc7af111fa0f06943d8dbebed73f88000f9902f0d0ee0882c57d0bd5c1a37ecb", size = 10189238, upload-time = "2025-09-04T00:25:09.472Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/f4/980cc81c21424026dcb48a541654fd6f4286891825a3d0dd51f02b65cbc3/litellm-1.76.2-py3-none-any.whl", hash = "sha256:a9a2ef64a598b5b4ae245f1de6afc400856477cd6f708ff633d95e2275605a45", size = 8973847, upload-time = "2025-09-04T00:25:05.353Z" },
]
[[package]]
name = "macholib"
@@ -1625,7 +1615,7 @@ wheels = [
[[package]]
name = "openhands"
version = "1.0.1"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "openhands-sdk" },
@@ -1652,8 +1642,8 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "openhands-sdk", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=50b094a92817e448ec4352d2950df4f19edd5a9f" },
{ name = "openhands-tools", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=50b094a92817e448ec4352d2950df4f19edd5a9f" },
{ name = "openhands-sdk", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=189979a5013751aa86852ab41afe9a79555e62ac" },
{ name = "openhands-tools", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=189979a5013751aa86852ab41afe9a79555e62ac" },
{ name = "prompt-toolkit", specifier = ">=3" },
{ name = "typer", specifier = ">=0.17.4" },
]
@@ -1677,10 +1667,9 @@ dev = [
[[package]]
name = "openhands-sdk"
version = "1.0.0"
source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=50b094a92817e448ec4352d2950df4f19edd5a9f#50b094a92817e448ec4352d2950df4f19edd5a9f" }
source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=189979a5013751aa86852ab41afe9a79555e62ac#189979a5013751aa86852ab41afe9a79555e62ac" }
dependencies = [
{ name = "fastmcp" },
{ name = "httpx" },
{ name = "litellm" },
{ name = "pydantic" },
{ name = "python-frontmatter" },
@@ -1692,7 +1681,7 @@ dependencies = [
[[package]]
name = "openhands-tools"
version = "1.0.0"
source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=50b094a92817e448ec4352d2950df4f19edd5a9f#50b094a92817e448ec4352d2950df4f19edd5a9f" }
source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=189979a5013751aa86852ab41afe9a79555e62ac#189979a5013751aa86852ab41afe9a79555e62ac" }
dependencies = [
{ name = "bashlex" },
{ name = "binaryornot" },
+10 -1
View File
@@ -103,12 +103,21 @@ def launch_gui_server(mount_cwd: bool = False, gpu: bool = False) -> None:
pull_cmd = ['docker', 'pull', runtime_image]
print_formatted_text(HTML(_format_docker_command_for_logging(pull_cmd)))
try:
subprocess.run(pull_cmd, check=True)
subprocess.run(
pull_cmd,
check=True,
timeout=300, # 5 minutes timeout
)
except subprocess.CalledProcessError:
print_formatted_text(
HTML('<ansired>❌ Failed to pull runtime image.</ansired>')
)
sys.exit(1)
except subprocess.TimeoutExpired:
print_formatted_text(
HTML('<ansired>❌ Timeout while pulling runtime image.</ansired>')
)
sys.exit(1)
print_formatted_text('')
print_formatted_text(
@@ -1,39 +0,0 @@
{% if issue_number %}
You are requested to fix issue #{{ issue_number }}: "{{ issue_title }}" in a repository on Bitbucket.
A comment on the issue has been addressed to you.
{% else %}
Your task is to fix the issue: "{{ issue_title }}".
{% endif %}
# Issue Body
{{ issue_body }}
{% if previous_comments %}
# Previous Comments
For reference, here are the previous comments on the issue:
{% for comment in previous_comments %}
- @{{ comment.author }} said:
{{ comment.body }}
{% if not loop.last %}\n\n{% endif %}
{% endfor %}
{% endif %}
# Guidelines
1. Review the task carefully.
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
# Final Checklist
Re-read the issue title, body, and comments and make sure that you have successfully implemented all requirements.
Use the Bitbucket token and Bitbucket APIs to:
1. Create a new branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
2. Commit your changes with a clear commit message
3. Push the branch to Bitbucket
4. Create a pull request that:
- Mentions that it "fixes" or "resolves" the issue number
- Has a clear description of the changes made
@@ -1,5 +0,0 @@
{% if issue_comment %}
{{ issue_comment }}
{% else %}
Please fix issue number #{{ issue_number }}.
{% endif %}
@@ -1,38 +0,0 @@
You are checked out to branch {{ branch_name }}, which has an open PR #{{ pr_number }}: "{{ pr_title }}" on Bitbucket.
A comment on the PR has been addressed to you.
# PR Description
{{ pr_body }}
{% if comments %}
# Previous Comments
You may find these other comments relevant:
{% for comment in comments %}
- @{{ comment.author }} said at {{ comment.created_at }}:
{{ comment.body }}
{% if not loop.last %}\n\n{% endif %}
{% endfor %}
{% endif %}
{% if file_location %}
# Comment location
The comment is in the file `{{ file_location }}` on line #{{ line_number }}
{% endif %}.
# Steps to Handle the Comment
## Understand the PR Context
Use the Bitbucket token and Bitbucket API to:
1. Retrieve the diff against the main branch to understand the changes
2. Fetch the PR body and any linked issues for context
## Process the Comment
If it's a question:
1. Answer the question asked
2. DO NOT leave any comments on the PR
If it requests a code update:
1. Modify the code accordingly in the current branch
2. Commit your changes with a clear commit message
3. Push the changes to Bitbucket to update the PR
4. DO NOT leave any comments on the PR
@@ -1 +0,0 @@
{{ pr_comment }}
+1 -1
View File
@@ -59,7 +59,7 @@ def apply_patch(repo_dir: str, patch: str) -> None:
continue
# Handle file rename
if old_path and new_path and 'rename from' in diff.text:
if old_path and new_path and 'rename from' in patch:
# Create parent directory of new path
os.makedirs(os.path.dirname(new_path), exist_ok=True)
try:
+1 -1
View File
@@ -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.59-nikolaik"
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.58-nikolaik"
```
#### Additional Kubernetes Options
@@ -15,7 +15,6 @@ class ConversationTrigger(Enum):
JIRA = 'jira'
JIRA_DC = 'jira_dc'
LINEAR = 'linear'
BITBUCKET = 'bitbucket'
@dataclass
+1 -1
View File
@@ -6,7 +6,7 @@ requires = [
[tool.poetry]
name = "openhands-ai"
version = "0.59.0"
version = "0.58.0"
description = "OpenHands: Code Less, Make More"
authors = [ "OpenHands" ]
license = "MIT"