mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cea1461fb |
@@ -12,5 +12,4 @@
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
},
|
||||
"postCreateCommand": ".devcontainer/setup.sh",
|
||||
"runArgs": ["--network=host"],
|
||||
}
|
||||
|
||||
0
.devcontainer/setup.sh
Executable file → Normal file
0
.devcontainer/setup.sh
Executable file → Normal file
@@ -1,23 +1,5 @@
|
||||
# NodeJS
|
||||
frontend/node_modules
|
||||
|
||||
# Configuration (except pyproject.toml)
|
||||
*.ini
|
||||
*.toml
|
||||
!pyproject.toml
|
||||
*.yml
|
||||
|
||||
# Documentation (except README.md)
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Hidden files and directories
|
||||
.*
|
||||
__pycache__
|
||||
|
||||
# Unneded files and directories
|
||||
/dev_config/
|
||||
/docs/
|
||||
/evaluation/
|
||||
/tests/
|
||||
CITATION.cff
|
||||
config.toml
|
||||
.envrc
|
||||
.env
|
||||
.git
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -5,7 +5,7 @@
|
||||
/frontend/ @rbren @amanape
|
||||
|
||||
# Evaluation code owners
|
||||
/evaluation/ @xingyaoww @neubig
|
||||
/evaluation/ @xingyaoww @neubig
|
||||
|
||||
# Documentation code owners
|
||||
/docs/ @mamoodi
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
@@ -33,7 +33,6 @@ body:
|
||||
- Docker command in README
|
||||
- GitHub resolver
|
||||
- Development workflow
|
||||
- CLI
|
||||
- app.all-hands.dev
|
||||
- Other
|
||||
default: 0
|
||||
|
||||
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -16,6 +16,7 @@ updates:
|
||||
mcp-packages:
|
||||
patterns:
|
||||
- "mcp"
|
||||
- "mcpm"
|
||||
security-all:
|
||||
applies-to: "security-updates"
|
||||
patterns:
|
||||
@@ -72,9 +73,3 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directories:
|
||||
- "containers/*"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
4
.github/workflows/ghcr-build.yml
vendored
4
.github/workflows/ghcr-build.yml
vendored
@@ -313,8 +313,6 @@ jobs:
|
||||
TEST_IN_CI=true \
|
||||
RUN_AS_OPENHANDS=false \
|
||||
poetry run pytest -n 7 -raRs --reruns 2 --reruns-delay 5 -s ./tests/runtime --ignore=tests/runtime/test_browsergym_envs.py --durations=10
|
||||
env:
|
||||
DEBUG: "1"
|
||||
|
||||
# Run unit tests with the Docker runtime Docker images as openhands user
|
||||
test_runtime_oh:
|
||||
@@ -380,8 +378,6 @@ jobs:
|
||||
TEST_IN_CI=true \
|
||||
RUN_AS_OPENHANDS=true \
|
||||
poetry run pytest -n 7 -raRs --reruns 2 --reruns-delay 5 -s ./tests/runtime --ignore=tests/runtime/test_browsergym_envs.py --durations=10
|
||||
env:
|
||||
DEBUG: "1"
|
||||
|
||||
# The two following jobs (named identically) are to check whether all the runtime tests have passed as the
|
||||
# "All Runtime Tests Passed" is a required job for PRs to merge
|
||||
|
||||
6
.github/workflows/py-unit-tests.yml
vendored
6
.github/workflows/py-unit-tests.yml
vendored
@@ -74,11 +74,5 @@ jobs:
|
||||
run: poetry install --with dev,test,runtime
|
||||
- name: Run Windows unit tests
|
||||
run: poetry run pytest -svv tests/unit/test_windows_bash.py
|
||||
env:
|
||||
DEBUG: "1"
|
||||
- name: Run Windows runtime tests with LocalRuntime
|
||||
run: $env:TEST_RUNTIME="local"; poetry run pytest -svv tests/runtime/test_bash.py
|
||||
env:
|
||||
TEST_RUNTIME: local
|
||||
DEBUG: "1"
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -166,6 +166,7 @@ cython_debug/
|
||||
# https://stackoverflow.com/questions/32964920/should-i-commit-the-vscode-folder-to-source-control
|
||||
.vscode/**/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
|
||||
@@ -136,7 +136,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.42-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.41-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
@@ -51,17 +51,17 @@ system requirements and more information.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.42
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41
|
||||
```
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
|
||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.42
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41
|
||||
```
|
||||
|
||||
您将在[http://localhost:3000](http://localhost:3000)找到运行中的OpenHands!
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
ARG OPENHANDS_BUILD_VERSION=dev
|
||||
FROM node:22.16.0-bookworm-slim AS frontend-builder
|
||||
FROM node:21.7.2-bookworm-slim AS frontend-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY frontend/package.json frontend/package-lock.json ./
|
||||
COPY ./frontend/package.json frontend/package-lock.json ./
|
||||
RUN npm install -g npm@10.5.1
|
||||
RUN npm ci
|
||||
|
||||
COPY frontend ./
|
||||
COPY ./frontend ./
|
||||
RUN npm run build
|
||||
|
||||
FROM python:3.12.10-slim AS base
|
||||
FROM base AS backend-builder
|
||||
FROM python:3.12.3-slim AS backend-builder
|
||||
|
||||
WORKDIR /app
|
||||
ENV PYTHONPATH='/app'
|
||||
@@ -22,18 +22,17 @@ ENV POETRY_NO_INTERACTION=1 \
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl make git build-essential \
|
||||
&& python3 -m pip install poetry --break-system-packages
|
||||
&& python3 -m pip install poetry==1.8.2 --break-system-packages
|
||||
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
COPY ./pyproject.toml ./poetry.lock ./
|
||||
RUN touch README.md
|
||||
RUN export POETRY_CACHE_DIR && poetry install --no-root && rm -rf $POETRY_CACHE_DIR
|
||||
|
||||
FROM base AS openhands-app
|
||||
FROM python:3.12.3-slim AS openhands-app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# re-declare for this section
|
||||
ARG OPENHANDS_BUILD_VERSION
|
||||
ARG OPENHANDS_BUILD_VERSION #re-declare for this section
|
||||
|
||||
ENV RUN_AS_OPENHANDS=true
|
||||
# A random number--we need this to be different from the user's UID on the host machine
|
||||
@@ -75,7 +74,12 @@ COPY --chown=openhands:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${V
|
||||
COPY --chown=openhands:app --chmod=770 ./microagents ./microagents
|
||||
COPY --chown=openhands:app --chmod=770 ./openhands ./openhands
|
||||
COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
|
||||
COPY --chown=openhands:app pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
|
||||
COPY --chown=openhands:app --chmod=770 ./openhands/agenthub ./openhands/agenthub
|
||||
COPY --chown=openhands:app ./pyproject.toml ./pyproject.toml
|
||||
COPY --chown=openhands:app ./poetry.lock ./poetry.lock
|
||||
COPY --chown=openhands:app ./README.md ./README.md
|
||||
COPY --chown=openhands:app ./MANIFEST.in ./MANIFEST.in
|
||||
COPY --chown=openhands:app ./LICENSE ./LICENSE
|
||||
|
||||
# This is run as "openhands" user, and will create __pycache__ with openhands:openhands ownership
|
||||
RUN python openhands/core/download.py # No-op to download assets
|
||||
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"}
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.42-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.41-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -37,8 +37,7 @@ repos:
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
[types-requests, types-setuptools, types-pyyaml, types-toml, types-docker, lxml]
|
||||
# To see gaps add `--html-report mypy-report/`
|
||||
[types-requests, types-setuptools, types-pyyaml, types-toml]
|
||||
entry: mypy --config-file dev_config/python/mypy.ini openhands/
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
|
||||
@@ -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.42-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.41-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-state for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
162
docs/docs.json
162
docs/docs.json
@@ -20,7 +20,7 @@
|
||||
"navigation": {
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Docs",
|
||||
"tab": "Getting started",
|
||||
"pages": [
|
||||
"index",
|
||||
"usage/installation",
|
||||
@@ -31,116 +31,116 @@
|
||||
"pages": [
|
||||
"usage/cloud/openhands-cloud",
|
||||
{
|
||||
"group": "Integrations",
|
||||
"group": "Installation",
|
||||
"pages": [
|
||||
"usage/cloud/github-installation",
|
||||
"usage/cloud/gitlab-installation"
|
||||
]
|
||||
},
|
||||
"usage/cloud/cloud-ui",
|
||||
"usage/cloud/cloud-issue-resolver",
|
||||
"usage/cloud/cloud-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Running OpenHands on Your Own",
|
||||
"group": "Usage Methods",
|
||||
"pages": [
|
||||
"usage/local-setup",
|
||||
"usage/how-to/gui-mode",
|
||||
"usage/how-to/cli-mode",
|
||||
"usage/how-to/headless-mode",
|
||||
"usage/how-to/github-action"
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Prompting and Customization",
|
||||
"pages": [
|
||||
"usage/prompting/prompting-best-practices",
|
||||
"usage/prompting/repository",
|
||||
{
|
||||
"group": "Customization",
|
||||
"group": "Microagents",
|
||||
"pages": [
|
||||
"usage/prompting/prompting-best-practices",
|
||||
"usage/prompting/repository",
|
||||
"usage/prompting/microagents-overview",
|
||||
"usage/prompting/microagents-repo",
|
||||
"usage/prompting/microagents-keyword",
|
||||
"usage/prompting/microagents-org",
|
||||
"usage/prompting/microagents-public"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Advanced Configuration",
|
||||
"pages": [
|
||||
{
|
||||
"group": "LLM Configuration",
|
||||
"pages": [
|
||||
"usage/llms/llms",
|
||||
{
|
||||
"group": "Microagents",
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
"usage/prompting/microagents-overview",
|
||||
"usage/prompting/microagents-repo",
|
||||
"usage/prompting/microagents-keyword",
|
||||
"usage/prompting/microagents-org",
|
||||
"usage/prompting/microagents-public"
|
||||
"usage/llms/azure-llms",
|
||||
"usage/llms/google-llms",
|
||||
"usage/llms/groq",
|
||||
"usage/llms/local-llms",
|
||||
"usage/llms/litellm-proxy",
|
||||
"usage/llms/openai-llms",
|
||||
"usage/llms/openrouter"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Advanced Configuration",
|
||||
"group": "Runtime Configuration",
|
||||
"pages": [
|
||||
{
|
||||
"group": "LLM Configuration",
|
||||
"pages": [
|
||||
"usage/llms/llms",
|
||||
{
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
"usage/llms/azure-llms",
|
||||
"usage/llms/google-llms",
|
||||
"usage/llms/groq",
|
||||
"usage/llms/local-llms",
|
||||
"usage/llms/litellm-proxy",
|
||||
"usage/llms/openai-llms",
|
||||
"usage/llms/openrouter"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Runtime Configuration",
|
||||
"pages": [
|
||||
"usage/runtimes/overview",
|
||||
{
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
"usage/runtimes/docker",
|
||||
"usage/runtimes/remote",
|
||||
"usage/runtimes/local",
|
||||
{
|
||||
"group": "Third-Party Providers",
|
||||
"pages": [
|
||||
"usage/runtimes/modal",
|
||||
"usage/runtimes/daytona",
|
||||
"usage/runtimes/runloop",
|
||||
"usage/runtimes/e2b"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage/configuration-options",
|
||||
"usage/how-to/custom-sandbox-guide",
|
||||
"usage/search-engine-setup",
|
||||
"usage/mcp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Troubleshooting & Feedback",
|
||||
"pages": [
|
||||
"usage/troubleshooting/troubleshooting",
|
||||
"usage/feedback"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "OpenHands Developers",
|
||||
"pages": [
|
||||
"usage/how-to/development-overview",
|
||||
"usage/runtimes/overview",
|
||||
{
|
||||
"group": "Architecture",
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
"usage/architecture/backend",
|
||||
"usage/architecture/runtime"
|
||||
"usage/runtimes/docker",
|
||||
"usage/runtimes/remote",
|
||||
"usage/runtimes/local",
|
||||
{
|
||||
"group": "Third-Party Providers",
|
||||
"pages": [
|
||||
"usage/runtimes/modal",
|
||||
"usage/runtimes/daytona",
|
||||
"usage/runtimes/runloop",
|
||||
"usage/runtimes/e2b"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage/how-to/debugging",
|
||||
"usage/how-to/evaluation-harness",
|
||||
"usage/how-to/websocket-connection"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"usage/configuration-options",
|
||||
"usage/how-to/custom-sandbox-guide",
|
||||
"usage/search-engine-setup",
|
||||
"usage/mcp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Troubleshooting & Feedback",
|
||||
"pages": [
|
||||
"usage/troubleshooting/troubleshooting",
|
||||
"usage/feedback"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "For OpenHands Developers",
|
||||
"pages": [
|
||||
"usage/how-to/development-overview",
|
||||
{
|
||||
"group": "Architecture",
|
||||
"pages": [
|
||||
"usage/architecture/backend",
|
||||
"usage/architecture/runtime"
|
||||
]
|
||||
},
|
||||
"usage/how-to/debugging",
|
||||
"usage/how-to/evaluation-harness",
|
||||
"usage/how-to/websocket-connection"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
BIN
docs/static/img/api-key-generation.png
vendored
BIN
docs/static/img/api-key-generation.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
BIN
docs/static/img/connect-repo.png
vendored
BIN
docs/static/img/connect-repo.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
BIN
docs/static/img/docs/api-key-generation.png
vendored
Normal file
BIN
docs/static/img/docs/api-key-generation.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -1,11 +1,9 @@
|
||||
---
|
||||
title: Cloud API
|
||||
description: OpenHands Cloud provides a REST API that allows you to programmatically interact with OpenHands.
|
||||
This guide explains how to obtain an API key and use the API to start conversations and retrieve their status.
|
||||
description: OpenHands Cloud provides a REST API that allows you to programmatically interact with the service. This guide explains how to obtain an API key and use the API to start conversations.
|
||||
---
|
||||
|
||||
For the available API endpoints, refer to the
|
||||
[OpenHands API Reference](https://docs.all-hands.dev/api-reference).
|
||||
For more detailed information about the API, refer to the [OpenHands API Reference](https://docs.all-hands.dev/swagger-ui/).
|
||||
|
||||
## Obtaining an API Key
|
||||
|
||||
@@ -18,7 +16,7 @@ To use the OpenHands Cloud API, you'll need to generate an API key:
|
||||
5. Give your key a descriptive name (Example: "Development" or "Production") and select `Create`.
|
||||
6. Copy the generated API key and store it securely. It will only be shown once.
|
||||
|
||||

|
||||

|
||||
|
||||
## API Usage
|
||||
|
||||
@@ -35,81 +33,87 @@ To start a new conversation with OpenHands to perform a task, you'll need to mak
|
||||
|
||||
#### Examples
|
||||
|
||||
<details>
|
||||
<summary>cURL</summary>
|
||||
|
||||
<Accordion title="cURL">
|
||||
```bash
|
||||
curl -X POST "https://app.all-hands.dev/api/conversations" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"initial_user_msg": "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
"repository": "yourusername/your-repo"
|
||||
}'
|
||||
```
|
||||
</Accordion>
|
||||
```bash
|
||||
curl -X POST "https://app.all-hands.dev/api/conversations" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"initial_user_msg": "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
"repository": "yourusername/your-repo"
|
||||
}'
|
||||
```
|
||||
</details>
|
||||
|
||||
<Accordion title="Python (with requests)">
|
||||
```python
|
||||
import requests
|
||||
<details>
|
||||
<summary>Python (with requests)</summary>
|
||||
|
||||
api_key = "YOUR_API_KEY"
|
||||
url = "https://app.all-hands.dev/api/conversations"
|
||||
```python
|
||||
import requests
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
api_key = "YOUR_API_KEY"
|
||||
url = "https://app.all-hands.dev/api/conversations"
|
||||
|
||||
data = {
|
||||
"initial_user_msg": "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
"repository": "yourusername/your-repo"
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
conversation = response.json()
|
||||
|
||||
print(f"Conversation Link: https://app.all-hands.dev/conversations/{conversation['conversation_id']}")
|
||||
print(f"Status: {conversation['status']}")
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="TypeScript/JavaScript (with fetch)">
|
||||
```typescript
|
||||
const apiKey = "YOUR_API_KEY";
|
||||
const url = "https://app.all-hands.dev/api/conversations";
|
||||
|
||||
const headers = {
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
}
|
||||
|
||||
const data = {
|
||||
initial_user_msg: "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
repository: "yourusername/your-repo"
|
||||
};
|
||||
data = {
|
||||
"initial_user_msg": "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
"repository": "yourusername/your-repo"
|
||||
}
|
||||
|
||||
async function startConversation() {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
conversation = response.json()
|
||||
|
||||
const conversation = await response.json();
|
||||
print(f"Conversation Link: https://app.all-hands.dev/conversations/{conversation['conversation_id']}")
|
||||
print(f"Status: {conversation['status']}")
|
||||
```
|
||||
</details>
|
||||
|
||||
console.log(`Conversation Link: https://app.all-hands.dev/conversations/${conversation.id}`);
|
||||
console.log(`Status: ${conversation.status}`);
|
||||
<details>
|
||||
<summary>TypeScript/JavaScript (with fetch)</summary>
|
||||
|
||||
return conversation;
|
||||
} catch (error) {
|
||||
console.error("Error starting conversation:", error);
|
||||
}
|
||||
```typescript
|
||||
const apiKey = "YOUR_API_KEY";
|
||||
const url = "https://app.all-hands.dev/api/conversations";
|
||||
|
||||
const headers = {
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
|
||||
const data = {
|
||||
initial_user_msg: "Check whether there is any incorrect information in the README.md file and send a PR to fix it if so.",
|
||||
repository: "yourusername/your-repo"
|
||||
};
|
||||
|
||||
async function startConversation() {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const conversation = await response.json();
|
||||
|
||||
console.log(`Conversation Link: https://app.all-hands.dev/conversations/${conversation.id}`);
|
||||
console.log(`Status: ${conversation.status}`);
|
||||
|
||||
return conversation;
|
||||
} catch (error) {
|
||||
console.error("Error starting conversation:", error);
|
||||
}
|
||||
}
|
||||
|
||||
startConversation();
|
||||
```
|
||||
</Accordion>
|
||||
startConversation();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Response
|
||||
|
||||
@@ -141,12 +145,14 @@ GET https://app.all-hands.dev/api/conversations/{conversation_id}
|
||||
|
||||
#### Example
|
||||
|
||||
<Accordion title="cURL">
|
||||
```bash
|
||||
curl -X GET "https://app.all-hands.dev/api/conversations/{conversation_id}" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY"
|
||||
```
|
||||
</Accordion>
|
||||
<details>
|
||||
<summary>cURL</summary>
|
||||
|
||||
```bash
|
||||
curl -X GET "https://app.all-hands.dev/api/conversations/{conversation_id}" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY"
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Response
|
||||
|
||||
|
||||
33
docs/usage/cloud/cloud-issue-resolver.mdx
Normal file
33
docs/usage/cloud/cloud-issue-resolver.mdx
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Cloud Issue Resolver
|
||||
description: The Cloud Issue Resolver automates code fixes and provides intelligent assistance for your repositories on GitHub.
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
The Cloud Issue Resolver is available automatically when you grant OpenHands Cloud repository access:
|
||||
- [GitHub repository access](./github-installation#adding-repository-access)
|
||||
|
||||
## Usage
|
||||
|
||||
After granting OpenHands Cloud repository access, you can use the Cloud Issue Resolver on issues and pull requests in your repositories.
|
||||
|
||||
### Working with Issues
|
||||
|
||||
On your repository, label an issue with `openhands` or add a message starting with
|
||||
`@openhands`. OpenHands will:
|
||||
1. Comment on the issue to let you know it is working on it
|
||||
- You can click on the link to track the progress on OpenHands Cloud
|
||||
2. Open a pull request if it determines that the issue has been successfully resolved
|
||||
3. Comment on the issue with a summary of the performed tasks and a link to the PR
|
||||
|
||||
### Working with Pull Requests
|
||||
|
||||
To get OpenHands to work on pull requests, mention `@openhands` in comments to:
|
||||
- Ask questions
|
||||
- Request updates
|
||||
- Get code explanations
|
||||
|
||||
OpenHands will:
|
||||
1. Comment to let you know it is working on it
|
||||
2. Perform the requested task
|
||||
@@ -1,36 +1,28 @@
|
||||
---
|
||||
title: Cloud UI
|
||||
description: The Cloud UI provides a web interface for interacting with OpenHands. This page explains how to use the
|
||||
OpenHands Cloud UI.
|
||||
description: The Cloud UI provides a web interface for interacting with OpenHands AI. This page explains how to access and use the OpenHands Cloud UI.
|
||||
---
|
||||
|
||||
## Landing Page
|
||||
|
||||
The landing page is where you can:
|
||||
## Accessing the UI
|
||||
|
||||
- [Add GitHub repository access](/usage/cloud/github-installation#adding-github-repository-access) to OpenHands.
|
||||
- [Select a GitHub repo](/usage/cloud/github-installation#working-with-github-repos-in-openhands-cloud) or
|
||||
[a GitLab repo](/usage/cloud/gitlab-installation#working-with-gitlab-repos-in-openhands-cloud) to start working on.
|
||||
- See `Suggested Tasks` for repositories that OpenHands has access to.
|
||||
- Launch an empty conversation using `Launch from Scratch`.
|
||||
The OpenHands Cloud UI can be accessed at [app.all-hands.dev](https://app.all-hands.dev). You'll need to sign in with your GitHub or GitLab account to access the interface.
|
||||
|
||||
## Settings
|
||||
|
||||
The Settings page allows you to:
|
||||
|
||||
- [Configure GitHub repository access](/usage/cloud/github-installation#modifying-repository-access) for OpenHands.
|
||||
- Set application settings like your preferred language, notifications and other preferences.
|
||||
- Add credits to your account.
|
||||
- Generate custom secrets.
|
||||
- Create API keys to work with OpenHands programmatically.
|
||||
|
||||
## Key Features
|
||||
|
||||
For an overview of the key features available inside a conversation, please refer to the [Key Features](/usage/key-features)
|
||||
section of the documentation.
|
||||
For detailed information about the features available in the OpenHands Cloud UI, please refer to the [Key Features](../key-features) section of the documentation.
|
||||
|
||||
## Settings
|
||||
|
||||
The settings page allows you to:
|
||||
|
||||
- Configure your account preferences.
|
||||
- Manage repository access.
|
||||
- Generate API keys for programmatic access.
|
||||
- Generate custom secrets for the agent.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Install GitHub Integration](/usage/cloud/github-installation) to use OpenHands with your GitHub repositories.
|
||||
- [Install GitLab Integration](/usage/cloud/gitlab-installation) to use OpenHands with your GitLab repositories.
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
- [Use the Cloud Issue Resolver](./cloud-issue-resolver) to automate code fixes and get assistance.
|
||||
- [Learn about the Cloud API](./cloud-api) for programmatic access.
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
---
|
||||
title: GitHub Integration
|
||||
description: This guide walks you through the process of installing OpenHands Cloud for your GitHub repositories. Once
|
||||
set up, it will allow OpenHands to work with your GitHub repository through the Cloud UI or straight from GitHub!
|
||||
title: GitHub Installation
|
||||
description: This guide walks you through the process of installing and configuring OpenHands Cloud for your GitHub repositories.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a GitHub account](/usage/cloud/openhands-cloud).
|
||||
- A GitHub account
|
||||
- Access to OpenHands Cloud
|
||||
|
||||
## Adding GitHub Repository Access
|
||||
## Installation Steps
|
||||
|
||||
You can grant OpenHands access to specific GitHub repositories:
|
||||
1. Log in to [OpenHands Cloud](https://app.all-hands.dev)
|
||||
2. If you haven't connected your GitHub account yet:
|
||||
- Click on `Connect to GitHub`
|
||||
- Review and accept the terms of service
|
||||
- Authorize the OpenHands AI application
|
||||
|
||||
1. Click on `Add GitHub repos` on the landing page.
|
||||
## Adding Repository Access
|
||||
|
||||
You can grant OpenHands access to specific repositories:
|
||||
|
||||
1. Click on `Add GitHub repos`
|
||||
2. Select your organization and choose the specific repositories to grant OpenHands access to.
|
||||
<Accordion title="OpenHands permissions">
|
||||
- OpenHands requests short-lived tokens (8-hour expiration) with these permissions:
|
||||
- OpenHands requests short-lived tokens (8-hour expiration) with these permissions:
|
||||
- Actions: Read and write
|
||||
- Administration: Read-only
|
||||
- Commit statuses: Read and write
|
||||
- Contents: Read and write
|
||||
- Issues: Read and write
|
||||
@@ -27,45 +35,20 @@ You can grant OpenHands access to specific GitHub repositories:
|
||||
- Repository access for a user is granted based on:
|
||||
- Permission granted for the repository
|
||||
- User's GitHub permissions (owner/collaborator)
|
||||
</Accordion>
|
||||
|
||||
3. Click `Install & Authorize`.
|
||||
3. Click `Install & Authorize`
|
||||
|
||||
## Modifying Repository Access
|
||||
|
||||
You can modify GitHub repository access at any time by:
|
||||
- Selecting `Add GitHub repos` on the landing page or
|
||||
- Visiting the Settings page and selecting `Configure GitHub Repositories` under the `Git` tab
|
||||
You can modify repository access at any time by visiting the Settings page and selecting `Configure GitHub Repositories` under the `Git` tab.
|
||||
|
||||
## Working With GitHub Repos in Openhands Cloud
|
||||
## Using OpenHands with GitHub
|
||||
|
||||
Once you've granted GitHub repository access, you can start working with your GitHub repository. Use the `select a repo`
|
||||
and `select a branch` dropdowns to select the appropriate repository and branch you'd like OpenHands to work on. Then
|
||||
click on `Launch` to start the conversation!
|
||||
Once you've granted repository access, you can use OpenHands with your GitHub repositories.
|
||||
|
||||

|
||||
|
||||
## Working on Github Issues and Pull Requests Using Openhands
|
||||
|
||||
Giving GitHub repository access to OpenHands also allows you to work on GitHub issues and pull requests directly.
|
||||
|
||||
### Working with Issues
|
||||
|
||||
On your repository, label an issue with `openhands` or add a message starting with
|
||||
`@openhands`. OpenHands will:
|
||||
1. Comment on the issue to let you know it is working on it.
|
||||
- You can click on the link to track the progress on OpenHands Cloud.
|
||||
2. Open a pull request if it determines that the issue has been successfully resolved.
|
||||
3. Comment on the issue with a summary of the performed tasks and a link to the PR.
|
||||
|
||||
### Working with Pull Requests
|
||||
|
||||
To get OpenHands to work on pull requests, mention `@openhands` in the comments to:
|
||||
- Ask questions
|
||||
- Request updates
|
||||
- Get code explanations
|
||||
For details on how to use OpenHands with GitHub issues and pull requests, see the [Cloud Issue Resolver](./cloud-issue-resolver) documentation.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
- [Access the Cloud UI](./cloud-ui) to interact with the web interface
|
||||
- [Use the Cloud Issue Resolver](./cloud-issue-resolver) to automate code fixes and get assistance
|
||||
- [Use the Cloud API](./cloud-api) to programmatically interact with OpenHands
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
---
|
||||
title: GitLab Integration
|
||||
description: This guide walks you through the process of installing OpenHands Cloud for your GitLab repositories. Once
|
||||
set up, it will allow OpenHands to work with your GitLab repository.
|
||||
title: GitLab Installation
|
||||
description: This guide walks you through the process of installing and configuring OpenHands Cloud for your GitLab repositories.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a GitLab account](/usage/cloud/openhands-cloud).
|
||||
- A GitLab account
|
||||
- Access to OpenHands Cloud
|
||||
|
||||
## Adding GitLab Repository Access
|
||||
## Installation Steps
|
||||
|
||||
Upon signing into OpenHands Cloud with a GitLab account, OpenHands will have access to your repositories.
|
||||
1. Log in to [OpenHands Cloud](https://app.all-hands.dev)
|
||||
2. If you haven't connected your GitLab account yet:
|
||||
- Click on `Log in with GitLab`
|
||||
- Authorize the OpenHands application
|
||||
|
||||
## Working With GitLab Repos in Openhands Cloud
|
||||
|
||||
After signing in with a Gitlab account, use the `select a repo` and `select a branch` dropdowns to select the
|
||||
appropriate repository and branch you'd like OpenHands to work on. Then click on `Launch` to start the conversation!
|
||||
|
||||

|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
- [Access the Cloud UI](./cloud-ui) to interact with the web interface
|
||||
- [Use the Cloud API](./cloud-api) to programmatically interact with OpenHands
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Getting started with OpenHands Cloud.
|
||||
description: Getting started with OpenHands Cloud
|
||||
---
|
||||
|
||||
OpenHands Cloud is the hosted cloud version of All Hands AI's OpenHands.
|
||||
|
||||
## Accessing OpenHands Cloud
|
||||
|
||||
OpenHands Cloud is the hosted cloud version of All Hands AI's OpenHands. To get started with OpenHands Cloud,
|
||||
visit [app.all-hands.dev](https://app.all-hands.dev).
|
||||
To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-hands.dev).
|
||||
|
||||
You'll be prompted to connect with your GitHub or GitLab account:
|
||||
|
||||
@@ -14,13 +15,13 @@ You'll be prompted to connect with your GitHub or GitLab account:
|
||||
2. Review the permissions requested by OpenHands and authorize the application.
|
||||
- OpenHands will require certain permissions from your account. To read more about these permissions,
|
||||
you can click the `Learn more` link on the authorization page.
|
||||
3. Review and accept the `terms of service` and select `Continue`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once you've connected your account, you can:
|
||||
|
||||
- [Install GitHub Integration](/usage/cloud/github-installation) to use OpenHands with your GitHub repositories.
|
||||
- [Install GitLab Integration](/usage/cloud/gitlab-installation) to use OpenHands with your GitLab repositories.
|
||||
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands.
|
||||
- [Install GitHub Integration](./github-installation) to use OpenHands with your GitHub repositories
|
||||
- [Install GitLab Integration](./gitlab-installation) to use OpenHands with your GitLab repositories
|
||||
- [Access the Cloud UI](./cloud-ui) to interact with the web interface
|
||||
- [Use the Cloud API](./cloud-api) to programmatically interact with OpenHands
|
||||
- [Set up the Cloud Issue Resolver](./cloud-issue-resolver) to automate code fixes and provide intelligent assistance
|
||||
|
||||
@@ -1,39 +1,24 @@
|
||||
---
|
||||
title: CLI
|
||||
description: The Command-Line Interface (CLI) provides a powerful interface that lets you engage with OpenHands
|
||||
directly from your terminal.
|
||||
title: CLI Mode
|
||||
description: CLI mode provides a powerful interactive Command-Line Interface (CLI) that lets you engage with OpenHands directly from your terminal.
|
||||
---
|
||||
|
||||
This mode is different from the [headless mode](/usage/how-to/headless-mode), which is non-interactive and better
|
||||
for scripting.
|
||||
This mode is different from the [headless mode](./headless-mode), which is non-interactive and better for scripting.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Running with Python
|
||||
|
||||
1. Install OpenHands using pip:
|
||||
|
||||
```bash
|
||||
pip install openhands-ai
|
||||
```
|
||||
|
||||
1. Ensure you have followed the [Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
2. Set your model, API key, and other preferences using environment variables or with the [`config.toml`](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml) file.
|
||||
3. Launch an interactive OpenHands conversation from the command line:
|
||||
|
||||
```bash
|
||||
openhands
|
||||
poetry run python -m openhands.cli.main
|
||||
```
|
||||
|
||||
This command opens an interactive prompt where you can type tasks or commands and get responses from OpenHands.
|
||||
|
||||
#### 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
|
||||
|
||||
1. Set the following environment variables in your terminal:
|
||||
@@ -46,7 +31,7 @@ poetry run python -m openhands.cli.main
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -55,7 +40,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--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.42 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ This will produce a new image called `custom-image`, which will be available in
|
||||
|
||||
## Using the Docker Command
|
||||
|
||||
When running OpenHands using [the docker command](/usage/local-setup#start-the-app), replace
|
||||
When running OpenHands using [the docker command](/usage/installation#start-the-app), replace
|
||||
`-e SANDBOX_RUNTIME_CONTAINER_IMAGE=...` with `-e SANDBOX_BASE_CONTAINER_IMAGE=<custom image name>`:
|
||||
|
||||
```commandline
|
||||
|
||||
@@ -48,6 +48,6 @@ The customization options you can set are:
|
||||
| `LLM_MODEL` | Variable | Set the LLM to use with OpenHands | `LLM_MODEL="anthropic/claude-3-5-sonnet-20241022"` |
|
||||
| `OPENHANDS_MAX_ITER` | Variable | Set max limit for agent iterations | `OPENHANDS_MAX_ITER=10` |
|
||||
| `OPENHANDS_MACRO` | Variable | Customize default macro for invoking the resolver | `OPENHANDS_MACRO=@resolveit` |
|
||||
| `OPENHANDS_BASE_CONTAINER_IMAGE` | Variable | Custom Sandbox ([learn more](/usage/how-to/custom-sandbox-guide)) | `OPENHANDS_BASE_CONTAINER_IMAGE="custom_image"` |
|
||||
| `OPENHANDS_BASE_CONTAINER_IMAGE` | Variable | Custom Sandbox ([learn more](https://docs.all-hands.dev/modules/usage/how-to/custom-sandbox-guide)) | `OPENHANDS_BASE_CONTAINER_IMAGE="custom_image"` |
|
||||
| `TARGET_BRANCH` | Variable | Merge to branch other than `main` | `TARGET_BRANCH="dev"` |
|
||||
| `TARGET_RUNNER` | Variable | Target runner to execute the agent workflow (default ubuntu-latest) | `TARGET_RUNNER="custom-runner"` |
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
title: GUI
|
||||
description: High level overview of the Graphical User Interface (GUI) in OpenHands.
|
||||
title: GUI Mode
|
||||
description: OpenHands provides a Graphical User Interface (GUI) mode for interacting with the AI assistant.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
## Installation and Setup
|
||||
|
||||
- [OpenHands is running](/usage/local-setup)
|
||||
1. Follow the installation instructions to install OpenHands.
|
||||
2. After running the command, access OpenHands at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
## Overview
|
||||
## Interacting with the GUI
|
||||
|
||||
### Initial Setup
|
||||
|
||||
@@ -18,23 +19,16 @@ description: High level overview of the Graphical User Interface (GUI) in OpenHa
|
||||
3. Enter the corresponding `API Key` for your chosen provider.
|
||||
4. Click `Save Changes` to apply the settings.
|
||||
|
||||
### Settings
|
||||
### Version Control Tokens
|
||||
|
||||
You can use the Settings page at any time to:
|
||||
OpenHands supports multiple version control providers. You can configure tokens for multiple providers simultaneously.
|
||||
|
||||
- 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)
|
||||
- Set application settings like your preferred language, notifications and other preferences.
|
||||
- Generate custom secrets.
|
||||
|
||||
#### GitHub Setup
|
||||
#### GitHub Token Setup
|
||||
|
||||
OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if provided:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Setting Up a GitHub Token">
|
||||
<details>
|
||||
<summary>Setting Up a GitHub Token</summary>
|
||||
|
||||
1. **Generate a Personal Access Token (PAT)**:
|
||||
- On GitHub, go to Settings > Developer Settings > Personal Access Tokens > Tokens (classic).
|
||||
@@ -43,11 +37,16 @@ OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if pro
|
||||
- `repo` (Full control of private repositories)
|
||||
- **Fine-Grained Tokens**
|
||||
- All Repositories (You can select specific repositories, but this will impact what returns in repo search)
|
||||
- Minimal Permissions (Select `Meta Data = Read-only` read for search, `Pull Requests = Read and Write` and `Content = Read and Write` for branch creation)
|
||||
- Minimal Permissions ( Select `Meta Data = Read-only` read for search, `Pull Requests = Read and Write` and `Content = Read and Write` for branch creation)
|
||||
2. **Enter Token in OpenHands**:
|
||||
- In the Settings page, navigate to the `Git` tab.
|
||||
- Click the Settings button (gear icon).
|
||||
- Navigate to the `Git` tab.
|
||||
- Paste your token in the `GitHub Token` field.
|
||||
- Click `Save Changes` to apply the changes.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Organizational Token Policies</summary>
|
||||
|
||||
If you're working with organizational repositories, additional setup may be required:
|
||||
|
||||
@@ -60,12 +59,15 @@ OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if pro
|
||||
- Look for the organization under `Organization access`.
|
||||
- If required, click `Enable SSO` next to your organization.
|
||||
- Complete the SSO authorization process.
|
||||
</Accordion>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Troubleshooting</summary>
|
||||
|
||||
<Accordion title="Troubleshooting">
|
||||
Common issues and solutions:
|
||||
|
||||
- **Token Not Recognized**:
|
||||
- Ensure the token is properly saved in settings.
|
||||
- Check that the token hasn't expired.
|
||||
- Verify the token has the required scopes.
|
||||
- Try regenerating the token.
|
||||
@@ -79,15 +81,15 @@ OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if pro
|
||||
- The app will show a green checkmark if the token is valid.
|
||||
- Try accessing a repository to confirm permissions.
|
||||
- Check the browser console for any error messages.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</details>
|
||||
|
||||
#### GitLab Setup
|
||||
#### GitLab Token Setup
|
||||
|
||||
OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if provided:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Setting Up a GitLab Token">
|
||||
<details>
|
||||
<summary>Setting Up a GitLab Token</summary>
|
||||
|
||||
1. **Generate a Personal Access Token (PAT)**:
|
||||
- On GitLab, go to User Settings > Access Tokens.
|
||||
- Create a new token with the following scopes:
|
||||
@@ -97,12 +99,15 @@ OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if pro
|
||||
- `write_repository` (Write repository)
|
||||
- Set an expiration date or leave it blank for a non-expiring token.
|
||||
2. **Enter Token in OpenHands**:
|
||||
- In the Settings page, navigate to the `Git` tab.
|
||||
- Click the Settings button (gear icon).
|
||||
- Navigate to the `Git` tab.
|
||||
- Paste your token in the `GitLab Token` field.
|
||||
- Click `Save Changes` to apply the changes.
|
||||
</Accordion>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Troubleshooting</summary>
|
||||
|
||||
<Accordion title="Troubleshooting">
|
||||
Common issues and solutions:
|
||||
|
||||
- **Token Not Recognized**:
|
||||
@@ -114,30 +119,25 @@ OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if pro
|
||||
- Verify project access permissions.
|
||||
- Check if the token has the necessary scopes.
|
||||
- For group/organization repositories, ensure you have proper access.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</details>
|
||||
|
||||
#### Advanced Settings
|
||||
### Advanced Settings
|
||||
|
||||
The `Advanced` settings allows configuration of additional LLM settings. Inside the Settings page, under the `LLM` tab,
|
||||
toggle `Advanced` options to access additional settings.
|
||||
1. Inside the Settings page, under the `LLM` tab, toggle `Advanced` options to access additional settings.
|
||||
2. Use the `Custom Model` text box to manually enter a model if it's not in the list.
|
||||
3. Specify a `Base URL` if required by your LLM provider.
|
||||
|
||||
- Custom Model: Use the `Custom Model` text box to manually enter a model. Make sure to use the correct prefix based on litellm docs.
|
||||
- Base URL: Specify a `Base URL` if required by your LLM provider.
|
||||
- Memory Condensation: The memory condenser manages the LLM's context by ensuring only the most important and relevant information is presented.
|
||||
- Confirmation Mode: Enabling this mode will cause OpenHands to confirm an action with the user before performing it.
|
||||
### Interacting with the AI
|
||||
|
||||
### Key Features
|
||||
|
||||
For an overview of the key features available inside a conversation, please refer to the [Key Features](/usage/key-features)
|
||||
section of the documentation.
|
||||
1. Type your prompt in the input box.
|
||||
2. Click the send button or press Enter to submit your message.
|
||||
3. The AI will process your input and provide a response in the chat window.
|
||||
4. You can continue the conversation by asking follow-up questions or providing additional information.
|
||||
|
||||
## Tips for Effective Use
|
||||
|
||||
- Be specific in your requests to get the most accurate and helpful responses, as described in the [prompting best practices](../prompting/prompting-best-practices).
|
||||
- Use one of the recommended models, as described in the [LLMs section](usage/llms/llms.md).
|
||||
|
||||
## Other Ways to Run Openhands
|
||||
- [Run OpenHands in a scriptable headless mode.](/usage/how-to/headless-mode)
|
||||
- [Run OpenHands with a friendly CLI.](/usage/how-to/cli-mode)
|
||||
- [Run OpenHands on GitHub issues with a GitHub action.](/usage/how-to/github-action)
|
||||
Remember, the GUI mode of OpenHands is designed to make your interaction with the AI assistant as smooth and intuitive
|
||||
as possible. Don't hesitate to explore its features to maximize your productivity.
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
title: Headless
|
||||
description: You can run OpenHands with a single command, without starting the web application. This makes it easy to
|
||||
write scripts and automate tasks with OpenHands.
|
||||
title: Headless Mode
|
||||
description: You can run OpenHands with a single command, without starting the web application. This makes it easy to write scripts and automate tasks with OpenHands.
|
||||
---
|
||||
|
||||
This is different from [the CLI](./cli-mode), which is interactive, and better for active development.
|
||||
This is different from [CLI Mode](./cli-mode), which is interactive, and better for active development.
|
||||
|
||||
## With Python
|
||||
|
||||
@@ -32,7 +31,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.42-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -42,7 +41,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--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.42 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Quick Start
|
||||
description: Running OpenHands Cloud or running on your local system.
|
||||
description: Running OpenHands on the cloud or your local desktop
|
||||
icon: rocket
|
||||
---
|
||||
|
||||
@@ -10,10 +10,164 @@ The easiest way to get started with OpenHands is on OpenHands Cloud, which comes
|
||||
|
||||
To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-hands.dev).
|
||||
|
||||
For more information see [getting started with OpenHands Cloud.](/usage/cloud/openhands-cloud)
|
||||
You'll be prompted to connect with your GitHub or GitLab account:
|
||||
|
||||
## Running OpenHands on Your Own
|
||||
1. Click `Log in with GitHub` or `Log in with GitLab`.
|
||||
2. Review the permissions requested by OpenHands and authorize the application.
|
||||
- OpenHands will require certain permissions from your account. To read more about these permissions,
|
||||
you can click the `Learn more` link on the authorization page.
|
||||
|
||||
Run OpenHands on your local system and bring your own LLM and API key.
|
||||
|
||||
For more information see [running OpenHands on your own.](/usage/local-setup)
|
||||
Once you've connected your account, you can:
|
||||
|
||||
- [Install GitHub Integration](/usage/cloud/github-installation) to use OpenHands with your GitHub repositories
|
||||
- [Install GitLab Integration](/usage/cloud/gitlab-installation) to use OpenHands with your GitLab repositories
|
||||
- [Access the Cloud UI](/usage/cloud/cloud-ui) to interact with the web interface
|
||||
- [Use the Cloud API](/usage/cloud/cloud-api) to programmatically interact with OpenHands
|
||||
- [Set up the Cloud Issue Resolver](/usage/cloud/cloud-issue-resolver) to automate code fixes and provide intelligent assistance
|
||||
|
||||
|
||||
## Running OpenHands on your local desktop
|
||||
|
||||
### System Requirements
|
||||
|
||||
- MacOS with [Docker Desktop support](https://docs.docker.com/desktop/setup/install/mac-install/#system-requirements)
|
||||
- Linux
|
||||
- Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) and [Docker Desktop support](https://docs.docker.com/desktop/setup/install/windows-install/#system-requirements)
|
||||
|
||||
A system with a modern processor and a minimum of **4GB RAM** is recommended to run OpenHands.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="MacOS">
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Mac](https://docs.docker.com/desktop/setup/install/mac-install).
|
||||
2. Open Docker Desktop, go to `Settings > Advanced` and ensure `Allow the default Docker socket to be used` is enabled.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Linux">
|
||||
|
||||
<Note>
|
||||
Tested with Ubuntu 22.04.
|
||||
</Note>
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Linux](https://docs.docker.com/desktop/setup/install/linux/).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Windows">
|
||||
|
||||
**WSL**
|
||||
|
||||
1. [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
2. Run `wsl --version` in powershell and confirm `Default Version: 2`.
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Windows](https://docs.docker.com/desktop/setup/install/windows-install).
|
||||
2. Open Docker Desktop, go to `Settings` and confirm the following:
|
||||
- General: `Use the WSL 2 based engine` is enabled.
|
||||
- Resources > WSL Integration: `Enable integration with my default WSL distro` is enabled.
|
||||
|
||||
<Note>
|
||||
The docker command below to start the app must be run inside the WSL terminal.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
### Start the App
|
||||
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41
|
||||
```
|
||||
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
|
||||
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes/docker#connecting-to-your-filesystem),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
|
||||
interact with it via a [friendly CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),
|
||||
or run it on tagged issues with [a GitHub action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
### Setup
|
||||
|
||||
After launching OpenHands, you **must** select an `LLM Provider` and `LLM Model` and enter a corresponding `API Key`.
|
||||
This can be done during the initial settings popup or by selecting the `Settings`
|
||||
button (gear icon) in the UI.
|
||||
|
||||
If the required model does not exist in the list, in `Settings` under the `LLM` tab, you can toggle `Advanced` options
|
||||
and manually enter it with the correct prefix in the `Custom Model` text box.
|
||||
The `Advanced` options also allow you to specify a `Base URL` if required.
|
||||
|
||||
#### Getting an API Key
|
||||
|
||||
OpenHands requires an API key to access most language models. Here's how to get an API key from the recommended providers:
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="Anthropic (Claude)">
|
||||
|
||||
1. [Create an Anthropic account](https://console.anthropic.com/).
|
||||
2. [Generate an API key](https://console.anthropic.com/settings/keys).
|
||||
3. [Set up billing](https://console.anthropic.com/settings/billing).
|
||||
|
||||
Consider setting usage limits to control costs.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="OpenAI">
|
||||
|
||||
1. [Create an OpenAI account](https://platform.openai.com/).
|
||||
2. [Generate an API key](https://platform.openai.com/api-keys).
|
||||
3. [Set up billing](https://platform.openai.com/account/billing/overview).
|
||||
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
#### Setting Up Search Engine
|
||||
|
||||
OpenHands can be configured to use a search engine to allow the agent to search the web for information when needed.
|
||||
|
||||
Search functionality is enabled by default in OpenHands Cloud. No additional setup is required.
|
||||
|
||||
To enable search functionality in self-hosted OpenHands:
|
||||
|
||||
1. Get a Tavily API key from [tavily.com](https://tavily.com/)
|
||||
2. Enter the API key in the Settings page under `LLM` tab, `Search API Key (Tavily)`
|
||||
|
||||
For more details, see the [Search Engine Setup](/usage/search-engine-setup) guide.
|
||||
|
||||
|
||||
Now you're ready to [get started with OpenHands](./getting-started).
|
||||
|
||||
#### Versions
|
||||
|
||||
The [docker command above](./installation#start-the-app) pulls the most recent stable release of OpenHands. You have other options as well:
|
||||
- For a specific release, replace `$VERSION` in `openhands:$VERSION` and `runtime:$VERSION`, with the version number.
|
||||
We use SemVer so `0.9` will automatically point to the latest `0.9.x` release, and `0` will point to the latest `0.x.x` release.
|
||||
- For the most up-to-date development version, replace `$VERSION` in `openhands:$VERSION` and `runtime:$VERSION`, with `main`.
|
||||
This version is unstable and is recommended for testing or development purposes only.
|
||||
|
||||
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
|
||||
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
|
||||
|
||||
@@ -48,31 +48,31 @@ We recommend using [LMStudio](https://lmstudio.ai/) for serving these models loc
|
||||
|
||||
### Start OpenHands with locally served model
|
||||
|
||||
Check [the installation guide](/usage/local-setup) to make sure you have all the prerequisites for running OpenHands.
|
||||
Check [the installation guide](https://docs.all-hands.dev/modules/usage/installation) to make sure you have all the prerequisites for running OpenHands.
|
||||
|
||||
```bash
|
||||
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.42-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
|
||||
|
||||
mkdir -p ~/.openhands-state && 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-state/settings.json
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.42
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.41
|
||||
```
|
||||
|
||||
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.42
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.41
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Getting started with running OpenHands on your own.
|
||||
---
|
||||
|
||||
## Recommended Methods for Running Openhands on Your Local System
|
||||
|
||||
### System Requirements
|
||||
|
||||
- MacOS with [Docker Desktop support](https://docs.docker.com/desktop/setup/install/mac-install/#system-requirements)
|
||||
- Linux
|
||||
- Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) and [Docker Desktop support](https://docs.docker.com/desktop/setup/install/windows-install/#system-requirements)
|
||||
|
||||
A system with a modern processor and a minimum of **4GB RAM** is recommended to run OpenHands.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="MacOS">
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Mac](https://docs.docker.com/desktop/setup/install/mac-install).
|
||||
2. Open Docker Desktop, go to `Settings > Advanced` and ensure `Allow the default Docker socket to be used` is enabled.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Linux">
|
||||
|
||||
<Note>
|
||||
Tested with Ubuntu 22.04.
|
||||
</Note>
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Linux](https://docs.docker.com/desktop/setup/install/linux/).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Windows">
|
||||
|
||||
**WSL**
|
||||
|
||||
1. [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
2. Run `wsl --version` in powershell and confirm `Default Version: 2`.
|
||||
|
||||
**Docker Desktop**
|
||||
|
||||
1. [Install Docker Desktop on Windows](https://docs.docker.com/desktop/setup/install/windows-install).
|
||||
2. Open Docker Desktop, go to `Settings` and confirm the following:
|
||||
- General: `Use the WSL 2 based engine` is enabled.
|
||||
- Resources > WSL Integration: `Enable integration with my default WSL distro` is enabled.
|
||||
|
||||
<Note>
|
||||
The docker command below to start the app must be run inside the WSL terminal.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
### Start the App
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.42-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.42
|
||||
```
|
||||
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
|
||||
### Setup
|
||||
|
||||
After launching OpenHands, you **must** select an `LLM Provider` and `LLM Model` and enter a corresponding `API Key`.
|
||||
This can be done during the initial settings popup or by selecting the `Settings`
|
||||
button (gear icon) in the UI.
|
||||
|
||||
If the required model does not exist in the list, in `Settings` under the `LLM` tab, you can toggle `Advanced` options
|
||||
and manually enter it with the correct prefix in the `Custom Model` text box.
|
||||
The `Advanced` options also allow you to specify a `Base URL` if required.
|
||||
|
||||
#### Getting an API Key
|
||||
|
||||
OpenHands requires an API key to access most language models. Here's how to get an API key from the recommended providers:
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="Anthropic (Claude)">
|
||||
|
||||
1. [Create an Anthropic account](https://console.anthropic.com/).
|
||||
2. [Generate an API key](https://console.anthropic.com/settings/keys).
|
||||
3. [Set up billing](https://console.anthropic.com/settings/billing).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="OpenAI">
|
||||
|
||||
1. [Create an OpenAI account](https://platform.openai.com/).
|
||||
2. [Generate an API key](https://platform.openai.com/api-keys).
|
||||
3. [Set up billing](https://platform.openai.com/account/billing/overview).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Google (Gemini)">
|
||||
|
||||
1. Create a Google account if you don't already have one.
|
||||
2. [Generate an API key](https://aistudio.google.com/apikey).
|
||||
3. [Set up billing](https://aistudio.google.com/usage?tab=billing).
|
||||
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
Consider setting usage limits to control costs.
|
||||
|
||||
#### Setting Up Search Engine
|
||||
|
||||
OpenHands can be configured to use a search engine to allow the agent to search the web for information when needed.
|
||||
|
||||
To enable search functionality in OpenHands:
|
||||
|
||||
1. Get a Tavily API key from [tavily.com](https://tavily.com/).
|
||||
2. Enter the Tavily API key in the Settings page under `LLM` tab > `Search API Key (Tavily)`
|
||||
|
||||
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:
|
||||
- For a specific release, replace `$VERSION` in `openhands:$VERSION` and `runtime:$VERSION`, with the version number.
|
||||
For example, `0.9` will automatically point to the latest `0.9.x` release, and `0` will point to the latest `0.x.x` release.
|
||||
- For the most up-to-date development version, replace `$VERSION` in `openhands:$VERSION` and `runtime:$VERSION`, with `main`.
|
||||
This version is unstable and is recommended for testing or development purposes only.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Connect OpenHands to your local filesystem.](/usage/runtimes/docker#connecting-to-your-filesystem) to use OpenHands with your GitHub repositories
|
||||
- [Run OpenHands in a scriptable headless mode.](/usage/how-to/headless-mode)
|
||||
- [Run OpenHands with a friendly CLI.](/usage/how-to/cli-mode)
|
||||
- [Run OpenHands on tagged issues with a GitHub action.](/usage/how-to/github-action)
|
||||
@@ -11,7 +11,7 @@ Currently OpenHands supports the following types of microagents:
|
||||
- [Keyword-Triggered Microagents](./microagents-keyword): Guidelines activated by specific keywords in prompts.
|
||||
|
||||
To customize OpenHands' behavior, create a .openhands/microagents/ directory in the root of your repository and
|
||||
add `<microagent_name>.md` files inside. For repository-specific guidelines, you can ask OpenHands to analyze your repository and create a comprehensive `repo.md` file (see [General Microagents](./microagents-repo) for details).
|
||||
add `<microagent_name>.md` files inside.
|
||||
|
||||
<Note>
|
||||
Loaded microagents take up space in the context window.
|
||||
|
||||
@@ -17,45 +17,13 @@ Frontmatter should be enclosed in triple dashes (---) and may include the follow
|
||||
|-----------|-----------------------------------------|----------|----------------|
|
||||
| `agent` | The agent this microagent applies to | No | 'CodeActAgent' |
|
||||
|
||||
## Creating a Comprehensive Repository Agent
|
||||
|
||||
To create an effective repository agent, you can ask OpenHands to analyze your repository with a prompt like:
|
||||
## Example
|
||||
|
||||
General microagent file example located at `.openhands/microagents/repo.md`:
|
||||
```
|
||||
Please browse the repository, look at the documentation and relevant code, and understand the purpose of this repository.
|
||||
|
||||
Specifically, I want you to create a `.openhands/microagents/repo.md` file. This file should contain succinct information that summarizes:
|
||||
1. The purpose of this repository
|
||||
2. The general setup of this repo
|
||||
3. A brief description of the structure of this repo
|
||||
|
||||
Read all the GitHub workflows under .github/ of the repository (if this folder exists) to understand the CI checks (e.g., linter, pre-commit), and include those in the repo.md file.
|
||||
```
|
||||
|
||||
This approach helps OpenHands capture repository context efficiently, reducing the need for repeated searches during conversations and ensuring more accurate solutions.
|
||||
|
||||
## Example Content
|
||||
|
||||
A comprehensive repository agent file (`.openhands/microagents/repo.md`) should include:
|
||||
|
||||
```
|
||||
# Repository Purpose
|
||||
This project is a TODO application that allows users to track TODO items.
|
||||
|
||||
# Setup Instructions
|
||||
To set it up, you can run `npm run build`.
|
||||
|
||||
# Repository Structure
|
||||
- `/src`: Core application code
|
||||
- `/tests`: Test suite
|
||||
- `/docs`: Documentation
|
||||
- `/.github`: CI/CD workflows
|
||||
|
||||
# CI/CD Workflows
|
||||
- `lint.yml`: Runs ESLint on all JavaScript files
|
||||
- `test.yml`: Runs the test suite on pull requests
|
||||
|
||||
# Development Guidelines
|
||||
Always make sure the tests are passing before committing changes. You can run the tests by running `npm run test`.
|
||||
```
|
||||
|
||||
|
||||
@@ -71,27 +71,10 @@ EVAL_CONDENSER=summarizer_for_eval \
|
||||
The name is up to you, but should match a name defined in your `config.toml` file. The last argument in the command specifies the condenser configuration to use. In this case, `summarizer_for_eval` is used, which refers to the LLM-based summarizing condenser as defined above.
|
||||
|
||||
If no condenser configuration is specified, the 'noop' condenser will be used by default, which keeps the full conversation history.
|
||||
```
|
||||
|
||||
For other configurations specific to evaluation, such as `save_trajectory_path`, these are typically set in the `get_config` function of the respective `run_infer.py` file for each benchmark.
|
||||
|
||||
### Enabling LLM-Based Editor Tools
|
||||
|
||||
The LLM-Based Editor tool (currently supported only for SWE-Bench) can be enabled by setting:
|
||||
```bash
|
||||
export ENABLE_LLM_EDITOR=true
|
||||
```
|
||||
|
||||
You can set the config for the Editor LLM as:
|
||||
```toml
|
||||
[llm.draft_editor]
|
||||
base_url = "http://localhost:9002/v1"
|
||||
model = "hosted_vllm/lite_coder_qwen_editor_3B"
|
||||
api_key = ""
|
||||
temperature = 0.7
|
||||
max_input_tokens = 10500
|
||||
max_output_tokens = 10500
|
||||
```
|
||||
|
||||
## Supported Benchmarks
|
||||
|
||||
The OpenHands evaluation harness supports a wide variety of benchmarks across [software engineering](#software-engineering), [web browsing](#web-browsing), [miscellaneous assistance](#misc-assistance), and [real-world](#real-world) tasks.
|
||||
|
||||
@@ -42,7 +42,7 @@ from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
@@ -62,7 +62,6 @@ from openhands.utils.shutdown_listener import sleep_if_should_continue
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
RUN_WITH_BROWSING = os.environ.get('RUN_WITH_BROWSING', 'false').lower() == 'true'
|
||||
ENABLE_LLM_EDITOR = os.environ.get('ENABLE_LLM_EDITOR', 'false').lower() == 'true'
|
||||
BenchMode = Literal['swe', 'swt', 'swt-ci']
|
||||
|
||||
|
||||
@@ -255,19 +254,15 @@ def get_config(
|
||||
workspace_base=None,
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
|
||||
config.set_llm_config(
|
||||
update_llm_config_for_completions_logging(
|
||||
metadata.llm_config, metadata.eval_output_dir, instance['instance_id']
|
||||
)
|
||||
)
|
||||
# get 'draft_editor' config if exists
|
||||
config.set_llm_config(get_llm_config_arg('draft_editor'), 'draft_editor')
|
||||
|
||||
agent_config = AgentConfig(
|
||||
enable_jupyter=False,
|
||||
enable_browsing=RUN_WITH_BROWSING,
|
||||
enable_llm_editor=ENABLE_LLM_EDITOR,
|
||||
enable_llm_editor=False,
|
||||
enable_mcp=False,
|
||||
condenser=metadata.condenser_config,
|
||||
enable_prompt_extensions=False,
|
||||
|
||||
@@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import type { Message } from "#/message";
|
||||
import { SUGGESTIONS } from "#/utils/suggestions";
|
||||
import { WsClientProviderStatus } from "#/context/ws-client-provider";
|
||||
import { ChatInterface } from "#/components/features/chat/chat-interface";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -18,7 +19,7 @@ describe("Empty state", () => {
|
||||
const { useWsClient: useWsClientMock } = vi.hoisted(() => ({
|
||||
useWsClient: vi.fn(() => ({
|
||||
send: sendMock,
|
||||
status: "CONNECTED",
|
||||
status: WsClientProviderStatus.CONNECTED,
|
||||
isLoadingMessages: false,
|
||||
})),
|
||||
}));
|
||||
@@ -63,7 +64,7 @@ describe("Empty state", () => {
|
||||
// this is to test that the message is in the UI before the socket is called
|
||||
useWsClientMock.mockImplementation(() => ({
|
||||
send: sendMock,
|
||||
status: "CONNECTED",
|
||||
status: WsClientProviderStatus.CONNECTED,
|
||||
isLoadingMessages: false,
|
||||
}));
|
||||
const user = userEvent.setup();
|
||||
@@ -86,7 +87,7 @@ describe("Empty state", () => {
|
||||
async () => {
|
||||
useWsClientMock.mockImplementation(() => ({
|
||||
send: sendMock,
|
||||
status: "CONNECTED",
|
||||
status: WsClientProviderStatus.CONNECTED,
|
||||
isLoadingMessages: false,
|
||||
}));
|
||||
const user = userEvent.setup();
|
||||
@@ -100,7 +101,7 @@ describe("Empty state", () => {
|
||||
|
||||
useWsClientMock.mockImplementation(() => ({
|
||||
send: sendMock,
|
||||
status: "CONNECTED",
|
||||
status: WsClientProviderStatus.CONNECTED,
|
||||
isLoadingMessages: false,
|
||||
}));
|
||||
rerender(<ChatInterface />);
|
||||
|
||||
@@ -6,21 +6,6 @@ import { renderWithProviders } from "test-utils";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import SettingsScreen from "#/routes/settings";
|
||||
import { PaymentForm } from "#/components/features/payment/payment-form";
|
||||
import * as useSettingsModule from "#/hooks/query/use-settings";
|
||||
|
||||
// Mock the useSettings hook
|
||||
vi.mock("#/hooks/query/use-settings", async () => {
|
||||
const actual = await vi.importActual<typeof import("#/hooks/query/use-settings")>("#/hooks/query/use-settings");
|
||||
return {
|
||||
...actual,
|
||||
useSettings: vi.fn().mockReturnValue({
|
||||
data: {
|
||||
EMAIL_VERIFIED: true, // Mock email as verified to prevent redirection
|
||||
},
|
||||
isLoading: false,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the i18next hook
|
||||
vi.mock("react-i18next", async () => {
|
||||
@@ -35,7 +20,6 @@ vi.mock("react-i18next", async () => {
|
||||
"SETTINGS$NAV_CREDITS": "Credits",
|
||||
"SETTINGS$NAV_API_KEYS": "API Keys",
|
||||
"SETTINGS$NAV_LLM": "LLM",
|
||||
"SETTINGS$NAV_USER": "User",
|
||||
"SETTINGS$TITLE": "Settings"
|
||||
};
|
||||
return translations[key] || key;
|
||||
@@ -63,10 +47,6 @@ describe("Settings Billing", () => {
|
||||
Component: () => <div data-testid="git-settings-screen" />,
|
||||
path: "/settings/git",
|
||||
},
|
||||
{
|
||||
Component: () => <div data-testid="user-settings-screen" />,
|
||||
path: "/settings/user",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.42.0",
|
||||
"version": "0.41.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.42.0",
|
||||
"version": "0.41.0",
|
||||
"dependencies": {
|
||||
"@heroui/react": "2.7.8",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.42.0",
|
||||
"version": "0.41.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -47,7 +47,6 @@ const SCAN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
||||
|
||||
// Attributes that typically don't contain user-facing text
|
||||
const NON_TEXT_ATTRIBUTES = [
|
||||
"allow",
|
||||
"className",
|
||||
"i18nKey",
|
||||
"testId",
|
||||
@@ -70,7 +69,6 @@ const NON_TEXT_ATTRIBUTES = [
|
||||
"aria-describedby",
|
||||
"aria-hidden",
|
||||
"role",
|
||||
"sandbox",
|
||||
];
|
||||
|
||||
function shouldIgnorePath(filePath) {
|
||||
@@ -116,7 +114,6 @@ const EXCLUDED_TECHNICAL_STRINGS = [
|
||||
"add-secret-form", // Test ID for secret form
|
||||
"edit-secret-form", // Test ID for secret form
|
||||
"search-api-key-input", // Input name for search API key
|
||||
"noopener,noreferrer", // Options for window.open
|
||||
];
|
||||
|
||||
function isExcludedTechnicalString(str) {
|
||||
|
||||
@@ -1,60 +1,5 @@
|
||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
||||
import axios from "axios";
|
||||
|
||||
export const openHands = axios.create({
|
||||
baseURL: `${window.location.protocol}//${import.meta.env.VITE_BACKEND_BASE_URL || window?.location.host}`,
|
||||
});
|
||||
|
||||
// Helper function to check if a response contains an email verification error
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const checkForEmailVerificationError = (data: any): boolean => {
|
||||
const EMAIL_NOT_VERIFIED = "EmailNotVerifiedError";
|
||||
|
||||
if (typeof data === "string") {
|
||||
return data.includes(EMAIL_NOT_VERIFIED);
|
||||
}
|
||||
|
||||
if (typeof data === "object" && data !== null) {
|
||||
if ("message" in data) {
|
||||
const { message } = data;
|
||||
if (typeof message === "string") {
|
||||
return message.includes(EMAIL_NOT_VERIFIED);
|
||||
}
|
||||
if (Array.isArray(message)) {
|
||||
return message.some(
|
||||
(msg) => typeof msg === "string" && msg.includes(EMAIL_NOT_VERIFIED),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Search any values in object in case message key is different
|
||||
return Object.values(data).some(
|
||||
(value) =>
|
||||
(typeof value === "string" && value.includes(EMAIL_NOT_VERIFIED)) ||
|
||||
(Array.isArray(value) &&
|
||||
value.some(
|
||||
(v) => typeof v === "string" && v.includes(EMAIL_NOT_VERIFIED),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Set up the global interceptor
|
||||
openHands.interceptors.response.use(
|
||||
(response: AxiosResponse) => response,
|
||||
(error: AxiosError) => {
|
||||
// Check if it's a 403 error with the email verification message
|
||||
if (
|
||||
error.response?.status === 403 &&
|
||||
checkForEmailVerificationError(error.response?.data)
|
||||
) {
|
||||
if (window.location.pathname !== "/settings/user") {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with the error for other error handlers
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -236,26 +236,6 @@ class OpenHands {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async startConversation(
|
||||
conversationId: string,
|
||||
): Promise<Conversation | null> {
|
||||
const { data } = await openHands.post<Conversation | null>(
|
||||
`/api/conversations/${conversationId}/start`,
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static async stopConversation(
|
||||
conversationId: string,
|
||||
): Promise<Conversation | null> {
|
||||
const { data } = await openHands.post<Conversation | null>(
|
||||
`/api/conversations/${conversationId}/stop`,
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings from the server or use the default settings if not found
|
||||
*/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" style="enable-background:new 0 0 468 222.5" version="1.1" viewBox="0 0 468 222.5">
|
||||
<style>
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#635bff}
|
||||
</style>
|
||||
<path d="M414 113.4c0-25.6-12.4-45.8-36.1-45.8-23.8 0-38.2 20.2-38.2 45.6 0 30.1 17 45.3 41.4 45.3 11.9 0 20.9-2.7 27.7-6.5v-20c-6.8 3.4-14.6 5.5-24.5 5.5-9.7 0-18.3-3.4-19.4-15.2h48.9c0-1.3.2-6.5.2-8.9zm-49.4-9.5c0-11.3 6.9-16 13.2-16 6.1 0 12.6 4.7 12.6 16h-25.8zM301.1 67.6c-9.8 0-16.1 4.6-19.6 7.8l-1.3-6.2h-22v116.6l25-5.3.1-28.3c3.6 2.6 8.9 6.3 17.7 6.3 17.9 0 34.2-14.4 34.2-46.1-.1-29-16.6-44.8-34.1-44.8zm-6 68.9c-5.9 0-9.4-2.1-11.8-4.7l-.1-37.1c2.6-2.9 6.2-4.9 11.9-4.9 9.1 0 15.4 10.2 15.4 23.3 0 13.4-6.2 23.4-15.4 23.4zM223.8 61.7l25.1-5.4V36l-25.1 5.3zM223.8 69.3h25.1v87.5h-25.1zM196.9 76.7l-1.6-7.4h-21.6v87.5h25V97.5c5.9-7.7 15.9-6.3 19-5.2v-23c-3.2-1.2-14.9-3.4-20.8 7.4zM146.9 47.6l-24.4 5.2-.1 80.1c0 14.8 11.1 25.7 25.9 25.7 8.2 0 14.2-1.5 17.5-3.3V135c-3.2 1.3-19 5.9-19-8.9V90.6h19V69.3h-19l.1-21.7zM79.3 94.7c0-3.9 3.2-5.4 8.5-5.4 7.6 0 17.2 2.3 24.8 6.4V72.2c-8.3-3.3-16.5-4.6-24.8-4.6C67.5 67.6 54 78.2 54 95.9c0 27.6 38 23.2 38 35.1 0 4.6-4 6.1-9.6 6.1-8.3 0-18.9-3.4-27.3-8v23.8c9.3 4 18.7 5.7 27.3 5.7 20.8 0 35.1-10.3 35.1-28.2-.1-29.8-38.2-24.5-38.2-35.7z" class="st0"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -9,7 +9,10 @@ import {
|
||||
AGENT_STATUS_MAP,
|
||||
IndicatorColor,
|
||||
} from "../../agent-status-map.constant";
|
||||
import { useWsClient } from "#/context/ws-client-provider";
|
||||
import {
|
||||
useWsClient,
|
||||
WsClientProviderStatus,
|
||||
} from "#/context/ws-client-provider";
|
||||
import { useNotification } from "#/hooks/useNotification";
|
||||
import { browserTab } from "#/utils/browser-tab";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
@@ -77,14 +80,11 @@ export function AgentStatusBar() {
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (conversation?.status === "CONNECTING") {
|
||||
setStatusMessage(t(I18nKey.STATUS$CONNECTING_TO_RUNTIME));
|
||||
setIndicatorColor(IndicatorColor.YELLOW);
|
||||
} else if (conversation?.status === "STARTING") {
|
||||
if (conversation?.status === "STARTING") {
|
||||
setStatusMessage(t(I18nKey.STATUS$STARTING_RUNTIME));
|
||||
setIndicatorColor(IndicatorColor.RED);
|
||||
} else if (status === "DISCONNECTED") {
|
||||
setStatusMessage(t(I18nKey.STATUS$WEBSOCKET_CLOSED));
|
||||
} else if (status === WsClientProviderStatus.DISCONNECTED) {
|
||||
setStatusMessage(t(I18nKey.STATUS$CONNECTED)); // Using STATUS$CONNECTED instead of STATUS$CONNECTING
|
||||
setIndicatorColor(IndicatorColor.RED);
|
||||
} else {
|
||||
setStatusMessage(AGENT_STATUS_MAP[curAgentState].message);
|
||||
|
||||
@@ -2,20 +2,9 @@ import ColdIcon from "./state-indicators/cold.svg?react";
|
||||
import RunningIcon from "./state-indicators/running.svg?react";
|
||||
|
||||
type SVGIcon = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
||||
export type ProjectStatus =
|
||||
| "RUNNING"
|
||||
| "STOPPED"
|
||||
| "STARTING"
|
||||
| "CONNECTING"
|
||||
| "CONNECTED"
|
||||
| "DISCONNECTED";
|
||||
export type ProjectStatus = "RUNNING" | "STOPPED" | "STARTING";
|
||||
|
||||
type ProjectStatusWithIcon = Exclude<
|
||||
ProjectStatus,
|
||||
"CONNECTING" | "CONNECTED" | "DISCONNECTED"
|
||||
>;
|
||||
|
||||
const INDICATORS: Record<ProjectStatusWithIcon, SVGIcon> = {
|
||||
const INDICATORS: Record<ProjectStatus, SVGIcon> = {
|
||||
STOPPED: ColdIcon,
|
||||
RUNNING: RunningIcon,
|
||||
STARTING: ColdIcon,
|
||||
@@ -28,7 +17,6 @@ interface ConversationStateIndicatorProps {
|
||||
export function ConversationStateIndicator({
|
||||
status,
|
||||
}: ConversationStateIndicatorProps) {
|
||||
// @ts-expect-error - Type 'ProjectStatus' is not assignable to type 'ProjectStatusWithIcon'.
|
||||
const StateIcon = INDICATORS[status];
|
||||
|
||||
return (
|
||||
|
||||
@@ -122,7 +122,7 @@ export function FileDiffViewer({ path, type }: FileDiffViewerProps) {
|
||||
modifiedEditor.onDidContentSizeChange(updateEditorHeight);
|
||||
};
|
||||
|
||||
const status = (type === "U" ? STATUS_MAP.A : STATUS_MAP[type]) || "?";
|
||||
const status = type === "U" ? STATUS_MAP.A : STATUS_MAP[type];
|
||||
|
||||
let statusIcon: React.ReactNode;
|
||||
if (typeof status === "string") {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useSettings } from "#/hooks/query/use-settings";
|
||||
|
||||
/**
|
||||
* A component that restricts access to routes based on email verification status.
|
||||
* If EMAIL_VERIFIED is false, only allows access to the /settings/user page.
|
||||
*/
|
||||
export function EmailVerificationGuard({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { data: settings, isLoading } = useSettings();
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
// If settings are still loading, don't do anything yet
|
||||
if (isLoading) return;
|
||||
|
||||
// If EMAIL_VERIFIED is explicitly false (not undefined or null)
|
||||
if (settings?.EMAIL_VERIFIED === false) {
|
||||
// Allow access to /settings/user but redirect from any other page
|
||||
if (pathname !== "/settings/user") {
|
||||
navigate("/settings/user", { replace: true });
|
||||
}
|
||||
}
|
||||
}, [settings?.EMAIL_VERIFIED, pathname, navigate, isLoading]);
|
||||
|
||||
return children;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { BrandButton } from "../settings/brand-button";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
import { amountIsValid } from "#/utils/amount-is-valid";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { PoweredByStripeTag } from "./powered-by-stripe-tag";
|
||||
|
||||
export function PaymentForm() {
|
||||
const { t } = useTranslation();
|
||||
@@ -80,7 +79,6 @@ export function PaymentForm() {
|
||||
{t(I18nKey.PAYMENT$ADD_CREDIT)}
|
||||
</BrandButton>
|
||||
{isPending && <LoadingSpinner size="small" />}
|
||||
<PoweredByStripeTag />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import stripeLogo from "#/assets/stripe.svg";
|
||||
|
||||
export function PoweredByStripeTag() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-medium font-semi-bold">
|
||||
{t(I18nKey.BILLING$POWERED_BY)}
|
||||
</span>
|
||||
<img src={stripeLogo} alt="Stripe" className="h-8" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -69,21 +69,16 @@ export function Sidebar() {
|
||||
<div className="flex items-center justify-center">
|
||||
<AllHandsLogoButton />
|
||||
</div>
|
||||
<NewProjectButton disabled={settings?.EMAIL_VERIFIED === false} />
|
||||
<NewProjectButton />
|
||||
<ConversationPanelButton
|
||||
isOpen={conversationPanelIsOpen}
|
||||
onClick={() =>
|
||||
settings?.EMAIL_VERIFIED === false
|
||||
? null
|
||||
: setConversationPanelIsOpen((prev) => !prev)
|
||||
}
|
||||
disabled={settings?.EMAIL_VERIFIED === false}
|
||||
onClick={() => setConversationPanelIsOpen((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row md:flex-col md:items-center gap-[26px] md:mb-4">
|
||||
<DocsButton disabled={settings?.EMAIL_VERIFIED === false} />
|
||||
<SettingsButton disabled={settings?.EMAIL_VERIFIED === false} />
|
||||
<DocsButton />
|
||||
<SettingsButton />
|
||||
<UserActions
|
||||
user={
|
||||
user.data ? { avatar_url: user.data.avatar_url } : undefined
|
||||
|
||||
@@ -8,13 +8,11 @@ import { cn } from "#/utils/utils";
|
||||
interface ConversationPanelButtonProps {
|
||||
isOpen: boolean;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function ConversationPanelButton({
|
||||
isOpen,
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: ConversationPanelButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -24,14 +22,10 @@ export function ConversationPanelButton({
|
||||
tooltip={t(I18nKey.SIDEBAR$CONVERSATIONS)}
|
||||
ariaLabel={t(I18nKey.SIDEBAR$CONVERSATIONS)}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<FaListUl
|
||||
size={22}
|
||||
className={cn(
|
||||
isOpen ? "text-white" : "text-[#9099AC]",
|
||||
disabled && "opacity-50",
|
||||
)}
|
||||
className={cn(isOpen ? "text-white" : "text-[#9099AC]")}
|
||||
/>
|
||||
</TooltipButton>
|
||||
);
|
||||
|
||||
@@ -3,24 +3,15 @@ import DocsIcon from "#/icons/academy.svg?react";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { TooltipButton } from "./tooltip-button";
|
||||
|
||||
interface DocsButtonProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function DocsButton({ disabled = false }: DocsButtonProps) {
|
||||
export function DocsButton() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.SIDEBAR$DOCS)}
|
||||
ariaLabel={t(I18nKey.SIDEBAR$DOCS)}
|
||||
href="https://docs.all-hands.dev"
|
||||
disabled={disabled}
|
||||
>
|
||||
<DocsIcon
|
||||
width={28}
|
||||
height={28}
|
||||
className={`text-[#9099AC] ${disabled ? "opacity-50" : ""}`}
|
||||
/>
|
||||
<DocsIcon width={28} height={28} className="text-[#9099AC]" />
|
||||
</TooltipButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ import { I18nKey } from "#/i18n/declaration";
|
||||
import PlusIcon from "#/icons/plus.svg?react";
|
||||
import { TooltipButton } from "./tooltip-button";
|
||||
|
||||
interface NewProjectButtonProps {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function NewProjectButton({ disabled = false }: NewProjectButtonProps) {
|
||||
export function NewProjectButton() {
|
||||
const { t } = useTranslation();
|
||||
const startNewProject = t(I18nKey.CONVERSATION$START_NEW);
|
||||
return (
|
||||
@@ -16,7 +12,6 @@ export function NewProjectButton({ disabled = false }: NewProjectButtonProps) {
|
||||
ariaLabel={startNewProject}
|
||||
navLinkTo="/"
|
||||
testId="new-project-button"
|
||||
disabled={disabled}
|
||||
>
|
||||
<PlusIcon width={28} height={28} />
|
||||
</TooltipButton>
|
||||
|
||||
@@ -5,13 +5,9 @@ import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface SettingsButtonProps {
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function SettingsButton({
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: SettingsButtonProps) {
|
||||
export function SettingsButton({ onClick }: SettingsButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -21,7 +17,6 @@ export function SettingsButton({
|
||||
ariaLabel={t(I18nKey.SETTINGS$TITLE)}
|
||||
onClick={onClick}
|
||||
navLinkTo="/settings"
|
||||
disabled={disabled}
|
||||
>
|
||||
<SettingsIcon width={28} height={28} />
|
||||
</TooltipButton>
|
||||
|
||||
@@ -12,7 +12,6 @@ export interface TooltipButtonProps {
|
||||
ariaLabel: string;
|
||||
testId?: string;
|
||||
className?: React.HTMLAttributes<HTMLButtonElement>["className"];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function TooltipButton({
|
||||
@@ -24,10 +23,9 @@ export function TooltipButton({
|
||||
ariaLabel,
|
||||
testId,
|
||||
className,
|
||||
disabled = false,
|
||||
}: TooltipButtonProps) {
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
if (onClick && !disabled) {
|
||||
if (onClick) {
|
||||
onClick();
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -39,12 +37,7 @@ export function TooltipButton({
|
||||
aria-label={ariaLabel}
|
||||
data-testid={testId}
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
"hover:opacity-80",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
className={cn("hover:opacity-80", className)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
@@ -52,7 +45,7 @@ export function TooltipButton({
|
||||
|
||||
let content;
|
||||
|
||||
if (navLinkTo && !disabled) {
|
||||
if (navLinkTo) {
|
||||
content = (
|
||||
<NavLink
|
||||
to={navLinkTo}
|
||||
@@ -70,24 +63,7 @@ export function TooltipButton({
|
||||
{children}
|
||||
</NavLink>
|
||||
);
|
||||
} else if (navLinkTo && disabled) {
|
||||
// If disabled and has navLinkTo, render a button that looks like a NavLink but doesn't navigate
|
||||
content = (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={ariaLabel}
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
"text-[#9099AC]",
|
||||
"opacity-50 cursor-not-allowed",
|
||||
className,
|
||||
)}
|
||||
disabled
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
} else if (href && !disabled) {
|
||||
} else if (href) {
|
||||
content = (
|
||||
<a
|
||||
href={href}
|
||||
@@ -100,19 +76,6 @@ export function TooltipButton({
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
} else if (href && disabled) {
|
||||
// If disabled and has href, render a button that looks like a link but doesn't navigate
|
||||
content = (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={ariaLabel}
|
||||
data-testid={testId}
|
||||
className={cn("opacity-50 cursor-not-allowed", className)}
|
||||
disabled
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
content = buttonContent;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ export function SettingsForm({ settings, models, onClose }: SettingsFormProps) {
|
||||
testId="llm-api-key-help-anchor"
|
||||
text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}
|
||||
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
|
||||
href="https://docs.all-hands.dev/usage/local-setup#getting-an-api-key"
|
||||
href="https://docs.all-hands.dev/usage/installation#getting-an-api-key"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
} from "#/types/core/guards";
|
||||
import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message";
|
||||
import { useWSErrorMessage } from "#/hooks/use-ws-error-message";
|
||||
import { ProjectStatus } from "#/components/features/conversation-panel/conversation-state-indicator";
|
||||
|
||||
const hasValidMessageProperty = (obj: unknown): obj is { message: string } =>
|
||||
typeof obj === "object" &&
|
||||
@@ -68,8 +67,14 @@ const isMessageAction = (
|
||||
): event is UserMessageAction | AssistantMessageAction =>
|
||||
isUserMessage(event) || isAssistantMessage(event);
|
||||
|
||||
export enum WsClientProviderStatus {
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
}
|
||||
|
||||
interface UseWsClient {
|
||||
status: ProjectStatus;
|
||||
status: WsClientProviderStatus;
|
||||
isLoadingMessages: boolean;
|
||||
events: Record<string, unknown>[];
|
||||
parsedEvents: (OpenHandsAction | OpenHandsObservation)[];
|
||||
@@ -77,7 +82,7 @@ interface UseWsClient {
|
||||
}
|
||||
|
||||
const WsClientContext = React.createContext<UseWsClient>({
|
||||
status: "DISCONNECTED",
|
||||
status: WsClientProviderStatus.DISCONNECTED,
|
||||
isLoadingMessages: true,
|
||||
events: [],
|
||||
parsedEvents: [],
|
||||
@@ -134,7 +139,9 @@ export function WsClientProvider({
|
||||
const { setErrorMessage, removeErrorMessage } = useWSErrorMessage();
|
||||
const queryClient = useQueryClient();
|
||||
const sioRef = React.useRef<Socket | null>(null);
|
||||
const [status, setStatus] = React.useState<ProjectStatus>("CONNECTING");
|
||||
const [status, setStatus] = React.useState(
|
||||
WsClientProviderStatus.DISCONNECTED,
|
||||
);
|
||||
const [events, setEvents] = React.useState<Record<string, unknown>[]>([]);
|
||||
const [parsedEvents, setParsedEvents] = React.useState<
|
||||
(OpenHandsAction | OpenHandsObservation)[]
|
||||
@@ -143,8 +150,7 @@ export function WsClientProvider({
|
||||
const { providers } = useUserProviders();
|
||||
|
||||
const messageRateHandler = useRate({ threshold: 250 });
|
||||
const { data: conversation, refetch: refetchConversation } =
|
||||
useActiveConversation();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
|
||||
function send(event: Record<string, unknown>) {
|
||||
if (!sioRef.current) {
|
||||
@@ -155,7 +161,7 @@ export function WsClientProvider({
|
||||
}
|
||||
|
||||
function handleConnect() {
|
||||
setStatus("CONNECTED");
|
||||
setStatus(WsClientProviderStatus.CONNECTED);
|
||||
removeErrorMessage();
|
||||
}
|
||||
|
||||
@@ -254,7 +260,7 @@ export function WsClientProvider({
|
||||
}
|
||||
|
||||
function handleDisconnect(data: unknown) {
|
||||
setStatus("DISCONNECTED");
|
||||
setStatus(WsClientProviderStatus.DISCONNECTED);
|
||||
const sio = sioRef.current;
|
||||
if (!sio) {
|
||||
return;
|
||||
@@ -263,12 +269,15 @@ export function WsClientProvider({
|
||||
sio.io.opts.query.latest_event_id = lastEventRef.current?.id;
|
||||
updateStatusWhenErrorMessagePresent(data);
|
||||
|
||||
setErrorMessage(hasValidMessageProperty(data) ? data.message : "");
|
||||
setErrorMessage(
|
||||
hasValidMessageProperty(data)
|
||||
? data.message
|
||||
: "The WebSocket connection was closed.",
|
||||
);
|
||||
}
|
||||
|
||||
function handleError(data: unknown) {
|
||||
// set status
|
||||
setStatus("DISCONNECTED");
|
||||
setStatus(WsClientProviderStatus.DISCONNECTED);
|
||||
updateStatusWhenErrorMessagePresent(data);
|
||||
|
||||
setErrorMessage(
|
||||
@@ -276,9 +285,6 @@ export function WsClientProvider({
|
||||
? data.message
|
||||
: "An unknown error occurred on the WebSocket connection.",
|
||||
);
|
||||
|
||||
// check if something went wrong with the conversation.
|
||||
refetchConversation();
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -287,26 +293,19 @@ export function WsClientProvider({
|
||||
// reset events when conversationId changes
|
||||
setEvents([]);
|
||||
setParsedEvents([]);
|
||||
setStatus("CONNECTING");
|
||||
setStatus(WsClientProviderStatus.DISCONNECTED);
|
||||
}, [conversationId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!conversationId) {
|
||||
throw new Error("No conversation ID provided");
|
||||
}
|
||||
if (
|
||||
!conversation ||
|
||||
["STOPPED", "STARTING"].includes(conversation.status)
|
||||
) {
|
||||
if (!conversation || conversation.status === "STARTING") {
|
||||
return () => undefined; // conversation not yet loaded
|
||||
}
|
||||
|
||||
let sio = sioRef.current;
|
||||
|
||||
if (sio?.connected) {
|
||||
sio.disconnect();
|
||||
}
|
||||
|
||||
const lastEvent = lastEventRef.current;
|
||||
const query = {
|
||||
latest_event_id: lastEvent?.id ?? -1,
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
import { useEffect } from "react";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import { useUserConversation } from "./use-user-conversation";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
const FIVE_MINUTES = 1000 * 60 * 5;
|
||||
|
||||
export const useActiveConversation = () => {
|
||||
const { conversationId } = useConversationId();
|
||||
const userConversation = useUserConversation(conversationId, (query) => {
|
||||
return useUserConversation(conversationId, (query) => {
|
||||
if (query.state.data?.status === "STARTING") {
|
||||
return 3000; // 3 seconds
|
||||
return 2000; // 2 seconds
|
||||
}
|
||||
return FIVE_MINUTES;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const conversation = userConversation.data;
|
||||
OpenHands.setCurrentConversation(conversation || null);
|
||||
}, [
|
||||
conversationId,
|
||||
userConversation.isFetched,
|
||||
userConversation?.data?.status,
|
||||
]);
|
||||
return userConversation;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React from "react";
|
||||
import { useWsClient } from "#/context/ws-client-provider";
|
||||
import {
|
||||
useWsClient,
|
||||
WsClientProviderStatus,
|
||||
} from "#/context/ws-client-provider";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
@@ -14,7 +17,7 @@ export const useConversationConfig = () => {
|
||||
if (!conversationId) throw new Error("No conversation ID");
|
||||
return OpenHands.getRuntimeId(conversationId);
|
||||
},
|
||||
enabled: status !== "DISCONNECTED" && !!conversationId,
|
||||
enabled: status !== WsClientProviderStatus.DISCONNECTED && !!conversationId,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
});
|
||||
|
||||
@@ -27,8 +27,7 @@ const getSettingsQueryFn = async (): Promise<Settings> => {
|
||||
apiSettings.enable_proactive_conversation_starters,
|
||||
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
|
||||
SEARCH_API_KEY: apiSettings.search_api_key || "",
|
||||
EMAIL: apiSettings.email || "",
|
||||
EMAIL_VERIFIED: apiSettings.email_verified,
|
||||
|
||||
MCP_CONFIG: apiSettings.mcp_config,
|
||||
IS_NEW_USER: false,
|
||||
};
|
||||
@@ -45,7 +44,6 @@ export const useSettings = () => {
|
||||
// would want to show the modal immediately if the
|
||||
// settings are not found
|
||||
retry: (_, error) => error.status !== 404,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
enabled: !isOnTosPage && !!userIsAuthenticated,
|
||||
|
||||
@@ -23,6 +23,7 @@ export const useUserConversation = (
|
||||
queryKey: ["user", "conversation", cid],
|
||||
queryFn: async () => {
|
||||
const conversation = await OpenHands.getConversation(cid!);
|
||||
OpenHands.setCurrentConversation(conversation);
|
||||
return conversation;
|
||||
},
|
||||
enabled: !!cid,
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
// this file generate by script, don't modify it manually!!!
|
||||
export enum I18nKey {
|
||||
MICROAGENT$NO_REPOSITORY_FOUND = "MICROAGENT$NO_REPOSITORY_FOUND",
|
||||
MICROAGENT$ADD_TO_MICROAGENT = "MICROAGENT$ADD_TO_MICROAGENT",
|
||||
MICROAGENT$WHAT_TO_ADD = "MICROAGENT$WHAT_TO_ADD",
|
||||
MICROAGENT$WHERE_TO_PUT = "MICROAGENT$WHERE_TO_PUT",
|
||||
MICROAGENT$ADD_TRIGGER = "MICROAGENT$ADD_TRIGGER",
|
||||
MICROAGENT$WAIT_FOR_RUNTIME = "MICROAGENT$WAIT_FOR_RUNTIME",
|
||||
MICROAGENT$ADDING_CONTEXT = "MICROAGENT$ADDING_CONTEXT",
|
||||
MICROAGENT$VIEW_CONVERSATION = "MICROAGENT$VIEW_CONVERSATION",
|
||||
MICROAGENT$SUCCESS_PR_READY = "MICROAGENT$SUCCESS_PR_READY",
|
||||
STATUS$CONNECTING_TO_RUNTIME = "STATUS$CONNECTING_TO_RUNTIME",
|
||||
STATUS$WEBSOCKET_CLOSED = "STATUS$WEBSOCKET_CLOSED",
|
||||
HOME$LAUNCH_FROM_SCRATCH = "HOME$LAUNCH_FROM_SCRATCH",
|
||||
HOME$READ_THIS = "HOME$READ_THIS",
|
||||
AUTH$LOGGING_BACK_IN = "AUTH$LOGGING_BACK_IN",
|
||||
@@ -149,9 +138,7 @@ export enum I18nKey {
|
||||
VSCODE$LOADING = "VSCODE$LOADING",
|
||||
VSCODE$URL_NOT_AVAILABLE = "VSCODE$URL_NOT_AVAILABLE",
|
||||
VSCODE$FETCH_ERROR = "VSCODE$FETCH_ERROR",
|
||||
VSCODE$CROSS_ORIGIN_WARNING = "VSCODE$CROSS_ORIGIN_WARNING",
|
||||
VSCODE$URL_PARSE_ERROR = "VSCODE$URL_PARSE_ERROR",
|
||||
VSCODE$OPEN_IN_NEW_TAB = "VSCODE$OPEN_IN_NEW_TAB",
|
||||
VSCODE$IFRAME_PERMISSIONS = "VSCODE$IFRAME_PERMISSIONS",
|
||||
INCREASE_TEST_COVERAGE = "INCREASE_TEST_COVERAGE",
|
||||
AUTO_MERGE_PRS = "AUTO_MERGE_PRS",
|
||||
FIX_README = "FIX_README",
|
||||
@@ -345,7 +332,6 @@ export enum I18nKey {
|
||||
SETTINGS$CONFIRMATION_MODE_TOOLTIP = "SETTINGS$CONFIRMATION_MODE_TOOLTIP",
|
||||
SETTINGS$AGENT_SELECT_ENABLED = "SETTINGS$AGENT_SELECT_ENABLED",
|
||||
SETTINGS$SECURITY_ANALYZER = "SETTINGS$SECURITY_ANALYZER",
|
||||
SETTINGS$SECURITY_ANALYZER_PLACEHOLDER = "SETTINGS$SECURITY_ANALYZER_PLACEHOLDER",
|
||||
SETTINGS$DONT_KNOW_API_KEY = "SETTINGS$DONT_KNOW_API_KEY",
|
||||
SETTINGS$CLICK_FOR_INSTRUCTIONS = "SETTINGS$CLICK_FOR_INSTRUCTIONS",
|
||||
SETTINGS$SAVED = "SETTINGS$SAVED",
|
||||
@@ -480,7 +466,6 @@ export enum I18nKey {
|
||||
BILLING$YOUVE_GOT_50 = "BILLING$YOUVE_GOT_50",
|
||||
BILLING$ERROR_WHILE_CREATING_SESSION = "BILLING$ERROR_WHILE_CREATING_SESSION",
|
||||
BILLING$CLAIM_YOUR_50 = "BILLING$CLAIM_YOUR_50",
|
||||
BILLING$POWERED_BY = "BILLING$POWERED_BY",
|
||||
BILLING$PROCEED_TO_STRIPE = "BILLING$PROCEED_TO_STRIPE",
|
||||
BILLING$YOURE_IN = "BILLING$YOURE_IN",
|
||||
PAYMENT$ADD_FUNDS = "PAYMENT$ADD_FUNDS",
|
||||
@@ -567,19 +552,4 @@ export enum I18nKey {
|
||||
TIPS$PROTIP = "TIPS$PROTIP",
|
||||
FEEDBACK$SUBMITTING_LABEL = "FEEDBACK$SUBMITTING_LABEL",
|
||||
FEEDBACK$SUBMITTING_MESSAGE = "FEEDBACK$SUBMITTING_MESSAGE",
|
||||
SETTINGS$NAV_USER = "SETTINGS$NAV_USER",
|
||||
SETTINGS$USER_TITLE = "SETTINGS$USER_TITLE",
|
||||
SETTINGS$USER_EMAIL = "SETTINGS$USER_EMAIL",
|
||||
SETTINGS$USER_EMAIL_LOADING = "SETTINGS$USER_EMAIL_LOADING",
|
||||
SETTINGS$SAVE = "SETTINGS$SAVE",
|
||||
SETTINGS$EMAIL_SAVED_SUCCESSFULLY = "SETTINGS$EMAIL_SAVED_SUCCESSFULLY",
|
||||
SETTINGS$EMAIL_VERIFIED_SUCCESSFULLY = "SETTINGS$EMAIL_VERIFIED_SUCCESSFULLY",
|
||||
SETTINGS$FAILED_TO_SAVE_EMAIL = "SETTINGS$FAILED_TO_SAVE_EMAIL",
|
||||
SETTINGS$SENDING = "SETTINGS$SENDING",
|
||||
SETTINGS$VERIFICATION_EMAIL_SENT = "SETTINGS$VERIFICATION_EMAIL_SENT",
|
||||
SETTINGS$EMAIL_VERIFICATION_REQUIRED = "SETTINGS$EMAIL_VERIFICATION_REQUIRED",
|
||||
SETTINGS$INVALID_EMAIL_FORMAT = "SETTINGS$INVALID_EMAIL_FORMAT",
|
||||
SETTINGS$EMAIL_VERIFICATION_RESTRICTION_MESSAGE = "SETTINGS$EMAIL_VERIFICATION_RESTRICTION_MESSAGE",
|
||||
SETTINGS$RESEND_VERIFICATION = "SETTINGS$RESEND_VERIFICATION",
|
||||
SETTINGS$FAILED_TO_RESEND_VERIFICATION = "SETTINGS$FAILED_TO_RESEND_VERIFICATION",
|
||||
}
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
{
|
||||
"STATUS$WEBSOCKET_CLOSED": {
|
||||
"en": "The WebSocket connection was closed.",
|
||||
"ja": "WebSocket接続が閉じられました。",
|
||||
"zh-CN": "WebSocket连接已关闭。",
|
||||
"zh-TW": "WebSocket連接已關閉。",
|
||||
"ko-KR": "WebSocket 연결이 닫혔습니다.",
|
||||
"no": "WebSocket-tilkoblingen ble lukket.",
|
||||
"it": "La connessione WebSocket è stata chiusa.",
|
||||
"pt": "A conexão WebSocket foi fechada.",
|
||||
"es": "La conexión WebSocket se ha cerrado.",
|
||||
"ar": "تم إغلاق اتصال WebSocket.",
|
||||
"fr": "La connexion WebSocket a été fermée.",
|
||||
"tr": "WebSocket bağlantısı kapatıldı.",
|
||||
"de": "Die WebSocket-Verbindung wurde geschlossen.",
|
||||
"uk": "З'єднання WebSocket було закрито."
|
||||
},
|
||||
"HOME$LAUNCH_FROM_SCRATCH": {
|
||||
"en": "Launch from Scratch",
|
||||
"ja": "ゼロから始める",
|
||||
@@ -2223,53 +2207,21 @@
|
||||
"tr": "VS Code URL'si alınamadı",
|
||||
"uk": "Не вдалося отримати VS Code URL"
|
||||
},
|
||||
"VSCODE$CROSS_ORIGIN_WARNING": {
|
||||
"en": "The code editor cannot be embedded due to browser security restrictions. Cross-origin cookies are being blocked.",
|
||||
"ja": "ブラウザのセキュリティ制限により、コードエディタを埋め込むことができません。クロスオリジンCookieがブロックされています。",
|
||||
"zh-CN": "由于浏览器安全限制,无法嵌入代码编辑器。跨源Cookie被阻止。",
|
||||
"zh-TW": "由於瀏覽器安全限制,無法嵌入代碼編輯器。跨源Cookie被阻止。",
|
||||
"ko-KR": "브라우저 보안 제한으로 인해 코드 편집기를 삽입할 수 없습니다. 교차 출처 쿠키가 차단되고 있습니다.",
|
||||
"de": "Der Code-Editor kann aufgrund von Browser-Sicherheitsbeschränkungen nicht eingebettet werden. Cross-Origin-Cookies werden blockiert.",
|
||||
"no": "Koderedigereren kan ikke bygges inn på grunn av nettleserens sikkerhetsbegrensninger. Cross-origin cookies blir blokkert.",
|
||||
"it": "L'editor di codice non può essere incorporato a causa delle restrizioni di sicurezza del browser. I cookie cross-origin vengono bloccati.",
|
||||
"pt": "O editor de código não pode ser incorporado devido a restrições de segurança do navegador. Cookies de origem cruzada estão sendo bloqueados.",
|
||||
"es": "El editor de código no se puede incrustar debido a las restricciones de seguridad del navegador. Las cookies de origen cruzado están siendo bloqueadas.",
|
||||
"ar": "لا يمكن تضمين محرر التعليمات البرمجية بسبب قيود أمان المتصفح. يتم حظر ملفات تعريف الارتباط عبر المصدر.",
|
||||
"fr": "L'éditeur de code ne peut pas être intégré en raison des restrictions de sécurité du navigateur. Les cookies cross-origin sont bloqués.",
|
||||
"tr": "Tarayıcı güvenlik kısıtlamaları nedeniyle kod düzenleyici yerleştirilemiyor. Çapraz kaynaklı çerezler engelleniyor.",
|
||||
"uk": "Редактор коду не може бути вбудований через обмеження безпеки браузера. Блокуються файли cookie з різних джерел."
|
||||
},
|
||||
"VSCODE$URL_PARSE_ERROR": {
|
||||
"en": "Error parsing URL",
|
||||
"ja": "URLの解析エラー",
|
||||
"zh-CN": "URL解析错误",
|
||||
"zh-TW": "URL解析錯誤",
|
||||
"ko-KR": "URL 구문 분석 오류",
|
||||
"de": "Fehler beim Parsen der URL",
|
||||
"no": "Feil ved parsing av URL",
|
||||
"it": "Errore durante l'analisi dell'URL",
|
||||
"pt": "Erro ao analisar URL",
|
||||
"es": "Error al analizar URL",
|
||||
"ar": "خطأ في تحليل عنوان URL",
|
||||
"fr": "Erreur d'analyse de l'URL",
|
||||
"tr": "URL ayrıştırma hatası",
|
||||
"uk": "Помилка аналізу URL"
|
||||
},
|
||||
"VSCODE$OPEN_IN_NEW_TAB": {
|
||||
"en": "Open in New Tab",
|
||||
"ja": "新しいタブで開く",
|
||||
"zh-CN": "在新标签页中打开",
|
||||
"zh-TW": "在新標籤頁中打開",
|
||||
"ko-KR": "새 탭에서 열기",
|
||||
"de": "In neuem Tab öffnen",
|
||||
"no": "Åpne i ny fane",
|
||||
"it": "Apri in una nuova scheda",
|
||||
"pt": "Abrir em nova aba",
|
||||
"es": "Abrir en nueva pestaña",
|
||||
"ar": "فتح في علامة تبويب جديدة",
|
||||
"fr": "Ouvrir dans un nouvel onglet",
|
||||
"tr": "Yeni Sekmede Aç",
|
||||
"uk": "Відкрити в новій вкладці"
|
||||
"VSCODE$IFRAME_PERMISSIONS": {
|
||||
"en": "clipboard-read; clipboard-write",
|
||||
"ja": "clipboard-read; clipboard-write",
|
||||
"zh-CN": "clipboard-read; clipboard-write",
|
||||
"zh-TW": "clipboard-read; clipboard-write",
|
||||
"ko-KR": "clipboard-read; clipboard-write",
|
||||
"de": "clipboard-read; clipboard-write",
|
||||
"no": "clipboard-read; clipboard-write",
|
||||
"it": "clipboard-read; clipboard-write",
|
||||
"pt": "clipboard-read; clipboard-write",
|
||||
"es": "clipboard-read; clipboard-write",
|
||||
"ar": "clipboard-read; clipboard-write",
|
||||
"fr": "clipboard-read; clipboard-write",
|
||||
"tr": "clipboard-read; clipboard-write",
|
||||
"uk": "clipboard-read; clipboard-write"
|
||||
},
|
||||
"INCREASE_TEST_COVERAGE": {
|
||||
"en": "Increase test coverage",
|
||||
@@ -5359,22 +5311,6 @@
|
||||
"ja": "セキュリティアナライザー",
|
||||
"uk": "Увімкнути аналізатор безпеки"
|
||||
},
|
||||
"SETTINGS$SECURITY_ANALYZER_PLACEHOLDER":{
|
||||
"en": "Select a security analyzer…",
|
||||
"de": "Wählen Sie einen Sicherheitsanalysator aus…",
|
||||
"zh-CN": "选择一个安全分析器…",
|
||||
"zh-TW": "選擇一個安全分析器…",
|
||||
"ko-KR": "보안 분석기를 선택하세요…",
|
||||
"no": "Velg en sikkerhetsanalysator…",
|
||||
"it": "Seleziona un analizzatore di sicurezza…",
|
||||
"pt": "Selecione um analisador de segurança…",
|
||||
"es": "Seleccione un analizador de seguridad…",
|
||||
"ar": "اختر محلل الأمان…",
|
||||
"fr": "Sélectionnez un analyseur de sécurité…",
|
||||
"tr": "Bir güvenlik analizörü seçin…",
|
||||
"ja": "セキュリティアナライザーを選択…",
|
||||
"uk": "Виберіть аналізатор безпеки…"
|
||||
},
|
||||
"SETTINGS$DONT_KNOW_API_KEY": {
|
||||
"en": "Don't know your API key?",
|
||||
"ja": "APIキーがわかりませんか?",
|
||||
@@ -5855,22 +5791,6 @@
|
||||
"tr": "Konuşmalar",
|
||||
"uk": "Розмови"
|
||||
},
|
||||
"STATUS$CONNECTING_TO_RUNTIME": {
|
||||
"en": "Connecting to runtime...",
|
||||
"zh-CN": "正在连接到运行时...",
|
||||
"zh-TW": "正在連接到執行時...",
|
||||
"de": "Verbinde mit der Laufzeitumgebung...",
|
||||
"ko-KR": "런타임에 연결 중...",
|
||||
"no": "Kobler til kjøretidsmiljø...",
|
||||
"it": "Connessione all'ambiente di esecuzione in corso...",
|
||||
"pt": "Conectando ao ambiente de execução...",
|
||||
"es": "Conectando al entorno de ejecución...",
|
||||
"ar": "جارٍ الاتصال ببيئة التشغيل...",
|
||||
"fr": "Connexion à l'environnement d'exécution en cours...",
|
||||
"tr": "Çalışma zamanı ortamına bağlanılıyor...",
|
||||
"ja": "ランタイムに接続中",
|
||||
"uk": "Підключення до середовища виконання..."
|
||||
},
|
||||
"STATUS$STARTING_RUNTIME": {
|
||||
"en": "Starting runtime...",
|
||||
"zh-CN": "启动运行时...",
|
||||
@@ -7535,22 +7455,6 @@
|
||||
"de": "Fügen Sie eine Kreditkarte mit Stripe hinzu, um $50 zu erhalten. <b>Wir belasten Sie nicht ohne vorherige Zustimmung!</b>",
|
||||
"uk": "Додайте кредитну картку до Stripe, щоб отримати свої 50 доларів. <b>Ми не стягуватимемо з вас плату без попереднього запиту!</b>"
|
||||
},
|
||||
"BILLING$POWERED_BY": {
|
||||
"en": "Powered by",
|
||||
"ja": "提供:",
|
||||
"zh-CN": "技术支持:",
|
||||
"zh-TW": "技術支援:",
|
||||
"ko-KR": "제공: ",
|
||||
"no": "Drevet av",
|
||||
"it": "Offerto da",
|
||||
"pt": "Oferecido por",
|
||||
"es": "Ofrecido por",
|
||||
"ar": "مشغل بواسطة",
|
||||
"fr": "Propulsé par",
|
||||
"tr": "Tarafından desteklenmektedir",
|
||||
"de": "Bereitgestellt von",
|
||||
"uk": "Працює на базі"
|
||||
},
|
||||
"BILLING$PROCEED_TO_STRIPE": {
|
||||
"en": "Add Billing Info",
|
||||
"ja": "請求情報を追加",
|
||||
@@ -8926,245 +8830,5 @@
|
||||
"tr": "Geri bildirim gönderiliyor, lütfen bekleyin...",
|
||||
"de": "Feedback senden, bitte warten...",
|
||||
"uk": "Відправляємо відгук, будь ласка, почекайте..."
|
||||
},
|
||||
"SETTINGS$NAV_USER": {
|
||||
"en": "User",
|
||||
"ja": "ユーザー",
|
||||
"zh-CN": "用户",
|
||||
"zh-TW": "用戶",
|
||||
"ko-KR": "사용자",
|
||||
"no": "Bruker",
|
||||
"it": "Utente",
|
||||
"pt": "Usuário",
|
||||
"es": "Usuario",
|
||||
"ar": "المستخدم",
|
||||
"fr": "Utilisateur",
|
||||
"tr": "Kullanıcı",
|
||||
"de": "Benutzer",
|
||||
"uk": "Користувач"
|
||||
},
|
||||
"SETTINGS$USER_TITLE": {
|
||||
"en": "User Information",
|
||||
"ja": "ユーザー情報",
|
||||
"zh-CN": "用户信息",
|
||||
"zh-TW": "用戶信息",
|
||||
"ko-KR": "사용자 정보",
|
||||
"no": "Brukerinformasjon",
|
||||
"it": "Informazioni utente",
|
||||
"pt": "Informações do usuário",
|
||||
"es": "Información del usuario",
|
||||
"ar": "معلومات المستخدم",
|
||||
"fr": "Informations utilisateur",
|
||||
"tr": "Kullanıcı Bilgileri",
|
||||
"de": "Benutzerinformationen",
|
||||
"uk": "Інформація про користувача"
|
||||
},
|
||||
"SETTINGS$USER_EMAIL": {
|
||||
"en": "Email",
|
||||
"ja": "メール",
|
||||
"zh-CN": "邮箱",
|
||||
"zh-TW": "郵箱",
|
||||
"ko-KR": "이메일",
|
||||
"no": "E-post",
|
||||
"it": "Email",
|
||||
"pt": "Email",
|
||||
"es": "Correo electrónico",
|
||||
"ar": "البريد الإلكتروني",
|
||||
"fr": "Email",
|
||||
"tr": "E-posta",
|
||||
"de": "E-Mail",
|
||||
"uk": "Електронна пошта"
|
||||
},
|
||||
"SETTINGS$USER_EMAIL_LOADING": {
|
||||
"en": "Loading...",
|
||||
"ja": "読み込み中...",
|
||||
"zh-CN": "加载中...",
|
||||
"zh-TW": "加載中...",
|
||||
"ko-KR": "로딩 중...",
|
||||
"no": "Laster...",
|
||||
"it": "Caricamento...",
|
||||
"pt": "Carregando...",
|
||||
"es": "Cargando...",
|
||||
"ar": "جار التحميل...",
|
||||
"fr": "Chargement...",
|
||||
"tr": "Yükleniyor...",
|
||||
"de": "Wird geladen...",
|
||||
"uk": "Завантаження..."
|
||||
},
|
||||
"SETTINGS$SAVE": {
|
||||
"en": "Save",
|
||||
"ja": "保存",
|
||||
"zh-CN": "保存",
|
||||
"zh-TW": "儲存",
|
||||
"ko-KR": "저장",
|
||||
"no": "Lagre",
|
||||
"it": "Salva",
|
||||
"pt": "Salvar",
|
||||
"es": "Guardar",
|
||||
"ar": "حفظ",
|
||||
"fr": "Enregistrer",
|
||||
"tr": "Kaydet",
|
||||
"de": "Speichern",
|
||||
"uk": "Зберегти"
|
||||
},
|
||||
"SETTINGS$EMAIL_SAVED_SUCCESSFULLY": {
|
||||
"en": "Email saved successfully",
|
||||
"ja": "メールが正常に保存されました",
|
||||
"zh-CN": "邮箱保存成功",
|
||||
"zh-TW": "郵箱儲存成功",
|
||||
"ko-KR": "이메일이 성공적으로 저장되었습니다",
|
||||
"no": "E-post lagret",
|
||||
"it": "Email salvata con successo",
|
||||
"pt": "Email salvo com sucesso",
|
||||
"es": "Correo electrónico guardado con éxito",
|
||||
"ar": "تم حفظ البريد الإلكتروني بنجاح",
|
||||
"fr": "Email enregistré avec succès",
|
||||
"tr": "E-posta başarıyla kaydedildi",
|
||||
"de": "E-Mail erfolgreich gespeichert",
|
||||
"uk": "Електронну пошту успішно збережено"
|
||||
},
|
||||
"SETTINGS$EMAIL_VERIFIED_SUCCESSFULLY": {
|
||||
"en": "Your email has been verified successfully!",
|
||||
"ja": "メールアドレスの確認が完了しました!",
|
||||
"zh-CN": "您的邮箱已成功验证!",
|
||||
"zh-TW": "您的郵箱已成功驗證!",
|
||||
"ko-KR": "이메일이 성공적으로 인증되었습니다!",
|
||||
"no": "E-posten din er bekreftet!",
|
||||
"it": "La tua email è stata verificata con successo!",
|
||||
"pt": "Seu email foi verificado com sucesso!",
|
||||
"es": "¡Tu correo electrónico ha sido verificado con éxito!",
|
||||
"ar": "تم التحقق من بريدك الإلكتروني بنجاح!",
|
||||
"fr": "Votre email a été vérifié avec succès !",
|
||||
"tr": "E-postanız başarıyla doğrulandı!",
|
||||
"de": "Ihre E-Mail wurde erfolgreich verifiziert!",
|
||||
"uk": "Вашу електронну пошту успішно підтверджено!"
|
||||
},
|
||||
"SETTINGS$FAILED_TO_SAVE_EMAIL": {
|
||||
"en": "Failed to save email",
|
||||
"ja": "メールの保存に失敗しました",
|
||||
"zh-CN": "保存邮箱失败",
|
||||
"zh-TW": "儲存郵箱失敗",
|
||||
"ko-KR": "이메일 저장 실패",
|
||||
"no": "Kunne ikke lagre e-post",
|
||||
"it": "Impossibile salvare l'email",
|
||||
"pt": "Falha ao salvar email",
|
||||
"es": "Error al guardar el correo electrónico",
|
||||
"ar": "فشل في حفظ البريد الإلكتروني",
|
||||
"fr": "Échec de l'enregistrement de l'email",
|
||||
"tr": "E-posta kaydedilemedi",
|
||||
"de": "E-Mail konnte nicht gespeichert werden",
|
||||
"uk": "Не вдалося зберегти електронну пошту"
|
||||
},
|
||||
"SETTINGS$SENDING": {
|
||||
"en": "Sending",
|
||||
"ja": "送信中",
|
||||
"zh-CN": "发送中",
|
||||
"zh-TW": "發送中",
|
||||
"ko-KR": "전송 중",
|
||||
"no": "Sender",
|
||||
"it": "Invio in corso",
|
||||
"pt": "Enviando",
|
||||
"es": "Enviando",
|
||||
"ar": "جاري الإرسال",
|
||||
"fr": "Envoi en cours",
|
||||
"tr": "Gönderiliyor",
|
||||
"de": "Wird gesendet",
|
||||
"uk": "Надсилання"
|
||||
},
|
||||
"SETTINGS$VERIFICATION_EMAIL_SENT": {
|
||||
"en": "Verification email sent",
|
||||
"ja": "確認メールを送信しました",
|
||||
"zh-CN": "验证邮件已发送",
|
||||
"zh-TW": "驗證郵件已發送",
|
||||
"ko-KR": "인증 이메일이 전송되었습니다",
|
||||
"no": "Bekreftelsese-post sendt",
|
||||
"it": "Email di verifica inviata",
|
||||
"pt": "Email de verificação enviado",
|
||||
"es": "Correo de verificación enviado",
|
||||
"ar": "تم إرسال بريد التحقق",
|
||||
"fr": "Email de vérification envoyé",
|
||||
"tr": "Doğrulama e-postası gönderildi",
|
||||
"de": "Bestätigungs-E-Mail gesendet",
|
||||
"uk": "Лист підтвердження надіслано"
|
||||
},
|
||||
"SETTINGS$EMAIL_VERIFICATION_REQUIRED": {
|
||||
"en": "You must verify your email address before using All Hands",
|
||||
"ja": "All Handsを使用する前にメールアドレスを確認する必要があります",
|
||||
"zh-CN": "使用All Hands前,您必须验证您的电子邮件地址",
|
||||
"zh-TW": "使用All Hands前,您必須驗證您的電子郵件地址",
|
||||
"ko-KR": "All Hands를 사용하기 전에 이메일 주소를 확인해야 합니다",
|
||||
"no": "Du må bekrefte e-postadressen din før du bruker All Hands",
|
||||
"it": "Devi verificare il tuo indirizzo email prima di utilizzare All Hands",
|
||||
"pt": "Você deve verificar seu endereço de e-mail antes de usar o All Hands",
|
||||
"es": "Debe verificar su dirección de correo electrónico antes de usar All Hands",
|
||||
"ar": "يجب عليك التحقق من عنوان بريدك الإلكتروني قبل استخدام All Hands",
|
||||
"fr": "Vous devez vérifier votre adresse e-mail avant d'utiliser All Hands",
|
||||
"tr": "All Hands'i kullanmadan önce e-posta adresinizi doğrulamanız gerekiyor",
|
||||
"de": "Sie müssen Ihre E-Mail-Adresse bestätigen, bevor Sie All Hands verwenden können",
|
||||
"uk": "Ви повинні підтвердити свою електронну адресу перед використанням All Hands"
|
||||
},
|
||||
"SETTINGS$INVALID_EMAIL_FORMAT": {
|
||||
"en": "Please enter a valid email address",
|
||||
"ja": "有効なメールアドレスを入力してください",
|
||||
"zh-CN": "请输入有效的电子邮件地址",
|
||||
"zh-TW": "請輸入有效的電子郵件地址",
|
||||
"ko-KR": "유효한 이메일 주소를 입력하세요",
|
||||
"no": "Vennligst skriv inn en gyldig e-postadresse",
|
||||
"it": "Inserisci un indirizzo email valido",
|
||||
"pt": "Por favor, insira um endereço de e-mail válido",
|
||||
"es": "Por favor, introduzca una dirección de correo electrónico válida",
|
||||
"ar": "الرجاء إدخال عنوان بريد إلكتروني صالح",
|
||||
"fr": "Veuillez entrer une adresse e-mail valide",
|
||||
"tr": "Lütfen geçerli bir e-posta adresi girin",
|
||||
"de": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
|
||||
"uk": "Будь ласка, введіть дійсну електронну адресу"
|
||||
},
|
||||
"SETTINGS$EMAIL_VERIFICATION_RESTRICTION_MESSAGE": {
|
||||
"en": "Your access is limited until your email is verified. You can only access this settings page.",
|
||||
"ja": "メールが確認されるまでアクセスが制限されています。この設定ページにのみアクセスできます。",
|
||||
"zh-CN": "在验证您的电子邮件之前,您的访问权限受到限制。您只能访问此设置页面。",
|
||||
"zh-TW": "在驗證您的電子郵件之前,您的訪問權限受到限制。您只能訪問此設置頁面。",
|
||||
"ko-KR": "이메일이 확인될 때까지 액세스가 제한됩니다. 이 설정 페이지만 액세스할 수 있습니다.",
|
||||
"no": "Din tilgang er begrenset til e-posten din er bekreftet. Du kan bare få tilgang til denne innstillingssiden.",
|
||||
"it": "Il tuo accesso è limitato fino a quando la tua email non viene verificata. Puoi accedere solo a questa pagina delle impostazioni.",
|
||||
"pt": "Seu acesso é limitado até que seu e-mail seja verificado. Você só pode acessar esta página de configurações.",
|
||||
"es": "Su acceso es limitado hasta que se verifique su correo electrónico. Solo puede acceder a esta página de configuración.",
|
||||
"ar": "وصولك محدود حتى يتم التحقق من بريدك الإلكتروني. يمكنك فقط الوصول إلى صفحة الإعدادات هذه.",
|
||||
"fr": "Votre accès est limité jusqu'à ce que votre e-mail soit vérifié. Vous ne pouvez accéder qu'à cette page de paramètres.",
|
||||
"tr": "E-postanız doğrulanana kadar erişiminiz sınırlıdır. Yalnızca bu ayarlar sayfasına erişebilirsiniz.",
|
||||
"de": "Ihr Zugriff ist eingeschränkt, bis Ihre E-Mail-Adresse bestätigt wurde. Sie können nur auf diese Einstellungsseite zugreifen.",
|
||||
"uk": "Ваш доступ обмежений, доки ваша електронна пошта не буде підтверджена. Ви можете отримати доступ лише до цієї сторінки налаштувань."
|
||||
},
|
||||
"SETTINGS$RESEND_VERIFICATION": {
|
||||
"en": "Resend verification",
|
||||
"ja": "確認メールを再送信",
|
||||
"zh-CN": "重新发送验证",
|
||||
"zh-TW": "重新發送驗證",
|
||||
"ko-KR": "인증 재전송",
|
||||
"no": "Send bekreftelse på nytt",
|
||||
"it": "Rinvia verifica",
|
||||
"pt": "Reenviar verificação",
|
||||
"es": "Reenviar verificación",
|
||||
"ar": "إعادة إرسال التحقق",
|
||||
"fr": "Renvoyer la vérification",
|
||||
"tr": "Doğrulamayı yeniden gönder",
|
||||
"de": "Bestätigung erneut senden",
|
||||
"uk": "Надіслати підтвердження повторно"
|
||||
},
|
||||
"SETTINGS$FAILED_TO_RESEND_VERIFICATION": {
|
||||
"en": "Failed to resend verification email",
|
||||
"ja": "確認メールの再送信に失敗しました",
|
||||
"zh-CN": "重新发送验证邮件失败",
|
||||
"zh-TW": "重新發送驗證郵件失敗",
|
||||
"ko-KR": "인증 이메일 재전송 실패",
|
||||
"no": "Kunne ikke sende bekreftelsese-post på nytt",
|
||||
"it": "Impossibile rinviare l'email di verifica",
|
||||
"pt": "Falha ao reenviar email de verificação",
|
||||
"es": "Error al reenviar el correo de verificación",
|
||||
"ar": "فشل في إعادة إرسال بريد التحقق",
|
||||
"fr": "Échec du renvoi de l'email de vérification",
|
||||
"tr": "Doğrulama e-postası yeniden gönderilemedi",
|
||||
"de": "Bestätigungs-E-Mail konnte nicht erneut gesendet werden",
|
||||
"uk": "Не вдалося повторно надіслати лист підтвердження"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ export default [
|
||||
route("settings", "routes/settings.tsx", [
|
||||
index("routes/llm-settings.tsx"),
|
||||
route("mcp", "routes/mcp-settings.tsx"),
|
||||
route("user", "routes/user-settings.tsx"),
|
||||
route("git", "routes/git-settings.tsx"),
|
||||
route("app", "routes/app-settings.tsx"),
|
||||
route("billing", "routes/billing.tsx"),
|
||||
|
||||
@@ -43,7 +43,7 @@ function AppContent() {
|
||||
const { t } = useTranslation();
|
||||
const { data: settings } = useSettings();
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation, isFetched, refetch } = useActiveConversation();
|
||||
const { data: conversation, isFetched } = useActiveConversation();
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
@@ -61,13 +61,8 @@ function AppContent() {
|
||||
"This conversation does not exist, or you do not have permission to access it.",
|
||||
);
|
||||
navigate("/");
|
||||
} else if (conversation?.status === "STOPPED") {
|
||||
// start the conversation if the state is stopped on initial load
|
||||
OpenHands.startConversation(conversation.conversation_id).then(() =>
|
||||
refetch(),
|
||||
);
|
||||
}
|
||||
}, [conversation?.conversation_id, isFetched, isAuthed]);
|
||||
}, [conversation, isFetched, isAuthed]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(clearTerminal());
|
||||
|
||||
@@ -304,7 +304,7 @@ function LlmSettingsScreen() {
|
||||
testId="llm-api-key-help-anchor"
|
||||
text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}
|
||||
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
|
||||
href="https://docs.all-hands.dev/usage/local-setup#getting-an-api-key"
|
||||
href="https://docs.all-hands.dev/usage/installation#getting-an-api-key"
|
||||
/>
|
||||
|
||||
<SettingsInput
|
||||
@@ -379,7 +379,7 @@ function LlmSettingsScreen() {
|
||||
testId="llm-api-key-help-anchor-advanced"
|
||||
text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}
|
||||
linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}
|
||||
href="https://docs.all-hands.dev/usage/local-setup#getting-an-api-key"
|
||||
href="https://docs.all-hands.dev/usage/installation#getting-an-api-key"
|
||||
/>
|
||||
|
||||
<SettingsInput
|
||||
@@ -469,9 +469,6 @@ function LlmSettingsScreen() {
|
||||
label: analyzer,
|
||||
})) || []
|
||||
}
|
||||
placeholder={t(
|
||||
I18nKey.SETTINGS$SECURITY_ANALYZER_PLACEHOLDER,
|
||||
)}
|
||||
defaultSelectedKey={settings.SECURITY_ANALYZER}
|
||||
isClearable
|
||||
showOptionalTag
|
||||
|
||||
@@ -25,7 +25,6 @@ import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||
import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
import { EmailVerificationGuard } from "#/components/features/guards/email-verification-guard";
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const error = useRouteError();
|
||||
@@ -205,9 +204,7 @@ export default function MainApp() {
|
||||
id="root-outlet"
|
||||
className="h-[calc(100%-50px)] md:h-full w-full relative overflow-auto"
|
||||
>
|
||||
<EmailVerificationGuard>
|
||||
<Outlet />
|
||||
</EmailVerificationGuard>
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
{renderAuthModal && (
|
||||
|
||||
@@ -15,7 +15,6 @@ function SettingsScreen() {
|
||||
const isSaas = config?.APP_MODE === "saas";
|
||||
|
||||
const saasNavItems = [
|
||||
{ to: "/settings/user", text: t("SETTINGS$NAV_USER") },
|
||||
{ to: "/settings/git", text: t("SETTINGS$NAV_GIT") },
|
||||
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
||||
{ to: "/settings/billing", text: t("SETTINGS$NAV_CREDITS") },
|
||||
@@ -34,11 +33,10 @@ function SettingsScreen() {
|
||||
React.useEffect(() => {
|
||||
if (isSaas) {
|
||||
if (pathname === "/settings") {
|
||||
navigate("/settings/user");
|
||||
navigate("/settings/git");
|
||||
}
|
||||
} else {
|
||||
const noEnteringPaths = [
|
||||
"/settings/user",
|
||||
"/settings/billing",
|
||||
"/settings/credits",
|
||||
"/settings/api-keys",
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSettings } from "#/hooks/query/use-settings";
|
||||
import { openHands } from "#/api/open-hands-axios";
|
||||
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
|
||||
// Email validation regex pattern
|
||||
const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
function EmailInputSection({
|
||||
email,
|
||||
onEmailChange,
|
||||
onSaveEmail,
|
||||
onResendVerification,
|
||||
isSaving,
|
||||
isResendingVerification,
|
||||
isEmailChanged,
|
||||
emailVerified,
|
||||
isEmailValid,
|
||||
children,
|
||||
}: {
|
||||
email: string;
|
||||
onEmailChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onSaveEmail: () => void;
|
||||
onResendVerification: () => void;
|
||||
isSaving: boolean;
|
||||
isResendingVerification: boolean;
|
||||
isEmailChanged: boolean;
|
||||
emailVerified?: boolean;
|
||||
isEmailValid: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm">{t("SETTINGS$USER_EMAIL")}</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={onEmailChange}
|
||||
className={`text-base text-white p-2 bg-base-tertiary rounded border ${
|
||||
isEmailChanged && !isEmailValid
|
||||
? "border-red-500"
|
||||
: "border-tertiary"
|
||||
} flex-grow focus:outline-none focus:border-transparent focus:ring-0`}
|
||||
placeholder={t("SETTINGS$USER_EMAIL_LOADING")}
|
||||
data-testid="email-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isEmailChanged && !isEmailValid && (
|
||||
<div
|
||||
className="text-red-500 text-sm mt-1"
|
||||
data-testid="email-validation-error"
|
||||
>
|
||||
{t("SETTINGS$INVALID_EMAIL_FORMAT")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSaveEmail}
|
||||
disabled={!isEmailChanged || isSaving || !isEmailValid}
|
||||
className="px-4 py-2 rounded bg-primary text-white hover:opacity-80 disabled:opacity-30 disabled:cursor-not-allowed disabled:text-[#0D0F11]"
|
||||
data-testid="save-email-button"
|
||||
>
|
||||
{isSaving ? t("SETTINGS$SAVING") : t("SETTINGS$SAVE")}
|
||||
</button>
|
||||
|
||||
{emailVerified === false && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onResendVerification}
|
||||
disabled={isResendingVerification}
|
||||
className="px-4 py-2 rounded bg-primary text-white hover:opacity-80 disabled:opacity-30 disabled:cursor-not-allowed disabled:text-[#0D0F11]"
|
||||
data-testid="resend-verification-button"
|
||||
>
|
||||
{isResendingVerification
|
||||
? t("SETTINGS$SENDING")
|
||||
: t("SETTINGS$RESEND_VERIFICATION")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VerificationAlert() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mt-4"
|
||||
role="alert"
|
||||
>
|
||||
<p className="font-bold">{t("SETTINGS$EMAIL_VERIFICATION_REQUIRED")}</p>
|
||||
<p className="text-sm">
|
||||
{t("SETTINGS$EMAIL_VERIFICATION_RESTRICTION_MESSAGE")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// These components have been replaced with toast notifications
|
||||
|
||||
function UserSettingsScreen() {
|
||||
const { t } = useTranslation();
|
||||
const { data: settings, isLoading, refetch } = useSettings();
|
||||
const [email, setEmail] = useState("");
|
||||
const [originalEmail, setOriginalEmail] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isResendingVerification, setIsResendingVerification] = useState(false);
|
||||
const [isEmailValid, setIsEmailValid] = useState(true);
|
||||
const queryClient = useQueryClient();
|
||||
const pollingIntervalRef = useRef<number | null>(null);
|
||||
const prevVerificationStatusRef = useRef<boolean | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings?.EMAIL) {
|
||||
setEmail(settings.EMAIL);
|
||||
setOriginalEmail(settings.EMAIL);
|
||||
setIsEmailValid(EMAIL_REGEX.test(settings.EMAIL));
|
||||
}
|
||||
}, [settings?.EMAIL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pollingIntervalRef.current) {
|
||||
window.clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
|
||||
if (
|
||||
prevVerificationStatusRef.current === false &&
|
||||
settings?.EMAIL_VERIFIED === true
|
||||
) {
|
||||
// Display toast notification instead of setting state
|
||||
displaySuccessToast(t("SETTINGS$EMAIL_VERIFIED_SUCCESSFULLY"));
|
||||
setTimeout(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
prevVerificationStatusRef.current = settings?.EMAIL_VERIFIED;
|
||||
|
||||
if (settings?.EMAIL_VERIFIED === false) {
|
||||
pollingIntervalRef.current = window.setInterval(() => {
|
||||
refetch();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
window.clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [settings?.EMAIL_VERIFIED, refetch, queryClient, t]);
|
||||
|
||||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newEmail = e.target.value;
|
||||
setEmail(newEmail);
|
||||
setIsEmailValid(EMAIL_REGEX.test(newEmail));
|
||||
};
|
||||
|
||||
const handleSaveEmail = async () => {
|
||||
if (email === originalEmail || !isEmailValid) return;
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await openHands.post("/api/email", { email }, { withCredentials: true });
|
||||
setOriginalEmail(email);
|
||||
// Display toast notification instead of setting state
|
||||
displaySuccessToast(t("SETTINGS$EMAIL_SAVED_SUCCESSFULLY"));
|
||||
queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(t("SETTINGS$FAILED_TO_SAVE_EMAIL"), error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResendVerification = async () => {
|
||||
try {
|
||||
setIsResendingVerification(true);
|
||||
await openHands.put("/api/email/verify", {}, { withCredentials: true });
|
||||
// Display toast notification instead of setting state
|
||||
displaySuccessToast(t("SETTINGS$VERIFICATION_EMAIL_SENT"));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(t("SETTINGS$FAILED_TO_RESEND_VERIFICATION"), error);
|
||||
} finally {
|
||||
setIsResendingVerification(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isEmailChanged = email !== originalEmail;
|
||||
|
||||
return (
|
||||
<div data-testid="user-settings-screen" className="flex flex-col h-full">
|
||||
<div className="p-9 flex flex-col gap-6">
|
||||
{isLoading ? (
|
||||
<div className="animate-pulse h-8 w-64 bg-tertiary rounded" />
|
||||
) : (
|
||||
<EmailInputSection
|
||||
email={email}
|
||||
onEmailChange={handleEmailChange}
|
||||
onSaveEmail={handleSaveEmail}
|
||||
onResendVerification={handleResendVerification}
|
||||
isSaving={isSaving}
|
||||
isResendingVerification={isResendingVerification}
|
||||
isEmailChanged={isEmailChanged}
|
||||
emailVerified={settings?.EMAIL_VERIFIED}
|
||||
isEmailValid={isEmailValid}
|
||||
>
|
||||
{settings?.EMAIL_VERIFIED === false && <VerificationAlert />}
|
||||
</EmailInputSection>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserSettingsScreen;
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useVSCodeUrl } from "#/hooks/query/use-vscode-url";
|
||||
import { VSCODE_IN_NEW_TAB } from "#/utils/feature-flags";
|
||||
|
||||
function VSCodeTab() {
|
||||
const { t } = useTranslation();
|
||||
@@ -13,31 +12,6 @@ function VSCodeTab() {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null);
|
||||
const [isCrossProtocol, setIsCrossProtocol] = useState(false);
|
||||
const [iframeError, setIframeError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.url) {
|
||||
try {
|
||||
const iframeProtocol = new URL(data.url).protocol;
|
||||
const currentProtocol = window.location.protocol;
|
||||
|
||||
// Check if the iframe URL has a different protocol than the current page
|
||||
setIsCrossProtocol(
|
||||
VSCODE_IN_NEW_TAB() || iframeProtocol !== currentProtocol,
|
||||
);
|
||||
} catch (e) {
|
||||
// Silently handle URL parsing errors
|
||||
setIframeError(t("VSCODE$URL_PARSE_ERROR"));
|
||||
}
|
||||
}
|
||||
}, [data?.url]);
|
||||
|
||||
const handleOpenInNewTab = () => {
|
||||
if (data?.url) {
|
||||
window.open(data.url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
};
|
||||
|
||||
if (isRuntimeInactive) {
|
||||
return (
|
||||
@@ -55,36 +29,14 @@ function VSCodeTab() {
|
||||
);
|
||||
}
|
||||
|
||||
if (error || (data && data.error) || !data?.url || iframeError) {
|
||||
if (error || (data && data.error) || !data?.url) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center text-center justify-center text-2xl text-tertiary-light">
|
||||
{iframeError ||
|
||||
data?.error ||
|
||||
String(error) ||
|
||||
t(I18nKey.VSCODE$URL_NOT_AVAILABLE)}
|
||||
{data?.error || String(error) || t(I18nKey.VSCODE$URL_NOT_AVAILABLE)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If cross-origin, show a button to open in new tab
|
||||
if (isCrossProtocol) {
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center gap-4">
|
||||
<div className="text-xl text-tertiary-light text-center max-w-md">
|
||||
{t("VSCODE$CROSS_ORIGIN_WARNING")}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenInNewTab}
|
||||
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary-dark transition-colors"
|
||||
>
|
||||
{t("VSCODE$OPEN_IN_NEW_TAB")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If same origin, use the iframe
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<iframe
|
||||
@@ -92,7 +44,7 @@ function VSCodeTab() {
|
||||
title={t(I18nKey.VSCODE$TITLE)}
|
||||
src={data.url}
|
||||
className="w-full h-full border-0"
|
||||
allow="clipboard-read; clipboard-write"
|
||||
allow={t(I18nKey.VSCODE$IFRAME_PERMISSIONS)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,8 +19,6 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS: false,
|
||||
SEARCH_API_KEY: "",
|
||||
IS_NEW_USER: true,
|
||||
EMAIL: "",
|
||||
EMAIL_VERIFIED: true, // Default to true to avoid restricting access unnecessarily
|
||||
MCP_CONFIG: {
|
||||
sse_servers: [],
|
||||
stdio_servers: [],
|
||||
|
||||
@@ -45,8 +45,6 @@ export type Settings = {
|
||||
SEARCH_API_KEY?: string;
|
||||
IS_NEW_USER?: boolean;
|
||||
MCP_CONFIG?: MCPConfig;
|
||||
EMAIL?: string;
|
||||
EMAIL_VERIFIED?: boolean;
|
||||
};
|
||||
|
||||
export type ApiSettings = {
|
||||
@@ -70,8 +68,6 @@ export type ApiSettings = {
|
||||
sse_servers: (string | MCPSSEServer)[];
|
||||
stdio_servers: MCPStdioServer[];
|
||||
};
|
||||
email?: string;
|
||||
email_verified?: boolean;
|
||||
};
|
||||
|
||||
export type PostSettings = Settings & {
|
||||
|
||||
@@ -14,6 +14,5 @@ export function loadFeatureFlag(
|
||||
|
||||
export const BILLING_SETTINGS = () => loadFeatureFlag("BILLING_SETTINGS");
|
||||
export const HIDE_LLM_SETTINGS = () => loadFeatureFlag("HIDE_LLM_SETTINGS");
|
||||
export const VSCODE_IN_NEW_TAB = () => loadFeatureFlag("VSCODE_IN_NEW_TAB");
|
||||
export const ENABLE_TRAJECTORY_REPLAY = () =>
|
||||
loadFeatureFlag("TRAJECTORY_REPLAY");
|
||||
|
||||
@@ -12,7 +12,7 @@ export const TIPS: Tip[] = [
|
||||
},
|
||||
{
|
||||
key: I18nKey.TIPS$SETUP_SCRIPT,
|
||||
link: "https://docs.all-hands.dev/usage/prompting/repository#setup-script",
|
||||
link: "https://docs.all-hands.dev/usage/customization/repository",
|
||||
},
|
||||
{ key: I18nKey.TIPS$VSCODE_INSTANCE },
|
||||
{ key: I18nKey.TIPS$SAVE_WORK },
|
||||
@@ -30,7 +30,7 @@ export const TIPS: Tip[] = [
|
||||
},
|
||||
{
|
||||
key: I18nKey.TIPS$GITHUB_HOOK,
|
||||
link: "https://docs.all-hands.dev/usage/cloud/github-installation#working-on-github-issues-and-pull-requests-using-openhands",
|
||||
link: "https://docs.all-hands.dev/usage/cloud/cloud-issue-resolver",
|
||||
},
|
||||
{
|
||||
key: I18nKey.TIPS$BLOG_SIGNUP,
|
||||
@@ -38,7 +38,7 @@ export const TIPS: Tip[] = [
|
||||
},
|
||||
{
|
||||
key: I18nKey.TIPS$API_USAGE,
|
||||
link: "https://docs.all-hands.dev/api-reference/health-check",
|
||||
link: "https://docs.all-hands.dev/swagger-ui/",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -117,10 +117,7 @@ You can see an example of a repo agent in [the agent for the OpenHands repo itse
|
||||
- Include repository structure details
|
||||
- Specify testing and build procedures
|
||||
- List environment requirements
|
||||
- Document CI workflows and checks
|
||||
- Include information about code quality standards
|
||||
- Maintain up-to-date team practices
|
||||
- Consider using OpenHands to generate a comprehensive repo.md (see [Creating a Repository Agent](#creating-a-repository-agent))
|
||||
- YAML frontmatter is optional - files without frontmatter will be loaded with default settings
|
||||
|
||||
### Submission Process
|
||||
|
||||
@@ -37,4 +37,5 @@ When creating a new microagent:
|
||||
For detailed information, see:
|
||||
|
||||
- [Microagents Overview](https://docs.all-hands.dev/usage/prompting/microagents-overview)
|
||||
- [Microagents Syntax](https://docs.all-hands.dev/usage/prompting/microagents-syntax)
|
||||
- [Example GitHub Microagent](https://github.com/All-Hands-AI/OpenHands/blob/main/microagents/github.md)
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
---
|
||||
name: add_repo_inst
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /add_repo_inst
|
||||
inputs:
|
||||
- name: REPO_FOLDER_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
---
|
||||
|
||||
Please browse the current repository under /workspace/{{ REPO_FOLDER_NAME }}, look at the documentation and relevant code, and understand the purpose of this repository.
|
||||
|
||||
Specifically, I want you to create a `.openhands/microagents/repo.md` file. This file should contain succinct information that summarizes (1) the purpose of this repository, (2) the general setup of this repo, and (3) a brief description of the structure of this repo.
|
||||
|
||||
Here's an example:
|
||||
```markdown
|
||||
---
|
||||
name: repo
|
||||
type: repo
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
This repository contains the code for OpenHands, an automated AI software engineer. It has a Python backend
|
||||
(in the `openhands` directory) and React frontend (in the `frontend` directory).
|
||||
|
||||
## General Setup:
|
||||
To set up the entire repo, including frontend and backend, run `make build`.
|
||||
You don't need to do this unless the user asks you to, or if you're trying to run the entire application.
|
||||
|
||||
Before pushing any changes, you should ensure that any lint errors or simple test errors have been fixed.
|
||||
|
||||
* If you've made changes to the backend, you should run `pre-commit run --all-files --config ./dev_config/python/.pre-commit-config.yaml`
|
||||
* If you've made changes to the frontend, you should run `cd frontend && npm run lint:fix && npm run build ; cd ..`
|
||||
|
||||
If either command fails, it may have automatically fixed some issues. You should fix any issues that weren't automatically fixed,
|
||||
then re-run the command to ensure it passes.
|
||||
|
||||
## Repository Structure
|
||||
Backend:
|
||||
- Located in the `openhands` directory
|
||||
- Testing:
|
||||
- All tests are in `tests/unit/test_*.py`
|
||||
- To test new code, run `poetry run pytest tests/unit/test_xxx.py` where `xxx` is the appropriate file for the current functionality
|
||||
- Write all tests with pytest
|
||||
|
||||
Frontend:
|
||||
- Located in the `frontend` directory
|
||||
- Prerequisites: A recent version of NodeJS / NPM
|
||||
- Setup: Run `npm install` in the frontend directory
|
||||
- Testing:
|
||||
- Run tests: `npm run test`
|
||||
- To run specific tests: `npm run test -- -t "TestName"`
|
||||
- Building:
|
||||
- Build for production: `npm run build`
|
||||
- Environment Variables:
|
||||
- Set in `frontend/.env` or as environment variables
|
||||
- Available variables: VITE_BACKEND_HOST, VITE_USE_TLS, VITE_INSECURE_SKIP_VERIFY, VITE_FRONTEND_PORT
|
||||
- Internationalization:
|
||||
- Generate i18n declaration file: `npm run make-i18n`
|
||||
```
|
||||
|
||||
Now, please write a similar markdown for the current repository.
|
||||
Read all the GitHub workflows under .github/ of the repository (if this folder exists) to understand the CI checks (e.g., linter, pre-commit), and include those in the repo.md file.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: address_pr_comments
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /address_pr_comments
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
---
|
||||
|
||||
First, check the branch {{ BRANCH_NAME }} and read the diff against the main branch to understand the purpose.
|
||||
|
||||
This branch corresponds to this PR {{ PR_URL }}
|
||||
|
||||
Next, you should use the GitHub API to read the reviews and comments on this PR and address them.
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: fix_test
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /fix_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
- name: FUNCTION_TO_FIX
|
||||
description: "The name of function to fix"
|
||||
- name: FILE_FOR_FUNCTION
|
||||
description: "The path of the file that contains the function"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
Help me fix these tests to pass by fixing the {{ FUNCTION_TO_FIX }} function in file {{ FILE_FOR_FUNCTION }}.
|
||||
|
||||
PLEASE DO NOT modify the tests by yourself -- Let me know if you think some of the tests are incorrect.
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: update_pr_description
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /update_pr_description
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
type: string
|
||||
validation:
|
||||
pattern: "^https://github.com/.+/.+/pull/[0-9]+$"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
type: string
|
||||
---
|
||||
|
||||
Please check the branch "{{ BRANCH_NAME }}" and look at the diff against the main branch. This branch belongs to this PR "{{ PR_URL }}".
|
||||
|
||||
Once you understand the purpose of the diff, please use Github API to read the existing PR description, and update it to be more reflective of the changes we've made when necessary.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: update_test
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /update_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
The current implementation of the code is correct BUT the test functions {{ FUNCTION_TO_FIX }} in file {{ FILE_FOR_FUNCTION }} are failing.
|
||||
|
||||
Please update the test file so that they pass with the current version of the implementation.
|
||||
@@ -141,9 +141,6 @@ def response_to_actions(
|
||||
content=arguments['content'],
|
||||
start=arguments.get('start', 1),
|
||||
end=arguments.get('end', -1),
|
||||
impl_source=arguments.get(
|
||||
'impl_source', FileEditSource.LLM_BASED_EDIT
|
||||
),
|
||||
)
|
||||
elif (
|
||||
tool_call.function.name
|
||||
|
||||
@@ -2,18 +2,10 @@ from litellm import ChatCompletionToolParam, ChatCompletionToolParamFunctionChun
|
||||
|
||||
_FILE_EDIT_DESCRIPTION = """Edit a file in plain-text format.
|
||||
* The assistant can edit files by specifying the file path and providing a draft of the new file content.
|
||||
* The draft content doesn't need to be exactly the same as the existing file; the assistant may skip unchanged lines using comments like `# ... existing code ...` to indicate unchanged sections.
|
||||
* The draft content doesn't need to be exactly the same as the existing file; the assistant may skip unchanged lines using comments like `# unchanged` to indicate unchanged sections.
|
||||
* IMPORTANT: For large files (e.g., > 300 lines), specify the range of lines to edit using `start` and `end` (1-indexed, inclusive). The range should be smaller than 300 lines.
|
||||
* -1 indicates the last line of the file when used as the `start` or `end` value.
|
||||
* Keep at least one unchanged line before the changed section and after the changed section wherever possible.
|
||||
* Make sure to set the `start` and `end` to include all the lines in the original file referred to in the draft of the new file content. Failure to do so will result in bad edits.
|
||||
* To append to a file, set both `start` and `end` to `-1`.
|
||||
* If the file doesn't exist, a new file will be created with the provided content.
|
||||
* IMPORTANT: Make sure you include all the required indentations for each line of code in the draft, otherwise the edited code will be incorrectly indented.
|
||||
* IMPORTANT: Make sure that the first line of the draft is also properly indented and has the required whitespaces.
|
||||
* IMPORTANT: NEVER include or make references to lines from outside the `start` and `end` range in the draft.
|
||||
* IMPORTANT: Start the content with a comment in the format: #EDIT: Reason for edit
|
||||
* IMPORTANT: If you are not appending to the file, avoid setting `start` and `end` to the same value.
|
||||
|
||||
**Example 1: general edit for short files**
|
||||
For example, given an existing file `/path/to/file.py` that looks like this:
|
||||
@@ -41,12 +33,13 @@ The assistant wants to edit the file to look like this:
|
||||
The assistant may produce an edit action like this:
|
||||
path="/path/to/file.txt" start=1 end=-1
|
||||
content=```
|
||||
#EDIT: I want to change the value of y to 2
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
# ... existing code ...
|
||||
# no changes before
|
||||
self.y = 2
|
||||
# self.z is removed
|
||||
|
||||
# MyClass().z is removed
|
||||
print(MyClass().y)
|
||||
```
|
||||
|
||||
@@ -65,7 +58,6 @@ For example, given an existing file `/path/to/file.py` that looks like this:
|
||||
|
||||
To append the following lines to the file:
|
||||
```python
|
||||
#EDIT: I want to print the value of y
|
||||
print(MyClass().y)
|
||||
```
|
||||
|
||||
@@ -101,9 +93,9 @@ The assistant wants to edit the file to look like this:
|
||||
(2000 more lines below)
|
||||
|
||||
The assistant may produce an edit action like this:
|
||||
path="/path/to/file.txt" start=1002 end=1008
|
||||
path="/path/to/file.txt" start=1001 end=1008
|
||||
content=```
|
||||
#EDIT: I want to change the value of y to 2
|
||||
class MyClass:
|
||||
def __init__(self):
|
||||
# no changes before
|
||||
self.y = 2
|
||||
|
||||
@@ -73,10 +73,7 @@ class SandboxConfig(BaseModel):
|
||||
runtime_startup_env_vars: dict[str, str] = Field(default_factory=dict)
|
||||
browsergym_eval_env: str | None = Field(default=None)
|
||||
platform: str | None = Field(default=None)
|
||||
close_delay: int = Field(
|
||||
default=3600,
|
||||
description='The delay in seconds before closing the sandbox after the agent is done.',
|
||||
)
|
||||
close_delay: int = Field(default=15)
|
||||
remote_runtime_resource_factor: int = Field(default=1)
|
||||
enable_gpu: bool = Field(default=False)
|
||||
docker_runtime_kwargs: dict | None = Field(default=None)
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Iterable
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import httpx # type: ignore
|
||||
from fastapi import status
|
||||
|
||||
from openhands.events.event import Event
|
||||
from openhands.events.event_filter import EventFilter
|
||||
@@ -43,9 +42,6 @@ class NestedEventStore(EventStoreABC):
|
||||
if self.session_api_key:
|
||||
headers['X-Session-API-Key'] = self.session_api_key
|
||||
response = httpx.get(url, headers=headers)
|
||||
if response.status_code == status.HTTP_404_NOT_FOUND:
|
||||
# Follow pattern of event store not throwing errors on not found
|
||||
return
|
||||
result_set = response.json()
|
||||
for result in result_set['events']:
|
||||
event = event_from_dict(result)
|
||||
|
||||
@@ -186,18 +186,9 @@ class EventStream(EventStore):
|
||||
|
||||
if event.id is not None:
|
||||
# Write the event to the store - this can take some time
|
||||
event_json = json.dumps(data)
|
||||
filename = self._get_filename_for_id(event.id, self.user_id)
|
||||
if len(event_json) > 1_000_000: # Roughly 1MB in bytes, ignoring encoding
|
||||
logger.warning(
|
||||
f'Saving event JSON over 1MB: {len(event_json):,} bytes, filename: {filename}',
|
||||
extra={
|
||||
'user_id': self.user_id,
|
||||
'session_id': self.sid,
|
||||
'size': len(event_json),
|
||||
},
|
||||
)
|
||||
self.file_store.write(filename, event_json)
|
||||
self.file_store.write(
|
||||
self._get_filename_for_id(event.id, self.user_id), json.dumps(data)
|
||||
)
|
||||
|
||||
# Store the cache page last - if it is not present during reads then it will simply be bypassed.
|
||||
self._store_cache_page(current_write_page)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import os
|
||||
|
||||
from openhands.server.session.conversation_init_data import ConversationInitData
|
||||
from openhands.utils.import_utils import get_impl
|
||||
|
||||
|
||||
class ExperimentManager:
|
||||
@staticmethod
|
||||
def run_conversation_variant_test(
|
||||
user_id: str, conversation_id: str, conversation_settings: ConversationInitData
|
||||
) -> ConversationInitData:
|
||||
return conversation_settings
|
||||
|
||||
|
||||
experiment_manager_cls = os.environ.get(
|
||||
'OPENHANDS_EXPERIMENT_MANAGER_CLS',
|
||||
'openhands.experiments.experiment_manager.ExperimentManager',
|
||||
)
|
||||
ExperimentManagerImpl = get_impl(ExperimentManager, experiment_manager_cls)
|
||||
@@ -483,30 +483,35 @@ class GitHubService(BaseGitService, GitService):
|
||||
- PR URL when successful
|
||||
- Error message when unsuccessful
|
||||
"""
|
||||
try:
|
||||
url = f'{self.BASE_URL}/repos/{repo_name}/pulls'
|
||||
|
||||
url = f'{self.BASE_URL}/repos/{repo_name}/pulls'
|
||||
# Set default body if none provided
|
||||
if not body:
|
||||
body = f'Merging changes from {source_branch} into {target_branch}'
|
||||
|
||||
# Set default body if none provided
|
||||
if not body:
|
||||
body = f'Merging changes from {source_branch} into {target_branch}'
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
'title': title,
|
||||
'head': source_branch,
|
||||
'base': target_branch,
|
||||
'body': body,
|
||||
'draft': draft,
|
||||
}
|
||||
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
'title': title,
|
||||
'head': source_branch,
|
||||
'base': target_branch,
|
||||
'body': body,
|
||||
'draft': draft,
|
||||
}
|
||||
# Make the POST request to create the PR
|
||||
response, _ = await self._make_request(
|
||||
url=url, params=payload, method=RequestMethod.POST
|
||||
)
|
||||
|
||||
# Make the POST request to create the PR
|
||||
response, _ = await self._make_request(
|
||||
url=url, params=payload, method=RequestMethod.POST
|
||||
)
|
||||
|
||||
# Return the HTML URL of the created PR
|
||||
return response['html_url']
|
||||
# Return the HTML URL of the created PR
|
||||
if 'html_url' in response:
|
||||
return response['html_url']
|
||||
else:
|
||||
return f'PR created but URL not found in response: {response}'
|
||||
|
||||
except Exception as e:
|
||||
return f'Error creating pull request: {str(e)}'
|
||||
|
||||
|
||||
github_service_cls = os.environ.get(
|
||||
|
||||
@@ -476,33 +476,38 @@ class GitLabService(BaseGitService, GitService):
|
||||
- MR URL when successful
|
||||
- Error message when unsuccessful
|
||||
"""
|
||||
try:
|
||||
# Convert string ID to URL-encoded path if needed
|
||||
project_id = str(id).replace('/', '%2F') if isinstance(id, str) else id
|
||||
url = f'{self.BASE_URL}/projects/{project_id}/merge_requests'
|
||||
|
||||
# Convert string ID to URL-encoded path if needed
|
||||
project_id = str(id).replace('/', '%2F') if isinstance(id, str) else id
|
||||
url = f'{self.BASE_URL}/projects/{project_id}/merge_requests'
|
||||
# Set default description if none provided
|
||||
if not description:
|
||||
description = (
|
||||
f'Merging changes from {source_branch} into {target_branch}'
|
||||
)
|
||||
|
||||
# Set default description if none provided
|
||||
if not description:
|
||||
description = (
|
||||
f'Merging changes from {source_branch} into {target_branch}'
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
'source_branch': source_branch,
|
||||
'target_branch': target_branch,
|
||||
'title': title,
|
||||
'description': description,
|
||||
}
|
||||
|
||||
# Make the POST request to create the MR
|
||||
response, _ = await self._make_request(
|
||||
url=url, params=payload, method=RequestMethod.POST
|
||||
)
|
||||
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
'source_branch': source_branch,
|
||||
'target_branch': target_branch,
|
||||
'title': title,
|
||||
'description': description,
|
||||
}
|
||||
|
||||
# Make the POST request to create the MR
|
||||
response, _ = await self._make_request(
|
||||
url=url, params=payload, method=RequestMethod.POST
|
||||
)
|
||||
|
||||
|
||||
return response['web_url']
|
||||
# Return the web URL of the created MR
|
||||
if 'web_url' in response:
|
||||
return response['web_url']
|
||||
else:
|
||||
return f'MR created but URL not found in response: {response}'
|
||||
|
||||
except Exception as e:
|
||||
return f'Error creating merge request: {str(e)}'
|
||||
|
||||
|
||||
gitlab_service_cls = os.environ.get(
|
||||
|
||||
@@ -167,11 +167,11 @@ class BaseGitService(ABC):
|
||||
return RateLimitError('GitHub API rate limit exceeded')
|
||||
|
||||
logger.warning(f'Status error on {self.provider} API: {e}')
|
||||
return UnknownException(f'Unknown error: {e}')
|
||||
return UnknownException('Unknown error')
|
||||
|
||||
def handle_http_error(self, e: HTTPError) -> UnknownException:
|
||||
logger.warning(f'HTTP error on {self.provider} API: {type(e).__name__} : {e}')
|
||||
return UnknownException(f'HTTP error {type(e).__name__} : {e}')
|
||||
return UnknownException(f'HTTP error {type(e).__name__}')
|
||||
|
||||
|
||||
class GitService(Protocol):
|
||||
|
||||
@@ -15,3 +15,4 @@ When you're done, make sure to
|
||||
2. Use the `create_pr` tool to open a new PR
|
||||
3. Name the branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
|
||||
4. The PR description should mention that it "fixes" or "closes" the issue number
|
||||
5. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
|
||||
|
||||
@@ -9,3 +9,4 @@ When you're done, make sure to
|
||||
|
||||
1. Use the `create_pr` tool to open a new PR
|
||||
2. The PR description should mention that it "fixes" or "closes" the issue number
|
||||
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
|
||||
|
||||
@@ -15,3 +15,4 @@ When you're done, make sure to
|
||||
2. Use the `create_mr` tool to open a new MR
|
||||
3. Name the branch using `openhands/` as a prefix (e.g `openhands/update-readme`)
|
||||
4. The MR description should mention that it "fixes" or "closes" the issue number
|
||||
5. Make sure to leave the following sentence at the end of the MR description: `@{{ username }} can click here to [continue refining the MR]({{ conversation_url }})`
|
||||
|
||||
@@ -9,3 +9,4 @@ When you're done, make sure to
|
||||
|
||||
1. Use the `create_mr` tool to open a new MR
|
||||
2. The MR description should mention that it "fixes" or "closes" the issue number
|
||||
3. Make sure to leave the following sentence at the end of the MR description: `@{{ username }} can click here to [continue refining the MR]({{ conversation_url }})`
|
||||
|
||||
@@ -5,3 +5,7 @@ These are a list of text messages attached in order of most recent.
|
||||
{{ message }}
|
||||
{% if not loop.last %}\n\n{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
If you opened a pull request, please leave the following comment at the end your summary and pull request description
|
||||
`{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
Please send a final message summarizing your work.
|
||||
Please summarize your work.
|
||||
|
||||
If you simply answered a question, this final message should re-state the answer to the question.
|
||||
|
||||
If you made changes, please first double-check the git diff, think carefully about the user's request(s), and check:
|
||||
1. whether the request has been completely addressed and all of the instructions have been followed faithfully (in checklist format if appropriate).
|
||||
2. whether the changes are concise (if there are any extraneous changes not important to addressing the user's request they should be reverted).
|
||||
If the request has been addressed and the changes are concise, then push your changes to the remote branch and send a final message summarizing the changes.
|
||||
If you answered a question, please re-state the answer to the question
|
||||
If you made changes, please create a concise overview on whether the request has been addressed successfully or if there are were issues with the attempt.
|
||||
If successful, make sure your changes are pushed to the remote branch.
|
||||
|
||||
@@ -22,7 +22,6 @@ from openhands.llm.tool_names import (
|
||||
BROWSER_TOOL_NAME,
|
||||
EXECUTE_BASH_TOOL_NAME,
|
||||
FINISH_TOOL_NAME,
|
||||
LLM_BASED_EDIT_TOOL_NAME,
|
||||
STR_REPLACE_EDITOR_TOOL_NAME,
|
||||
)
|
||||
|
||||
@@ -252,58 +251,6 @@ noop(1000) # Wait for page to load
|
||||
USER: EXECUTION RESULT of [browser]:
|
||||
[Browser shows the numbers in a table format]
|
||||
"""
|
||||
},
|
||||
'edit_file': {
|
||||
'create_file': """
|
||||
ASSISTANT: There is no `app.py` file in the current directory. Let me create a Python file `app.py`:
|
||||
<function=edit_file>
|
||||
<parameter=path>/workspace/app.py</parameter>
|
||||
<parameter=start>1</parameter>
|
||||
<parameter=end>-1</parameter>
|
||||
<parameter=content>
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
</parameter>
|
||||
</function>
|
||||
|
||||
USER: EXECUTION RESULT of [edit_file]:
|
||||
File created successfully at: /workspace/app.py
|
||||
""",
|
||||
'edit_file': """
|
||||
ASSISTANT:
|
||||
Now let me display the numbers in a table format:
|
||||
<function=edit_file>
|
||||
<parameter=path>/workspace/app.py</parameter>
|
||||
<parameter=start>6</parameter>
|
||||
<parameter=end>9</parameter>
|
||||
<parameter=content>
|
||||
numbers = list(range(1, 11))
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
# ... existing code ...
|
||||
if __name__ == '__main__':
|
||||
</parameter>
|
||||
</function>
|
||||
|
||||
USER: EXECUTION RESULT of [edit_file]:
|
||||
The file /workspace/app.py has been edited. Here's the result of running `cat -n` on a snippet of /workspace/app.py:
|
||||
3
|
||||
4 @app.route('/')
|
||||
5 def index():
|
||||
6 numbers = list(range(1, 11))
|
||||
7 return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
8
|
||||
9 if __name__ == '__main__':
|
||||
10 app.run(port=5000)
|
||||
Review the changes and make sure they are as expected. Edit the file again if necessary.
|
||||
""",
|
||||
},
|
||||
'finish': {
|
||||
'task_completed': """
|
||||
@@ -332,8 +279,6 @@ def get_example_for_tools(tools: list[dict]) -> str:
|
||||
available_tools.add('browser')
|
||||
elif name == FINISH_TOOL_NAME:
|
||||
available_tools.add('finish')
|
||||
elif name == LLM_BASED_EDIT_TOOL_NAME:
|
||||
available_tools.add('edit_file')
|
||||
|
||||
if not available_tools:
|
||||
return ''
|
||||
@@ -352,8 +297,6 @@ USER: Create a list of numbers from 1 to 10, and display them in a web page at p
|
||||
|
||||
if 'str_replace_editor' in available_tools:
|
||||
example += TOOL_EXAMPLES['str_replace_editor']['create_file']
|
||||
elif 'edit_file' in available_tools:
|
||||
example += TOOL_EXAMPLES['edit_file']['create_file']
|
||||
|
||||
if 'execute_bash' in available_tools:
|
||||
example += TOOL_EXAMPLES['execute_bash']['run_server']
|
||||
@@ -366,8 +309,6 @@ USER: Create a list of numbers from 1 to 10, and display them in a web page at p
|
||||
|
||||
if 'str_replace_editor' in available_tools:
|
||||
example += TOOL_EXAMPLES['str_replace_editor']['edit_file']
|
||||
elif 'edit_file' in available_tools:
|
||||
example += TOOL_EXAMPLES['edit_file']['edit_file']
|
||||
|
||||
if 'execute_bash' in available_tools:
|
||||
example += TOOL_EXAMPLES['execute_bash']['run_server_again']
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user