Compare commits

..

1 Commits

Author SHA1 Message Date
openhands
40b586c74c Add enable_bash configuration option to AgentConfig
- Rename enable_cmd to enable_bash in AgentConfig for better naming consistency
- Follow existing pattern of enable_browsing and enable_jupyter
- When set to false, disables the bash tool for the LLM
- Update config template to use new field name
- Maintain backward compatibility through field renaming
2025-06-20 18:20:03 +00:00
248 changed files with 2525 additions and 6802 deletions

View File

@@ -45,13 +45,6 @@ body:
description: What version of OpenHands are you using?
placeholder: ex. 0.9.8, main, etc.
- type: input
id: model-name
attributes:
label: Model Name
description: What model are you using?
placeholder: ex. gpt-4o, claude-3-5-sonnet, openrouter/deepseek-r1, etc.
- type: dropdown
id: os
attributes:

View File

@@ -40,7 +40,9 @@ jobs:
# Only build nikolaik on PRs, otherwise build both nikolaik and ubuntu.
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
json=$(jq -n -c '[
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" }
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" },
{ image: "ubuntu:24.04", tag: "ubuntu" }
]')
else
json=$(jq -n -c '[

View File

@@ -121,7 +121,7 @@ A specialized prompt that enhances OpenHands with domain-specific knowledge, rep
A central repository of available microagents and their configurations.
#### Public Microagent
A general-purpose microagent available to all OpenHands users, triggered by specific keywords. Located in `microagents/`.
A general-purpose microagent available to all OpenHands users, triggered by specific keywords.
#### Repository Microagent
A type of microagent that provides repository-specific context and guidelines, stored in the `.openhands/microagents/` directory.

View File

@@ -68,29 +68,6 @@ If you are starting a pull request (PR), please follow the template in `.github/
These details may or may not be useful for your current task.
### Microagents
Microagents are specialized prompts that enhance OpenHands with domain-specific knowledge and task-specific workflows. They are Markdown files that can include frontmatter for configuration.
#### Types:
- **Public Microagents**: Located in `microagents/`, available to all users
- **Repository Microagents**: Located in `.openhands/microagents/`, specific to this repository
#### Loading Behavior:
- **Without frontmatter**: Always loaded into LLM context
- **With triggers in frontmatter**: Only loaded when user's message matches the specified trigger keywords
#### Structure:
```yaml
---
triggers:
- keyword1
- keyword2
---
# Microagent Content
Your specialized knowledge and instructions here...
```
### Frontend
#### Action Handling:

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.47-nikolaik`
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.44-nikolaik`
## Develop inside Docker container

View File

@@ -11,7 +11,7 @@
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
<br/>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits"></a>
<br/>
@@ -48,7 +48,7 @@ Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or [sign up for
## ☁️ OpenHands Cloud
The easiest way to get started with OpenHands is on [OpenHands Cloud](https://app.all-hands.dev),
which comes with $20 in free credits for new users.
which comes with $50 in free credits for new users.
## 💻 Running OpenHands Locally
@@ -62,17 +62,17 @@ system requirements and more information.
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47
docker.all-hands.dev/all-hands-ai/openhands:0.44
```
> **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.
@@ -85,14 +85,15 @@ works best, but you have [many options](https://docs.all-hands.dev/usage/llms).
## 💡 Other ways to run OpenHands
> [!WARNING]
> [!CAUTION]
> OpenHands is meant to be run by a single user on their local workstation.
> It is not appropriate for multi-tenant deployments where multiple users share the same instance. There is no built-in authentication, isolation, or scalability.
>
> If you're interested in running OpenHands in a multi-tenant environment, check out the source-available, commercially-licensed
> [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud)
> If you're interested in running OpenHands in a multi-tenant environment, please
> [get in touch with us](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform)
> for advanced deployment options.
You can [connect OpenHands to your local filesystem](https://docs.all-hands.dev/usage/runtimes/docker#connecting-to-your-filesystem),
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/usage/runtimes/docker#connecting-to-your-filesystem),
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/usage/how-to/headless-mode),
interact with it via a [friendly CLI](https://docs.all-hands.dev/usage/how-to/cli-mode),
or run it on tagged issues with [a github action](https://docs.all-hands.dev/usage/how-to/github-action).
@@ -117,7 +118,7 @@ troubleshooting resources, and advanced configuration options.
OpenHands is a community-driven project, and we welcome contributions from everyone. We do most of our communication
through Slack, so this is the best place to start, but we also are happy to have you contact us on Discord or Github:
- [Join our Slack workspace](https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA) - Here we talk about research, architecture, and future development.
- [Join our Slack workspace](https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A) - Here we talk about research, architecture, and future development.
- [Join our Discord server](https://discord.gg/ESHStjSjD4) - This is a community-run server for general discussion, questions, and feedback.
- [Read or post Github Issues](https://github.com/All-Hands-AI/OpenHands/issues) - Check out the issues we're working on, or add your own ideas.

View File

@@ -12,7 +12,7 @@
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
<br/>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="加入我们的Slack社区"></a>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="加入我们的Slack社区"></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="加入我们的Discord社区"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="致谢"></a>
<br/>
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47
docker.all-hands.dev/all-hands-ai/openhands:0.44
```
> **注意**: 如果您在0.44版本之前使用过OpenHands您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
@@ -107,7 +107,7 @@ docker run -it --rm --pull=always \
OpenHands是一个社区驱动的项目我们欢迎每个人的贡献。我们大部分沟通
通过Slack进行因此这是开始的最佳场所但我们也很乐意您通过Discord或Github与我们联系
- [加入我们的Slack工作空间](https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA) - 这里我们讨论研究、架构和未来发展。
- [加入我们的Slack工作空间](https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A) - 这里我们讨论研究、架构和未来发展。
- [加入我们的Discord服务器](https://discord.gg/ESHStjSjD4) - 这是一个社区运营的服务器,用于一般讨论、问题和反馈。
- [阅读或发布Github问题](https://github.com/All-Hands-AI/OpenHands/issues) - 查看我们正在处理的问题,或添加您自己的想法。

View File

@@ -10,7 +10,7 @@
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
<br/>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Slackコミュニティに参加"></a>
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Slackコミュニティに参加"></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Discordコミュニティに参加"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="クレジット"></a>
<br/>
@@ -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.47-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47
docker.all-hands.dev/all-hands-ai/openhands:0.44
```
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。

View File

@@ -1,156 +0,0 @@
# Resolver Runtime Refactoring Plan
## Task Overview
Refactor the resolver component to reuse setup.py functions for runtime initialization, connection, and completion instead of reinventing the wheel.
## Repository Cloning Patterns Analysis
### Repository Cloning Patterns Across OpenHands Entry Points
#### 1. **Resolver (issue_resolver.py)** - DIFFERENT PATTERN (Legacy)
```python
# Step 1: Clone to separate location
subprocess.check_output(['git', 'clone', url, f'{output_dir}/repo'])
# Step 2: Later, copy repo to workspace
shutil.copytree(os.path.join(self.output_dir, 'repo'), self.workspace_base)
# Step 3: Create and connect runtime
runtime = create_runtime(config)
await runtime.connect()
# Step 4: Initialize runtime (git config, setup scripts)
self.initialize_runtime(runtime)
```
#### 2. **Main.py** - STANDARD PATTERN
```python
# Step 1: Create and connect runtime
runtime = create_runtime(config)
await runtime.connect()
# Step 2: Clone directly into runtime workspace + setup
repo_directory = initialize_repository_for_runtime(runtime, selected_repository)
```
#### 3. **Server/Session** - STANDARD PATTERN
```python
# Step 1: Create and connect runtime
# Step 2: Clone directly into runtime workspace
await runtime.clone_or_init_repo(tokens, repo, branch)
# Step 3: Run setup scripts
await runtime.maybe_run_setup_script()
await runtime.maybe_setup_git_hooks()
```
#### 4. **Setup.py's initialize_repository_for_runtime()** - STANDARD PATTERN
```python
# Calls runtime.clone_or_init_repo() + setup scripts
repo_directory = runtime.clone_or_init_repo(tokens, repo, branch)
runtime.maybe_run_setup_script()
runtime.maybe_setup_git_hooks()
```
### The Issue
The **resolver is the odd one out** - it uses a 2-step process (clone to temp location, then copy to workspace) due to **legacy reasons** (it was originally developed as a separate app built on OH, not a component of OH). All other entry points use the standard pattern (clone directly into runtime workspace).
## Current State Analysis
### ✅ What Resolver Already Does Right:
- [x] Uses `create_runtime()` from setup.py for runtime creation
### ❌ What Needs to be Fixed:
- [ ] **Resolver uses legacy 2-step cloning instead of standard runtime.clone_or_init_repo()**
- [ ] Resolver has custom `initialize_runtime()` method that duplicates setup.py logic
- [ ] Resolver has custom `complete_runtime()` method with no setup.py equivalent
- [ ] Resolver doesn't follow proper runtime cleanup patterns like main.py
- [ ] Runtime connection pattern is inconsistent across codebase
## Refactoring Steps
### Phase 1: Fix Repository Cloning Pattern (PRIORITY)
**Goal**: Make resolver use the same repository cloning pattern as all other OpenHands entry points.
- [ ] **Step 1.1**: Replace resolver's legacy 2-step cloning with standard pattern
- Remove `subprocess.check_output(['git', 'clone', ...])` from `resolve_issue()`
- Remove `shutil.copytree()` from `process_issue()`
- Use `initialize_repository_for_runtime()` instead
- This will clone directly into runtime workspace AND run setup scripts
- [ ] **Step 1.2**: Update resolver workflow to match standard pattern
- Create and connect runtime first
- Then call `initialize_repository_for_runtime()` for cloning + setup
- Remove the manual repo copying step entirely
- Ensure base_commit is still captured correctly
### Phase 2: Refactor Runtime Initialization and Completion
**Goal**: Remove code duplication between resolver and setup.py for runtime operations.
- [ ] **Step 2.1**: Create missing functions in setup.py
- Create `setup_runtime_environment()` for git config and platform-specific setup
- Create `complete_runtime_session()` for git patch generation
- Create `cleanup_runtime()` for proper resource cleanup
- [ ] **Step 2.2**: Replace resolver's `initialize_runtime()`
- Use setup.py's `setup_runtime_environment()` instead
- Remove duplicate git configuration code
- Maintain platform-specific behavior (GitLab CI)
- [ ] **Step 2.3**: Replace resolver's `complete_runtime()`
- Use setup.py's `complete_runtime_session()` instead
- Move git patch generation logic to setup.py
- Ensure return values match resolver's expectations
- [ ] **Step 2.4**: Add proper runtime cleanup to resolver
- Use setup.py's `cleanup_runtime()` function
- Ensure resources are properly released in try/finally blocks
### Phase 3: Testing and Validation
- [ ] **Step 3.1**: Test resolver functionality with refactored code
- Verify git operations work correctly
- Verify setup scripts are executed
- Verify git hooks are set up
- [ ] **Step 3.2**: Test runtime lifecycle (create → connect → clone → initialize → complete → cleanup)
- Ensure no resource leaks
- Verify proper error handling
- [ ] **Step 3.3**: Verify resolver output remains consistent
- Git patches are generated correctly
- Issue resolution works as before
- No regression in functionality
### Phase 4: Code Quality and Documentation
- [ ] **Step 4.1**: Add proper documentation to new setup.py functions
- Document parameters and return values
- Add usage examples
- Document platform-specific behavior
- [ ] **Step 4.2**: Remove obsolete code from resolver
- Delete old `initialize_runtime()` method
- Delete old `complete_runtime()` method
- Clean up imports and unused code
- [ ] **Step 4.3**: Update any other components that might benefit from these functions
- Check if other entry points could use the same patterns
- Ensure consistency across the codebase
## Success Criteria
- [ ] **Resolver uses standard repository cloning pattern (runtime.clone_or_init_repo)**
- [ ] Resolver uses setup.py functions for all runtime operations
- [ ] No code duplication between resolver and setup.py
- [ ] Proper runtime lifecycle management (connect → initialize → complete → cleanup)
- [ ] All existing resolver functionality preserved
- [ ] Consistent patterns across all OpenHands entry points
- [ ] Proper error handling and resource cleanup
## Files to Modify
1. `/openhands/core/setup.py` - Add new runtime management functions
2. `/openhands/resolver/issue_resolver.py` - Refactor to use setup.py functions
3. Any tests related to resolver functionality
## Risk Mitigation
- Maintain backward compatibility during refactoring
- Test thoroughly before removing old code
- Keep git patch generation logic identical to avoid breaking issue resolution
- Ensure platform-specific behavior (GitLab CI) is preserved

View File

@@ -10,7 +10,18 @@
# General core configurations
##############################################################################
[core]
# API keys and configuration for core services
# API key for E2B
#e2b_api_key = ""
# API key for Modal
#modal_api_token_id = ""
#modal_api_token_secret = ""
# API key for Daytona
#daytona_api_key = ""
# Daytona Target
#daytona_target = ""
# Base path for the workspace
#workspace_base = "./workspace"
@@ -190,27 +201,6 @@ model = "gpt-4o"
#native_tool_calling = None
# Safety settings for models that support them (e.g., Mistral AI, Gemini)
# Example for Mistral AI:
# safety_settings = [
# { "category" = "hate", "threshold" = "low" },
# { "category" = "harassment", "threshold" = "low" },
# { "category" = "sexual", "threshold" = "low" },
# { "category" = "dangerous", "threshold" = "low" }
# ]
#
# Example for Gemini:
# safety_settings = [
# { "category" = "HARM_CATEGORY_HARASSMENT", "threshold" = "BLOCK_NONE" },
# { "category" = "HARM_CATEGORY_HATE_SPEECH", "threshold" = "BLOCK_NONE" },
# { "category" = "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold" = "BLOCK_NONE" },
# { "category" = "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold" = "BLOCK_NONE" }
# ]
#safety_settings = []
[llm.draft_editor]
# The number of times llm_editor tries to fix an error when editing.
correct_num = 5
[llm.gpt4o-mini]
api_key = ""
@@ -238,8 +228,8 @@ enable_editor = true
# Whether the IPython tool is enabled
enable_jupyter = true
# Whether the command tool is enabled
enable_cmd = true
# Whether the bash tool is enabled
enable_bash = true
# Whether the think tool is enabled
enable_think = true
@@ -260,9 +250,6 @@ enable_finish = true
# length limit
enable_history_truncation = true
# Whether the condensation request tool is enabled
enable_condensation_request = false
[agent.RepoExplorerAgent]
# Example: use a cheaper model for RepoExplorerAgent to reduce cost, especially
# useful when an agent doesn't demand high quality but uses a lot of tokens
@@ -331,9 +318,6 @@ classpath = "my_package.my_module.MyCustomAgent"
# Enable GPU support in the runtime
#enable_gpu = false
# When there are multiple cards, you can specify the GPU by ID
#cuda_visible_devices = ''
# Additional Docker runtime kwargs
#docker_runtime_kwargs = {}

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.47-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.44-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:

View File

@@ -1,4 +1,4 @@
FROM ubuntu:24.04
FROM ubuntu:22.04
# install basic packages
RUN apt-get update && apt-get install -y \

View File

@@ -3,9 +3,9 @@ repos:
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: ^(docs/|modules/|python/|openhands-ui/|third_party/)
exclude: docs/modules/python
- id: end-of-file-fixer
exclude: ^(docs/|modules/|python/|openhands-ui/|third_party/)
exclude: docs/modules/python
- id: check-yaml
args: ["--allow-multiple-documents"]
- id: debug-statements
@@ -28,19 +28,17 @@ repos:
entry: ruff check --config dev_config/python/ruff.toml
types_or: [python, pyi, jupyter]
args: [--fix, --unsafe-fixes]
exclude: third_party/
# Run the formatter.
- id: ruff-format
entry: ruff format --config dev_config/python/ruff.toml
types_or: [python, pyi, jupyter]
exclude: third_party/
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies:
[types-requests, types-setuptools, types-pyyaml, types-toml, types-docker, pydantic, lxml]
[types-requests, types-setuptools, types-pyyaml, types-toml, types-docker, lxml]
# To see gaps add `--html-report mypy-report/`
entry: mypy --config-file dev_config/python/mypy.ini openhands/
always_run: true

View File

@@ -7,9 +7,3 @@ warn_unreachable = True
warn_redundant_casts = True
no_implicit_optional = True
strict_optional = True
# Exclude third-party runtime directory from type checking
exclude = third_party/
[mypy-openhands.memory.condenser.impl.*]
disable_error_code = override

View File

@@ -1,6 +1,3 @@
# Exclude third-party runtime directory from linting
exclude = ["third_party/"]
[lint]
select = [
"E",

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.47-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.44-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:

View File

@@ -26,7 +26,7 @@
"usage/installation",
"usage/getting-started",
"usage/key-features",
"usage/faqs",
"usage/faq",
{
"group": "OpenHands Cloud",
"pages": [
@@ -44,7 +44,7 @@
]
},
{
"group": "Run OpenHands on Your Own",
"group": "Running OpenHands on Your Own",
"pages": [
"usage/local-setup",
"usage/how-to/gui-mode",
@@ -104,9 +104,8 @@
]
},
{
"group": "Customizations & Settings",
"group": "Customization",
"pages": [
"usage/common-settings",
"usage/prompting/repository",
{
"group": "Microagents",
@@ -151,12 +150,6 @@
}
]
},
{
"tab": "Success Stories",
"pages": [
"success-stories/index"
]
},
{
"tab": "API Reference",
"openapi": "/openapi.json"
@@ -196,7 +189,7 @@
},
"footer": {
"socials": {
"slack": "https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA",
"slack": "https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A",
"github": "https://github.com/All-Hands-AI/OpenHands",
"discord": "https://discord.gg/ESHStjSjD4"
}

View File

@@ -1,217 +0,0 @@
---
title: "Success Stories"
description: "Real-world examples of what you can achieve with OpenHands"
---
Discover how developers and teams are using OpenHands to automate their software development workflows. From quick fixes to complex projects, see what's possible with AI-powered development assistance.
Check out the [#success-stories](https://www.linen.dev/s/openhands/c/success-stories) channel on our Slack for more!
<Update label="2025-06-13 OpenHands helps frontline support" description="@Joe Pelletier">
## One of the cool things about OpenHands, and especially the Slack Integration, is the ability to empower folks who are on the front lines with customers.
For example, often times Support and Customer Success teams will field bug reports, doc questions, and other nits from customers. They tend to have few options to deal with this, other than file a feedback ticket with product teams and hope it gets prioritized in an upcoming sprint.
Instead, with tools like OpenHands and the Slack integration, they can request OpenHands to make fixes proactively and then have someone on the engineering team (like a lead engineer, a merge engineer, or even technical product manager) review the PR and approve it — thus reducing the cycle time for quick wins from weeks to just a few hours.
Here's how we do that with the OpenHands project:
<iframe
width="560"
height="560"
src="https://www.linen.dev/s/openhands/t/29118545/seems-mcp-config-from-config-toml-is-being-overwritten-hence#629f8e2b-cde8-427e-920c-390557a06cc9"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
[Original Slack thread](https://www.linen.dev/s/openhands/t/29124350/one-of-the-cool-things-about-openhands-and-especially-the-sl#25029f37-7b0d-4535-9187-83b3e06a4011)
</Update>
<Update label="2025-06-13 Ask OpenHands to show me some love" description="@Graham Neubig">
## Asked openhands to “show me some love” and...
Asked openhands to “show me some love” and it coded up this app for me, actually kinda genuinely feel loved
<video
controls
autoplay
className="w-full aspect-video"
src="/success-stories/stories/2025-06-13-show-love/v1.mp4"
></video>
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100731/asked-openhands-to-show-me-some-love-and-it-coded-up-this-ap#1e08af6b-b7d5-4167-8a53-17e6806555e0)
</Update>
<Update label="2025-06-11 OpenHands does 100% of my infra IAM research for me" description="@Xingyao Wang">
## Now, OpenHands does 100% of my infra IAM research for me
Got an IAM error on GCP? Send a screenshot to OH... and it just works!!!
Can't imagine going back to the early days without OH: I'd spend an entire afternoon figuring how to get IAM right
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100732/now-openhands-does-100-of-my-infra-iam-research-for-me-sweat#20482a73-4e2e-4edd-b6d1-c9e8442fccd1)
![](/success-stories/stories/2025-06-11-infra-iam/s1.png)
![](/success-stories/stories/2025-06-11-infra-iam/s2.png)
</Update>
<Update label="2025-06-08 OpenHands builds an interactive map for me" description="@Rodrigo Argenton Freire (ODLab)">
## Very simple example, but baby steps....
I am a professor of architecture and urban design. We built, me and some students, an interactive map prototype to help visitors and new students to find important places in the campus. Considering that we lack a lot of knowledge in programming, that was really nice to build and a smooth process.
We first created the main components with all-hands and then adjusted some details locally. Definitely, saved us a lot of time and money.
That's a prototype but we will have all the info by tuesday.
https://buriti-emau.github.io/Mapa-UFU/
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100736/very-simple-example-but-baby-steps-i-am-a-professor-of-archi#8f2e3f3f-44e6-44ea-b9a8-d53487470179)
![](/success-stories/stories/2025-06-08-map/s1.png)
</Update>
<Update label="2025-06-06 Web Search Saves the Day" description="@Ian Walker">
## Tavily adapter helps solve persistent debugging issue
Big congratulations to the new [Tavily adapter](https://www.all-hands.dev/blog/building-a-provably-versatile-agent)... OpenHands and I have been beavering away at a Lightstreamer client library for most of this week but were getting a persistent (and unhelpful) "unexpected error" from the server.
Coming back to the problem today, after trying several unsuccessful fixes prompted by me, OH decided all by itself to search the web, and found the cause of the problem (of course it was simply CRLF line endings...). I was on the verge of giving up - good thing OH has more stamina than me!
This demonstrates how OpenHands' web search capabilities can help solve debugging issues that would otherwise require extensive manual research.
<iframe
width="560"
height="560"
src="https://www.linen.dev/s/openhands/t/29100737/big-congratulations-to-the-new-tavily-adapter-openhands-and-#87b027e5-188b-425e-8aa9-719dcb4929f4"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100737/big-congratulations-to-the-new-tavily-adapter-openhands-and-#76f1fb26-6ef7-4709-b9ea-fb99105e47e4)
</Update>
<Update label="2025-06-05 OpenHands updates my personal website for a new paper" description="@Xingyao Wang">
## I asked OpenHands to update my personal website for the "OpenHands Versa" paper.
It is an extremely trivial task: You just need to browse to arxiv, copy the author names, format them for BibTeX, and then modify the papers.bib file. But now I'm getting way too lazy to even open my IDE and actually do this one-file change!
[Original Tweet/X thread](https://x.com/xingyaow_/status/1930796287919542410)
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100738/i-asked-openhands-to-update-my-personal-website-for-the-open#f0324022-b12b-4d34-b12b-bdbc43823f69)
</Update>
<Update label="2025-06-02 OpenHands makes an animated gif of swe-bench verified scores over time" description="@Graham Neubig">
## I asked OpenHands to make an animated gif of swe-bench verified scores over time.
It took a bit of prompting but ended up looking pretty nice I think
<video width="560" height="315" autoPlay loop muted src="/success-stories/stories/2025-06-02-swebench-score/s1.mp4"></video>
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100744/i-asked-openhands-to-make-an-animated-gif-of-swe-bench-verif#fb3b82c9-6222-4311-b97b-b2ac1cfe6dff)
</Update>
<Update label="2025-05-30 AWS Troubleshooting" description="@Graham Neubig">
## Quick AWS security group fix
I really don't like trying to fix issues with AWS, especially security groups and other finicky things like this. But I started up an instance and wasn't able to ssh in. So I asked OpenHands:
> Currently, the following ssh command is timing out:
>
> $ ssh -i gneubig.pem ubuntu@XXX.us-east-2.compute.amazonaws.com
> ssh: connect to host XXX.us-east-2.compute.amazonaws.com port 22: Operation timed out
>
> Use the provided AWS credentials to take a look at i-XXX and examine why
And 2 minutes later I was able to SSH in!
This shows how OpenHands can quickly diagnose and fix AWS infrastructure issues that would normally require manual investigation.
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100747/i-really-don-t-like-trying-to-fix-issues-with-aws-especially#d92a66d2-3bc1-4467-9d09-dc983004d083)
</Update>
<Update label="2025-05-04 Chrome Extension Development" description="@Xingyao Wang">
## OpenHands builds Chrome extension for GitHub integration
I asked OpenHands to write a Chrome extension based on our [OpenHands Cloud API](https://docs.all-hands.dev/modules/usage/cloud/cloud-api). Once installed, you can now easily launch an OpenHands cloud session from your GitHub webpage/PR!
This demonstrates OpenHands' ability to create browser extensions and integrate with external APIs, enabling seamless workflows between GitHub and OpenHands Cloud.
![Chrome extension](/success-stories/stories/2025-05-04-chrome-extension/s1.png)
![Chrome extension](/success-stories/stories/2025-05-04-chrome-extension/s2.png)
[GitHub Repository](https://github.com/xingyaoww/openhands-chrome-extension)
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100755/i-asked-openhands-to-write-a-chrome-extension-based-on-our-h#88f14b7f-f8ff-40a6-83c2-bd64e95924c5)
</Update>
<Update label="2025-04-11 Visual UI Testing" description="@Xingyao Wang">
## OpenHands tests UI automatically with visual browsing
Thanks to visual browsing -- OpenHands can actually test some simple UI by serving the website, clicking the button in the browser and looking at screenshots now!
Prompt is just:
```
I want to create a Hello World app in Javascript that:
* Displays Hello World in the middle.
* Has a button that when clicked, changes the greeting with a bouncing animation to fun versions of Hello.
* Has a counter for how many times the button has been clicked.
* Has another button that changes the app's background color.
```
Eager-to-work Sonnet 3.7 will test stuff for you without you asking!
This showcases OpenHands' visual browsing capabilities, enabling it to create, serve, and automatically test web applications through actual browser interactions and screenshot analysis.
![Visual UI testing](/success-stories/stories/2025-04-11-visual-ui/s1.png)
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100764/thanks-to-u07k0p3bdb9-s-visual-browsing-openhands-can-actual#21beb9bc-1a04-4272-87e9-4d3e3b9925e7)
</Update>
<Update label="2025-03-07 Proactive Error Handling" description="@Graham Neubig">
## OpenHands fixes crashes before you notice them
Interesting story, I asked OpenHands to start an app on port 12000, it showed up on the app pane. I started using the app, and then it crashed... But because it crashed in OpenHands, OpenHands immediately saw the error message and started fixing the problem without me having to do anything. It was already fixing the problem before I even realized what was going wrong.
This demonstrates OpenHands' proactive monitoring capabilities - it doesn't just execute commands, but actively watches for errors and begins remediation automatically, often faster than human reaction time.
</Update>
<Update label="2024-12-03 Creative Design Acceleration" description="@Rohit Malhotra">
## Pair programming for interactive design projects
Used OpenHands as a pair programmer to do heavy lifting for a creative/interactive design project in p5js.
I usually take around 2 days for high fidelity interactions (planning strategy + writing code + circling back with designer), did this in around 5hrs instead with the designer watching curiously the entire time.
This showcases how OpenHands can accelerate creative and interactive design workflows, reducing development time by 75% while maintaining high quality output.
[Original Tweet](https://x.com/rohit_malh5/status/1863995531657425225)
</Update>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

View File

@@ -1,7 +1,7 @@
---
title: Cloud UI
description: The Cloud UI provides a web interface for interacting with OpenHands. This page provides references on
how to use the OpenHands Cloud UI.
description: The Cloud UI provides a web interface for interacting with OpenHands. This page explains how to use the
OpenHands Cloud UI.
---
## Landing Page
@@ -19,12 +19,10 @@ The landing page is where you can:
The Settings page allows you to:
- [Configure GitHub repository access](/usage/cloud/github-installation#modifying-repository-access) for OpenHands.
- [Install the OpenHands Slack app](/usage/cloud/slack-installation).
- Set application settings like your preferred language, notifications and other preferences.
- Add credits to your account.
- [Generate custom secrets](/usage/common-settings#secrets-management).
- [Create API keys to work with OpenHands programmatically](/usage/cloud/cloud-api).
- Change your email address.
- Generate custom secrets.
- Create API keys to work with OpenHands programmatically.
## Key Features

View File

@@ -3,18 +3,9 @@ title: Slack Integration (Beta)
description: This guide walks you through installing the OpenHands Slack app.
---
<iframe
className="w-full aspect-video"
src="https://www.youtube.com/embed/hbloGmfZsJ4"
title="OpenHands Slack Integration Tutorial"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen>
</iframe>
## Prerequisites
- Access to OpenHands Cloud.
- Access to OpenHands Cloud
## Installation Steps
@@ -32,11 +23,11 @@ description: This guide walks you through installing the OpenHands Slack app.
<Accordion title="Authorize Slack App (for all Slack workspace members)">
**Make sure your Slack workspace admin/owner has installed OpenHands Slack App first.**
**Make sure your Slack workspace admin/owner has installed OpenHands Slack App first**
Every user in the Slack workspace (including admins/owners) must link their OpenHands Cloud account to the OpenHands Slack App. To do this:
Every user in the Slack workspace (including admins/owners) must link their Cloud OpenHands account to the OpenHands Slack App. To do this:
1. Visit [integrations settings](https://app.all-hands.dev/settings/integrations) in OpenHands Cloud.
2. Click `Install OpenHands Slack App`.
2. Click the button "Install Slack App".
3. In the top right corner, select the workspace to install the OpenHands Slack app.
4. Review permissions and click allow.

View File

@@ -1,52 +0,0 @@
---
title: OpenHands Settings
description: Overview of some of the settings available in OpenHands.
---
## Openhands Cloud vs Running on Your Own
There are some differences between the settings available in OpenHands Cloud and those available when running OpenHands
on your own:
* [OpenHands Cloud settings](/usage/cloud/cloud-ui#settings)
* [Settings available when running on your own](/usage/how-to/gui-mode#settings)
Refer to these pages for more detailed information.
## Secrets Management
OpenHands provides a secrets manager that allows you to securely store and manage sensitive information that can be
accessed by the agent during runtime, such as API keys. These secrets are automatically exported as environment
variables in the agent's runtime environment.
### Accessing the Secrets Manager
In the Settings page, navigate to the `Secrets` tab. Here, you'll see a list of all your existing custom secrets.
### Adding a New Secret
1. Click `Add a new secret`.
2. Fill in the following fields:
- **Name**: A unique identifier for your secret (e.g., `AWS_ACCESS_KEY`). This will be the environment variable name.
- **Value**: The sensitive information you want to store.
- **Description** (optional): A brief description of what the secret is used for, which is also provided to the agent.
3. Click `Add secret` to save.
### Editing a Secret
1. Click the `Edit` button next to the secret you want to modify.
2. You can update the name and description of the secret.
<Note>
For security reasons, you cannot view or edit the value of an existing secret. If you need to change the
value, delete the secret and create a new one.
</Note>
### Deleting a Secret
1. Click the `Delete` button next to the secret you want to remove.
2. Select `Confirm` to delete the secret.
### Using Secrets in the Agent
- All custom secrets are automatically exported as environment variables in the agent's runtime environment.
- You can access them in your code using standard environment variable access methods
(e.g., `os.environ['SECRET_NAME']` in Python).
- Example: If you create a secret named `OPENAI_API_KEY`, you can access it in your code as
`process.env.OPENAI_API_KEY` in JavaScript or `os.environ['OPENAI_API_KEY']` in Python.

View File

@@ -1,17 +1,28 @@
---
title: Configuration Options
description: This page outlines all available configuration options for OpenHands, allowing you to customize its
behavior and integrate it with other services.
description: This page outlines all available configuration options for OpenHands, allowing you to customize its behavior and integrate it with other services. In GUI Mode, any settings applied through the Settings UI will take precedence.
---
<Note>
In GUI Mode, any settings applied through the Settings UI will take precedence.
</Note>
## Core Configuration
The core configuration options are defined in the `[core]` section of the `config.toml` file.
### API Keys
- `e2b_api_key`
- Type: `str`
- Default: `""`
- Description: API key for E2B
- `modal_api_token_id`
- Type: `str`
- Default: `""`
- Description: API token ID for Modal
- `modal_api_token_secret`
- Type: `str`
- Default: `""`
- Description: API token secret for Modal
### Workspace
- `workspace_base` **(Deprecated)**
- Type: `str`

View File

@@ -8,33 +8,26 @@ icon: question
### I'm new to OpenHands. Where should I start?
1. **Quick start**: Use [OpenHands Cloud](/usage/cloud/openhands-cloud) to get started quickly with
[GitHub](/usage/cloud/github-installation), [GitLab](/usage/cloud/gitlab-installation),
and [Slack](/usage/cloud/slack-installation) integrations.
2. **Run on your own**: If you prefer to run it on your own hardware, follow our [Getting Started guide](/usage/local-setup).
3. **First steps**: Complete the [start building tutorial](/usage/getting-started) to learn the basics.
1. **Quick Start**: Use [OpenHands Cloud](/usage/cloud/openhands-cloud) to get started quickly with [GitHub](/usage/cloud/github-installation), [GitLab](/usage/cloud/gitlab-installation), and [Slack](/usage/cloud/slack-installation) integration
2. **Local Setup**: If you prefer to run it on your own hardware, follow our [Getting Started guide](/usage/local-setup)
3. **First steps**: Complete the [start building tutorial](/usage/getting-started) to learn the basics
### Can I use OpenHands for production workloads?
OpenHands is meant to be run by a single user on their local workstation. It is not appropriate for multi-tenant
deployments where multiple users share the same instance. There is no built-in authentication, isolation, or scalability.
OpenHands is meant to be run by a single user on their local workstation. It is not appropriate for multi-tenant deployments where multiple users share the same instance. There is no built-in authentication, isolation, or scalability.
If you're interested in running OpenHands in a multi-tenant environment, check out the source-available,
commercially-licensed [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud).
If you're interested in running OpenHands in a multi-tenant environment, check out the source-available, commercially-licensed [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud).
<Info>
Using OpenHands for work? We'd love to chat! Fill out
[this short form](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform)
to join our Design Partner program, where you'll get early access to commercial features and the opportunity to provide
input on our product roadmap.
Using OpenHands for work? We'd love to chat! Fill out [this short form](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform) to join our Design Partner program, where you'll get early access to commercial features and the opportunity to provide input on our product roadmap.
</Info>
## Safety and Security
### It's doing stuff without asking, is that safe?
**Generally yes, but with important considerations.** OpenHands runs all code in a secure, isolated Docker container
(called a "sandbox") that is separate from your host system. However, the safety depends on your configuration:
**Generally yes, but with important considerations.** OpenHands runs all code in a secure, isolated Docker container (called a "sandbox") that is separate from your host system. However, the safety depends on your configuration:
**What's protected:**
- Your host system files and programs (unless you mount them using [this feature](/usage/runtimes/docker#connecting-to-your-filesystem))
@@ -42,14 +35,12 @@ input on our product roadmap.
- Other containers and processes
**Potential risks to consider:**
- The agent can access the internet from within the container.
- If you provide credentials (API keys, tokens), the agent can use them.
- Mounted files and directories can be modified or deleted.
- Network requests can be made to external services.
- The agent can access the internet from within the container
- If you provide credentials (API keys, tokens), the agent can use them
- Mounted files and directories can be modified or deleted
- Network requests can be made to external services
For detailed security information, see our [Runtime Architecture](/usage/architecture/runtime),
[Security Configuration](/usage/configuration-options#security-configuration),
and [Hardened Docker Installation](/usage/runtimes/docker#hardened-docker-installation) documentation.
For detailed security information, see our [Runtime Architecture](/usage/architecture/runtime), [Security Configuration](/usage/configuration-options#security-configuration), and [Hardened Docker Installation](/usage/runtimes/docker#hardened-docker-installation) documentation.
## File Storage and Access
@@ -58,19 +49,20 @@ and [Hardened Docker Installation](/usage/runtimes/docker#hardened-docker-instal
Your files are stored in different locations depending on how you've configured OpenHands:
**Default behavior (no file mounting):**
- Files created by the agent are stored inside the runtime Docker container.
- These files are temporary and will be lost when the container is removed.
- The agent works in the `/workspace` directory inside the runtime container.
- Files created by the agent are stored inside the Docker container
- These files are temporary and will be lost when the container is removed
- The agent works in the `/workspace` directory inside the container
**When you mount your local filesystem (following [this](/usage/runtimes/docker#connecting-to-your-filesystem)):**
- Your local files are mounted into the container's `/workspace` directory.
- Changes made by the agent are reflected in your local filesystem.
- Files persist after the container is stopped.
- Your local files are mounted into the container's `/workspace` directory
- Changes made by the agent are reflected in your local filesystem
- Files persist after the container is stopped
<Warning>
Be careful when mounting your filesystem - the agent can modify or delete any files in the mounted directory.
</Warning>
## Development Tools and Environment
### How do I get the dev tools I need?
@@ -79,18 +71,15 @@ OpenHands comes with a basic runtime environment that includes Python and Node.j
It also has the ability to install any tools it needs, so usually it's sufficient to ask it to set up its environment.
If you would like to set things up more systematically, you can:
- **Use setup.sh**: Add a [setup.sh file](/usage/prompting/repository#setup-script) file to
your repository, which will be run every time the agent starts.
- **Use a custom sandbox**: Use a [custom docker image](/usage/how-to/custom-sandbox-guide) to initialize the sandbox.
- **Use setup.sh**: Add a [setup.sh file](https://docs.all-hands.dev/usage/prompting/repository#setup-script) file to your repository, which will be run every time the agent starts
- **Use a custom sandbox**: Use a [custom docker image](/usage/how-to/custom-sandbox-guide) to initialize the sandbox
### Something's not working. Where can I get help?
1. **Search existing issues**: Check our [GitHub issues](https://github.com/All-Hands-AI/OpenHands/issues) to see if
others have encountered the same problem.
2. **Join our community**: Get help from other users and developers:
- [Slack community](https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA)
1. **Check our troubleshooting guide**: Common issues and solutions are documented in [Troubleshooting](/usage/troubleshooting/troubleshooting)
2. **Search existing issues**: Check our [GitHub issues](https://github.com/All-Hands-AI/OpenHands/issues) to see if others have encountered the same problem
3. **Join our community**: Get help from other users and developers:
- [Slack community](https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A)
- [Discord server](https://discord.gg/ESHStjSjD4)
3. **Check our troubleshooting guide**: Common issues and solutions are documented in
[Troubleshooting](/usage/troubleshooting/troubleshooting).
4. **Report bugs**: If you've found a bug, please [create an issue](https://github.com/All-Hands-AI/OpenHands/issues/new)
and fill in as much detail as possible.
4. **Report bugs**: If you've found a bug, please [create an issue](https://github.com/All-Hands-AI/OpenHands/issues/new) with details about your setup and the problem

View File

@@ -1,6 +1,6 @@
---
title: Start Building
description: So you've [run OpenHands](/usage/installation). Now what?
description: So you've [run OpenHands](./installation) and have [set up your LLM](./installation#setup). Now what?
icon: code
---

View File

@@ -7,15 +7,6 @@ description: The Command-Line Interface (CLI) provides a powerful interface that
This mode is different from the [headless mode](/usage/how-to/headless-mode), which is non-interactive and better
for scripting.
<iframe
className="w-full aspect-video"
src="https://www.youtube.com/embed/PfvIx4y8h7w"
title="OpenHands CLI Tutorial"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen>
</iframe>
## Getting Started
### Running with Python
@@ -23,34 +14,34 @@ for scripting.
**Note** - OpenHands requires Python version 3.12 or higher (Python 3.14 is not currently supported)
1. Install OpenHands using pip:
```bash
pip install openhands-ai
```
Or if you prefer not to manage your own Python environment, you can use `uvx`:
Or if you prefer not to manage your own Python environment, you can use `uvx`:
```bash
uvx --python 3.12 --from openhands-ai openhands
```
2. Launch an interactive OpenHands conversation from the command line:
```bash
openhands
```
<Note>
If you have cloned the repository, you can also run the CLI directly using Poetry:
poetry run python -m openhands.cli.main
</Note>
3. Set your model, API key, and other preferences using the UI (or alternatively environment variables, below).
This command opens an interactive prompt where you can type tasks or commands and get responses from OpenHands.
The first time you run the CLI, it will take you through configuring the required LLM
settings. These will be saved for future sessions.
The conversation history will be saved in `~/.openhands/sessions`.
#### For Developers
If you have cloned the repository, you can run the CLI directly using Poetry:
```bash
poetry run python -m openhands.cli.main
```
### Running with Docker
@@ -64,7 +55,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.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
-e LLM_API_KEY=$LLM_API_KEY \
@@ -73,21 +64,16 @@ 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.47 \
docker.all-hands.dev/all-hands-ai/openhands:0.44 \
python -m openhands.cli.main --override-cli-mode true
```
<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.
</Note>
> **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.
This launches the CLI in Docker, allowing you to interact with OpenHands.
This launches the CLI in Docker, allowing you to interact with OpenHands as described above.
The `-e SANDBOX_USER_ID=$(id -u)` ensures files created by the agent in your workspace have the correct permissions.
The conversation history will be saved in `~/.openhands/sessions`.
## Interactive CLI Overview
### What is CLI Mode?

View File

@@ -1,7 +1,6 @@
---
title: Custom Sandbox
description: This guide is for users that would like to use their own custom Docker image for the runtime.
For example, with certain tools or programming languages pre-installed.
description: This guide is for users that would like to use their own custom Docker image for the runtime. For example, with certain tools or programming languages pre-installed.
---
The sandbox is where the agent performs its tasks. Instead of running commands directly on your computer

View File

@@ -25,9 +25,9 @@ You can use the Settings page at any time to:
- Setup the LLM provider and model for OpenHands.
- [Setup the search engine](/usage/search-engine-setup).
- [Configure MCP servers](/usage/mcp).
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup) and [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup).
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup) and [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup)
- Set application settings like your preferred language, notifications and other preferences.
- [Manage custom secrets](/usage/common-settings#secrets-management).
- [Manage custom secrets](/usage/how-to/gui-mode#secrets-management).
#### GitHub Setup
@@ -157,6 +157,37 @@ OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if pro
</AccordionGroup>
#### Secrets Management
OpenHands provides a secrets manager that allows you to securely store and manage sensitive information that can be accessed by the agent during runtime, such as API keys. These secrets are automatically exported as environment variables in the agent's runtime environment.
1. **Accessing the Secrets Manager**:
- In the Settings page, navigate to the `Secrets` tab.
- You'll see a list of all your existing custom secrets (if any).
2. **Adding a New Secret**:
- Click the `Add New Secret` button.
- Fill in the following fields:
- **Name**: A unique identifier for your secret (e.g., `AWS_ACCESS_KEY`). This will be the environment variable name.
- **Value**: The sensitive information you want to store.
- **Description** (optional): A brief description of what the secret is used for, which is also provided to the agent.
- Click `Add Secret` to save.
3. **Editing a Secret**:
- Click the `Edit` button next to the secret you want to modify.
- You can update the name and description of the secret.
- Note: For security reasons, you cannot view or edit the value of an existing secret. If you need to change the value, delete the secret and create a new one.
4. **Deleting a Secret**:
- Click the `Delete` button next to the secret you want to remove.
- Confirm the deletion when prompted.
5. **Using Secrets in the Agent**:
- All custom secrets are automatically exported as environment variables in the agent's runtime environment.
- You can access them in your code using standard environment variable access methods (e.g., `os.environ['SECRET_NAME']` in Python).
- Example: If you create a secret named `OPENAI_API_KEY`, you can access it in your code as `process.env.OPENAI_API_KEY` in JavaScript or `os.environ['OPENAI_API_KEY']` in Python.
#### Advanced Settings
The `Advanced` settings allows configuration of additional LLM settings. Inside the Settings page, under the `LLM` tab,
@@ -177,11 +208,11 @@ section of the documentation.
The status indicator located in the bottom left of the screen will cycle through a number of states as a new conversation
is loaded. Typically these include:
* `Disconnected` : The frontend is not connected to any conversation.
* `Disconnected` : The frontend is not connected to any conversation
* `Connecting` : The frontend is connecting a websocket to a conversation.
* `Building Runtime...` : The server is building a runtime. This is typically in development mode only while building a docker image.
* `Starting Runtime...` : The server is starting a new runtime instance - probably a new docker container or remote runtime.
* `Initializing Agent...` : The server is starting the agent loop (This step does not appear at present with Nested runtimes).
* `Initializing Agent...` : The server is starting the agent loop. (This step does not appear at present with Nested runtimes)
* `Setting up workspace...` : Usually this means a `git clone ...` operation.
* `Setting up git hooks` : Setting up the git pre commit hooks for the workspace.
* `Agent is awaiting user input...` : Ready to go!

View File

@@ -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.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47 \
docker.all-hands.dev/all-hands-ai/openhands:0.44 \
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.

View File

@@ -1,12 +1,12 @@
---
title: Quick Start
description: Running OpenHands Cloud or running on your own.
description: Running OpenHands Cloud or running on your local system.
icon: rocket
---
## OpenHands Cloud
The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $20 in free credits for new users.
The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $50 in free credits for new users.
To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-hands.dev).

View File

@@ -73,15 +73,6 @@ We have a few guides for running OpenHands with specific model providers:
- [OpenAI](/usage/llms/openai-llms)
- [OpenRouter](/usage/llms/openrouter)
## Model Customization
LLM providers have specific settings that can be customized to optimize their performance with OpenHands, such as:
- **Custom Tokenizers**: For specialized models, you can add a suitable tokenizer
- **Native Tool Calling**: Toggle native function/tool calling capabilities
For detailed information about model customization, see [LLM Configuration Options](configuration-options#llm-customization).
### API retries and rate limits
LLM providers typically have rate limits, sometimes very low, and may require retries. OpenHands will automatically

View File

@@ -6,85 +6,75 @@ description: When using a Local LLM, OpenHands may have limited functionality. I
## News
- 2025/05/21: We collaborated with Mistral AI and released [Devstral Small](https://mistral.ai/news/devstral) that achieves [46.8% on SWE-Bench Verified](https://github.com/SWE-bench/experiments/pull/228)!
- 2025/03/31: We released an open model OpenHands LM 32B v0.1 that achieves 37.1% on SWE-Bench Verified
- 2025/03/31: We released an open model OpenHands LM v0.1 32B that achieves 37.1% on SWE-Bench Verified
([blog](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model), [model](https://huggingface.co/all-hands/openhands-lm-32b-v0.1)).
## Quickstart: Running OpenHands with a Local LLM using LM Studio
This guide explains how to serve a local Devstral LLM using [LM Studio](https://lmstudio.ai/) and have OpenHands connect to it.
## Quickstart: Running OpenHands on Your Macbook
We recommend:
- **LM Studio** as the local model server, which handles metadata downloads automatically and offers a simple, user-friendly interface for configuration.
- **Devstral Small 2505** as the LLM for software development, trained on real GitHub issues and optimized for agent-style workflows like OpenHands.
### Serve the model on your Macbook
### Hardware Requirements
We recommend using [LMStudio](https://lmstudio.ai/) for serving these models locally.
Running Devstral requires a recent GPU with at least 16GB of VRAM, or a Mac with Apple Silicon (M1, M2, etc.) with at least 32GB of RAM.
1. Download [LM Studio](https://lmstudio.ai/) and install it
### 1. Install LM Studio
2. Download the model:
- Option 1: Directly download the LLM from [this link](https://lmstudio.ai/model/devstral-small-2505-mlx) or by searching for the name `Devstral-Small-2505` in LM Studio
- Option 2: Download a LLM in GGUF format. For example, to download [Devstral Small 2505 GGUF](https://huggingface.co/mistralai/Devstral-Small-2505_gguf), using `huggingface-cli download mistralai/Devstral-Small-2505_gguf --local-dir mistralai/Devstral-Small-2505_gguf`. Then in bash terminal, run `lms import {model_name}` in the directory where you've downloaded the model checkpoint (e.g. run `lms import devstralQ4_K_M.gguf` in `mistralai/Devstral-Small-2505_gguf`)
Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstudio.ai/).
3. Open LM Studio application, you should first switch to `power user` mode, and then open the developer tab:
### 2. Download Devstral Small
![image](./screenshots/1_select_power_user.png)
1. Make sure to set the User Interface Complexity Level to "Power User", by clicking on the appropriate label at the bottom of the window.
2. Click the "Discover" button (Magnifying Glass icon) on the left navigation bar to open the Models download page.
4. Then click `Select a model to load` on top of the application:
![image](./screenshots/01_lm_studio_open_model_hub.png)
![image](./screenshots/2_select_model.png)
3. Search for the "Devstral Small 2505" model, confirm it's the official Mistral AI (mistralai) model, then proceed to download.
5. And choose the model you want to use, holding `option` on mac to enable advanced loading options:
![image](./screenshots/02_lm_studio_download_devstral.png)
![image](./screenshots/3_select_devstral.png)
4. Wait for the download to finish.
6. You should then pick an appropriate context window for OpenHands based on your hardware configuration (larger than 32768 is recommended for using OpenHands, but too large may cause you to run out of memory); Flash attention is also recommended if it works on your machine.
### 3. Load the Model
![image](./screenshots/4_set_context_window.png)
1. Click the "Developer" button (Console icon) on the left navigation bar to open the Developer Console.
2. Click the "Select a model to load" dropdown at the top of the application window.
7. And you should start the server (if it is not already in `Running` status), un-toggle `Serve on Local Network` and remember the port number of the LMStudio URL (`1234` is the port number for `http://127.0.0.1:1234` in this example):
![image](./screenshots/03_lm_studio_open_load_model.png)
![image](./screenshots/5_copy_url.png)
3. Enable the "Manually choose model load parameters" switch.
4. Select 'Devstral Small 2505' from the model list.
8. Finally, you can click the `copy` button near model name to copy the model name (`imported-models/uncategorized/devstralq4_k_m.gguf` in this example):
![image](./screenshots/04_lm_studio_setup_devstral_part_1.png)
![image](./screenshots/6_copy_to_get_model_name.png)
5. Enable the "Show advanced settings" switch at the bottom of the Model settings flyout to show all the available settings.
6. Set "Context Length" to at least 32768 and enable Flash Attention.
7. Click "Load Model" to start loading the model.
### Start OpenHands with locally served model
![image](./screenshots/05_lm_studio_setup_devstral_part_2.png)
### 4. Start the LLM server
1. Enable the switch next to "Status" at the top-left of the Window.
2. Take note of the Model API Identifier shown on the sidebar on the right.
![image](./screenshots/06_lm_studio_start_server.png)
### 5. Start OpenHands
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
Check [the installation guide](/usage/local-setup) to make sure you have all the prerequisites for running OpenHands.
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik
export LMSTUDIO_MODEL_NAME="imported-models/uncategorized/devstralq4_k_m.gguf" # <- Replace this with the model name you copied from LMStudio
export LMSTUDIO_URL="http://host.docker.internal:1234" # <- Replace this with the port from LMStudio
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
mkdir -p ~/.openhands && echo '{"language":"en","agent":"CodeActAgent","max_iterations":null,"security_analyzer":null,"confirmation_mode":false,"llm_model":"lm_studio/'$LMSTUDIO_MODEL_NAME'","llm_api_key":"dummy","llm_base_url":"'$LMSTUDIO_URL/v1'","remote_runtime_resource_factor":null,"github_token":null,"enable_default_condenser":true,"user_consents_to_analytics":true}' > ~/.openhands/settings.json
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47
docker.all-hands.dev/all-hands-ai/openhands:0.44
```
2. Wait until the server is running (see log below):
> **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.
Once your server is running -- you can visit `http://localhost:3000` in your browser to use OpenHands with local Devstral model:
```
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.47
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.44
Starting OpenHands...
Running OpenHands as root
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
@@ -94,88 +84,65 @@ INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:3000 (Press CTRL+C to quit)
```
3. Visit `http://localhost:3000` in your browser.
### 6. Configure OpenHands to use the LLM server
## Advanced: Serving LLM on GPUs
Once you open OpenHands in your browser, you'll need to configure it to use the local LLM server you just started.
### Download model checkpoints
When started for the first time, OpenHands will prompt you to set up the LLM provider.
<Note>
The model checkpoints downloaded here should NOT be in GGUF format.
</Note>
1. Click "see advanced settings" to open the LLM Settings page.
![image](./screenshots/07_openhands_open_advanced_settings.png)
2. Enable the "Advanced" switch at the top of the page to show all the available settings.
3. Set the following values:
- **Custom Model**: `openai/mistralai/devstral-small-2505` (the Model API identifier from LM Studio, prefixed with "openai/")
- **Base URL**: `http://host.docker.internal:1234/v1`
- **API Key**: `local-llm`
4. Click "Save Settings" to save the configuration.
![image](./screenshots/08_openhands_configure_local_llm_parameters.png)
That's it! You can now start using OpenHands with the local LLM server.
If you encounter any issues, let us know on [Slack](https://join.slack.com/t/openhands-ai/shared_invite/zt-3847of6xi-xuYJIPa6YIPg4ElbDWbtSA) or [Discord](https://discord.gg/ESHStjSjD4).
## Advanced: Alternative LLM Backends
This section describes how to run local LLMs with OpenHands using alternative backends like Ollama, SGLang, or vLLM — without relying on LM Studio.
### Create an OpenAI-Compatible Endpoint with Ollama
- Install Ollama following [the official documentation](https://ollama.com/download).
- Example launch command for Devstral Small 2505:
For example, to download [OpenHands LM 32B v0.1](https://huggingface.co/all-hands/openhands-lm-32b-v0.1):
```bash
# ⚠️ WARNING: OpenHands requires a large context size to work properly.
# When using Ollama, set OLLAMA_CONTEXT_LENGTH to at least 32768.
# The default (4096) is way too small — not even the system prompt will fit, and the agent will not behave correctly.
OLLAMA_CONTEXT_LENGTH=32768 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_KEEP_ALIVE=-1 nohup ollama serve &
ollama pull devstral:latest
huggingface-cli download all-hands/openhands-lm-32b-v0.1 --local-dir all-hands/openhands-lm-32b-v0.1
```
### Create an OpenAI-Compatible Endpoint with vLLM or SGLang
First, download the model checkpoints. For [Devstral Small 2505](https://huggingface.co/mistralai/Devstral-Small-2505):
```bash
huggingface-cli download mistralai/Devstral-Small-2505 --local-dir mistralai/Devstral-Small-2505
```
#### Serving the model using SGLang
### Create an OpenAI-Compatible Endpoint With SGLang
- Install SGLang following [the official documentation](https://docs.sglang.ai/start/install.html).
- Example launch command for Devstral Small 2505 (with at least 2 GPUs):
- Example launch command for OpenHands LM 32B (with at least 2 GPUs):
```bash
SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1 python3 -m sglang.launch_server \
--model mistralai/Devstral-Small-2505 \
--served-model-name Devstral-Small-2505 \
--model all-hands/openhands-lm-32b-v0.1 \
--served-model-name openhands-lm-32b-v0.1 \
--port 8000 \
--tp 2 --dp 1 \
--host 0.0.0.0 \
--api-key mykey --context-length 131072
```
#### Serving the model using vLLM
### Create an OpenAI-Compatible Endpoint with vLLM
- Install vLLM following [the official documentation](https://docs.vllm.ai/en/latest/getting_started/installation.html).
- Example launch command for Devstral Small 2505 (with at least 2 GPUs):
- Example launch command for OpenHands LM 32B (with at least 2 GPUs):
```bash
vllm serve mistralai/Devstral-Small-2505 \
vllm serve all-hands/openhands-lm-32b-v0.1 \
--host 0.0.0.0 --port 8000 \
--api-key mykey \
--tensor-parallel-size 2 \
--served-model-name Devstral-Small-2505 \
--served-model-name openhands-lm-32b-v0.1
--enable-prefix-caching
```
### Run OpenHands (Alternative Backends)
### Create an OpenAI-Compatible Endpoint with Ollama
- Install Ollama following [the official documentation](https://ollama.com/download).
- For Ollama configuration, use `ollama/<modelname>` as custom model in web. Api key also can be set to `ollama`.
- Example launch command for Devstral LM 24B:
```bash
OLLAMA_CONTEXT_LENGTH=32768 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_KEEP_ALIVE=-1 nohup ollama serve&
#The minimum context size is ~8196, even the system prompt won't fit smaller
ollama pull devstral:latest
```
## Advanced: Run and Configure OpenHands
### Run OpenHands
#### Using Docker
@@ -184,20 +151,24 @@ Run OpenHands using [the official docker run command](../installation#start-the-
#### Using Development Mode
Use the instructions in [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) to build OpenHands.
Ensure `config.toml` exists by running `make setup-config` which will create one for you. In the `config.toml`, enter the following:
```
[core]
workspace_base="/path/to/your/workspace"
[llm]
model="openhands-lm-32b-v0.1"
ollama_base_url="http://localhost:8000"
```
Start OpenHands using `make run`.
### Configure OpenHands (Alternative Backends)
### Configure OpenHands
Once OpenHands is running, open the Settings page in the UI and go to the `LLM` tab.
1. Click **"see advanced settings"** to access the full configuration panel.
2. Enable the **Advanced** toggle at the top of the page.
3. Set the following parameters, if you followed the examples above:
- **Custom Model**: `openai/<served-model-name>`
e.g. `openai/devstral` if you're using Ollama, or `openai/Devstral-Small-2505` for SGLang or vLLM.
- **Base URL**: `http://host.docker.internal:<port>/v1`
Use port `11434` for Ollama, or `8000` for SGLang and vLLM.
- **API Key**:
- For **Ollama**: any placeholder value (e.g. `dummy`, `local-llm`)
- For **SGLang** or **vLLM**: use the same key provided when starting the server (e.g. `mykey`)
Once OpenHands is running, you'll need to set the following in the OpenHands UI through the Settings under the `LLM` tab:
1. Enable `Advanced` options.
2. Set the following:
- `Custom Model` to `openai/<served-model-name>` (e.g. `openai/openhands-lm-32b-v0.1`)
- `Base URL` to `http://host.docker.internal:8000`
- `API key` to the same string you set when serving the model (e.g. `mykey`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -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.47-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.47-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-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.47
docker.all-hands.dev/all-hands-ai/openhands:0.44
```
> **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.
@@ -153,6 +153,8 @@ To enable search functionality in OpenHands:
For more details, see the [Search Engine Setup](/usage/search-engine-setup) guide.
Now you're ready to [get started with OpenHands](/usage/getting-started).
### Versions
The [docker command above](/usage/local-setup#start-the-app) pulls the most recent stable release of OpenHands. You have other options as well:

View File

@@ -1,7 +1,6 @@
---
title: Model Context Protocol (MCP)
description: This page outlines how to configure and use the Model Context Protocol (MCP) in OpenHands, allowing you
to extend the agent's capabilities with custom tools.
description: This page outlines how to configure and use the Model Context Protocol (MCP) in OpenHands, allowing you to extend the agent's capabilities with custom tools.
---
## Overview

View File

@@ -11,8 +11,6 @@ accordingly. However, they are applied to all repositories belonging to the orga
Add a `.openhands` repository under the organization or user and create a `microagents` directory and place the
microagents in that directory.
For GitLab organizations, use `openhands-config` as the repository name instead of `.openhands`, since GitLab doesn't support repository names starting with non-alphanumeric characters.
## Example
General microagent file example for organization `Great-Co` located inside the `.openhands` repository:
@@ -22,5 +20,3 @@ General microagent file example for organization `Great-Co` located inside the `
* Document interfaces and public APIs; use implementation comments only for non-obvious logic.
* Follow the same naming convention for variables, classes, constants, etc. already used in each repository.
```
For GitLab organizations, the same microagent would be located inside the `openhands-config` repository.

View File

@@ -3,6 +3,7 @@ title: Daytona Runtime
description: You can use [Daytona](https://www.daytona.io/) as a runtime provider.
---
## Step 1: Retrieve Your Daytona API Key
1. Visit the [Daytona Dashboard](https://app.daytona.io/dashboard/keys).
2. Click **"Create Key"**.

View File

@@ -3,6 +3,8 @@ title: Docker Runtime
description: This is the default Runtime that's used when you start OpenHands.
---
This is the default Runtime that's used when you start OpenHands.
## Image
The `SANDBOX_RUNTIME_CONTAINER_IMAGE` from nikolaik is a pre-built runtime image
that contains our Runtime server, as well as some basic utilities for Python and NodeJS.
@@ -126,7 +128,3 @@ docker network create openhands-network
docker run # ... \
--network openhands-network \
```
<Note>
**Docker Desktop Required**: Network isolation features, including custom networks and `host.docker.internal` routing, require Docker Desktop. Docker Engine alone does not support these features on localhost across custom networks. If you're using Docker Engine without Docker Desktop, network isolation may not work as expected.
</Note>

View File

@@ -3,8 +3,7 @@ title: E2B Runtime
description: E2B is an open-source secure cloud environment (sandbox) made for running AI-generated code and agents.
---
[E2B](https://e2b.dev) offers [Python](https://pypi.org/project/e2b/) and [JS/TS](https://www.npmjs.com/package/e2b)
SDK to spawn and control these sandboxes.
[E2B](https://e2b.dev) offers [Python](https://pypi.org/project/e2b/) and [JS/TS](https://www.npmjs.com/package/e2b) SDK to spawn and control these sandboxes.
## Getting started
@@ -19,13 +18,9 @@ SDK to spawn and control these sandboxes.
Full CLI API is [here](https://e2b.dev/docs/cli/installation).
## OpenHands sandbox
You can use the E2B CLI to create a custom sandbox with a Dockerfile. Read the full guide
[here](https://e2b.dev/docs/guide/custom-sandbox). The premade OpenHands sandbox for E2B is set up in the `containers`
directory. and it's called `openhands`.
You can use the E2B CLI to create a custom sandbox with a Dockerfile. Read the full guide [here](https://e2b.dev/docs/guide/custom-sandbox). The premade OpenHands sandbox for E2B is set up in the `containers` directory. and it's called `openhands`.
## Debugging
You can connect to a running E2B sandbox with E2B CLI in your terminal.
- List all running sandboxes (based on your API key)
@@ -39,6 +34,5 @@ You can connect to a running E2B sandbox with E2B CLI in your terminal.
```
## Links
- [E2B Docs](https://e2b.dev/docs)
- [E2B GitHub](https://github.com/e2b-dev/e2b)

View File

@@ -1,8 +1,6 @@
---
title: Local Runtime
description: The Local Runtime allows the OpenHands agent to execute actions directly on your local machine without
using Docker. This runtime is primarily intended for controlled environments like CI pipelines or testing scenarios
where Docker is not available.
description: The Local Runtime allows the OpenHands agent to execute actions directly on your local machine without using Docker. This runtime is primarily intended for controlled environments like CI pipelines or testing scenarios where Docker is not available.
---
<Warning>

View File

@@ -9,6 +9,8 @@ commands.
By default, OpenHands uses a [Docker-based runtime](/usage/runtimes/docker), running on your local computer.
This means you only have to pay for the LLM you're using, and your code is only ever sent to the LLM.
We also support other runtimes, which are typically managed by third-parties.
Additionally, we provide a [Local Runtime](/usage/runtimes/local) that runs directly on your machine without Docker,
which can be useful in controlled environments like CI pipelines.
@@ -19,18 +21,6 @@ OpenHands supports several different runtime environments:
- [Docker Runtime](/usage/runtimes/docker) - The default runtime that uses Docker containers for isolation (recommended for most users).
- [OpenHands Remote Runtime](/usage/runtimes/remote) - Cloud-based runtime for parallel execution (beta).
- [Local Runtime](/usage/runtimes/local) - Direct execution on your local machine without Docker.
### Third-Party Runtimes
The following third-party runtimes are available when you install the `third_party_runtimes` extra:
```bash
pip install openhands-ai[third_party_runtimes]
```
- [E2B Runtime](/usage/runtimes/e2b) - Open source runtime using E2B sandboxes.
- [Modal Runtime](/usage/runtimes/modal) - Serverless runtime using Modal infrastructure.
- [Runloop Runtime](/usage/runtimes/runloop) - Cloud runtime using Runloop infrastructure.
- [Daytona Runtime](/usage/runtimes/daytona) - Development environment runtime using Daytona.
**Note**: These third-party runtimes are supported by their respective developers, not by the OpenHands team. For issues specific to these runtimes, please refer to their documentation or contact their support teams.
- And more third-party runtimes:
- [Modal Runtime](/usage/runtimes/modal) - Runtime provided by our partners at Modal.
- [Daytona Runtime](/usage/runtimes/daytona) - Runtime provided by Daytona.

View File

@@ -1,11 +1,7 @@
---
title: Remote Runtime
description: This runtime is specifically designed for agent evaluation purposes only through the
[OpenHands evaluation harness](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation). It should not be
used to launch production OpenHands applications.
description: This runtime is specifically designed for agent evaluation purposes only through the [OpenHands evaluation harness](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation). It should not be used to launch production OpenHands applications.
---
OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details),
it allows you to launch runtimes in parallel in the cloud. Fill out
[this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to
apply if you want to try this out!
OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details), it allows you to launch runtimes
in parallel in the cloud. Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!

View File

@@ -1,7 +1,6 @@
---
title: Runloop Runtime
description: Runloop provides a fast, secure and scalable AI sandbox (Devbox). Check out the
[runloop docs](https://docs.runloop.ai/overview/what-is-runloop) for more detail.
description: Runloop provides a fast, secure and scalable AI sandbox (Devbox). Check out the [runloop docs](https://docs.runloop.ai/overview/what-is-runloop) for more detail.
---
## Access

View File

@@ -1,6 +1,6 @@
---
title: Search Engine Setup
description: Configure OpenHands to use Tavily as a search engine.
description: Configure OpenHands to use Tavily as a search engine
---
## Setting Up Search Engine in OpenHands
@@ -11,10 +11,10 @@ OpenHands can be configured to use [Tavily](https://tavily.com/) as a search eng
To use the search functionality in OpenHands, you'll need to obtain a Tavily API key:
1. Visit [Tavily's website](https://tavily.com/) and sign up for an account.
2. Navigate to the API section in your dashboard.
3. Generate a new API key.
4. Copy the API key (it should start with `tvly-`).
1. Visit [Tavily's website](https://tavily.com/) and sign up for an account
2. Navigate to the API section in your dashboard
3. Generate a new API key
4. Copy the API key (it should start with `tvly-`)
### Configuring Search in OpenHands
@@ -22,12 +22,13 @@ Once you have your Tavily API key, you can configure OpenHands to use it:
#### In the OpenHands UI
1. Open OpenHands and navigate to the Settings page.
2. Under the `LLM` tab, enter your Tavily API key (starting with `tvly-`) in the `Search API Key (Tavily)` field.
3. Click `Save` to apply the changes.
1. Open OpenHands and navigate to the Settings page by clicking the gear icon
2. In the LLM settings tab, locate the "Search API Key (Tavily)" field
3. Enter your Tavily API key (starting with `tvly-`)
4. Click "Save" to apply the changes
<Note>
The search API key field is optional. If you don't provide a key, the search functionality will not be available to the agent.
The search API key field is optional. If you don't provide a key, the search functionality will not be available to the agent.
</Note>
#### Using Configuration Files
@@ -44,23 +45,22 @@ search_api_key = "tvly-your-api-key-here"
When the search engine is configured:
- The agent can decide to search the web when it needs external information.
- Search queries are sent to Tavily's API via [Tavily's MCP server](https://github.com/tavily-ai/tavily-mcp) which
includes a variety of [tools](https://docs.tavily.com/documentation/api-reference/introduction) (search, extract, crawl, map).
- Results are returned and incorporated into the agent's context.
- The agent can use this information to provide more accurate and up-to-date responses.
1. The agent can decide to search the web when it needs external information
2. Search queries are sent to Tavily's API via [Tavily's MCP server](https://github.com/tavily-ai/tavily-mcp) which includes a variety of [tools](https://docs.tavily.com/documentation/api-reference/introduction) (search, extract, crawl, map).
3. Results are returned and incorporated into the agent's context
4. The agent can use this information to provide more accurate and up-to-date responses
### Limitations
- Search results depend on Tavily's coverage and freshness.
- Usage may be subject to Tavily's rate limits and pricing tiers.
- The agent will only search when it determines that external information is needed.
- Search results depend on Tavily's coverage and freshness
- Usage may be subject to Tavily's rate limits and pricing tiers
- The agent will only search when it determines that external information is needed
### Troubleshooting
If you encounter issues with the search functionality:
- Verify that your API key is correct and active.
- Check that your API key starts with `tvly-`.
- Ensure you have an active internet connection.
- Check Tavily's status page for any service disruptions.
- Verify that your API key is correct and active
- Check that your API key starts with `tvly-`
- Ensure you have an active internet connection
- Check Tavily's status page for any service disruptions

View File

@@ -133,66 +133,13 @@ This guide provides step-by-step instructions for running OpenHands on a Windows
> **Note**: If you're running the frontend in development mode (using `npm run dev`), use port 3001 instead: `http://localhost:3001`
## Installing and Running the CLI
To install and run the OpenHands CLI on Windows without WSL, follow these steps:
### 1. Install uv (Python Package Manager)
Open PowerShell as Administrator and run:
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
### 2. Install .NET SDK (Required)
The OpenHands CLI **requires** the .NET Core runtime for PowerShell integration. Without it, the CLI will fail to start with a `coreclr` error. Install the .NET SDK which includes the runtime:
```powershell
winget install Microsoft.DotNet.SDK.8
```
Alternatively, you can download and install the .NET SDK from the [official Microsoft website](https://dotnet.microsoft.com/download).
After installation, restart your PowerShell session to ensure the environment variables are updated.
### 3. Install and Run OpenHands
After installing the prerequisites, you can install and run OpenHands with:
```powershell
uvx --python 3.12 --from openhands-ai openhands
```
### Troubleshooting CLI Issues
#### CoreCLR Error
If you encounter an error like `Failed to load CoreCLR` or `pythonnet.load('coreclr')` when running OpenHands CLI, this indicates that the .NET Core runtime is missing or not properly configured. To fix this:
1. Install the .NET SDK as described in step 2 above
2. Verify that your system PATH includes the .NET SDK directories
3. Restart your PowerShell session completely after installing the .NET SDK
4. Make sure you're using PowerShell 7 (pwsh) rather than Windows PowerShell
To verify your .NET installation, run:
```powershell
dotnet --info
```
This should display information about your installed .NET SDKs and runtimes. If this command fails, the .NET SDK is not properly installed or not in your PATH.
If the issue persists after installing the .NET SDK, try installing the specific .NET Runtime version 6.0 or later from the [.NET download page](https://dotnet.microsoft.com/download).
## 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. The CLI implementation attempts to load the CoreCLR at startup with `pythonnet.load('coreclr')` and will fail with an error if .NET Core is not properly installed.
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.

View File

@@ -3,20 +3,13 @@ import copy
import functools
import os
import re
import shutil
import zipfile
import huggingface_hub
import pandas as pd
from datasets import load_dataset
from PIL import Image
from pydantic import SecretStr
from evaluation.benchmarks.gaia.scorer import question_scorer
from evaluation.benchmarks.gaia.utils import (
image_to_jpg_base64_url,
image_to_png_base64_url,
)
from evaluation.utils.shared import (
EvalMetadata,
EvalOutput,
@@ -104,44 +97,27 @@ def initialize_runtime(
if instance['file_name'] != '':
# if this question comes with a file, we need to save it to the workspace
assert metadata.data_split is not None
extension_name = instance['file_name'].split('.')[-1]
src_file = os.path.join(
DATASET_CACHE_DIR, '2023', metadata.data_split, instance['file_name']
)
assert os.path.exists(src_file)
if extension_name == 'zip':
temp_dir = os.path.join(
DATASET_CACHE_DIR, '2023', metadata.data_split, 'tmp_file'
)
os.makedirs(temp_dir, exist_ok=True)
with zipfile.ZipFile(src_file, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
for root, dirs, files in os.walk(temp_dir):
for file in files:
dest_file = '/workspace'
runtime.copy_to(os.path.join(root, file), dest_file)
shutil.rmtree(temp_dir)
elif extension_name not in ['jpg', 'png']:
dest_file = '/workspace'
runtime.copy_to(src_file, dest_file)
dest_file = os.path.join('/workspace', instance['file_name'])
runtime.copy_to(src_file, dest_file)
# rename to file.extension_name
action = CmdRunAction(
command=f'mv /workspace/{instance["file_name"]} /workspace/file.{extension_name}'
)
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
assert obs.exit_code == 0
# rename to file.extension_name
extension_name = instance['file_name'].split('.')[-1]
action = CmdRunAction(
command=f'mv /workspace/{instance["file_name"]} /workspace/file.{extension_name}'
)
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
assert obs.exit_code == 0
action = CmdRunAction(command='cd /workspace')
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
assert obs.exit_code == 0
action = CmdRunAction(
command='apt-get update && apt-get install -y ffmpeg && apt-get install -y ffprobe'
)
runtime.run_action(action)
logger.info(f'{"-" * 50} END Runtime Initialization Fn {"-" * 50}')
@@ -175,31 +151,8 @@ Here is the task:
task_question=instance['Question'],
)
logger.info(f'Instruction: {instruction}')
image_urls = []
if dest_file:
if extension_name not in ['jpg', 'png', 'zip']:
instruction += f'To solve this task you will have to use the attached file provided in the workspace at location: {dest_file}\n\n'
elif extension_name == 'zip':
filenames = []
src_file = os.path.join(
DATASET_CACHE_DIR, '2023', metadata.data_split, instance['file_name']
)
with zipfile.ZipFile(src_file, 'r') as zip_ref:
filenames = zip_ref.namelist()
filenames = [f'/workspace/{file}' for file in filenames]
filenames = ', '.join(filenames)
instruction += f'To solve this task you will have to use the attached files provided in the workspace at locations: {filenames}\n\n'
else: # Image files: jpg, png
src_file = os.path.join(
DATASET_CACHE_DIR, '2023', metadata.data_split, instance['file_name']
)
instruction += 'Image: To solve this task you will have to use the image shown below.\n\n'
image = Image.open(src_file)
if extension_name == 'jpg':
image_urls.append(image_to_jpg_base64_url(image))
else:
image_urls.append(image_to_png_base64_url(image))
instruction += f'\n\nThe mentioned file is provided in the workspace at: {dest_file.split("/")[-1]}'
instruction += """IMPORTANT: When seeking information from a website, REFRAIN from arbitrary URL navigation. You should utilize the designated search engine tool with precise keywords to obtain relevant URLs or use the specific website's search interface. DO NOT navigate directly to specific URLs as they may not exist.\n\nFor example: if you want to search for a research paper on Arxiv, either use the search engine tool with specific keywords or navigate to arxiv.org and then use its interface.\n"""
instruction += 'IMPORTANT: You should NEVER ask for Human Help.\n'
@@ -221,9 +174,7 @@ Here is the task:
state: State | None = asyncio.run(
run_controller(
config=config,
initial_user_action=MessageAction(
content=instruction, image_urls=image_urls
),
initial_user_action=MessageAction(content=instruction),
runtime=runtime,
fake_user_response_fn=AGENT_CLS_TO_FAKE_USER_RESPONSE_FN[
metadata.agent_class

View File

@@ -1,43 +0,0 @@
import base64
import io
import numpy as np
from PIL import Image
def image_to_png_base64_url(
image: np.ndarray | Image.Image, add_data_prefix: bool = True
):
"""Convert a numpy array to a base64 encoded png image url."""
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
if image.mode in ('RGBA', 'LA'):
image = image.convert('RGB')
buffered = io.BytesIO()
image.save(buffered, format='PNG')
image_base64 = base64.b64encode(buffered.getvalue()).decode()
return (
f'data:image/png;base64,{image_base64}'
if add_data_prefix
else f'{image_base64}'
)
def image_to_jpg_base64_url(
image: np.ndarray | Image.Image, add_data_prefix: bool = True
):
"""Convert a numpy array to a base64 encoded jpeg image url."""
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
if image.mode in ('RGBA', 'LA'):
image = image.convert('RGB')
buffered = io.BytesIO()
image.save(buffered, format='JPEG')
image_base64 = base64.b64encode(buffered.getvalue()).decode()
return (
f'data:image/jpeg;base64,{image_base64}'
if add_data_prefix
else f'{image_base64}'
)

View File

@@ -109,7 +109,7 @@ def codeact_user_response(
) -> str:
encaps_str = (
(
'Your final answer MUST be encapsulated within <solution> and </solution>.\n'
'Please encapsulate your final answer (answer ONLY) within <solution> and </solution>.\n'
'For example: The answer to the question is <solution> 42 </solution>.\n'
)
if encapsulate_solution
@@ -117,7 +117,7 @@ def codeact_user_response(
)
msg = (
'Please continue working on the task on whatever approach you think is suitable.\n'
'When you think you have solved the question, please use the finish tool and include your final answer in the message parameter of the finish tool.\n'
'If you think you have solved the task, please first send your answer to user through message and then finish the interaction.\n'
f'{encaps_str}'
'IMPORTANT: YOU SHOULD NEVER ASK FOR HUMAN HELP.\n'
)

View File

@@ -1,194 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { screen } from "@testing-library/react";
import { renderWithProviders } from "test-utils";
import { EventMessage } from "#/components/features/chat/event-message";
vi.mock("#/hooks/query/use-config", () => ({
useConfig: () => ({
data: { APP_MODE: "saas" },
}),
}));
vi.mock("#/hooks/query/use-feedback-exists", () => ({
useFeedbackExists: (eventId: number | undefined) => ({
data: { exists: false },
isLoading: false,
}),
}));
describe("EventMessage", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("should render LikertScale for finish action when it's the last message", () => {
const finishEvent = {
id: 123,
source: "agent" as const,
action: "finish" as const,
args: {
final_thought: "Task completed successfully",
task_completed: "success" as const,
outputs: {},
thought: "Task completed successfully",
},
message: "Task completed successfully",
timestamp: new Date().toISOString(),
};
renderWithProviders(
<EventMessage
event={finishEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={true}
isInLast10Actions={true}
/>
);
expect(screen.getByLabelText("Rate 1 stars")).toBeInTheDocument();
expect(screen.getByLabelText("Rate 5 stars")).toBeInTheDocument();
});
it("should render LikertScale for assistant message when it's the last message", () => {
const assistantMessageEvent = {
id: 456,
source: "agent" as const,
action: "message" as const,
args: {
thought: "I need more information to proceed.",
image_urls: null,
file_urls: [],
wait_for_response: true,
},
message: "I need more information to proceed.",
timestamp: new Date().toISOString(),
};
renderWithProviders(
<EventMessage
event={assistantMessageEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={true}
isInLast10Actions={true}
/>
);
expect(screen.getByLabelText("Rate 1 stars")).toBeInTheDocument();
expect(screen.getByLabelText("Rate 5 stars")).toBeInTheDocument();
});
it("should render LikertScale for error observation when it's the last message", () => {
const errorEvent = {
id: 789,
source: "user" as const,
observation: "error" as const,
content: "An error occurred",
extras: {
error_id: "test-error-123",
},
message: "An error occurred",
timestamp: new Date().toISOString(),
cause: 123,
};
renderWithProviders(
<EventMessage
event={errorEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={true}
isInLast10Actions={true}
/>
);
expect(screen.getByLabelText("Rate 1 stars")).toBeInTheDocument();
expect(screen.getByLabelText("Rate 5 stars")).toBeInTheDocument();
});
it("should NOT render LikertScale when not the last message", () => {
const finishEvent = {
id: 101,
source: "agent" as const,
action: "finish" as const,
args: {
final_thought: "Task completed successfully",
task_completed: "success" as const,
outputs: {},
thought: "Task completed successfully",
},
message: "Task completed successfully",
timestamp: new Date().toISOString(),
};
renderWithProviders(
<EventMessage
event={finishEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={false}
isInLast10Actions={false}
/>
);
expect(screen.queryByLabelText("Rate 1 stars")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Rate 5 stars")).not.toBeInTheDocument();
});
it("should render LikertScale for error observation when in last 10 actions but not last message", () => {
const errorEvent = {
id: 999,
source: "user" as const,
observation: "error" as const,
content: "An error occurred",
extras: {
error_id: "test-error-456",
},
message: "An error occurred",
timestamp: new Date().toISOString(),
cause: 123,
};
renderWithProviders(
<EventMessage
event={errorEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={false}
isInLast10Actions={true}
/>
);
expect(screen.getByLabelText("Rate 1 stars")).toBeInTheDocument();
expect(screen.getByLabelText("Rate 5 stars")).toBeInTheDocument();
});
it("should NOT render LikertScale for error observation when not in last 10 actions", () => {
const errorEvent = {
id: 888,
source: "user" as const,
observation: "error" as const,
content: "An error occurred",
extras: {
error_id: "test-error-789",
},
message: "An error occurred",
timestamp: new Date().toISOString(),
cause: 123,
};
renderWithProviders(
<EventMessage
event={errorEvent}
hasObservationPair={false}
isAwaitingUserConfirmation={false}
isLastMessage={false}
isInLast10Actions={false}
/>
);
expect(screen.queryByLabelText("Rate 1 stars")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Rate 5 stars")).not.toBeInTheDocument();
});
});

View File

@@ -128,7 +128,7 @@ describe("RepoConnector", () => {
renderRepoConnector();
await screen.findByText("HOME$ADD_GITHUB_REPOS");
await screen.findByText("Add GitHub repos");
});
it("should not render the 'add git(hub|lab) repos' links if oss mode", async () => {

View File

@@ -53,7 +53,7 @@ describe("TaskSuggestions", () => {
it("should render an empty message if there are no tasks", async () => {
getSuggestedTasksSpy.mockResolvedValue([]);
renderTaskSuggestions();
await screen.findByText("TASKS$NO_TASKS_AVAILABLE");
await screen.findByText(/No tasks available/i);
});
it("should render the task groups with the correct titles", async () => {

View File

@@ -1,95 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "test-utils";
import { LikertScale } from "#/components/features/feedback/likert-scale";
import { I18nKey } from "#/i18n/declaration";
// Mock the mutation hook
vi.mock("#/hooks/mutation/use-submit-conversation-feedback", () => ({
useSubmitConversationFeedback: () => ({
mutate: vi.fn(),
}),
}));
describe("LikertScale", () => {
const user = userEvent.setup();
afterEach(() => {
vi.clearAllMocks();
});
it("should render with proper localized text for rating prompt", () => {
renderWithProviders(<LikertScale eventId={1} />);
// Check that the rating prompt is displayed with proper translation key
expect(screen.getByText(I18nKey.FEEDBACK$RATE_AGENT_PERFORMANCE)).toBeInTheDocument();
});
it("should show localized feedback reasons when rating is 3 or below", async () => {
renderWithProviders(<LikertScale eventId={1} />);
// Click on a rating of 3 (which should show reasons)
const threeStarButton = screen.getAllByRole("button")[2]; // 3rd button (rating 3)
await user.click(threeStarButton);
// Wait for reasons to appear
await waitFor(() => {
expect(screen.getByText(I18nKey.FEEDBACK$SELECT_REASON)).toBeInTheDocument();
});
// Check that all feedback reasons are properly localized
expect(screen.getByText(I18nKey.FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION)).toBeInTheDocument();
expect(screen.getByText(I18nKey.FEEDBACK$REASON_FORGOT_CONTEXT)).toBeInTheDocument();
expect(screen.getByText(I18nKey.FEEDBACK$REASON_UNNECESSARY_CHANGES)).toBeInTheDocument();
expect(screen.getByText(I18nKey.FEEDBACK$REASON_OTHER)).toBeInTheDocument();
});
it("should show countdown message with proper localization", async () => {
renderWithProviders(<LikertScale eventId={1} />);
// Click on a rating of 2 (which should show reasons and countdown)
const twoStarButton = screen.getAllByRole("button")[1]; // 2nd button (rating 2)
await user.click(twoStarButton);
// Wait for countdown to appear
await waitFor(() => {
expect(screen.getByText(I18nKey.FEEDBACK$SELECT_REASON_COUNTDOWN)).toBeInTheDocument();
});
});
it("should show thank you message after submission", () => {
renderWithProviders(
<LikertScale eventId={1} initiallySubmitted={true} initialRating={4} />
);
// Check that thank you message is displayed with proper translation key
expect(screen.getByText(I18nKey.FEEDBACK$THANK_YOU_FOR_FEEDBACK)).toBeInTheDocument();
});
it("should render all 5 star rating buttons", () => {
renderWithProviders(<LikertScale eventId={1} />);
// Check that all 5 star buttons are rendered
const starButtons = screen.getAllByRole("button");
expect(starButtons).toHaveLength(5);
// Check that each button has proper aria-label
for (let i = 1; i <= 5; i++) {
expect(screen.getByLabelText(`Rate ${i} stars`)).toBeInTheDocument();
}
});
it("should not show reasons for ratings above 3", async () => {
renderWithProviders(<LikertScale eventId={1} />);
// Click on a rating of 5 (which should NOT show reasons)
const fiveStarButton = screen.getAllByRole("button")[4]; // 5th button (rating 5)
await user.click(fiveStarButton);
// Wait a bit to ensure reasons don't appear
await waitFor(() => {
expect(screen.queryByText(I18nKey.FEEDBACK$SELECT_REASON)).not.toBeInTheDocument();
});
});
});

View File

@@ -473,7 +473,7 @@ describe("Secret actions", () => {
// make POST request
expect(createSecretSpy).not.toHaveBeenCalled();
expect(screen.queryByText("SECRETS$SECRET_ALREADY_EXISTS")).toBeInTheDocument();
expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument();
await userEvent.clear(nameInput);
await userEvent.type(nameInput, "My_Custom_Secret");
@@ -557,7 +557,7 @@ describe("Secret actions", () => {
// make POST request
expect(createSecretSpy).not.toHaveBeenCalled();
expect(screen.queryByText("SECRETS$SECRET_ALREADY_EXISTS")).toBeInTheDocument();
expect(screen.queryByText(/secret already exists/i)).toBeInTheDocument();
expect(nameInput).toHaveValue(MOCK_GET_SECRETS_RESPONSE[0].name);
expect(valueInput).toHaveValue("my-custom-secret-value");

View File

@@ -1,54 +0,0 @@
import { describe, it, expect } from "vitest";
import { extractSettings } from "#/utils/settings-utils";
describe("Model name case preservation", () => {
it("should preserve the original case of model names in extractSettings", () => {
// Create FormData with proper casing
const formData = new FormData();
formData.set("llm-provider-input", "SambaNova");
formData.set("llm-model-input", "Meta-Llama-3.1-8B-Instruct");
formData.set("agent", "CodeActAgent");
formData.set("language", "en");
const settings = extractSettings(formData);
// Test that model names maintain their original casing
expect(settings.LLM_MODEL).toBe("SambaNova/Meta-Llama-3.1-8B-Instruct");
});
it("should preserve openai model case", () => {
const formData = new FormData();
formData.set("llm-provider-input", "openai");
formData.set("llm-model-input", "gpt-4o");
formData.set("agent", "CodeActAgent");
formData.set("language", "en");
const settings = extractSettings(formData);
expect(settings.LLM_MODEL).toBe("openai/gpt-4o");
});
it("should preserve anthropic model case", () => {
const formData = new FormData();
formData.set("llm-provider-input", "anthropic");
formData.set("llm-model-input", "claude-sonnet-4-20250514");
formData.set("agent", "CodeActAgent");
formData.set("language", "en");
const settings = extractSettings(formData);
expect(settings.LLM_MODEL).toBe("anthropic/claude-sonnet-4-20250514");
});
it("should not automatically lowercase model names", () => {
const formData = new FormData();
formData.set("llm-provider-input", "SambaNova");
formData.set("llm-model-input", "Meta-Llama-3.1-8B-Instruct");
formData.set("agent", "CodeActAgent");
formData.set("language", "en");
const settings = extractSettings(formData);
// Test that camelCase and PascalCase are preserved
expect(settings.LLM_MODEL).not.toBe("sambanova/meta-llama-3.1-8b-instruct");
expect(settings.LLM_MODEL).toBe("SambaNova/Meta-Llama-3.1-8B-Instruct");
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +1,39 @@
{
"name": "openhands-frontend",
"version": "0.47.0",
"version": "0.44.0",
"private": true,
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@heroui/react": "^2.8.0-beta.10",
"@heroui/react": "^2.8.0-beta.7",
"@microlink/react-json-view": "^1.26.2",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.6.3",
"@react-router/serve": "^7.6.3",
"@react-router/node": "^7.6.2",
"@react-router/serve": "^7.6.2",
"@react-types/shared": "^3.29.1",
"@reduxjs/toolkit": "^2.8.2",
"@stripe/react-stripe-js": "^3.7.0",
"@stripe/stripe-js": "^7.4.0",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.81.4",
"@vitejs/plugin-react": "^4.6.0",
"@stripe/stripe-js": "^7.3.1",
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/vite": "^4.1.10",
"@tanstack/react-query": "^5.80.7",
"@vitejs/plugin-react": "^4.5.2",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.4.0",
"axios": "^1.10.0",
"clsx": "^2.1.1",
"eslint-config-airbnb-typescript": "^18.0.0",
"framer-motion": "^12.19.2",
"framer-motion": "^12.18.1",
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.28",
"jose": "^6.0.11",
"lucide-react": "^0.525.0",
"lucide-react": "^0.517.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.255.1",
"posthog-js": "^1.255.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-highlight": "^0.15.0",
@@ -42,14 +42,14 @@
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-redux": "^9.2.0",
"react-router": "^7.6.3",
"react-router": "^7.6.2",
"react-syntax-highlighter": "^15.6.1",
"react-textarea-autosize": "^8.5.9",
"remark-gfm": "^4.0.1",
"sirv-cli": "^3.0.1",
"socket.io-client": "^4.8.1",
"tailwind-merge": "^3.3.1",
"vite": "^7.0.0",
"vite": "^6.3.5",
"web-vitals": "^5.0.3",
"ws": "^8.18.2"
},
@@ -80,19 +80,19 @@
]
},
"devDependencies": {
"@babel/parser": "^7.27.7",
"@babel/traverse": "^7.27.7",
"@babel/parser": "^7.27.1",
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.0",
"@mswjs/socket.io-binding": "^0.2.0",
"@playwright/test": "^1.53.1",
"@react-router/dev": "^7.6.3",
"@playwright/test": "^1.53.0",
"@react-router/dev": "^7.6.2",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/eslint-plugin-query": "^5.81.2",
"@tanstack/eslint-plugin-query": "^5.78.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.0.5",
"@types/node": "^24.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-highlight": "^0.12.8",
@@ -107,9 +107,9 @@
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-prettier": "^5.5.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-unused-imports": "^4.1.4",
@@ -117,7 +117,7 @@
"jsdom": "^26.1.0",
"lint-staged": "^16.1.2",
"msw": "^2.6.6",
"prettier": "^3.6.2",
"prettier": "^3.5.3",
"stripe": "^18.2.1",
"tailwindcss": "^4.1.8",
"typescript": "^5.8.3",

View File

@@ -56,6 +56,8 @@ const NON_TEXT_ATTRIBUTES = [
"type",
"href",
"src",
"alt",
"placeholder",
"rel",
"target",
"style",
@@ -63,6 +65,7 @@ const NON_TEXT_ATTRIBUTES = [
"onChange",
"onSubmit",
"data-testid",
"aria-label",
"aria-labelledby",
"aria-describedby",
"aria-hidden",
@@ -136,7 +139,6 @@ function isLikelyCode(str) {
}
function isCommonDevelopmentString(str) {
// Technical patterns that are definitely not UI strings
const technicalPatterns = [
// URLs and paths
@@ -189,7 +191,7 @@ function isCommonDevelopmentString(str) {
// CSS units and values
const cssUnitsPattern =
/\b\d+(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$|^(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/;
/(px|rem|em|vh|vw|vmin|vmax|ch|ex|fr|deg|rad|turn|grad|ms|s)$/;
const cssValuesPattern =
/(rgb|rgba|hsl|hsla|#[0-9a-fA-F]+|solid|absolute|relative|sticky|fixed|static|block|inline|flex|grid|none|auto|hidden|visible)/;
@@ -392,7 +394,6 @@ function isCommonDevelopmentString(str) {
}
function isLikelyUserFacingText(str) {
// Basic validation - skip very short strings or strings without letters
if (!str || str.length <= 2 || !/[a-zA-Z]/.test(str)) {
return false;
@@ -539,8 +540,8 @@ function isInTranslationContext(path) {
}
function scanFileForUnlocalizedStrings(filePath) {
// Skip suggestion content files as they contain special strings that are already properly localized
if (filePath.includes("utils/suggestions/") || filePath.includes("mocks/task-suggestions-handlers.ts")) {
// Skip all suggestion files as they contain special strings
if (filePath.includes("suggestions")) {
return [];
}

View File

@@ -34,7 +34,7 @@ export function ActionSuggestions({
const terms = {
pr,
prShort,
pushToBranch: `Please push the changes to a remote branch on ${getProviderName()}, but do NOT create a ${pr}. Check your current branch name first - if it's main, master, deploy, or another common default branch name, create a new branch with a descriptive name related to your changes. Otherwise, use the exact SAME branch name as the one you are currently on.`,
pushToBranch: `Please push the changes to a remote branch on ${getProviderName()}, but do NOT create a ${pr}. Please use the exact SAME branch name as the one you are currently on.`,
createPR: `Please push the changes to ${getProviderName()} and open a ${pr}. Please create a meaningful branch name that describes the changes. If a ${pr} template exists in the repository, please follow it when creating the ${prShort} description.`,
pushToPR: `Please push the latest changes to the existing ${pr}.`,
};

View File

@@ -35,7 +35,6 @@ interface EventMessageProps {
hasObservationPair: boolean;
isAwaitingUserConfirmation: boolean;
isLastMessage: boolean;
isInLast10Actions: boolean;
}
export function EventMessage({
@@ -43,52 +42,24 @@ export function EventMessage({
hasObservationPair,
isAwaitingUserConfirmation,
isLastMessage,
isInLast10Actions,
}: EventMessageProps) {
const shouldShowConfirmationButtons =
isLastMessage && event.source === "agent" && isAwaitingUserConfirmation;
const { data: config } = useConfig();
// Use our query hook to check if feedback exists and get rating/reason
const {
data: feedbackData = { exists: false },
isLoading: isCheckingFeedback,
} = useFeedbackExists(event.id);
const renderLikertScale = () => {
if (config?.APP_MODE !== "saas" || isCheckingFeedback) {
return null;
}
// For error observations, show if in last 10 actions
// For other events, show only if it's the last message
const shouldShow = isErrorObservation(event)
? isInLast10Actions
: isLastMessage;
if (!shouldShow) {
return null;
}
return (
<LikertScale
eventId={event.id}
initiallySubmitted={feedbackData.exists}
initialRating={feedbackData.rating}
initialReason={feedbackData.reason}
/>
);
};
} = useFeedbackExists(isFinishAction(event) ? event.id : undefined);
if (isErrorObservation(event)) {
return (
<>
<ErrorMessage
errorId={event.extras.error_id}
defaultMessage={event.message}
/>
{renderLikertScale()}
</>
<ErrorMessage
errorId={event.extras.error_id}
defaultMessage={event.message}
/>
);
}
@@ -99,11 +70,24 @@ export function EventMessage({
return null;
}
const showLikertScale =
config?.APP_MODE === "saas" &&
isFinishAction(event) &&
isLastMessage &&
!isCheckingFeedback;
if (isFinishAction(event)) {
return (
<>
<ChatMessage type="agent" message={getEventContent(event).details} />
{renderLikertScale()}
{showLikertScale && (
<LikertScale
eventId={event.id}
initiallySubmitted={feedbackData.exists}
initialRating={feedbackData.rating}
initialReason={feedbackData.reason}
/>
)}
</>
);
}
@@ -112,20 +96,15 @@ export function EventMessage({
const message = parseMessageFromEvent(event);
return (
<>
<ChatMessage type={event.source} message={message}>
{event.args.image_urls && event.args.image_urls.length > 0 && (
<ImageCarousel size="small" images={event.args.image_urls} />
)}
{event.args.file_urls && event.args.file_urls.length > 0 && (
<FileList files={event.args.file_urls} />
)}
{shouldShowConfirmationButtons && <ConfirmationButtons />}
</ChatMessage>
{isAssistantMessage(event) &&
event.action === "message" &&
renderLikertScale()}
</>
<ChatMessage type={event.source} message={message}>
{event.args.image_urls && event.args.image_urls.length > 0 && (
<ImageCarousel size="small" images={event.args.image_urls} />
)}
{event.args.file_urls && event.args.file_urls.length > 0 && (
<FileList files={event.args.file_urls} />
)}
{shouldShowConfirmationButtons && <ConfirmationButtons />}
</ChatMessage>
);
}

View File

@@ -39,7 +39,6 @@ export const Messages: React.FC<MessagesProps> = React.memo(
hasObservationPair={actionHasObservationPair(message)}
isAwaitingUserConfirmation={isAwaitingUserConfirmation}
isLastMessage={messages.length - 1 === index}
isInLast10Actions={messages.length - 1 - index < 10}
/>
))}

View File

@@ -2,15 +2,15 @@ export function TypingIndicator() {
return (
<div className="flex items-center space-x-1.5 bg-tertiary px-3 py-1.5 rounded-full">
<span
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[1px]"
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[-2px]"
style={{ animationDelay: "0ms" }}
/>
<span
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[1px]"
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[-2px]"
style={{ animationDelay: "75ms" }}
/>
<span
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[1px]"
className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-[bounce_0.5s_infinite] translate-y-[-2px]"
style={{ animationDelay: "150ms" }}
/>
</div>

View File

@@ -1,7 +1,6 @@
import React, { useState, useEffect, useContext } from "react";
import { useTranslation } from "react-i18next";
import { cn } from "#/utils/utils";
import { I18nKey } from "#/i18n/declaration";
import i18n from "#/i18n";
import { useSubmitConversationFeedback } from "#/hooks/mutation/use-submit-conversation-feedback";
import { ScrollContext } from "#/context/scroll-context";
@@ -15,14 +14,19 @@ interface LikertScaleProps {
initialReason?: string;
}
const FEEDBACK_REASONS = [
i18n.t("FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION"),
i18n.t("FEEDBACK$REASON_FORGOT_CONTEXT"),
i18n.t("FEEDBACK$REASON_UNNECESSARY_CHANGES"),
i18n.t("FEEDBACK$REASON_OTHER"),
];
export function LikertScale({
eventId,
initiallySubmitted = false,
initialRating,
initialReason,
}: LikertScaleProps) {
const { t } = useTranslation();
const [selectedRating, setSelectedRating] = useState<number | null>(
initialRating || null,
);
@@ -39,15 +43,6 @@ export function LikertScale({
// Get scroll context
const scrollContext = useContext(ScrollContext);
// Define feedback reasons using the translation hook
const FEEDBACK_REASONS = [
t(I18nKey.FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION),
t(I18nKey.FEEDBACK$REASON_FORGOT_CONTEXT),
t(I18nKey.FEEDBACK$REASON_UNNECESSARY_CHANGES),
t(I18nKey.FEEDBACK$REASON_SHOULD_ASK_FIRST),
t(I18nKey.FEEDBACK$REASON_OTHER),
];
// If scrollContext is undefined, we're not inside a ScrollProvider
const scrollToBottom = scrollContext?.scrollDomToBottom;
const autoScroll = scrollContext?.autoScroll;
@@ -193,8 +188,8 @@ export function LikertScale({
<div className="mt-3 flex flex-col gap-1">
<div className="text-sm text-gray-500 mb-1">
{isSubmitted
? t(I18nKey.FEEDBACK$THANK_YOU_FOR_FEEDBACK)
: t(I18nKey.FEEDBACK$RATE_AGENT_PERFORMANCE)}
? i18n.t("FEEDBACK$THANK_YOU_FOR_FEEDBACK")
: i18n.t("FEEDBACK$RATE_AGENT_PERFORMANCE")}
</div>
<div className="flex flex-col gap-1">
<span className="flex gap-2 items-center flex-wrap">
@@ -225,11 +220,11 @@ export function LikertScale({
{showReasons && !isSubmitted && (
<div className="mt-1 flex flex-col gap-1">
<div className="text-xs text-gray-500 mb-1">
{t(I18nKey.FEEDBACK$SELECT_REASON)}
{i18n.t("FEEDBACK$SELECT_REASON")}
</div>
{countdown > 0 && (
<div className="text-xs text-gray-400 mb-1 italic">
{t(I18nKey.FEEDBACK$SELECT_REASON_COUNTDOWN, {
{i18n.t("FEEDBACK$SELECT_REASON_COUNTDOWN", {
countdown,
})}
</div>

View File

@@ -1,9 +1,6 @@
import { useTranslation } from "react-i18next";
import { useConfig } from "#/hooks/query/use-config";
import { I18nKey } from "#/i18n/declaration";
export function RepoProviderLinks() {
const { t } = useTranslation();
const { data: config } = useConfig();
const githubHref = config
@@ -13,7 +10,7 @@ export function RepoProviderLinks() {
return (
<div className="flex flex-col text-sm underline underline-offset-2 text-content-2 gap-4 w-fit">
<a href={githubHref} target="_blank" rel="noopener noreferrer">
{t(I18nKey.HOME$ADD_GITHUB_REPOS)}
Add GitHub repos
</a>
</div>
);

View File

@@ -1,7 +1,5 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { SettingsDropdownInput } from "../../settings/settings-dropdown-input";
import { I18nKey } from "#/i18n/declaration";
export interface BranchDropdownProps {
items: { key: React.Key; label: string }[];
@@ -18,13 +16,11 @@ export function BranchDropdown({
isDisabled,
selectedKey,
}: BranchDropdownProps) {
const { t } = useTranslation();
return (
<SettingsDropdownInput
testId="branch-dropdown"
name="branch-dropdown"
placeholder={t(I18nKey.REPOSITORY$SELECT_BRANCH)}
placeholder="Select a branch"
items={items}
wrapperClassName="max-w-[500px]"
onSelectionChange={onSelectionChange}

View File

@@ -1,7 +1,5 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { SettingsDropdownInput } from "../../settings/settings-dropdown-input";
import { I18nKey } from "#/i18n/declaration";
export interface RepositoryDropdownProps {
items: { key: React.Key; label: string }[];
@@ -16,13 +14,11 @@ export function RepositoryDropdown({
onInputChange,
defaultFilter,
}: RepositoryDropdownProps) {
const { t } = useTranslation();
return (
<SettingsDropdownInput
testId="repo-dropdown"
name="repo-dropdown"
placeholder={t(I18nKey.REPOSITORY$SELECT_REPO)}
placeholder="Select a repo"
items={items}
wrapperClassName="max-w-[500px]"
onSelectionChange={onSelectionChange}

View File

@@ -1,16 +1,13 @@
import { useTranslation } from "react-i18next";
import { TaskGroup } from "./task-group";
import { useSuggestedTasks } from "#/hooks/query/use-suggested-tasks";
import { TaskSuggestionsSkeleton } from "./task-suggestions-skeleton";
import { cn } from "#/utils/utils";
import { I18nKey } from "#/i18n/declaration";
interface TaskSuggestionsProps {
filterFor?: string | null;
}
export function TaskSuggestions({ filterFor }: TaskSuggestionsProps) {
const { t } = useTranslation();
const { data: tasks, isLoading } = useSuggestedTasks();
const suggestedTasks = filterFor
? tasks?.filter((task) => task.title === filterFor)
@@ -23,13 +20,11 @@ export function TaskSuggestions({ filterFor }: TaskSuggestionsProps) {
data-testid="task-suggestions"
className={cn("flex flex-col w-full", !hasSuggestedTasks && "gap-6")}
>
<h2 className="heading">{t(I18nKey.TASKS$SUGGESTED_TASKS)}</h2>
<h2 className="heading">Suggested Tasks</h2>
<div className="flex flex-col gap-6">
{isLoading && <TaskSuggestionsSkeleton />}
{!hasSuggestedTasks && !isLoading && (
<p>{t(I18nKey.TASKS$NO_TASKS_AVAILABLE)}</p>
)}
{!hasSuggestedTasks && !isLoading && <p>No tasks available</p>}
{suggestedTasks?.map((taskGroup, index) => (
<TaskGroup
key={index}

View File

@@ -64,7 +64,7 @@ export function PaymentForm() {
onChange={handleTopUpInputChange}
type="number"
label={t(I18nKey.PAYMENT$ADD_FUNDS)}
placeholder={t(I18nKey.PAYMENT$SPECIFY_AMOUNT_USD)}
placeholder="Specify an amount in USD to add - min $10"
className="w-[680px]"
min={10}
max={25000}

View File

@@ -1,9 +1,7 @@
import { Trans, useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
export function BitbucketTokenHelpAnchor() {
const { t } = useTranslation();
return (
<p data-testid="bitbucket-token-help-anchor" className="text-xs">
<Trans
@@ -11,7 +9,7 @@ export function BitbucketTokenHelpAnchor() {
components={[
<a
key="bitbucket-token-help-anchor-link"
aria-label={t(I18nKey.GIT$BITBUCKET_TOKEN_HELP_LINK)}
aria-label="Bitbucket token help link"
href="https://bitbucket.org/account/settings/app-passwords/new?scopes=repository:write,pullrequest:write,issue:write"
target="_blank"
className="underline underline-offset-2"
@@ -19,7 +17,7 @@ export function BitbucketTokenHelpAnchor() {
/>,
<a
key="bitbucket-token-help-anchor-link-2"
aria-label={t(I18nKey.GIT$BITBUCKET_TOKEN_SEE_MORE_LINK)}
aria-label="Bitbucket token see more link"
href="https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/"
target="_blank"
className="underline underline-offset-2"

View File

@@ -1,9 +1,7 @@
import { Trans, useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
export function GitHubTokenHelpAnchor() {
const { t } = useTranslation();
return (
<p data-testid="github-token-help-anchor" className="text-xs">
<Trans
@@ -11,7 +9,7 @@ export function GitHubTokenHelpAnchor() {
components={[
<a
key="github-token-help-anchor-link"
aria-label={t(I18nKey.GIT$GITHUB_TOKEN_HELP_LINK)}
aria-label="GitHub token help link"
href="https://github.com/settings/tokens/new?description=openhands-app&scopes=repo,user,workflow"
target="_blank"
className="underline underline-offset-2"
@@ -19,7 +17,7 @@ export function GitHubTokenHelpAnchor() {
/>,
<a
key="github-token-help-anchor-link-2"
aria-label={t(I18nKey.GIT$GITHUB_TOKEN_SEE_MORE_LINK)}
aria-label="GitHub token see more link"
href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token"
target="_blank"
className="underline underline-offset-2"

View File

@@ -1,9 +1,7 @@
import { Trans, useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
export function GitLabTokenHelpAnchor() {
const { t } = useTranslation();
return (
<p data-testid="gitlab-token-help-anchor" className="text-xs">
<Trans
@@ -11,7 +9,7 @@ export function GitLabTokenHelpAnchor() {
components={[
<a
key="gitlab-token-help-anchor-link"
aria-label={t(I18nKey.GIT$GITLAB_TOKEN_HELP_LINK)}
aria-label="Gitlab token help link"
href="https://gitlab.com/-/user_settings/personal_access_tokens?name=openhands-app&scopes=api,read_user,read_repository,write_repository"
target="_blank"
className="underline underline-offset-2"
@@ -19,7 +17,7 @@ export function GitLabTokenHelpAnchor() {
/>,
<a
key="gitlab-token-help-anchor-link-2"
aria-label={t(I18nKey.GIT$GITLAB_TOKEN_SEE_MORE_LINK)}
aria-label="GitLab token see more link"
href="https://docs.gitlab.com/user/profile/personal_access_tokens/"
target="_blank"
className="underline underline-offset-2"

View File

@@ -111,7 +111,7 @@ export function SecretForm({
(secret) => secret.name === name && secret.name !== selectedSecret,
);
if (isNameAlreadyUsed) {
setError(t("SECRETS$SECRET_ALREADY_EXISTS"));
setError("Secret already exists");
return;
}
@@ -144,7 +144,7 @@ export function SecretForm({
className="w-full max-w-[350px]"
required
defaultValue={mode === "edit" && selectedSecret ? selectedSecret : ""}
placeholder={t("SECRETS$API_KEY_EXAMPLE")}
placeholder="e.g. OpenAI_API_Key"
pattern="^\S*$"
/>
{error && <p className="text-red-500 text-sm">{error}</p>}

View File

@@ -7,7 +7,6 @@ import { ModalBody } from "#/components/shared/modals/modal-body";
import { BrandButton } from "../settings/brand-button";
import GitHubLogo from "#/assets/branding/github-logo.svg?react";
import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
import BitbucketLogo from "#/assets/branding/bitbucket-logo.svg?react";
import { useAuthUrl } from "#/hooks/use-auth-url";
import { GetConfigResponse } from "#/api/open-hands.types";
@@ -24,11 +23,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
identityProvider: "gitlab",
});
const bitbucketAuthUrl = useAuthUrl({
appMode: appMode || null,
identityProvider: "bitbucket",
});
const handleGitHubAuth = () => {
if (githubAuthUrl) {
// Always start the OIDC flow, let the backend handle TOS check
@@ -43,13 +37,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
}
};
const handleBitbucketAuth = () => {
if (bitbucketAuthUrl) {
// Always start the OIDC flow, let the backend handle TOS check
window.location.href = bitbucketAuthUrl;
}
};
return (
<ModalBackdrop>
<ModalBody className="border border-tertiary">
@@ -80,16 +67,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
>
{t(I18nKey.GITLAB$CONNECT_TO_GITLAB)}
</BrandButton>
<BrandButton
type="button"
variant="primary"
onClick={handleBitbucketAuth}
className="w-full"
startContent={<BitbucketLogo width={20} height={20} />}
>
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
</BrandButton>
</div>
</ModalBody>
</ModalBackdrop>

View File

@@ -28,7 +28,7 @@ export function CustomModelInput({
id="custom-model"
name="custom-model"
defaultValue={defaultValue}
aria-label={t(I18nKey.MODEL$CUSTOM_MODEL)}
aria-label="Custom Model"
classNames={{
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
}}

View File

@@ -198,46 +198,46 @@ function SecurityInvariant() {
{t(I18nKey.INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL)}
</p>
<Select
placeholder={t(I18nKey.SECURITY$SELECT_RISK_SEVERITY)}
placeholder="Select risk severity"
value={selectedRisk}
onChange={(e) =>
setSelectedRisk(Number(e.target.value) as ActionSecurityRisk)
}
className={getRiskColor(selectedRisk)}
selectedKeys={new Set([selectedRisk.toString()])}
aria-label={t(I18nKey.SECURITY$SELECT_RISK_SEVERITY)}
aria-label="Select risk severity"
>
<SelectItem
key={ActionSecurityRisk.UNKNOWN}
aria-label={t(I18nKey.SECURITY$UNKNOWN_RISK)}
aria-label="Unknown Risk"
className={getRiskColor(ActionSecurityRisk.UNKNOWN)}
>
{getRiskText(ActionSecurityRisk.UNKNOWN)}
</SelectItem>
<SelectItem
key={ActionSecurityRisk.LOW}
aria-label={t(I18nKey.SECURITY$LOW_RISK)}
aria-label="Low Risk"
className={getRiskColor(ActionSecurityRisk.LOW)}
>
{getRiskText(ActionSecurityRisk.LOW)}
</SelectItem>
<SelectItem
key={ActionSecurityRisk.MEDIUM}
aria-label={t(I18nKey.SECURITY$MEDIUM_RISK)}
aria-label="Medium Risk"
className={getRiskColor(ActionSecurityRisk.MEDIUM)}
>
{getRiskText(ActionSecurityRisk.MEDIUM)}
</SelectItem>
<SelectItem
key={ActionSecurityRisk.HIGH}
aria-label={t(I18nKey.SECURITY$HIGH_RISK)}
aria-label="High Risk"
className={getRiskColor(ActionSecurityRisk.HIGH)}
>
{getRiskText(ActionSecurityRisk.HIGH)}
</SelectItem>
<SelectItem
key={ActionSecurityRisk.HIGH + 1}
aria-label={t(I18nKey.SECURITY$DONT_ASK_CONFIRMATION)}
aria-label="Don't ask for confirmation"
>
{t(I18nKey.INVARIANT$DONT_ASK_FOR_CONFIRMATION_LABEL)}
</SelectItem>

View File

@@ -34,7 +34,10 @@ export const useAuthCallback = () => {
const loginMethod = searchParams.get("login_method");
// Set the login method if it's valid
if (Object.values(LoginMethod).includes(loginMethod as LoginMethod)) {
if (
loginMethod === LoginMethod.GITHUB ||
loginMethod === LoginMethod.GITLAB
) {
setLoginMethod(loginMethod as LoginMethod);
// Clean up the URL by removing the login_method parameter

View File

@@ -602,30 +602,7 @@ export enum I18nKey {
FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION = "FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION",
FEEDBACK$REASON_FORGOT_CONTEXT = "FEEDBACK$REASON_FORGOT_CONTEXT",
FEEDBACK$REASON_UNNECESSARY_CHANGES = "FEEDBACK$REASON_UNNECESSARY_CHANGES",
FEEDBACK$REASON_SHOULD_ASK_FIRST = "FEEDBACK$REASON_SHOULD_ASK_FIRST",
FEEDBACK$REASON_OTHER = "FEEDBACK$REASON_OTHER",
FEEDBACK$THANK_YOU_FOR_FEEDBACK = "FEEDBACK$THANK_YOU_FOR_FEEDBACK",
FEEDBACK$FAILED_TO_SUBMIT = "FEEDBACK$FAILED_TO_SUBMIT",
HOME$ADD_GITHUB_REPOS = "HOME$ADD_GITHUB_REPOS",
REPOSITORY$SELECT_BRANCH = "REPOSITORY$SELECT_BRANCH",
REPOSITORY$SELECT_REPO = "REPOSITORY$SELECT_REPO",
TASKS$SUGGESTED_TASKS = "TASKS$SUGGESTED_TASKS",
TASKS$NO_TASKS_AVAILABLE = "TASKS$NO_TASKS_AVAILABLE",
PAYMENT$SPECIFY_AMOUNT_USD = "PAYMENT$SPECIFY_AMOUNT_USD",
GIT$BITBUCKET_TOKEN_HELP_LINK = "GIT$BITBUCKET_TOKEN_HELP_LINK",
GIT$BITBUCKET_TOKEN_SEE_MORE_LINK = "GIT$BITBUCKET_TOKEN_SEE_MORE_LINK",
GIT$GITHUB_TOKEN_HELP_LINK = "GIT$GITHUB_TOKEN_HELP_LINK",
GIT$GITHUB_TOKEN_SEE_MORE_LINK = "GIT$GITHUB_TOKEN_SEE_MORE_LINK",
GIT$GITLAB_TOKEN_HELP_LINK = "GIT$GITLAB_TOKEN_HELP_LINK",
GIT$GITLAB_TOKEN_SEE_MORE_LINK = "GIT$GITLAB_TOKEN_SEE_MORE_LINK",
SECRETS$SECRET_ALREADY_EXISTS = "SECRETS$SECRET_ALREADY_EXISTS",
SECRETS$API_KEY_EXAMPLE = "SECRETS$API_KEY_EXAMPLE",
MODEL$CUSTOM_MODEL = "MODEL$CUSTOM_MODEL",
SECURITY$SELECT_RISK_SEVERITY = "SECURITY$SELECT_RISK_SEVERITY",
SECURITY$DONT_ASK_CONFIRMATION = "SECURITY$DONT_ASK_CONFIRMATION",
SETTINGS$MAXIMUM_BUDGET_USD = "SETTINGS$MAXIMUM_BUDGET_USD",
GIT$DISCONNECT_TOKENS = "GIT$DISCONNECT_TOKENS",
API$TAVILY_KEY_EXAMPLE = "API$TAVILY_KEY_EXAMPLE",
API$TVLY_KEY_EXAMPLE = "API$TVLY_KEY_EXAMPLE",
SECRETS$CONNECT_GIT_PROVIDER = "SECRETS$CONNECT_GIT_PROVIDER",
}

View File

@@ -833,10 +833,10 @@
},
"HOME$LETS_START_BUILDING": {
"en": "Let's Start Building!",
"ja": "開発を始めましょう!",
"zh-CN": "让我们开始开发",
"zh-TW": "讓我們開始開發",
"ko-KR": "개발을 시작합시다!",
"ja": "構築を始めましょう!",
"zh-CN": "让我们开始构建",
"zh-TW": "讓我們開始構建",
"ko-KR": "구축을 시작합시다!",
"no": "La oss begynne å bygge!",
"it": "Iniziamo a costruire!",
"pt": "Vamos começar a construir!",
@@ -849,7 +849,7 @@
},
"HOME$OPENHANDS_DESCRIPTION": {
"en": "OpenHands makes it easy to build and maintain software using AI-driven development.",
"ja": "OpenHandsはAI駆動の開発を使用してソフトウェアの開発と維持を容易にします。",
"ja": "OpenHandsはAI駆動の開発を使用してソフトウェアの構築と維持を容易にします。",
"zh-CN": "OpenHands使用AI驱动的开发方式轻松构建和维护软件。",
"zh-TW": "OpenHands使用AI驅動的開發方式輕鬆構建和維護軟件。",
"ko-KR": "OpenHands는 AI 기반 개발을 사용하여 소프트웨어를 쉽게 구축하고 유지할 수 있게 합니다.",
@@ -9631,22 +9631,6 @@
"de": "Der Agent hat unnötige Änderungen vorgenommen",
"uk": "Агент зробив непотрібні зміни"
},
"FEEDBACK$REASON_SHOULD_ASK_FIRST": {
"en": "The agent should've asked me first before doing it!",
"ja": "エージェントは実行する前に私に確認すべきでした!",
"zh-CN": "代理应该先问我再做这件事!",
"zh-TW": "代理應該先問我再做這件事!",
"ko-KR": "에이전트는 먼저 나에게 물어봤어야 했습니다!",
"no": "Agenten burde ha spurt meg først før den gjorde det!",
"it": "L'agente avrebbe dovuto chiedermelo prima di farlo!",
"pt": "O agente deveria ter me perguntado primeiro antes de fazer isso!",
"es": "¡El agente debería haberme preguntado primero antes de hacerlo!",
"ar": "كان يجب على الوكيل أن يسألني أولاً قبل القيام بذلك!",
"fr": "L'agent aurait dû me demander d'abord avant de le faire !",
"tr": "Ajan bunu yapmadan önce bana sormalıydı!",
"de": "Der Agent hätte mich vorher fragen sollen!",
"uk": "Агент повинен був спочатку запитати мене, перш ніж це робити!"
},
"FEEDBACK$REASON_OTHER": {
"en": "Other",
"ja": "その他",
@@ -9694,357 +9678,5 @@
"tr": "Geri bildirim gönderilemedi",
"de": "Feedback konnte nicht gesendet werden",
"uk": "Не вдалося надіслати відгук"
},
"HOME$ADD_GITHUB_REPOS": {
"en": "Add GitHub repos",
"ja": "GitHubリポジトリを追加",
"zh-CN": "添加GitHub仓库",
"zh-TW": "新增GitHub儲存庫",
"ko-KR": "GitHub 저장소 추가",
"no": "Legg til GitHub-repositorier",
"it": "Aggiungi repository GitHub",
"pt": "Adicionar repositórios GitHub",
"es": "Agregar repositorios de GitHub",
"ar": "إضافة مستودعات GitHub",
"fr": "Ajouter des dépôts GitHub",
"tr": "GitHub depoları ekle",
"de": "GitHub-Repositories hinzufügen",
"uk": "Додати репозиторії GitHub"
},
"REPOSITORY$SELECT_BRANCH": {
"en": "Select a branch",
"ja": "ブランチを選択",
"zh-CN": "选择分支",
"zh-TW": "選擇分支",
"ko-KR": "브랜치 선택",
"no": "Velg en gren",
"it": "Seleziona un ramo",
"pt": "Selecionar um branch",
"es": "Seleccionar una rama",
"ar": "اختر فرع",
"fr": "Sélectionner une branche",
"tr": "Bir dal seç",
"de": "Einen Branch auswählen",
"uk": "Вибрати гілку"
},
"REPOSITORY$SELECT_REPO": {
"en": "Select a repo",
"ja": "リポジトリを選択",
"zh-CN": "选择仓库",
"zh-TW": "選擇儲存庫",
"ko-KR": "저장소 선택",
"no": "Velg et repositorium",
"it": "Seleziona un repository",
"pt": "Selecionar um repositório",
"es": "Seleccionar un repositorio",
"ar": "اختر مستودع",
"fr": "Sélectionner un dépôt",
"tr": "Bir depo seç",
"de": "Ein Repository auswählen",
"uk": "Вибрати репозиторій"
},
"TASKS$SUGGESTED_TASKS": {
"en": "Suggested Tasks",
"ja": "推奨タスク",
"zh-CN": "建议任务",
"zh-TW": "建議任務",
"ko-KR": "추천 작업",
"no": "Foreslåtte oppgaver",
"it": "Attività suggerite",
"pt": "Tarefas sugeridas",
"es": "Tareas sugeridas",
"ar": "المهام المقترحة",
"fr": "Tâches suggérées",
"tr": "Önerilen görevler",
"de": "Vorgeschlagene Aufgaben",
"uk": "Запропоновані завдання"
},
"TASKS$NO_TASKS_AVAILABLE": {
"en": "No tasks available",
"ja": "利用可能なタスクがありません",
"zh-CN": "没有可用任务",
"zh-TW": "沒有可用任務",
"ko-KR": "사용 가능한 작업이 없습니다",
"no": "Ingen oppgaver tilgjengelig",
"it": "Nessuna attività disponibile",
"pt": "Nenhuma tarefa disponível",
"es": "No hay tareas disponibles",
"ar": "لا توجد مهام متاحة",
"fr": "Aucune tâche disponible",
"tr": "Mevcut görev yok",
"de": "Keine Aufgaben verfügbar",
"uk": "Немає доступних завдань"
},
"PAYMENT$SPECIFY_AMOUNT_USD": {
"en": "Specify an amount in USD to add - min $10",
"ja": "追加するUSD金額を指定してください - 最小$10",
"zh-CN": "指定要添加的美元金额 - 最少$10",
"zh-TW": "指定要新增的美元金額 - 最少$10",
"ko-KR": "추가할 USD 금액을 지정하세요 - 최소 $10",
"no": "Spesifiser et beløp i USD å legge til - min $10",
"it": "Specifica un importo in USD da aggiungere - min $10",
"pt": "Especifique um valor em USD para adicionar - mín $10",
"es": "Especifique una cantidad en USD para agregar - mín $10",
"ar": "حدد مبلغًا بالدولار الأمريكي لإضافته - الحد الأدنى 10 دولارات",
"fr": "Spécifiez un montant en USD à ajouter - min 10 $",
"tr": "Eklenecek USD tutarını belirtin - min $10",
"de": "Geben Sie einen USD-Betrag zum Hinzufügen an - min $10",
"uk": "Вкажіть суму в доларах США для додавання - мін $10"
},
"GIT$BITBUCKET_TOKEN_HELP_LINK": {
"en": "Bitbucket token help link",
"ja": "Bitbucketトークンヘルプリンク",
"zh-CN": "Bitbucket令牌帮助链接",
"zh-TW": "Bitbucket令牌幫助連結",
"ko-KR": "Bitbucket 토큰 도움말 링크",
"no": "Bitbucket token hjelpelenke",
"it": "Link di aiuto per il token Bitbucket",
"pt": "Link de ajuda do token Bitbucket",
"es": "Enlace de ayuda del token de Bitbucket",
"ar": "رابط مساعدة رمز Bitbucket",
"fr": "Lien d'aide pour le jeton Bitbucket",
"tr": "Bitbucket token yardım bağlantısı",
"de": "Bitbucket-Token-Hilfe-Link",
"uk": "Посилання на довідку токена Bitbucket"
},
"GIT$BITBUCKET_TOKEN_SEE_MORE_LINK": {
"en": "Bitbucket token see more link",
"ja": "Bitbucketトークン詳細リンク",
"zh-CN": "Bitbucket令牌查看更多链接",
"zh-TW": "Bitbucket令牌查看更多連結",
"ko-KR": "Bitbucket 토큰 더 보기 링크",
"no": "Bitbucket token se mer lenke",
"it": "Link per vedere di più sul token Bitbucket",
"pt": "Link para ver mais sobre o token Bitbucket",
"es": "Enlace para ver más del token de Bitbucket",
"ar": "رابط لرؤية المزيد حول رمز Bitbucket",
"fr": "Lien pour en voir plus sur le jeton Bitbucket",
"tr": "Bitbucket token daha fazla görme bağlantısı",
"de": "Bitbucket-Token mehr sehen Link",
"uk": "Посилання для перегляду більше про токен Bitbucket"
},
"GIT$GITHUB_TOKEN_HELP_LINK": {
"en": "GitHub token help link",
"ja": "GitHubトークンヘルプリンク",
"zh-CN": "GitHub令牌帮助链接",
"zh-TW": "GitHub令牌幫助連結",
"ko-KR": "GitHub 토큰 도움말 링크",
"no": "GitHub token hjelpelenke",
"it": "Link di aiuto per il token GitHub",
"pt": "Link de ajuda do token GitHub",
"es": "Enlace de ayuda del token de GitHub",
"ar": "رابط مساعدة رمز GitHub",
"fr": "Lien d'aide pour le jeton GitHub",
"tr": "GitHub token yardım bağlantısı",
"de": "GitHub-Token-Hilfe-Link",
"uk": "Посилання на довідку токена GitHub"
},
"GIT$GITHUB_TOKEN_SEE_MORE_LINK": {
"en": "GitHub token see more link",
"ja": "GitHubトークン詳細リンク",
"zh-CN": "GitHub令牌查看更多链接",
"zh-TW": "GitHub令牌查看更多連結",
"ko-KR": "GitHub 토큰 더 보기 링크",
"no": "GitHub token se mer lenke",
"it": "Link per vedere di più sul token GitHub",
"pt": "Link para ver mais sobre o token GitHub",
"es": "Enlace para ver más del token de GitHub",
"ar": "رابط لرؤية المزيد حول رمز GitHub",
"fr": "Lien pour en voir plus sur le jeton GitHub",
"tr": "GitHub token daha fazla görme bağlantısı",
"de": "GitHub-Token mehr sehen Link",
"uk": "Посилання для перегляду більше про токен GitHub"
},
"GIT$GITLAB_TOKEN_HELP_LINK": {
"en": "Gitlab token help link",
"ja": "GitLabトークンヘルプリンク",
"zh-CN": "GitLab令牌帮助链接",
"zh-TW": "GitLab令牌幫助連結",
"ko-KR": "GitLab 토큰 도움말 링크",
"no": "GitLab token hjelpelenke",
"it": "Link di aiuto per il token GitLab",
"pt": "Link de ajuda do token GitLab",
"es": "Enlace de ayuda del token de GitLab",
"ar": "رابط مساعدة رمز GitLab",
"fr": "Lien d'aide pour le jeton GitLab",
"tr": "GitLab token yardım bağlantısı",
"de": "GitLab-Token-Hilfe-Link",
"uk": "Посилання на довідку токена GitLab"
},
"GIT$GITLAB_TOKEN_SEE_MORE_LINK": {
"en": "GitLab token see more link",
"ja": "GitLabトークン詳細リンク",
"zh-CN": "GitLab令牌查看更多链接",
"zh-TW": "GitLab令牌查看更多連結",
"ko-KR": "GitLab 토큰 더 보기 링크",
"no": "GitLab token se mer lenke",
"it": "Link per vedere di più sul token GitLab",
"pt": "Link para ver mais sobre o token GitLab",
"es": "Enlace para ver más del token de GitLab",
"ar": "رابط لرؤية المزيد حول رمز GitLab",
"fr": "Lien pour en voir plus sur le jeton GitLab",
"tr": "GitLab token daha fazla görme bağlantısı",
"de": "GitLab-Token mehr sehen Link",
"uk": "Посилання для перегляду більше про токен GitLab"
},
"SECRETS$SECRET_ALREADY_EXISTS": {
"en": "Secret already exists",
"ja": "シークレットは既に存在します",
"zh-CN": "密钥已存在",
"zh-TW": "密鑰已存在",
"ko-KR": "시크릿이 이미 존재합니다",
"no": "Hemmelighet eksisterer allerede",
"it": "Il segreto esiste già",
"pt": "Segredo já existe",
"es": "El secreto ya existe",
"ar": "السر موجود بالفعل",
"fr": "Le secret existe déjà",
"tr": "Gizli anahtar zaten mevcut",
"de": "Geheimnis existiert bereits",
"uk": "Секрет вже існує"
},
"SECRETS$API_KEY_EXAMPLE": {
"en": "e.g. OpenAI_API_Key",
"ja": "例: OpenAI_API_Key",
"zh-CN": "例如 OpenAI_API_Key",
"zh-TW": "例如 OpenAI_API_Key",
"ko-KR": "예: OpenAI_API_Key",
"no": "f.eks. OpenAI_API_Key",
"it": "es. OpenAI_API_Key",
"pt": "ex. OpenAI_API_Key",
"es": "ej. OpenAI_API_Key",
"ar": "مثل OpenAI_API_Key",
"fr": "ex. OpenAI_API_Key",
"tr": "örn. OpenAI_API_Key",
"de": "z.B. OpenAI_API_Key",
"uk": "наприклад OpenAI_API_Key"
},
"MODEL$CUSTOM_MODEL": {
"en": "Custom Model",
"ja": "カスタムモデル",
"zh-CN": "自定义模型",
"zh-TW": "自訂模型",
"ko-KR": "사용자 정의 모델",
"no": "Tilpasset modell",
"it": "Modello personalizzato",
"pt": "Modelo personalizado",
"es": "Modelo personalizado",
"ar": "نموذج مخصص",
"fr": "Modèle personnalisé",
"tr": "Özel model",
"de": "Benutzerdefiniertes Modell",
"uk": "Користувацька модель"
},
"SECURITY$SELECT_RISK_SEVERITY": {
"en": "Select risk severity",
"ja": "リスクの重要度を選択",
"zh-CN": "选择风险严重程度",
"zh-TW": "選擇風險嚴重程度",
"ko-KR": "위험 심각도 선택",
"no": "Velg risikoalvorlighet",
"it": "Seleziona gravità del rischio",
"pt": "Selecionar gravidade do risco",
"es": "Seleccionar gravedad del riesgo",
"ar": "اختر شدة المخاطر",
"fr": "Sélectionner la gravité du risque",
"tr": "Risk ciddiyetini seç",
"de": "Risikoschweregrad auswählen",
"uk": "Вибрати ступінь ризику"
},
"SECURITY$DONT_ASK_CONFIRMATION": {
"en": "Don't ask for confirmation",
"ja": "確認を求めない",
"zh-CN": "不要求确认",
"zh-TW": "不要求確認",
"ko-KR": "확인을 요청하지 않음",
"no": "Ikke spør om bekreftelse",
"it": "Non chiedere conferma",
"pt": "Não pedir confirmação",
"es": "No pedir confirmación",
"ar": "لا تطلب التأكيد",
"fr": "Ne pas demander de confirmation",
"tr": "Onay isteme",
"de": "Nicht nach Bestätigung fragen",
"uk": "Не запитувати підтвердження"
},
"SETTINGS$MAXIMUM_BUDGET_USD": {
"en": "Maximum budget per conversation in USD",
"ja": "会話あたりの最大予算USD",
"zh-CN": "每次对话的最大预算(美元)",
"zh-TW": "每次對話的最大預算(美元)",
"ko-KR": "대화당 최대 예산(USD)",
"no": "Maksimalt budsjett per samtale i USD",
"it": "Budget massimo per conversazione in USD",
"pt": "Orçamento máximo por conversa em USD",
"es": "Presupuesto máximo por conversación en USD",
"ar": "الحد الأقصى للميزانية لكل محادثة بالدولار الأمريكي",
"fr": "Budget maximum par conversation en USD",
"tr": "Konuşma başına maksimum bütçe (USD)",
"de": "Maximales Budget pro Gespräch in USD",
"uk": "Максимальний бюджет на розмову в доларах США"
},
"GIT$DISCONNECT_TOKENS": {
"en": "Disconnect Tokens",
"ja": "トークンを切断",
"zh-CN": "断开令牌连接",
"zh-TW": "中斷令牌連接",
"ko-KR": "토큰 연결 해제",
"no": "Koble fra tokens",
"it": "Disconnetti token",
"pt": "Desconectar tokens",
"es": "Desconectar tokens",
"ar": "قطع اتصال الرموز",
"fr": "Déconnecter les jetons",
"tr": "Token bağlantısını kes",
"de": "Token trennen",
"uk": "Відключити токени"
},
"API$TAVILY_KEY_EXAMPLE": {
"en": "sk-tavily-...",
"ja": "sk-tavily-...",
"zh-CN": "sk-tavily-...",
"zh-TW": "sk-tavily-...",
"ko-KR": "sk-tavily-...",
"no": "sk-tavily-...",
"it": "sk-tavily-...",
"pt": "sk-tavily-...",
"es": "sk-tavily-...",
"ar": "sk-tavily-...",
"fr": "sk-tavily-...",
"tr": "sk-tavily-...",
"de": "sk-tavily-...",
"uk": "sk-tavily-..."
},
"API$TVLY_KEY_EXAMPLE": {
"en": "tvly-...",
"ja": "tvly-...",
"zh-CN": "tvly-...",
"zh-TW": "tvly-...",
"ko-KR": "tvly-...",
"no": "tvly-...",
"it": "tvly-...",
"pt": "tvly-...",
"es": "tvly-...",
"ar": "tvly-...",
"fr": "tvly-...",
"tr": "tvly-...",
"de": "tvly-...",
"uk": "tvly-..."
},
"SECRETS$CONNECT_GIT_PROVIDER": {
"en": "Connect a Git provider to manage secrets",
"ja": "シークレットを管理するためにGitプロバイダーに接続",
"zh-CN": "连接Git提供商以管理密钥",
"zh-TW": "連接Git提供商以管理密鑰",
"ko-KR": "시크릿 관리를 위해 Git 제공자에 연결",
"no": "Koble til en Git-leverandør for å administrere hemmeligheter",
"it": "Connetti un provider Git per gestire i segreti",
"pt": "Conectar um provedor Git para gerenciar segredos",
"es": "Conectar un proveedor Git para gestionar secretos",
"ar": "اتصل بمزود Git لإدارة الأسرار",
"fr": "Connecter un fournisseur Git pour gérer les secrets",
"tr": "Gizli anahtarları yönetmek için bir Git sağlayıcısına bağlan",
"de": "Git-Anbieter verbinden, um Geheimnisse zu verwalten",
"uk": "Підключити провайдера Git для управління секретами"
}
}

View File

@@ -111,7 +111,6 @@ const openHandsHandlers = [
"gpt-4o-mini",
"anthropic/claude-3.5",
"anthropic/claude-sonnet-4-20250514",
"sambanova/Meta-Llama-3.1-8B-Instruct",
]),
),

Some files were not shown because too many files have changed in this diff Show More