Compare commits

..

39 Commits

Author SHA1 Message Date
Ray Myers 30eac6f6a1 Bump version for 0.43.0 release 2025-06-13 12:19:19 -05:00
jpelletier1 144d09a578 Code review microagent (#9093)
Co-authored-by: Graham Neubig <neubig@gmail.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-06-13 01:35:44 +00:00
llamantino f97a837d46 fix: fix unreachable runtime container in make docker-dev (#9072) 2025-06-12 12:46:10 -04:00
dependabot[bot] eadec4ce9e chore(deps): bump the version-all group in /frontend with 8 updates (#9095)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-12 15:17:45 +00:00
dependabot[bot] 49e8737779 chore(deps): bump the version-all group across 1 directory with 24 updates (#9066)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
2025-06-12 14:31:35 +00:00
Graham Neubig 4711e74101 Fix default provider in CLI to be 'anthropic' instead of 'openai' (#9004)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
2025-06-12 03:02:03 +00:00
mamoodi c87f1cc8c0 Move Advanced Configurations under Running OpenHands on your Own (#9082) 2025-06-11 16:36:17 -04:00
Rohit Malhotra 33b64786b0 [Docs]: add info about lower scope tokens for gitlab (#9017)
Co-authored-by: mamoodi <mamoodiha@gmail.com>
2025-06-11 19:34:06 +00:00
Rohit Malhotra 12fc50299b [Docs]: add slack integration docs (#8903)
Co-authored-by: mamoodi <mamoodiha@gmail.com>
Co-authored-by: openhands <openhands@all-hands.dev>
2025-06-11 19:32:54 +00:00
Tim O'Farrell 57fee17348 Fix VSCode workspace dir (#9080) 2025-06-11 13:31:59 -06:00
Engel Nyst 77517d8ba0 Save CLI settings directly under ~/.openhands (#9079) 2025-06-11 21:07:40 +02:00
Calvin Smith a356f56237 fix: Context window truncation makes progress (#9052)
Co-authored-by: Calvin Smith <calvin@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
2025-06-11 12:47:34 -06:00
chuckbutkus 7dede37fd8 Make sure redirect URI is HTTPS unless it is for localhost (#9076) 2025-06-11 18:19:15 +00:00
Ray Myers c11dcad309 Add more log context on key events (#9056) 2025-06-11 11:34:16 -05:00
Tim O'Farrell 47209e794a Runtime Status Fixes (#9050)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-06-11 09:28:17 -06:00
Xingyao Wang 3f50eb0079 feat: Add microagents UI to conversation context menu (#8984)
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
2025-06-11 23:12:27 +08:00
sp.wack f27b02411b chore: Add deprecated tag to ActionMessage type (#9063) 2025-06-11 18:34:07 +04:00
llamantino d151093872 docs: added devstral to llms list, added local llms in local setup (#9062)
Co-authored-by: mamoodi <mamoodiha@gmail.com>
2025-06-11 10:22:15 -04:00
neo ea7294b7f9 docs: add links to other language versions of README (#9038)
Co-authored-by: mamoodi <mamoodiha@gmail.com>
2025-06-11 09:49:40 -04:00
Xingyao Wang 9097f487a6 Move get_agent_obs_text function to browser utils and add return_all option (#9019)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-06-11 12:32:38 +08:00
Rohit Malhotra fd921a4f88 [Fix]: model tracking in convo metadata (#9053) 2025-06-10 22:19:33 -04:00
Xingyao Wang 96fe5a50d6 Update repo.md (#9054) 2025-06-10 21:51:13 -04:00
Howie Zhou b634e10b45 Add JSON serialization for array and object parameters when converting tools (#8780) 2025-06-10 16:48:49 -04:00
Xingyao Wang 73f01657eb docs: Add TanStack Query state management documentation (#9047) 2025-06-10 16:44:00 -04:00
mamoodi 5d328183d5 Release 0.42.0 (#9046) 2025-06-10 16:34:10 -04:00
Mislav Lukach b7da65d373 chore(ui): update tailwind (#9049)
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
2025-06-10 18:20:04 +00:00
sp.wack dca9c7bdc6 feat(backend): New "update microagent prompt" API (#8357)
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
2025-06-10 22:10:55 +04:00
Rene Leonhardt 07862c32cb chore(docker): update docker base images (#8796)
Co-authored-by: Xingyao Wang <xingyaoww@gmail.com>
2025-06-10 22:48:46 +08:00
Emmanuel Ferdman e04f876df9 Migrate to modern logger interface in server utils (#8965)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-06-10 10:25:06 -04:00
Mislav Lukach 78d707de83 chore(billing): add stripe powered by (#9016)
Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
2025-06-10 18:10:09 +04:00
sp.wack 058153292f fix(ui): startup message ui (#9007) 2025-06-10 16:50:18 +04:00
dependabot[bot] 53b5e08804 chore(deps): bump the version-all group across 1 directory with 15 updates (#9027)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 18:04:14 -04:00
Ray Myers 7cee7dca64 chore - log size of large events (#9024) 2025-06-09 16:47:40 -05:00
mamoodi e12a62d006 Update GUI docs (#9020) 2025-06-09 15:38:44 -04:00
llamantino 77a0c5e073 feat: increase requests timeout to 60s (#8974)
Co-authored-by: llamantino <12345678+yourusername@users.noreply.github.com>
2025-06-09 12:42:03 -06:00
Tim O'Farrell e5d21e003d Added environment variable allowing skipping dependency checks (#9010) 2025-06-09 11:14:39 -06:00
mamoodi c6a4324bda Update Cloud API docs (#9008) 2025-06-09 11:42:37 -04:00
Tim O'Farrell 9ac8f011fe Converted exponential backoff to fixed (#9006) 2025-06-09 09:02:52 -06:00
Leander Maben d84befe28f Adding LLM Based Editing capability (#8677)
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Co-authored-by: Engel Nyst <engel.nyst@gmail.com>
2025-06-09 21:57:20 +08:00
151 changed files with 7026 additions and 4263 deletions
+22 -4
View File
@@ -1,5 +1,23 @@
# NodeJS
frontend/node_modules
config.toml
.envrc
.env
.git
# 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
+6
View File
@@ -72,3 +72,9 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directories:
- "containers/*"
schedule:
interval: "weekly"
+7 -1
View File
@@ -44,7 +44,13 @@ Frontend:
- Available variables: VITE_BACKEND_HOST, VITE_USE_TLS, VITE_INSECURE_SKIP_VERIFY, VITE_FRONTEND_PORT
- Internationalization:
- Generate i18n declaration file: `npm run make-i18n`
- Data Fetching & Cache Management:
- We use TanStack Query (fka React Query) for data fetching and cache management
- Data Access Layer: API client methods are located in `frontend/src/api` and should never be called directly from UI components - they must always be wrapped with TanStack Query
- Custom hooks are located in `frontend/src/hooks/query/` and `frontend/src/hooks/mutation/`
- Query hooks should follow the pattern use[Resource] (e.g., `useConversationMicroagents`)
- Mutation hooks should follow the pattern use[Action] (e.g., `useDeleteConversation`)
- Architecture rule: UI components → TanStack Query hooks → Data Access Layer (`frontend/src/api`) → API endpoints
## Template for Github Pull Request
+1 -1
View File
@@ -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.41-nikolaik`
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.43-nikolaik`
## Develop inside Docker container
+14 -3
View File
@@ -18,6 +18,17 @@
<a href="https://docs.all-hands.dev/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation"></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
<a href="https://docs.google.com/spreadsheets/d/1wOUdFCMyY6Nt0AIqF705KN4JKOWgeI4wUGUP60krXXs/edit?gid=0#gid=0"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score"></a>
<!-- Keep these links. Translations will automatically update with the README. -->
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=de">Deutsch</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=es">Español</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=fr">français</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=ja">日本語</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=ko">한국어</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=pt">Português</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=ru">Русский</a> |
<a href="https://www.readme-i18n.com/All-Hands-AI/OpenHands?lang=zh">中文</a>
<hr>
</div>
@@ -51,17 +62,17 @@ system requirements and more information.
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.43-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-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
docker.all-hands.dev/all-hands-ai/openhands:0.43
```
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
+3 -3
View File
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.43-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-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
docker.all-hands.dev/all-hands-ai/openhands:0.43
```
您将在[http://localhost:3000](http://localhost:3000)找到运行中的OpenHands
+11 -15
View File
@@ -1,16 +1,16 @@
ARG OPENHANDS_BUILD_VERSION=dev
FROM node:21.7.2-bookworm-slim AS frontend-builder
FROM node:22.16.0-bookworm-slim AS frontend-builder
WORKDIR /app
COPY ./frontend/package.json frontend/package-lock.json ./
RUN npm install -g npm@10.5.1
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY ./frontend ./
COPY frontend ./
RUN npm run build
FROM python:3.12.3-slim AS backend-builder
FROM python:3.12.10-slim AS base
FROM base AS backend-builder
WORKDIR /app
ENV PYTHONPATH='/app'
@@ -22,17 +22,18 @@ ENV POETRY_NO_INTERACTION=1 \
RUN apt-get update -y \
&& apt-get install -y curl make git build-essential \
&& python3 -m pip install poetry==1.8.2 --break-system-packages
&& python3 -m pip install poetry --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 python:3.12.3-slim AS openhands-app
FROM base AS openhands-app
WORKDIR /app
ARG OPENHANDS_BUILD_VERSION #re-declare for this section
# re-declare for this section
ARG OPENHANDS_BUILD_VERSION
ENV RUN_AS_OPENHANDS=true
# A random number--we need this to be different from the user's UID on the host machine
@@ -74,12 +75,7 @@ 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 --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
COPY --chown=openhands:app pyproject.toml poetry.lock README.md MANIFEST.in 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
+2 -1
View File
@@ -11,11 +11,12 @@ 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.41-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.43-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:
- "3000:3000"
network_mode: host
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
+1 -1
View File
@@ -7,7 +7,7 @@ services:
image: openhands:latest
container_name: openhands-app-${DATE:-}
environment:
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.43-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:
+17
View File
@@ -0,0 +1,17 @@
# Setup
```
npm install -g mint
```
or
```
yarn global add mint
```
# Preview
```
mint dev
```
+55 -49
View File
@@ -34,7 +34,8 @@
"group": "Integrations",
"pages": [
"usage/cloud/github-installation",
"usage/cloud/gitlab-installation"
"usage/cloud/gitlab-installation",
"usage/cloud/slack-installation"
]
},
"usage/cloud/cloud-ui",
@@ -48,13 +49,62 @@
"usage/how-to/gui-mode",
"usage/how-to/cli-mode",
"usage/how-to/headless-mode",
"usage/how-to/github-action"
"usage/how-to/github-action",
{
"group": "Advanced 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": "Customization",
"pages": [
"usage/prompting/prompting-best-practices",
"usage/prompting/repository",
{
"group": "Microagents",
@@ -69,53 +119,9 @@
]
},
{
"group": "Advanced Configuration",
"group": "Tips and Tricks",
"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"
"usage/prompting/prompting-best-practices"
]
},
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

+79 -85
View File
@@ -1,9 +1,11 @@
---
title: Cloud API
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.
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.
---
For more detailed information about the API, refer to the [OpenHands API Reference](https://docs.all-hands.dev/swagger-ui/).
For the available API endpoints, refer to the
[OpenHands API Reference](https://docs.all-hands.dev/api-reference).
## Obtaining an API Key
@@ -16,7 +18,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 Key Generation](/static/img/docs/api-key-generation.png)
![API Key Generation](/static/img/api-key-generation.png)
## API Usage
@@ -33,87 +35,81 @@ To start a new conversation with OpenHands to perform a task, you'll need to mak
#### Examples
<details>
<summary>cURL</summary>
```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="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>
<details>
<summary>Python (with requests)</summary>
<Accordion title="Python (with requests)">
```python
import requests
```python
import requests
api_key = "YOUR_API_KEY"
url = "https://app.all-hands.dev/api/conversations"
api_key = "YOUR_API_KEY"
url = "https://app.all-hands.dev/api/conversations"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
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']}")
```
</details>
<details>
<summary>TypeScript/JavaScript (with fetch)</summary>
```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);
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
}
startConversation();
```
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"
}
</details>
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}`,
"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>
#### Response
@@ -145,14 +141,12 @@ GET https://app.all-hands.dev/api/conversations/{conversation_id}
#### Example
<details>
<summary>cURL</summary>
```bash
curl -X GET "https://app.all-hands.dev/api/conversations/{conversation_id}" \
-H "Authorization: Bearer YOUR_API_KEY"
```
</details>
<Accordion title="cURL">
```bash
curl -X GET "https://app.all-hands.dev/api/conversations/{conversation_id}" \
-H "Authorization: Bearer YOUR_API_KEY"
```
</Accordion>
#### Response
+1 -1
View File
@@ -26,7 +26,7 @@ The Settings page allows you to:
## Key Features
For an overview of the key features available inside a conversation, please refer to the [Key Features](../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.
## Next Steps
+6
View File
@@ -19,6 +19,12 @@ appropriate repository and branch you'd like OpenHands to work on. Then click on
![Connect Repo](/static/img/connect-repo.png)
## Using Tokens with Reduced Scopes
OpenHands requests an API-scoped token during OAuth authentication. By default, this token is provided to the agent.
To restrict the agent's permissions, you can define a custom secret `GITLAB_TOKEN`, which will override the default token assigned to the agent.
While the high-permission API token is still requested and used for other components of the application (e.g. opening merge requests), the agent will not have access to it.
## Next Steps
- [Learn about the Cloud UI](/usage/cloud/cloud-ui).
+52
View File
@@ -0,0 +1,52 @@
---
title: Slack Integration - Coming soon...
description: This guide walks you through installing the OpenHands Slack app.
---
<Warning>This integration is not live yet, but will be available soon.</Warning>
## Prerequisites
- You are a slack workspace admin
- Access to OpenHands Cloud
## Installation Steps
1. Log in to [OpenHands Cloud](https://app.all-hands.dev)
2. Click the button below to OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
3. In the top right corner, select the workspace to install the OpenHands Slack app.
4. Review permissions and click allow
## Working With the Slack App
To start a new conversation, you can mention `@openhands` in a new message or a thread inside any Slack channel.
Once a conversation is started, all thread messages underneath it will be follow-up messages to OpenHands.
To send follow-up messages for the same conversation, mention `@openhands` in a thread reply to the original message. You must be the user who started the conversation.
## Example conversation
### Start a new conversation, and select repo
Conversation is started by mentioning `@openhands`.
![slack-create-convo.png](/static/img/slack-create-convo.png)
### See agent response and send follow up messages
Initial request is followed up by mentioning `@openhands` in a thread reply.
![slack-results-and-follow-up.png](/static/img/slack-results-and-follow-up.png)
## Pro tip
You can mention a repo name when starting a new conversation in the following formats
1. "My-Repo" repo (e.g `@openhands in the openhands repo ...`)
2. "All-Hands-AI/OpenHands" (e.g `@openhands in All-Hands-AI/OpenHands ...`)
The repo match is case insensitive. If a repo name match is made, it will kick off the conversation.
If the repo name partially matches against, multiple repos, you'll be asked to select a repo from the filtered list.
![slack-pro-tip.png](/static/img/slack-pro-tip.png)
+7 -5
View File
@@ -1,9 +1,11 @@
---
title: CLI Mode
description: CLI mode provides a powerful interactive Command-Line Interface (CLI) that lets you engage with OpenHands directly from your terminal.
title: CLI
description: The Command-Line Interface (CLI) provides a powerful interface that lets you engage with OpenHands
directly from your terminal.
---
This mode is different from the [headless mode](./headless-mode), which is non-interactive and better for scripting.
This mode is different from the [headless mode](/usage/how-to/headless-mode), which is non-interactive and better
for scripting.
## Getting Started
@@ -44,7 +46,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.41-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
-e LLM_API_KEY=$LLM_API_KEY \
@@ -53,7 +55,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.41 \
docker.all-hands.dev/all-hands-ai/openhands:0.43 \
python -m openhands.cli.main --override-cli-mode true
```
+64 -45
View File
@@ -1,14 +1,13 @@
---
title: GUI Mode
description: OpenHands provides a Graphical User Interface (GUI) mode for interacting with the AI assistant.
title: GUI
description: High level overview of the Graphical User Interface (GUI) in OpenHands.
---
## Installation and Setup
## Prerequisites
1. Follow the installation instructions to install OpenHands.
2. After running the command, access OpenHands at [http://localhost:3000](http://localhost:3000).
- [OpenHands is running](/usage/local-setup)
## Interacting with the GUI
## Overview
### Initial Setup
@@ -19,16 +18,23 @@ description: OpenHands provides a Graphical User Interface (GUI) mode for intera
3. Enter the corresponding `API Key` for your chosen provider.
4. Click `Save Changes` to apply the settings.
### Version Control Tokens
### Settings
OpenHands supports multiple version control providers. You can configure tokens for multiple providers simultaneously.
You can use the Settings page at any time to:
#### GitHub Token Setup
- 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
OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if provided:
<details>
<summary>Setting Up a GitHub Token</summary>
<AccordionGroup>
<Accordion title="Setting Up a GitHub Token">
1. **Generate a Personal Access Token (PAT)**:
- On GitHub, go to Settings > Developer Settings > Personal Access Tokens > Tokens (classic).
@@ -37,16 +43,11 @@ 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**:
- Click the Settings button (gear icon).
- Navigate to the `Git` tab.
- In the Settings page, 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:
@@ -59,15 +60,12 @@ 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.
</details>
<details>
<summary>Troubleshooting</summary>
</Accordion>
<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.
@@ -81,15 +79,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.
</details>
</Accordion>
</AccordionGroup>
#### GitLab Token Setup
#### GitLab Setup
OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if provided:
<details>
<summary>Setting Up a GitLab Token</summary>
<AccordionGroup>
<Accordion title="Setting Up a GitLab Token">
1. **Generate a Personal Access Token (PAT)**:
- On GitLab, go to User Settings > Access Tokens.
- Create a new token with the following scopes:
@@ -99,15 +97,17 @@ 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**:
- Click the Settings button (gear icon).
- Navigate to the `Git` tab.
- In the Settings page, navigate to the `Git` tab.
- Paste your token in the `GitLab Token` field.
- Click `Save Changes` to apply the changes.
</details>
<details>
<summary>Troubleshooting</summary>
3. **(Optional): Restrict agent permissions**
- Create another PAT using Step 1 and exclude `api` scope .
- In the Settings page, in the `Secrets` tab, create a new secret `GITLAB_TOKEN` and paste your lower scope token.
- OpenHands will use the higher scope token, and the agent will use the lower scope token
</Accordion>
<Accordion title="Troubleshooting">
Common issues and solutions:
- **Token Not Recognized**:
@@ -119,25 +119,44 @@ 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.
</details>
</Accordion>
</AccordionGroup>
### Advanced Settings
#### Advanced 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.
The `Advanced` settings allows configuration of additional LLM settings. Inside the Settings page, under the `LLM` tab,
toggle `Advanced` options to access additional settings.
### Interacting with the AI
- 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.
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.
### 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.
### Status Indicator
The status indicator located in the bottom left of the screen will cycle through a number of states as a new conversation
is loaded. Typically these include:
* `Disconnected` : The frontend is not connected to any conversation
* `Connecting` : The frontend is connecting a websocket to a conversation.
* `Building Runtime...` : The server is building a runtime. This is typically in development mode only while building a docker image.
* `Starting Runtime...` : The server is starting a new runtime instance - probably a new docker container or remote runtime.
* `Initializing Agent...` : The server is starting the agent loop. (This step does not appear at present with Nested runtimes)
* `Setting up workspace...` : Usually this means a `git clone ...` operation.
* `Setting up git hooks` : Setting up the git pre commit hooks for the workspace.
* `Agent is awaiting user input...` : Ready to go!
## 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).
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.
## 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)
+6 -5
View File
@@ -1,9 +1,10 @@
---
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.
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.
---
This is different from [CLI Mode](./cli-mode), which is interactive, and better for active development.
This is different from [the CLI](./cli-mode), which is interactive, and better for active development.
## With Python
@@ -31,7 +32,7 @@ To run OpenHands in Headless mode with Docker:
```bash
docker run -it \
--pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.41-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
-e LLM_API_KEY=$LLM_API_KEY \
@@ -41,7 +42,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.41 \
docker.all-hands.dev/all-hands-ai/openhands:0.43 \
python -m openhands.core.main -t "write a bash script that prints hi"
```
+1 -1
View File
@@ -12,7 +12,7 @@ To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-h
For more information see [getting started with OpenHands Cloud.](/usage/cloud/openhands-cloud)
## Running OpenHands Locally
## Running OpenHands on Your Own
Run OpenHands on your local system and bring your own LLM and API key.
+10 -5
View File
@@ -14,23 +14,28 @@ recommendations for model selection. Our latest benchmarking results can be foun
Based on these findings and community feedback, these are the latest models that have been verified to work reasonably well with OpenHands:
### Cloud / API-Based Models
- [anthropic/claude-sonnet-4-20250514](https://www.anthropic.com/api) (recommended)
- [openai/o4-mini](https://openai.com/index/introducing-o3-and-o4-mini/)
- [gemini/gemini-2.5-pro](https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025/)
- [deepseek/deepseek-chat](https://api-docs.deepseek.com/)
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) -- available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
If you have successfully run OpenHands with specific providers, we encourage you to open a PR to share your setup process
to help others using the same provider!
For a full list of the providers and models available, please consult the
[litellm documentation](https://docs.litellm.ai/docs/providers).
<Warning>
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
limits and monitor usage.
</Warning>
If you have successfully run OpenHands with specific providers, we encourage you to open a PR to share your setup process
to help others using the same provider!
### Local / Self-Hosted Models
For a full list of the providers and models available, please consult the
[litellm documentation](https://docs.litellm.ai/docs/providers).
- [mistralai/devstral-small](https://www.all-hands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free)
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
<Note>
Most current local and open source models are not as powerful. When using such models, you may see long
+4 -4
View File
@@ -54,25 +54,25 @@ Check [the installation guide](/usage/local-setup) to make sure you have all the
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.41-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.43-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.41-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-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
docker.all-hands.dev/all-hands-ai/openhands:0.43
```
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.41
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.43
Starting OpenHands...
Running OpenHands as root
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
+18 -5
View File
@@ -1,6 +1,6 @@
---
title: Getting Started
description: Getting started with running OpenHands locally.
description: Getting started with running OpenHands on your own.
---
## Recommended Methods for Running Openhands on Your Local System
@@ -62,17 +62,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
### Start the App
```bash
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.40-nikolaik
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.43-nikolaik
docker run -it --rm --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.40-nikolaik \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.43-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.40
docker.all-hands.dev/all-hands-ai/openhands:0.43
```
You'll find OpenHands running at http://localhost:3000!
@@ -117,10 +117,24 @@ OpenHands requires an API key to access most language models. Here's how to get
</Accordion>
<Accordion title="Local LLM (e.g. LM Studio, llama.cpp, Ollama)">
If your local LLM server isnt behind an authentication proxy, you can enter any value as the API key (e.g. `local-key`, `test123`) — it wont be used.
</Accordion>
</AccordionGroup>
Consider setting usage limits to control costs.
#### Using a Local LLM
<Note>
Effective use of local models for agent tasks requires capable hardware, along with models specifically tuned for instruction-following and agent-style behavior.
</Note>
To run OpenHands with a locally hosted language model instead of a cloud provider, see the [Local LLMs guide](/usage/llms/local-llms) for setup instructions.
#### 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.
@@ -132,7 +146,6 @@ To enable search functionality in OpenHands:
For more details, see the [Search Engine Setup](/usage/search-engine-setup) guide.
Now you're ready to [get started with OpenHands](/usage/getting-started).
### Versions
+18
View File
@@ -74,6 +74,24 @@ If no condenser configuration is specified, the 'noop' condenser will be used by
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.
+7 -2
View File
@@ -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,6 +62,7 @@ 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']
@@ -254,15 +255,19 @@ 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=False,
enable_llm_editor=ENABLE_LLM_EDITOR,
enable_mcp=False,
condenser=metadata.condenser_config,
enable_prompt_extensions=False,
@@ -4,7 +4,6 @@ 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
@@ -19,7 +18,7 @@ describe("Empty state", () => {
const { useWsClient: useWsClientMock } = vi.hoisted(() => ({
useWsClient: vi.fn(() => ({
send: sendMock,
status: WsClientProviderStatus.CONNECTED,
status: "CONNECTED",
isLoadingMessages: false,
})),
}));
@@ -64,7 +63,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: WsClientProviderStatus.CONNECTED,
status: "CONNECTED",
isLoadingMessages: false,
}));
const user = userEvent.setup();
@@ -87,7 +86,7 @@ describe("Empty state", () => {
async () => {
useWsClientMock.mockImplementation(() => ({
send: sendMock,
status: WsClientProviderStatus.CONNECTED,
status: "CONNECTED",
isLoadingMessages: false,
}));
const user = userEvent.setup();
@@ -101,7 +100,7 @@ describe("Empty state", () => {
useWsClientMock.mockImplementation(() => ({
send: sendMock,
status: WsClientProviderStatus.CONNECTED,
status: "CONNECTED",
isLoadingMessages: false,
}));
rerender(<ChatInterface />);
@@ -478,7 +478,7 @@ describe("ConversationCard", () => {
title="Conversation 1"
selectedRepository={null}
lastUpdatedAt="2021-10-01T12:00:00Z"
status="RUNNING"
conversationStatus="RUNNING"
/>,
);
@@ -48,6 +48,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-01T12:00:00Z",
created_at: "2021-10-01T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -60,6 +61,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-02T12:00:00Z",
created_at: "2021-10-02T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -72,6 +74,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-03T12:00:00Z",
created_at: "2021-10-03T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -158,6 +161,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-01T12:00:00Z",
created_at: "2021-10-01T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -170,6 +174,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-02T12:00:00Z",
created_at: "2021-10-02T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -182,6 +187,7 @@ describe("ConversationPanel", () => {
last_updated_at: "2021-10-03T12:00:00Z",
created_at: "2021-10-03T12:00:00Z",
status: "STOPPED" as const,
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -65,6 +65,7 @@ describe("WsClientProvider", () => {
last_updated_at: "2021-10-01T12:00:00Z",
created_at: "2021-10-01T12:00:00Z",
status: "RUNNING" as const,
runtime_status: "STATUS$READY",
url: null,
session_api_key: null,
}}},
@@ -334,10 +334,7 @@ describe("Settings 404", () => {
renderHomeScreen();
// small hack to wait for the modal to not appear
await expect(
screen.findByTestId("ai-config-modal", {}, { timeout: 1000 }),
).rejects.toThrow();
expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
});
});
+18
View File
@@ -0,0 +1,18 @@
import { heroui } from "@heroui/react";
export default heroui({
defaultTheme: "dark",
layout: {
radius: {
small: "5px",
large: "20px",
},
},
themes: {
dark: {
colors: {
primary: "#4465DB",
},
},
},
});
+3642 -2960
View File
File diff suppressed because it is too large Load Diff
+25 -24
View File
@@ -1,37 +1,39 @@
{
"name": "openhands-frontend",
"version": "0.41.0",
"version": "0.43.0",
"private": true,
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@heroui/react": "2.7.8",
"@heroui/react": "^2.8.0-beta.7",
"@microlink/react-json-view": "^1.26.2",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.6.1",
"@react-router/serve": "^7.6.1",
"@react-router/node": "^7.6.2",
"@react-router/serve": "^7.6.2",
"@react-types/shared": "^3.29.1",
"@reduxjs/toolkit": "^2.8.2",
"@stripe/react-stripe-js": "^3.7.0",
"@stripe/stripe-js": "^7.3.0",
"@tanstack/react-query": "^5.77.2",
"@vitejs/plugin-react": "^4.4.0",
"@stripe/stripe-js": "^7.3.1",
"@tailwindcss/postcss": "^4.1.10",
"@tailwindcss/vite": "^4.1.10",
"@tanstack/react-query": "^5.80.7",
"@vitejs/plugin-react": "^4.5.2",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.4.0",
"axios": "^1.9.0",
"clsx": "^2.1.1",
"eslint-config-airbnb-typescript": "^18.0.0",
"framer-motion": "^12.14.0",
"framer-motion": "^12.17.3",
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.1.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.28",
"jose": "^6.0.11",
"lucide-react": "^0.511.0",
"lucide-react": "^0.514.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.245.2",
"posthog-js": "^1.251.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-highlight": "^0.15.0",
@@ -40,15 +42,15 @@
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-redux": "^9.2.0",
"react-router": "^7.6.1",
"react-router": "^7.6.2",
"react-syntax-highlighter": "^15.6.1",
"react-textarea-autosize": "^8.5.9",
"remark-gfm": "^4.0.1",
"sirv-cli": "^3.0.1",
"socket.io-client": "^4.8.1",
"tailwind-merge": "^3.3.0",
"tailwind-merge": "^3.3.1",
"vite": "^6.3.5",
"web-vitals": "^5.0.1",
"web-vitals": "^5.0.3",
"ws": "^8.18.2"
},
"scripts": {
@@ -82,23 +84,23 @@
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.0",
"@mswjs/socket.io-binding": "^0.1.1",
"@playwright/test": "^1.52.0",
"@react-router/dev": "^7.6.1",
"@playwright/test": "^1.53.0",
"@react-router/dev": "^7.6.2",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/eslint-plugin-query": "^5.78.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^22.15.21",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"@types/node": "^24.0.1",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitest/coverage-v8": "^3.1.4",
"@vitest/coverage-v8": "^3.2.3",
"autoprefixer": "^10.4.21",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
@@ -113,12 +115,11 @@
"eslint-plugin-unused-imports": "^4.1.4",
"husky": "^9.1.7",
"jsdom": "^26.1.0",
"lint-staged": "^16.0.0",
"lint-staged": "^16.1.0",
"msw": "^2.6.6",
"postcss": "^8.5.2",
"prettier": "^3.5.3",
"stripe": "^18.1.1",
"tailwindcss": "^3.4.17",
"stripe": "^18.2.1",
"tailwindcss": "^4.1.8",
"typescript": "^5.8.3",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^5.1.4",
+2 -3
View File
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
}
};
@@ -117,6 +117,9 @@ const EXCLUDED_TECHNICAL_STRINGS = [
"edit-secret-form", // Test ID for secret form
"search-api-key-input", // Input name for search API key
"noopener,noreferrer", // Options for window.open
"STATUS$READY",
"STATUS$STOPPED",
"STATUS$ERROR",
];
function isExcludedTechnicalString(str) {
+31
View File
@@ -11,6 +11,8 @@ import {
GetTrajectoryResponse,
GitChangeDiff,
GitChange,
GetMicroagentsResponse,
GetMicroagentPromptResponse,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { ApiSettings, PostApiSettings, Provider } from "#/types/settings";
@@ -393,6 +395,35 @@ class OpenHands {
return data;
}
/**
* Get the available microagents associated with a conversation
* @param conversationId The ID of the conversation
* @returns The available microagents associated with the conversation
*/
static async getMicroagents(
conversationId: string,
): Promise<GetMicroagentsResponse> {
const url = `${this.getConversationUrl(conversationId)}/microagents`;
const { data } = await openHands.get<GetMicroagentsResponse>(url, {
headers: this.getConversationHeaders(),
});
return data;
}
static async getMicroagentPrompt(
conversationId: string,
eventId: number,
): Promise<string> {
const { data } = await openHands.get<GetMicroagentPromptResponse>(
`/api/conversations/${conversationId}/remember_prompt`,
{
params: { event_id: eventId },
},
);
return data.prompt;
}
}
export default OpenHands;
+25 -2
View File
@@ -1,4 +1,5 @@
import { ProjectStatus } from "#/components/features/conversation-panel/conversation-state-indicator";
import { ConversationStatus } from "#/types/conversation-status";
import { RuntimeStatus } from "#/types/runtime-status";
export interface ErrorResponse {
error: string;
@@ -80,7 +81,8 @@ export interface Conversation {
git_provider: string | null;
last_updated_at: string;
created_at: string;
status: ProjectStatus;
status: ConversationStatus;
runtime_status: RuntimeStatus | null;
trigger?: ConversationTrigger;
url: string | null;
session_api_key: string | null;
@@ -102,3 +104,24 @@ export interface GitChangeDiff {
modified: string;
original: string;
}
export interface InputMetadata {
name: string;
description: string;
}
export interface Microagent {
name: string;
type: "repo" | "knowledge";
content: string;
triggers: string[];
}
export interface GetMicroagentsResponse {
microagents: Microagent[];
}
export interface GetMicroagentPromptResponse {
status: string;
prompt: string;
}
+6
View File
@@ -0,0 +1,6 @@
<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>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,68 +0,0 @@
import { I18nKey } from "#/i18n/declaration";
import { AgentState } from "#/types/agent-state";
export enum IndicatorColor {
BLUE = "bg-blue-500",
GREEN = "bg-green-500",
ORANGE = "bg-orange-500",
YELLOW = "bg-yellow-500",
RED = "bg-red-500",
DARK_ORANGE = "bg-orange-800",
}
export const AGENT_STATUS_MAP: {
[k: string]: { message: string; indicator: IndicatorColor };
} = {
[AgentState.INIT]: {
message: I18nKey.CHAT_INTERFACE$AGENT_INIT_MESSAGE,
indicator: IndicatorColor.BLUE,
},
[AgentState.RUNNING]: {
message: I18nKey.CHAT_INTERFACE$AGENT_RUNNING_MESSAGE,
indicator: IndicatorColor.GREEN,
},
[AgentState.AWAITING_USER_INPUT]: {
message: I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE,
indicator: IndicatorColor.BLUE,
},
[AgentState.PAUSED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_PAUSED_MESSAGE,
indicator: IndicatorColor.YELLOW,
},
[AgentState.LOADING]: {
message: I18nKey.CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE,
indicator: IndicatorColor.DARK_ORANGE,
},
[AgentState.STOPPED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_STOPPED_MESSAGE,
indicator: IndicatorColor.RED,
},
[AgentState.FINISHED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_FINISHED_MESSAGE,
indicator: IndicatorColor.GREEN,
},
[AgentState.REJECTED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_REJECTED_MESSAGE,
indicator: IndicatorColor.YELLOW,
},
[AgentState.ERROR]: {
message: I18nKey.CHAT_INTERFACE$AGENT_ERROR_MESSAGE,
indicator: IndicatorColor.RED,
},
[AgentState.AWAITING_USER_CONFIRMATION]: {
message: I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE,
indicator: IndicatorColor.ORANGE,
},
[AgentState.USER_CONFIRMED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE,
indicator: IndicatorColor.GREEN,
},
[AgentState.USER_REJECTED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE,
indicator: IndicatorColor.RED,
},
[AgentState.RATE_LIMITED]: {
message: I18nKey.CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE,
indicator: IndicatorColor.YELLOW,
},
};
@@ -132,7 +132,7 @@ export function ChatInput({
maxRows={maxRows}
data-dragging-over={isDraggingOver}
className={cn(
"grow text-sm self-center placeholder:text-neutral-400 text-white resize-none outline-none ring-0",
"grow text-sm self-center placeholder:text-neutral-400 text-white resize-none outline-hidden ring-0",
"transition-all duration-200 ease-in-out",
isDraggingOver
? "bg-neutral-600/50 rounded-lg px-2"
@@ -114,7 +114,7 @@ export function ExpandableMessage({
{t(I18nKey.STATUS$ERROR_LLM_OUT_OF_CREDITS)}
</div>
<Link
className="mt-2 mb-2 w-full h-10 rounded flex items-center justify-center gap-2 bg-primary text-[#0D0F11]"
className="mt-2 mb-2 w-full h-10 rounded-sm flex items-center justify-center gap-2 bg-primary text-[#0D0F11]"
to="/settings/billing"
>
{t(I18nKey.BILLING$CLICK_TO_TOP_UP)}
@@ -1,21 +1,14 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
import { I18nKey } from "#/i18n/declaration";
import { showErrorToast } from "#/utils/error-handler";
import { RootState } from "#/store";
import { AgentState } from "#/types/agent-state";
import {
AGENT_STATUS_MAP,
IndicatorColor,
} from "../../agent-status-map.constant";
import {
useWsClient,
WsClientProviderStatus,
} from "#/context/ws-client-provider";
import { useWsClient } from "#/context/ws-client-provider";
import { useNotification } from "#/hooks/useNotification";
import { browserTab } from "#/utils/browser-tab";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { getIndicatorColor, getStatusCode } from "#/utils/status";
const notificationStates = [
AgentState.AWAITING_USER_INPUT,
@@ -27,39 +20,61 @@ export function AgentStatusBar() {
const { t, i18n } = useTranslation();
const { curAgentState } = useSelector((state: RootState) => state.agent);
const { curStatusMessage } = useSelector((state: RootState) => state.status);
const { status } = useWsClient();
const { notify } = useNotification();
const { webSocketStatus } = useWsClient();
const { data: conversation } = useActiveConversation();
const indicatorColor = getIndicatorColor(
webSocketStatus,
conversation?.status || null,
conversation?.runtime_status || null,
curAgentState,
);
const statusCode = getStatusCode(
curStatusMessage,
webSocketStatus,
conversation?.status || null,
conversation?.runtime_status || null,
curAgentState,
);
const { notify } = useNotification();
const [statusMessage, setStatusMessage] = React.useState<string>("");
const updateStatusMessage = () => {
// Show error toast if required
React.useEffect(() => {
if (curStatusMessage?.type !== "error") {
return;
}
let message = curStatusMessage.message || "";
if (curStatusMessage?.id) {
const id = curStatusMessage.id.trim();
if (id === "STATUS$READY") {
message = "awaiting_user_input";
}
if (i18n.exists(id)) {
message = t(curStatusMessage.id.trim()) || message;
}
}
if (curStatusMessage?.type === "error") {
showErrorToast({
message,
source: "agent-status",
metadata: { ...curStatusMessage },
});
return;
}
if (message.trim()) {
setStatusMessage(message);
} else {
setStatusMessage(AGENT_STATUS_MAP[curAgentState].message);
}
};
React.useEffect(() => {
updateStatusMessage();
showErrorToast({
message,
source: "agent-status",
metadata: { ...curStatusMessage },
});
}, [curStatusMessage.id]);
// Handle notify
React.useEffect(() => {
if (notificationStates.includes(curAgentState)) {
const message = t(statusCode);
notify(message, {
body: t(`Agent state changed to ${curAgentState}`),
playSound: true,
});
// Update browser tab if window exists and is not focused
if (typeof document !== "undefined" && !document.hasFocus()) {
browserTab.startNotification(message);
}
}
}, [curAgentState, statusCode]);
// Handle window focus/blur
React.useEffect(() => {
if (typeof window === "undefined") return undefined;
@@ -75,42 +90,13 @@ export function AgentStatusBar() {
};
}, []);
const [indicatorColor, setIndicatorColor] = React.useState<string>(
AGENT_STATUS_MAP[curAgentState].indicator,
);
React.useEffect(() => {
if (conversation?.status === "STARTING") {
setStatusMessage(t(I18nKey.STATUS$STARTING_RUNTIME));
setIndicatorColor(IndicatorColor.RED);
} else if (status === WsClientProviderStatus.DISCONNECTED) {
setStatusMessage(t(I18nKey.STATUS$WEBSOCKET_CLOSED));
setIndicatorColor(IndicatorColor.RED);
} else {
setStatusMessage(AGENT_STATUS_MAP[curAgentState].message);
setIndicatorColor(AGENT_STATUS_MAP[curAgentState].indicator);
if (notificationStates.includes(curAgentState)) {
const message = t(AGENT_STATUS_MAP[curAgentState].message);
notify(t(AGENT_STATUS_MAP[curAgentState].message), {
body: t(`Agent state changed to ${curAgentState}`),
playSound: true,
});
// Update browser tab if window exists and is not focused
if (typeof document !== "undefined" && !document.hasFocus()) {
browserTab.startNotification(message);
}
}
}
}, [curAgentState, status, notify, t, conversation?.status]);
return (
<div className="flex flex-col items-center">
<div className="flex items-center bg-base-secondary px-2 py-1 text-gray-400 rounded-[100px] text-sm gap-[6px]">
<div
className={`w-2 h-2 rounded-full animate-pulse ${indicatorColor}`}
/>
<span className="text-sm text-stone-400">{t(statusMessage)}</span>
<span className="text-sm text-stone-400">{t(statusCode)}</span>
</div>
</div>
);
@@ -30,7 +30,7 @@ export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) {
title={conversation?.title ?? ""}
lastUpdatedAt={conversation?.created_at ?? ""}
selectedRepository={conversation?.selected_repository ?? null}
status={conversation?.status}
conversationStatus={conversation?.status}
conversationId={conversation?.conversation_id}
/>
</div>
@@ -1,7 +1,9 @@
import { useTranslation } from "react-i18next";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
import { cn } from "#/utils/utils";
import { ContextMenu } from "../context-menu/context-menu";
import { ContextMenuListItem } from "../context-menu/context-menu-list-item";
import { I18nKey } from "#/i18n/declaration";
interface ConversationCardContextMenuProps {
onClose: () => void;
@@ -9,6 +11,7 @@ interface ConversationCardContextMenuProps {
onEdit?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowMicroagents?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
}
@@ -19,9 +22,11 @@ export function ConversationCardContextMenu({
onEdit,
onDisplayCost,
onShowAgentTools,
onShowMicroagents,
onDownloadViaVSCode,
position = "bottom",
}: ConversationCardContextMenuProps) {
const { t } = useTranslation();
const ref = useClickOutsideElement<HTMLUListElement>(onClose);
return (
@@ -68,6 +73,14 @@ export function ConversationCardContextMenu({
Show Agent Tools & Metadata
</ContextMenuListItem>
)}
{onShowMicroagents && (
<ContextMenuListItem
testId="show-microagents-button"
onClick={onShowMicroagents}
>
{t(I18nKey.CONVERSATION$SHOW_MICROAGENTS)}
</ContextMenuListItem>
)}
</ContextMenu>
);
}
@@ -4,13 +4,11 @@ import posthog from "posthog-js";
import { useTranslation } from "react-i18next";
import { formatTimeDelta } from "#/utils/format-time-delta";
import { ConversationRepoLink } from "./conversation-repo-link";
import {
ProjectStatus,
ConversationStateIndicator,
} from "./conversation-state-indicator";
import { ConversationStateIndicator } from "./conversation-state-indicator";
import { EllipsisButton } from "./ellipsis-button";
import { ConversationCardContextMenu } from "./conversation-card-context-menu";
import { SystemMessageModal } from "./system-message-modal";
import { MicroagentsModal } from "./microagents-modal";
import { cn } from "#/utils/utils";
import { BaseModal } from "../../shared/modals/base-modal/base-modal";
import { RootState } from "#/store";
@@ -19,6 +17,7 @@ import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
import OpenHands from "#/api/open-hands";
import { useWsClient } from "#/context/ws-client-provider";
import { isSystemMessage } from "#/types/core/guards";
import { ConversationStatus } from "#/types/conversation-status";
interface ConversationCardProps {
onClick?: () => void;
@@ -30,7 +29,7 @@ interface ConversationCardProps {
selectedRepository: string | null;
lastUpdatedAt: string; // ISO 8601
createdAt?: string; // ISO 8601
status?: ProjectStatus;
conversationStatus?: ConversationStatus;
variant?: "compact" | "default";
conversationId?: string; // Optional conversation ID for VS Code URL
}
@@ -49,7 +48,7 @@ export function ConversationCard({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
lastUpdatedAt,
createdAt,
status = "STOPPED",
conversationStatus = "STOPPED",
variant = "default",
conversationId,
}: ConversationCardProps) {
@@ -59,6 +58,8 @@ export function ConversationCard({
const [titleMode, setTitleMode] = React.useState<"view" | "edit">("view");
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
const [microagentsModalVisible, setMicroagentsModalVisible] =
React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);
const systemMessage = parsedEvents.find(isSystemMessage);
@@ -142,6 +143,13 @@ export function ConversationCard({
setSystemModalVisible(true);
};
const handleShowMicroagents = (
event: React.MouseEvent<HTMLButtonElement>,
) => {
event.stopPropagation();
setMicroagentsModalVisible(true);
};
React.useEffect(() => {
if (titleMode === "edit") {
inputRef.current?.focus();
@@ -196,7 +204,9 @@ export function ConversationCard({
</div>
<div className="flex items-center">
<ConversationStateIndicator status={status} />
<ConversationStateIndicator
conversationStatus={conversationStatus}
/>
{hasContextMenu && (
<div className="pl-2">
<EllipsisButton
@@ -225,6 +235,11 @@ export function ConversationCard({
? handleShowAgentTools
: undefined
}
onShowMicroagents={
showOptions && conversationId
? handleShowMicroagents
: undefined
}
position={variant === "compact" ? "top" : "bottom"}
/>
)}
@@ -367,6 +382,13 @@ export function ConversationCard({
onClose={() => setSystemModalVisible(false)}
systemMessage={systemMessage ? systemMessage.args : null}
/>
{microagentsModalVisible && (
<MicroagentsModal
onClose={() => setMicroagentsModalVisible(false)}
conversationId={conversationId}
/>
)}
</>
);
}
@@ -91,7 +91,7 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
selectedRepository={project.selected_repository}
lastUpdatedAt={project.last_updated_at}
createdAt={project.created_at}
status={project.status}
conversationStatus={project.status}
conversationId={project.conversation_id}
/>
)}
@@ -1,26 +1,27 @@
import ColdIcon from "./state-indicators/cold.svg?react";
import { ConversationStatus } from "#/types/conversation-status";
import RunningIcon from "./state-indicators/running.svg?react";
import StartingIcon from "./state-indicators/starting.svg?react";
import StoppedIcon from "./state-indicators/stopped.svg?react";
type SVGIcon = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
export type ProjectStatus = "RUNNING" | "STOPPED" | "STARTING";
const INDICATORS: Record<ProjectStatus, SVGIcon> = {
STOPPED: ColdIcon,
const CONVERSATION_STATUS_INDICATORS: Record<ConversationStatus, SVGIcon> = {
STOPPED: StoppedIcon,
RUNNING: RunningIcon,
STARTING: ColdIcon,
STARTING: StartingIcon,
};
interface ConversationStateIndicatorProps {
status: ProjectStatus;
conversationStatus: ConversationStatus;
}
export function ConversationStateIndicator({
status,
conversationStatus,
}: ConversationStateIndicatorProps) {
const StateIcon = INDICATORS[status];
const StateIcon = CONVERSATION_STATUS_INDICATORS[conversationStatus];
return (
<div data-testid={`${status}-indicator`}>
<div data-testid={`${conversationStatus}-indicator`}>
<StateIcon />
</div>
);
@@ -0,0 +1,142 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { ChevronDown, ChevronRight } from "lucide-react";
import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalBody } from "#/components/shared/modals/modal-body";
import { I18nKey } from "#/i18n/declaration";
import { useConversationMicroagents } from "#/hooks/query/use-conversation-microagents";
interface MicroagentsModalProps {
onClose: () => void;
conversationId: string | undefined;
}
export function MicroagentsModal({
onClose,
conversationId,
}: MicroagentsModalProps) {
const { t } = useTranslation();
const [expandedAgents, setExpandedAgents] = useState<Record<string, boolean>>(
{},
);
const {
data: microagents,
isLoading,
isError,
} = useConversationMicroagents({
conversationId,
enabled: true,
});
const toggleAgent = (agentName: string) => {
setExpandedAgents((prev) => ({
...prev,
[agentName]: !prev[agentName],
}));
};
return (
<ModalBackdrop onClose={onClose}>
<ModalBody
width="medium"
className="max-h-[80vh] flex flex-col items-start"
testID="microagents-modal"
>
<div className="flex flex-col gap-6 w-full">
<BaseModalTitle title={t(I18nKey.MICROAGENTS_MODAL$TITLE)} />
</div>
<div className="w-full h-[60vh] overflow-auto rounded-md">
{isLoading && (
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary" />
</div>
)}
{!isLoading &&
(isError || !microagents || microagents.length === 0) && (
<div className="flex items-center justify-center h-full p-4">
<p className="text-gray-400">
{isError
? t(I18nKey.MICROAGENTS_MODAL$FETCH_ERROR)
: t(I18nKey.CONVERSATION$NO_MICROAGENTS)}
</p>
</div>
)}
{!isLoading && microagents && microagents.length > 0 && (
<div className="p-2 space-y-3">
{microagents.map((agent) => {
const isExpanded = expandedAgents[agent.name] || false;
return (
<div key={agent.name} className="rounded-md overflow-hidden">
<button
type="button"
onClick={() => toggleAgent(agent.name)}
className="w-full py-3 px-2 text-left flex items-center justify-between hover:bg-gray-700 transition-colors"
>
<div className="flex items-center">
<h3 className="font-bold text-gray-100">
{agent.name}
</h3>
</div>
<div className="flex items-center">
<span className="px-2 py-1 text-xs rounded-full bg-gray-800 mr-2">
{agent.type === "repo" ? "Repository" : "Knowledge"}
</span>
<span className="text-gray-300">
{isExpanded ? (
<ChevronDown size={18} />
) : (
<ChevronRight size={18} />
)}
</span>
</div>
</button>
{isExpanded && (
<div className="px-2 pb-3 pt-1">
{agent.triggers && agent.triggers.length > 0 && (
<div className="mt-2 mb-3">
<h4 className="text-sm font-semibold text-gray-300 mb-2">
{t(I18nKey.MICROAGENTS_MODAL$TRIGGERS)}
</h4>
<div className="flex flex-wrap gap-1">
{agent.triggers.map((trigger) => (
<span
key={trigger}
className="px-2 py-1 text-xs rounded-full bg-blue-900"
>
{trigger}
</span>
))}
</div>
</div>
)}
<div className="mt-2">
<h4 className="text-sm font-semibold text-gray-300 mb-2">
{t(I18nKey.MICROAGENTS_MODAL$CONTENT)}
</h4>
<div className="text-sm mt-2 p-3 bg-gray-900 rounded-md overflow-auto text-gray-300 max-h-[400px] shadow-inner">
<pre className="whitespace-pre-wrap font-mono text-sm leading-relaxed">
{agent.content ||
t(I18nKey.MICROAGENTS_MODAL$NO_CONTENT)}
</pre>
</div>
</div>
</div>
)}
</div>
);
})}
</div>
)}
</div>
</ModalBody>
</ModalBackdrop>
);
}
@@ -1,4 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 16.8599C13.4183 16.8599 17 13.2781 17 8.85986C17 4.44159 13.4183 0.859863 9 0.859863C4.58172 0.859863 1 4.44159 1 8.85986C1 13.2781 4.58172 16.8599 9 16.8599Z" fill="#779FD4"/>
<path d="M4.61035 8.43014L7.86035 12.0301L13.3904 6.64014" stroke="#231F20" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 433 B

@@ -1,4 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.76039 6.99002C8.478 6.99002 9.87039 5.59763 9.87039 3.88002C9.87039 2.16241 8.478 0.77002 6.76039 0.77002C5.04279 0.77002 3.65039 2.16241 3.65039 3.88002C3.65039 5.59763 5.04279 6.99002 6.76039 6.99002Z" fill="#FFE165"/>
<path d="M1.0802 17.0799C1.0802 17.0799 0.610196 11.5499 3.0102 9.67992C4.7902 8.29992 7.3302 9.44992 9.7802 7.95992C11.5802 6.86992 13.6102 4.10992 14.5202 2.49992C14.9302 1.77992 15.9102 1.62992 16.6102 2.05992C17.3802 2.51992 17.6102 3.53992 17.1102 4.28992C16.2302 5.58992 14.1802 8.85992 13.1202 10.3699C10.7602 13.7599 11.4302 17.0799 11.4302 17.0799H1.0702H1.0802Z" fill="#FFE165"/>
</svg>

Before

Width:  |  Height:  |  Size: 726 B

@@ -1,4 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.87012 2.08984C9.87012 1.53756 9.4224 1.08984 8.87012 1.08984C8.31783 1.08984 7.87012 1.53756 7.87012 2.08984V8.08984C7.87012 8.64213 8.31783 9.08984 8.87012 9.08984C9.4224 9.08984 9.87012 8.64213 9.87012 8.08984V2.08984Z" fill="#60BB46"/>
<path d="M10.8702 2.50988V2.64988C10.8702 3.01988 11.0702 3.36988 11.4102 3.51988C13.6802 4.51988 15.2202 6.88988 14.9702 9.56988C14.7002 12.5599 12.1002 14.9599 9.09021 15.0099C5.74021 15.0599 2.99021 12.3499 2.99021 9.00988C2.99021 6.65988 4.35021 4.62988 6.31021 3.64988C6.64021 3.48988 6.86021 3.16988 6.86021 2.80988V2.63988C6.86021 1.94988 6.14021 1.51988 5.51021 1.81988C2.42021 3.30988 0.430214 6.71988 1.12021 10.5199C1.69021 13.6799 4.22021 16.2499 7.37021 16.8699C12.4902 17.8699 16.9802 13.9699 16.9802 9.02988C16.9802 5.71988 14.9702 2.88988 12.1002 1.66988C11.5102 1.41988 10.8502 1.88988 10.8502 2.52988L10.8702 2.50988Z" fill="#60BB46"/>
</svg>

Before

Width:  |  Height:  |  Size: 1008 B

@@ -101,7 +101,7 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
name="email"
type="email"
placeholder={t(I18nKey.FEEDBACK$EMAIL_PLACEHOLDER)}
className="bg-[#27272A] px-3 py-[10px] rounded"
className="bg-[#27272A] px-3 py-[10px] rounded-sm"
/>
</label>
@@ -6,7 +6,7 @@ export function BranchErrorState() {
return (
<div
data-testid="branch-dropdown-error"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded text-red-500"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm text-red-500"
>
<span className="text-sm">{t("HOME$FAILED_TO_LOAD_BRANCHES")}</span>
</div>
@@ -7,7 +7,7 @@ export function BranchLoadingState() {
return (
<div
data-testid="branch-dropdown-loading"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm"
>
<Spinner size="sm" />
<span className="text-sm">{t("HOME$LOADING_BRANCHES")}</span>
@@ -6,7 +6,7 @@ export function RepositoryErrorState() {
return (
<div
data-testid="repo-dropdown-error"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded text-red-500"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm text-red-500"
>
<span className="text-sm">{t("HOME$FAILED_TO_LOAD_REPOSITORIES")}</span>
</div>
@@ -7,7 +7,7 @@ export function RepositoryLoadingState() {
return (
<div
data-testid="repo-dropdown-loading"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded"
className="flex items-center gap-2 max-w-[500px] h-10 px-3 bg-tertiary border border-[#717888] rounded-sm"
>
<Spinner size="sm" />
<span className="text-sm">{t("HOME$LOADING_REPOSITORIES")}</span>
@@ -12,7 +12,7 @@ export function Thumbnail({ src, size = "small" }: ThumbnailProps) {
alt=""
src={src}
className={cn(
"rounded object-cover",
"rounded-sm object-cover",
size === "small" && "w-[62px] h-[62px]",
size === "large" && "w-[100px] h-[100px]",
)}
@@ -9,6 +9,7 @@ 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();
@@ -42,7 +43,7 @@ export function PaymentForm() {
>
<div
className={cn(
"flex items-center justify-between w-[680px] bg-[#7F7445] rounded px-3 py-2",
"flex items-center justify-between w-[680px] bg-[#7F7445] rounded-sm px-3 py-2",
"text-[28px] leading-8 -tracking-[0.02em] font-bold",
)}
>
@@ -79,6 +80,7 @@ export function PaymentForm() {
{t(I18nKey.PAYMENT$ADD_CREDIT)}
</BrandButton>
{isPending && <LoadingSpinner size="small" />}
<PoweredByStripeTag />
</div>
</div>
</form>
@@ -0,0 +1,16 @@
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>
);
}
@@ -32,7 +32,7 @@ export function BrandButton({
type={type}
onClick={onClick}
className={cn(
"w-fit p-2 text-sm rounded disabled:opacity-30 disabled:cursor-not-allowed hover:opacity-80",
"w-fit p-2 text-sm rounded-sm disabled:opacity-30 disabled:cursor-not-allowed hover:opacity-80",
variant === "primary" && "bg-primary text-[#0D0F11]",
variant === "secondary" && "border border-primary text-primary",
variant === "danger" && "bg-red-600 text-white hover:bg-red-700",
@@ -69,7 +69,7 @@ export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) {
{t(I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION)}
</div>
<textarea
className="w-full h-64 p-2 text-sm font-mono bg-base-tertiary rounded-md focus:border-blue-500 focus:outline-none"
className="w-full h-64 p-2 text-sm font-mono bg-base-tertiary rounded-md focus:border-blue-500 focus:outline-hidden"
value={configText}
onChange={handleTextChange}
spellCheck="false"
@@ -158,7 +158,7 @@ export function SecretForm({
required
className={cn(
"resize-none",
"bg-tertiary border border-[#717888] rounded p-2 placeholder:italic placeholder:text-tertiary-alt",
"bg-tertiary border border-[#717888] rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt",
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
)}
rows={8}
@@ -177,7 +177,7 @@ export function SecretForm({
defaultValue={secretDescription}
className={cn(
"resize-none",
"bg-tertiary border border-[#717888] rounded p-2 placeholder:italic placeholder:text-tertiary-alt",
"bg-tertiary border border-[#717888] rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt",
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
)}
/>
@@ -63,7 +63,7 @@ export function SettingsDropdownInput({
inputProps={{
classNames: {
inputWrapper:
"bg-tertiary border border-[#717888] h-10 w-full rounded p-2 placeholder:italic",
"bg-tertiary border border-[#717888] h-10 w-full rounded-sm p-2 placeholder:italic",
},
}}
defaultFilter={defaultFilter}
@@ -62,7 +62,7 @@ export function SettingsInput({
required={required}
pattern={pattern}
className={cn(
"bg-tertiary border border-[#717888] h-10 w-full rounded p-2 placeholder:italic placeholder:text-tertiary-alt",
"bg-tertiary border border-[#717888] h-10 w-full rounded-sm p-2 placeholder:italic placeholder:text-tertiary-alt",
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",
)}
/>
@@ -16,7 +16,7 @@ export function ReplaySuggestionBox({ onChange }: ReplaySuggestionBoxProps) {
htmlFor="import-trajectory"
className="w-full flex justify-center"
>
<span className="border-2 border-dashed border-neutral-600 rounded px-2 py-1 cursor-pointer">
<span className="border-2 border-dashed border-neutral-600 rounded-sm px-2 py-1 cursor-pointer">
{t(I18nKey.LANDING$UPLOAD_TRAJECTORY)}
</span>
<input
@@ -18,7 +18,7 @@ export function EditorActionButton({
onClick={onClick}
disabled={disabled}
className={cn(
"text-sm py-0.5 rounded w-20",
"text-sm py-0.5 rounded-sm w-20",
"hover:bg-tertiary disabled:opacity-50 disabled:cursor-not-allowed",
className,
)}
@@ -31,7 +31,7 @@ export function ModalButton({
disabled={disabled}
onClick={onClick}
className={clsx(
variant === "default" && "text-sm font-[500] py-[10px] rounded",
variant === "default" && "text-sm font-[500] py-[10px] rounded-sm",
variant === "text-like" && "text-xs leading-4 font-normal",
icon && "flex items-center justify-center gap-2",
disabled && "opacity-50 cursor-not-allowed",
@@ -36,7 +36,7 @@ export function CustomInput({
required={required}
defaultValue={defaultValue}
type={type}
className="bg-[#27272A] text-xs py-[10px] px-3 rounded"
className="bg-[#27272A] text-xs py-[10px] px-3 rounded-sm"
/>
</label>
);
@@ -16,7 +16,7 @@ export function ErrorToast({ id, error }: ErrorToastProps) {
<button
type="button"
onClick={() => toast.dismiss(id)}
className="bg-neutral-500 px-1 rounded h-full"
className="bg-neutral-500 px-1 rounded-sm h-full"
>
{t(I18nKey.ERROR_TOAST$CLOSE_BUTTON_LABEL)}
</button>
@@ -269,19 +269,19 @@ function SecurityInvariant() {
<hr className="border-t border-neutral-600 my-2" />
<ul className="space-y-2">
<div
className={`cursor-pointer p-2 rounded ${activeSection === "logs" && "bg-neutral-600"}`}
className={`cursor-pointer p-2 rounded-sm ${activeSection === "logs" && "bg-neutral-600"}`}
onClick={() => setActiveSection("logs")}
>
{t(I18nKey.INVARIANT$LOG_LABEL)}
</div>
<div
className={`cursor-pointer p-2 rounded ${activeSection === "policy" && "bg-neutral-600"}`}
className={`cursor-pointer p-2 rounded-sm ${activeSection === "policy" && "bg-neutral-600"}`}
onClick={() => setActiveSection("policy")}
>
{t(I18nKey.INVARIANT$POLICY_LABEL)}
</div>
<div
className={`cursor-pointer p-2 rounded ${activeSection === "settings" && "bg-neutral-600"}`}
className={`cursor-pointer p-2 rounded-sm ${activeSection === "settings" && "bg-neutral-600"}`}
onClick={() => setActiveSection("settings")}
>
{t(I18nKey.INVARIANT$SETTINGS_LABEL)}
@@ -92,7 +92,7 @@ export function ModelSelector({
inputProps={{
classNames: {
inputWrapper:
"bg-tertiary border border-[#717888] h-10 w-full rounded p-2 placeholder:italic",
"bg-tertiary border border-[#717888] h-10 w-full rounded-sm p-2 placeholder:italic",
},
}}
>
@@ -142,7 +142,7 @@ export function ModelSelector({
inputProps={{
classNames: {
inputWrapper:
"bg-tertiary border border-[#717888] h-10 w-full rounded p-2 placeholder:italic",
"bg-tertiary border border-[#717888] h-10 w-full rounded-sm p-2 placeholder:italic",
},
}}
>
+27 -22
View File
@@ -29,6 +29,8 @@ import {
import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message";
import { useWSErrorMessage } from "#/hooks/use-ws-error-message";
export type WebSocketStatus = "CONNECTING" | "CONNECTED" | "DISCONNECTED";
const hasValidMessageProperty = (obj: unknown): obj is { message: string } =>
typeof obj === "object" &&
obj !== null &&
@@ -67,14 +69,8 @@ const isMessageAction = (
): event is UserMessageAction | AssistantMessageAction =>
isUserMessage(event) || isAssistantMessage(event);
export enum WsClientProviderStatus {
CONNECTED,
DISCONNECTED,
CONNECTING,
}
interface UseWsClient {
status: WsClientProviderStatus;
webSocketStatus: WebSocketStatus;
isLoadingMessages: boolean;
events: Record<string, unknown>[];
parsedEvents: (OpenHandsAction | OpenHandsObservation)[];
@@ -82,7 +78,7 @@ interface UseWsClient {
}
const WsClientContext = React.createContext<UseWsClient>({
status: WsClientProviderStatus.DISCONNECTED,
webSocketStatus: "DISCONNECTED",
isLoadingMessages: true,
events: [],
parsedEvents: [],
@@ -139,9 +135,8 @@ export function WsClientProvider({
const { setErrorMessage, removeErrorMessage } = useWSErrorMessage();
const queryClient = useQueryClient();
const sioRef = React.useRef<Socket | null>(null);
const [status, setStatus] = React.useState(
WsClientProviderStatus.DISCONNECTED,
);
const [webSocketStatus, setWebSocketStatus] =
React.useState<WebSocketStatus>("DISCONNECTED");
const [events, setEvents] = React.useState<Record<string, unknown>[]>([]);
const [parsedEvents, setParsedEvents] = React.useState<
(OpenHandsAction | OpenHandsObservation)[]
@@ -162,7 +157,7 @@ export function WsClientProvider({
}
function handleConnect() {
setStatus(WsClientProviderStatus.CONNECTED);
setWebSocketStatus("CONNECTED");
removeErrorMessage();
}
@@ -261,7 +256,7 @@ export function WsClientProvider({
}
function handleDisconnect(data: unknown) {
setStatus(WsClientProviderStatus.DISCONNECTED);
setWebSocketStatus("DISCONNECTED");
const sio = sioRef.current;
if (!sio) {
return;
@@ -275,7 +270,7 @@ export function WsClientProvider({
function handleError(data: unknown) {
// set status
setStatus(WsClientProviderStatus.DISCONNECTED);
setWebSocketStatus("DISCONNECTED");
updateStatusWhenErrorMessagePresent(data);
setErrorMessage(
@@ -294,17 +289,14 @@ export function WsClientProvider({
// reset events when conversationId changes
setEvents([]);
setParsedEvents([]);
setStatus(WsClientProviderStatus.DISCONNECTED);
setWebSocketStatus("CONNECTING");
}, [conversationId]);
React.useEffect(() => {
if (!conversationId) {
throw new Error("No conversation ID provided");
}
if (
!conversation ||
["STOPPED", "STARTING"].includes(conversation.status)
) {
if (conversation?.status !== "RUNNING" && !conversation?.runtime_status) {
return () => undefined; // conversation not yet loaded
}
@@ -314,6 +306,9 @@ export function WsClientProvider({
sio.disconnect();
}
// Set initial status...
setWebSocketStatus("CONNECTING");
const lastEvent = lastEventRef.current;
const query = {
latest_event_id: lastEvent?.id ?? -1,
@@ -348,7 +343,12 @@ export function WsClientProvider({
sio.off("connect_failed", handleError);
sio.off("disconnect", handleDisconnect);
};
}, [conversationId, conversation?.url, conversation?.status]);
}, [
conversationId,
conversation?.url,
conversation?.status,
conversation?.runtime_status,
]);
React.useEffect(
() => () => {
@@ -363,13 +363,18 @@ export function WsClientProvider({
const value = React.useMemo<UseWsClient>(
() => ({
status,
webSocketStatus,
isLoadingMessages: messageRateHandler.isUnderThreshold,
events,
parsedEvents,
send,
}),
[status, messageRateHandler.isUnderThreshold, events, parsedEvents],
[
webSocketStatus,
messageRateHandler.isUnderThreshold,
events,
parsedEvents,
],
);
return <WsClientContext value={value}>{children}</WsClientContext>;
@@ -1,15 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import React from "react";
import {
useWsClient,
WsClientProviderStatus,
} from "#/context/ws-client-provider";
import { useConversationId } from "#/hooks/use-conversation-id";
import OpenHands from "#/api/open-hands";
import { useRuntimeIsReady } from "../use-runtime-is-ready";
export const useConversationConfig = () => {
const { status } = useWsClient();
const { conversationId } = useConversationId();
const runtimeIsReady = useRuntimeIsReady();
const query = useQuery({
queryKey: ["conversation_config", conversationId],
@@ -17,7 +14,7 @@ export const useConversationConfig = () => {
if (!conversationId) throw new Error("No conversation ID");
return OpenHands.getRuntimeId(conversationId);
},
enabled: status !== WsClientProviderStatus.DISCONNECTED && !!conversationId,
enabled: runtimeIsReady && !!conversationId,
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
});
@@ -0,0 +1,25 @@
import { useQuery } from "@tanstack/react-query";
import OpenHands from "#/api/open-hands";
interface UseConversationMicroagentsOptions {
conversationId: string | undefined;
enabled?: boolean;
}
export const useConversationMicroagents = ({
conversationId,
enabled = true,
}: UseConversationMicroagentsOptions) =>
useQuery({
queryKey: ["conversation", conversationId, "microagents"],
queryFn: async () => {
if (!conversationId) {
throw new Error("No conversation ID provided");
}
const data = await OpenHands.getMicroagents(conversationId);
return data.microagents;
},
enabled: !!conversationId && enabled,
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
});
@@ -0,0 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { useConversationId } from "../use-conversation-id";
import OpenHands from "#/api/open-hands";
export const useGetMicroagentPrompt = ({ eventId }: { eventId: number }) => {
const { conversationId } = useConversationId();
return useQuery({
queryKey: ["conversation", "remember_prompt", conversationId, eventId],
queryFn: () => OpenHands.getMicroagentPrompt(conversationId, eventId),
});
};
+16 -4
View File
@@ -248,6 +248,9 @@ export enum I18nKey {
INVARIANT$TRACE_EXPORTED_MESSAGE = "INVARIANT$TRACE_EXPORTED_MESSAGE",
INVARIANT$POLICY_UPDATED_MESSAGE = "INVARIANT$POLICY_UPDATED_MESSAGE",
INVARIANT$SETTINGS_UPDATED_MESSAGE = "INVARIANT$SETTINGS_UPDATED_MESSAGE",
CHAT_INTERFACE$DISCONNECTED = "CHAT_INTERFACE$DISCONNECTED",
CHAT_INTERFACE$CONNECTING = "CHAT_INTERFACE$CONNECTING",
CHAT_INTERFACE$STOPPED = "CHAT_INTERFACE$STOPPED",
CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE = "CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE",
CHAT_INTERFACE$AGENT_INIT_MESSAGE = "CHAT_INTERFACE$AGENT_INIT_MESSAGE",
CHAT_INTERFACE$AGENT_RUNNING_MESSAGE = "CHAT_INTERFACE$AGENT_RUNNING_MESSAGE",
@@ -366,10 +369,8 @@ export enum I18nKey {
FEEDBACK$PUBLIC_LABEL = "FEEDBACK$PUBLIC_LABEL",
FEEDBACK$PRIVATE_LABEL = "FEEDBACK$PRIVATE_LABEL",
SIDEBAR$CONVERSATIONS = "SIDEBAR$CONVERSATIONS",
STATUS$CONNECTING_TO_RUNTIME = "STATUS$CONNECTING_TO_RUNTIME",
STATUS$STARTING_RUNTIME = "STATUS$STARTING_RUNTIME",
STATUS$STARTING_CONTAINER = "STATUS$STARTING_CONTAINER",
STATUS$PREPARING_CONTAINER = "STATUS$PREPARING_CONTAINER",
STATUS$CONTAINER_STARTED = "STATUS$CONTAINER_STARTED",
STATUS$SETTING_UP_WORKSPACE = "STATUS$SETTING_UP_WORKSPACE",
STATUS$SETTING_UP_GIT_HOOKS = "STATUS$SETTING_UP_GIT_HOOKS",
ACCOUNT_SETTINGS_MODAL$DISCONNECT = "ACCOUNT_SETTINGS_MODAL$DISCONNECT",
@@ -460,7 +461,7 @@ export enum I18nKey {
CONVERSATION$NO_CONVERSATIONS = "CONVERSATION$NO_CONVERSATIONS",
LANDING$SELECT_GIT_REPO = "LANDING$SELECT_GIT_REPO",
BUTTON$SEND = "BUTTON$SEND",
STATUS$WAITING_FOR_CLIENT = "STATUS$WAITING_FOR_CLIENT",
STATUS$BUILDING_RUNTIME = "STATUS$BUILDING_RUNTIME",
SUGGESTIONS$WHAT_TO_BUILD = "SUGGESTIONS$WHAT_TO_BUILD",
SETTINGS_FORM$ENABLE_DEFAULT_CONDENSER_SWITCH_LABEL = "SETTINGS_FORM$ENABLE_DEFAULT_CONDENSER_SWITCH_LABEL",
BUTTON$MARK_HELPFUL = "BUTTON$MARK_HELPFUL",
@@ -470,6 +471,7 @@ 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",
@@ -543,6 +545,16 @@ export enum I18nKey {
TOS$CONTINUE = "TOS$CONTINUE",
TOS$ERROR_ACCEPTING = "TOS$ERROR_ACCEPTING",
TIPS$CUSTOMIZE_MICROAGENT = "TIPS$CUSTOMIZE_MICROAGENT",
CONVERSATION$SHOW_MICROAGENTS = "CONVERSATION$SHOW_MICROAGENTS",
CONVERSATION$NO_MICROAGENTS = "CONVERSATION$NO_MICROAGENTS",
CONVERSATION$FAILED_TO_FETCH_MICROAGENTS = "CONVERSATION$FAILED_TO_FETCH_MICROAGENTS",
MICROAGENTS_MODAL$TITLE = "MICROAGENTS_MODAL$TITLE",
MICROAGENTS_MODAL$TRIGGERS = "MICROAGENTS_MODAL$TRIGGERS",
MICROAGENTS_MODAL$INPUTS = "MICROAGENTS_MODAL$INPUTS",
MICROAGENTS_MODAL$TOOLS = "MICROAGENTS_MODAL$TOOLS",
MICROAGENTS_MODAL$CONTENT = "MICROAGENTS_MODAL$CONTENT",
MICROAGENTS_MODAL$NO_CONTENT = "MICROAGENTS_MODAL$NO_CONTENT",
MICROAGENTS_MODAL$FETCH_ERROR = "MICROAGENTS_MODAL$FETCH_ERROR",
TIPS$SETUP_SCRIPT = "TIPS$SETUP_SCRIPT",
TIPS$VSCODE_INSTANCE = "TIPS$VSCODE_INSTANCE",
TIPS$SAVE_WORK = "TIPS$SAVE_WORK",
+283 -91
View File
@@ -3967,21 +3967,69 @@
"ja": "設定を更新しました",
"uk": "Налаштування оновлено"
},
"CHAT_INTERFACE$DISCONNECTED": {
"en": "Disconnected",
"ja": "切断されました",
"zh-CN": "已断开连接",
"zh-TW": "已斷開連接",
"ko-KR": "연결 끊김",
"no": "Frakoblet",
"it": "Disconnesso",
"pt": "Desconectado",
"es": "Desconectado",
"ar": "تم قطع الاتصال",
"fr": "Déconnecté",
"tr": "Bağlantı kesildi",
"de": "Getrennt",
"uk": "Від'єднано"
},
"CHAT_INTERFACE$CONNECTING": {
"en": "Connecting",
"ja": "接続中",
"zh-CN": "正在连接",
"zh-TW": "正在連接",
"ko-KR": "연결 중",
"no": "Kobler til",
"it": "Connessione in corso",
"pt": "Conectando",
"es": "Conectando",
"ar": "جاري الاتصال",
"fr": "Connexion en cours",
"tr": "Bağlanıyor",
"de": "Verbindung wird hergestellt",
"uk": "З'єднання"
},
"CHAT_INTERFACE$STOPPED": {
"en": "Stopped",
"ja": "停止しました",
"zh-CN": "已停止",
"zh-TW": "已停止",
"ko-KR": "중지됨",
"no": "Stoppet",
"it": "Fermato",
"pt": "Parado",
"es": "Detenido",
"ar": "توقف",
"fr": "Arrêté",
"tr": "Durduruldu",
"de": "Angehalten",
"uk": "Зупинено"
},
"CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE": {
"en": "Starting up!",
"de": "Wird gestartet!",
"zh-CN": "正在启动!",
"zh-TW": "正在啟動!",
"ko-KR": "시작 중입니다!",
"no": "Starter opp!",
"it": "Avvio in corso!",
"pt": "Iniciando!",
"es": "¡Iniciando!",
"ar": "جارٍ البدء!",
"fr": "Démarrage en cours !",
"tr": "Başlatılıyor!",
"ja": "起動中!",
"uk": "Запуск!"
"en": "Initializing Agent...",
"de": "Agent wird initialisiert...",
"zh-CN": "正在初始化智能体...",
"zh-TW": "正在初始化智能體...",
"ko-KR": "에이전트 초기화 중...",
"no": "Initialiserer agent...",
"it": "Inizializzazione dell'agente...",
"pt": "Inicializando o Agente...",
"es": "Inicializando Agente...",
"ar": "جارٍ تهيئة الوكيل...",
"fr": "Initialisation de l'agent...",
"tr": "Ajan başlatılıyor...",
"ja": "エージェントを初期化中...",
"uk": "Ініціалізація агента..."
},
"CHAT_INTERFACE$AGENT_INIT_MESSAGE": {
"en": "Agent is initialized, waiting for task...",
@@ -5855,6 +5903,22 @@
"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": "启动运行时...",
@@ -5871,54 +5935,6 @@
"ja": "ランタイムを開始中",
"uk": "Запуск середовища виконання..."
},
"STATUS$STARTING_CONTAINER": {
"en": "Preparing container, this might take a few minutes...",
"zh-CN": "正在准备容器,这可能需要几分钟...",
"zh-TW": "正在準備容器,這可能需要幾分鐘...",
"de": "Container wird vorbereitet, dies kann einige Minuten dauern...",
"ko-KR": "컨테이너를 준비 중입니다. 몇 분 정도 걸릴 수 있습니다...",
"no": "Forbereder container, dette kan ta noen minutter...",
"it": "Preparazione del container in corso, potrebbe richiedere alcuni minuti...",
"pt": "Preparando o container, isso pode levar alguns minutos...",
"es": "Preparando el contenedor, esto puede tardar unos minutos...",
"ar": "جارٍ إعداد الحاوية، قد يستغرق هذا بضع دقائق...",
"fr": "Préparation du conteneur, cela peut prendre quelques minutes...",
"tr": "Konteyner hazırlanıyor, bu işlem birkaç dakika sürebilir...",
"ja": "コンテナを開始中",
"uk": "Підготовка контейнера, це може тривати кілька хвилин..."
},
"STATUS$PREPARING_CONTAINER": {
"en": "Preparing to start container...",
"zh-CN": "正在准备启动容器...",
"zh-TW": "正在準備啟動容器...",
"de": "Vorbereitung zum Starten des Containers...",
"ko-KR": "컨테이너 시작 준비 중...",
"no": "Forbereder å starte container...",
"it": "Preparazione all'avvio del container...",
"pt": "Preparando para iniciar o container...",
"es": "Preparando para iniciar el contenedor...",
"ar": "جارٍ التحضير لبدء الحاوية...",
"fr": "Préparation du démarrage du conteneur...",
"tr": "Konteyner başlatılmaya hazırlanıyor...",
"ja": "コンテナを準備中",
"uk": "Preparing to start container..."
},
"STATUS$CONTAINER_STARTED": {
"en": "Container started.",
"zh-CN": "容器已启动。",
"zh-TW": "容器已啟動。",
"de": "Container gestartet.",
"ko-KR": "컨테이너가 시작되었습니다.",
"no": "Container startet.",
"it": "Container avviato.",
"pt": "Container iniciado.",
"es": "Contenedor iniciado.",
"ar": "تم بدء الحاوية.",
"fr": "Conteneur démarré.",
"tr": "Konteyner başlatıldı.",
"ja": "コンテナが開始されました",
"uk": "Контейнер запущено."
},
"STATUS$SETTING_UP_WORKSPACE": {
"en": "Setting up workspace...",
"zh-CN": "正在设置工作区...",
@@ -7359,21 +7375,21 @@
"tr": "Gönder",
"uk": "Надіслати"
},
"STATUS$WAITING_FOR_CLIENT": {
"en": "Waiting for client to become ready...",
"zh-CN": "等待客户端准备就绪...",
"zh-TW": "等待客戶端準備就緒...",
"de": "Warten auf Bereitschaft des Clients...",
"ko-KR": "클라이언트가 준비될 때까지 기다리는 중...",
"no": "Venter på at klienten skal bli klar...",
"it": "In attesa che il client sia pronto...",
"pt": "Aguardando o cliente ficar pronto...",
"es": "Esperando a que el cliente esté listo...",
"ar": "في انتظار جاهزية العميل...",
"fr": "En attente que le client soit prêt...",
"tr": "İstemcinin hazır olması bekleniyor...",
"ja": "クライアントの準備を待機中",
"uk": "Чекаємо на готовність клієнта..."
"STATUS$BUILDING_RUNTIME": {
"en": "Building Runtime...",
"ja": "ランタイムを構築中...",
"zh-CN": "正在构建运行时...",
"zh-TW": "正在構建運行時...",
"ko-KR": "런타임 구축 중...",
"no": "Bygger kjøretidsmiljø...",
"it": "Costruzione del runtime in corso...",
"pt": "Construindo ambiente de execução...",
"es": "Construyendo entorno de ejecución...",
"ar": "جاري بناء بيئة التشغيل...",
"fr": "Construction de l'environnement d'exécution...",
"tr": "Çalışma ortamı oluşturuluyor...",
"de": "Laufzeitumgebung wird erstellt...",
"uk": "Створення середовища виконання..."
},
"SUGGESTIONS$WHAT_TO_BUILD": {
"en": "What do you want to build?",
@@ -7519,6 +7535,22 @@
"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": "請求情報を追加",
@@ -8672,20 +8704,180 @@
"tr": "Hizmet Şartlarını kabul ederken hata oluştu"
},
"TIPS$CUSTOMIZE_MICROAGENT": {
"en": "You can customize OpenHands for your repo using a microagent. Ask OpenHands to put a description of the repo, including how to run the code, into .openhands/microagents/repo.md.",
"ja": "マイクロエージェントを使用して、リポジトリ用にOpenHandsをカスタマイズできます。OpenHandsに、コードの実行方法を含むリポジトリの説明を.openhands/microagents/repo.mdに入れるよう依頼してください。",
"zh-CN": "您可以使用微代理为您的仓库自定义OpenHands。请OpenHands将仓库的描述(包括如何运行代码)放入.openhands/microagents/repo.md。",
"zh-TW": "您可以使用微代理為您的倉庫自定義OpenHands。請OpenHands將倉庫的描述(包括如何運行代碼)放入.openhands/microagents/repo.md。",
"ko-KR": "마이크로에이전트를 사용하여 저장소에 맞게 OpenHands를 사용자 정의할 수 있습니다. OpenHands에게 코드 실행 방법을 포함한 저장소 설명을 .openhands/microagents/repo.md에 넣도록 요청하세요.",
"no": "Du kan tilpasse OpenHands for ditt repo ved å bruke en mikroagent. Be OpenHands om å legge en beskrivelse av repoet, inkludert hvordan du kjører koden, i .openhands/microagents/repo.md.",
"it": "Puoi personalizzare OpenHands per il tuo repository utilizzando un microagente. Chiedi a OpenHands di inserire una descrizione del repository, incluso come eseguire il codice, in .openhands/microagents/repo.md.",
"pt": "Você pode personalizar o OpenHands para seu repositório usando um microagente. Peça ao OpenHands para colocar uma descrição do repositório, incluindo como executar o código, em .openhands/microagents/repo.md.",
"es": "Puede personalizar OpenHands para su repositorio utilizando un microagente. Pídale a OpenHands que ponga una descripción del repositorio, incluido cómo ejecutar el código, en .openhands/microagents/repo.md.",
"ar": "يمكنك تخصيص OpenHands لمستودعك باستخدام وكيل مصغر. اطلب من OpenHands وضع وصف للمستودع، بما في ذلك كيفية تشغيل الكود، في .openhands/microagents/repo.md.",
"fr": "Vous pouvez personnaliser OpenHands pour votre dépôt en utilisant un micro-agent. Demandez à OpenHands de mettre une description du dépôt, y compris comment exécuter le code, dans .openhands/microagents/repo.md.",
"tr": "Bir mikro ajan kullanarak deponuz için OpenHands'i özelleştirebilirsiniz. OpenHands'ten kodun nasıl çalıştırılacağı da dahil olmak üzere deponun açıklamasını .openhands/microagents/repo.md dosyasına koymasını isteyin.",
"de": "Sie können OpenHands für Ihr Repository mit einem Mikroagenten anpassen. Bitten Sie OpenHands, eine Beschreibung des Repositorys, einschließlich der Ausführung des Codes, in .openhands/microagents/repo.md zu platzieren.",
"uk": "Ви можете налаштувати OpenHands для вашого репозиторію за допомогою мікроагента. Попросіть OpenHands розмістити опис репозиторію, включаючи інструкції з запуску коду, у файлі .openhands/microagents/repo.md."
"en": "You can customize OpenHands for your repo using an available microagent. Ask OpenHands to put a description of the repo, including how to run the code, into .openhands/microagents/repo.md.",
"ja": "利用可能なマイクロエージェントを使用して、リポジトリ用にOpenHandsをカスタマイズできます。OpenHandsに、コードの実行方法を含むリポジトリの説明を.openhands/microagents/repo.mdに入れるよう依頼してください。",
"zh-CN": "您可以使用可用微代理为您的仓库自定义OpenHands。请OpenHands将仓库的描述(包括如何运行代码)放入.openhands/microagents/repo.md。",
"zh-TW": "您可以使用可用微代理為您的倉庫自定義OpenHands。請OpenHands將倉庫的描述(包括如何運行代碼)放入.openhands/microagents/repo.md。",
"ko-KR": "사용 가능한 마이크로에이전트를 사용하여 저장소에 맞게 OpenHands를 사용자 정의할 수 있습니다. OpenHands에게 코드 실행 방법을 포함한 저장소 설명을 .openhands/microagents/repo.md에 넣도록 요청하세요.",
"no": "Du kan tilpasse OpenHands for ditt repo ved å bruke en tilgjengelig mikroagent. Be OpenHands om å legge en beskrivelse av repoet, inkludert hvordan du kjører koden, i .openhands/microagents/repo.md.",
"it": "Puoi personalizzare OpenHands per il tuo repository utilizzando un microagente disponibile. Chiedi a OpenHands di inserire una descrizione del repository, incluso come eseguire il codice, in .openhands/microagents/repo.md.",
"pt": "Você pode personalizar o OpenHands para seu repositório usando um microagente disponível. Peça ao OpenHands para colocar uma descrição do repositório, incluindo como executar o código, em .openhands/microagents/repo.md.",
"es": "Puede personalizar OpenHands para su repositorio utilizando un microagente disponible. Pídale a OpenHands que ponga una descripción del repositorio, incluido cómo ejecutar el código, en .openhands/microagents/repo.md.",
"ar": "يمكنك تخصيص OpenHands لمستودعك باستخدام وكيل مصغر متاح. اطلب من OpenHands وضع وصف للمستودع، بما في ذلك كيفية تشغيل الكود، في .openhands/microagents/repo.md.",
"de": "Sie können OpenHands für Ihr Repository mit einem verfügbaren Mikroagenten anpassen. Bitten Sie OpenHands, eine Beschreibung des Repositorys, einschließlich der Ausführung des Codes, in .openhands/microagents/repo.md zu platzieren.",
"fr": "Vous pouvez personnaliser OpenHands pour votre dépôt en utilisant un micro-agent disponible. Demandez à OpenHands de mettre une description du dépôt, y compris comment exécuter le code, dans .openhands/microagents/repo.md.",
"tr": "Kullanılabilir bir mikro ajan kullanarak OpenHands'i deponuz için özelleştirebilirsiniz. OpenHands'ten deponun açıklamasını, kodun nasıl çalıştırılacağı dahil, .openhands/microagents/repo.md dosyasına koymasını isteyin.",
"uk": "Ви можете налаштувати OpenHands для свого репозиторію за допомогою доступного мікроагента. Попросіть OpenHands розмістити опис репозиторію, включаючи інформацію про те, як запустити код, у файлі .openhands/microagents/repo.md."
},
"CONVERSATION$SHOW_MICROAGENTS": {
"en": "Show Available Microagents",
"ja": "利用可能なマイクロエージェントを表示",
"zh-CN": "显示可用微代理",
"zh-TW": "顯示可用微代理",
"ko-KR": "사용 가능한 마이크로에이전트 표시",
"no": "Vis tilgjengelige mikroagenter",
"ar": "عرض الوكلاء المصغرين المتاحة",
"de": "Verfügbare Mikroagenten anzeigen",
"fr": "Afficher les micro-agents disponibles",
"it": "Mostra microagenti disponibili",
"pt": "Mostrar microagentes disponíveis",
"es": "Mostrar microagentes disponibles",
"tr": "Kullanılabilir mikro ajanları göster",
"uk": "Показати доступних мікроагентів"
},
"CONVERSATION$NO_MICROAGENTS": {
"en": "No available microagents found for this conversation.",
"ja": "この会話用の利用可能なマイクロエージェントが見つかりませんでした。",
"zh-CN": "未找到此对话的可用微代理。",
"zh-TW": "未找到此對話的可用微代理。",
"ko-KR": "이 대화에 대한 사용 가능한 마이크로에이전트를 찾을 수 없습니다.",
"no": "Ingen tilgjengelige mikroagenter funnet for denne samtalen.",
"ar": "لم يتم العثور على وكلاء مصغرين متاحة لهذه المحادثة.",
"de": "Keine verfügbaren Mikroagenten für dieses Gespräch gefunden.",
"fr": "Aucun micro-agent disponible trouvé pour cette conversation.",
"it": "Nessun microagente disponibile trovato per questa conversazione.",
"pt": "Nenhum microagente disponível encontrado para esta conversa.",
"es": "No se encontraron microagentes disponibles para esta conversación.",
"tr": "Bu konuşma için kullanılabilir mikro ajan bulunamadı.",
"uk": "Для цієї розмови не знайдено доступних мікроагентів."
},
"CONVERSATION$FAILED_TO_FETCH_MICROAGENTS": {
"en": "Failed to fetch available microagents",
"ja": "利用可能なマイクロエージェントの取得に失敗しました",
"zh-CN": "获取可用微代理失败",
"zh-TW": "獲取可用微代理失敗",
"ko-KR": "사용 가능한 마이크로에이전트를 가져오지 못했습니다",
"no": "Kunne ikke hente tilgjengelige mikroagenter",
"ar": "فشل في جلب الوكلاء المصغرين المتاحة",
"de": "Fehler beim Abrufen von verfügbaren Mikroagenten",
"fr": "Échec de la récupération des micro-agents disponibles",
"it": "Impossibile recuperare i microagenti disponibili",
"pt": "Falha ao buscar microagentes disponíveis",
"es": "Error al obtener microagentes disponibles",
"tr": "Kullanılabilir mikro ajanlar getirilemedi",
"uk": "Не вдалося отримати доступних мікроагентів"
},
"MICROAGENTS_MODAL$TITLE": {
"en": "Available Microagents",
"ja": "利用可能なマイクロエージェント",
"zh-CN": "可用微代理",
"zh-TW": "可用微代理",
"ko-KR": "사용 가능한 마이크로에이전트",
"no": "Tilgjengelige mikroagenter",
"ar": "الوكلاء المصغرين المتاحة",
"de": "Verfügbare Mikroagenten",
"fr": "Micro-agents disponibles",
"it": "Microagenti disponibili",
"pt": "Microagentes disponíveis",
"es": "Microagentes disponibles",
"tr": "Kullanılabilir mikro ajanlar",
"uk": "Доступні мікроагенти"
},
"MICROAGENTS_MODAL$TRIGGERS": {
"en": "Triggers",
"ja": "トリガー",
"zh-CN": "触发器",
"zh-TW": "觸發器",
"ko-KR": "트리거",
"no": "Utløsere",
"ar": "المحفزات",
"de": "Auslöser",
"fr": "Déclencheurs",
"it": "Trigger",
"pt": "Gatilhos",
"es": "Disparadores",
"tr": "Tetikleyiciler",
"uk": "Тригери"
},
"MICROAGENTS_MODAL$INPUTS": {
"en": "Inputs",
"ja": "入力",
"zh-CN": "输入",
"zh-TW": "輸入",
"ko-KR": "입력",
"no": "Inndata",
"ar": "المدخلات",
"de": "Eingaben",
"fr": "Entrées",
"it": "Input",
"pt": "Entradas",
"es": "Entradas",
"tr": "Girdiler",
"uk": "Вхідні дані"
},
"MICROAGENTS_MODAL$TOOLS": {
"en": "Tools",
"ja": "ツール",
"zh-CN": "工具",
"zh-TW": "工具",
"ko-KR": "도구",
"no": "Verktøy",
"ar": "الأدوات",
"de": "Werkzeuge",
"fr": "Outils",
"it": "Strumenti",
"pt": "Ferramentas",
"es": "Herramientas",
"tr": "Araçlar",
"uk": "Інструменти"
},
"MICROAGENTS_MODAL$CONTENT": {
"en": "Content",
"ja": "コンテンツ",
"zh-CN": "内容",
"zh-TW": "內容",
"ko-KR": "콘텐츠",
"no": "Innhold",
"ar": "المحتوى",
"de": "Inhalt",
"fr": "Contenu",
"it": "Contenuto",
"pt": "Conteúdo",
"es": "Contenido",
"tr": "İçerik",
"uk": "Вміст"
},
"MICROAGENTS_MODAL$NO_CONTENT": {
"en": "Microagent has no content",
"ja": "マイクロエージェントにコンテンツがありません",
"zh-CN": "微代理没有内容",
"zh-TW": "微代理沒有內容",
"ko-KR": "마이크로에이전트에 콘텐츠가 없습니다",
"no": "Mikroagenten har ikke innhold",
"ar": "الوكيل المصغر ليس لديه محتوى",
"de": "Mikroagent hat keinen Inhalt",
"fr": "Le micro-agent n'a pas de contenu",
"it": "Il microagente non ha contenuto",
"pt": "Microagente não tem conteúdo",
"es": "El microagente no tiene contenido",
"tr": "Mikroajanın içeriği yok",
"uk": "Мікроагент не має вмісту"
},
"MICROAGENTS_MODAL$FETCH_ERROR": {
"en": "Failed to fetch microagents. Please try again later.",
"ja": "マイクロエージェントの取得に失敗しました。後でもう一度お試しください。",
"zh-CN": "获取微代理失败。请稍后再试。",
"zh-TW": "獲取微代理失敗。請稍後再試。",
"ko-KR": "마이크로에이전트를 가져오지 못했습니다. 나중에 다시 시도해 주세요.",
"no": "Kunne ikke hente mikroagenter. Prøv igjen senere.",
"ar": "فشل في جلب الوكلاء المصغرين. يرجى المحاولة مرة أخرى لاحقًا.",
"de": "Mikroagenten konnten nicht abgerufen werden. Bitte versuchen Sie es später erneut.",
"fr": "Échec de la récupération des micro-agents. Veuillez réessayer plus tard.",
"it": "Impossibile recuperare i microagenti. Riprova più tardi.",
"pt": "Falha ao buscar microagentes. Por favor, tente novamente mais tarde.",
"es": "Error al obtener microagentes. Por favor, inténtelo de nuevo más tarde.",
"tr": "Mikroajanlar getirilemedi. Lütfen daha sonra tekrar deneyin.",
"uk": "Не вдалося отримати мікроагентів. Будь ласка, спробуйте пізніше."
},
"TIPS$SETUP_SCRIPT": {
"en": "You can add .openhands/setup.sh to your repository to automatically run a setup script every time you start an OpenHands conversation.",
+4
View File
@@ -57,6 +57,7 @@ const conversations: Conversation[] = [
last_updated_at: new Date().toISOString(),
created_at: new Date().toISOString(),
status: "RUNNING",
runtime_status: "STATUS$READY",
url: null,
session_api_key: null,
},
@@ -72,6 +73,7 @@ const conversations: Conversation[] = [
).toISOString(),
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
status: "STOPPED",
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -87,6 +89,7 @@ const conversations: Conversation[] = [
).toISOString(),
created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
status: "STOPPED",
runtime_status: null,
url: null,
session_api_key: null,
},
@@ -282,6 +285,7 @@ export const handlers = [
last_updated_at: new Date().toISOString(),
created_at: new Date().toISOString(),
status: "RUNNING",
runtime_status: "STATUS$READY",
url: null,
session_api_key: null,
};
+1
View File
@@ -19,6 +19,7 @@ const chat = ws.link(`ws://${window?.location.host}/socket.io`);
export const handlers: WebSocketHandler[] = [
chat.addEventListener("connection", (connection) => {
// @ts-expect-error - MSW v2 type incompatibility
const io = toSocketIo(connection);
// @ts-expect-error - accessing private property for testing purposes
const { url }: { url: URL } = io.client.connection;
+6 -6
View File
@@ -41,11 +41,11 @@ function EmailInputSection({
type="email"
value={email}
onChange={onEmailChange}
className={`text-base text-white p-2 bg-base-tertiary rounded border ${
className={`text-base text-white p-2 bg-base-tertiary rounded-sm border ${
isEmailChanged && !isEmailValid
? "border-red-500"
: "border-tertiary"
} flex-grow focus:outline-none focus:border-transparent focus:ring-0`}
} flex-grow focus:outline-hidden focus:border-transparent focus:ring-0`}
placeholder={t("SETTINGS$USER_EMAIL_LOADING")}
data-testid="email-input"
/>
@@ -65,7 +65,7 @@ function EmailInputSection({
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]"
className="px-4 py-2 rounded-sm 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")}
@@ -76,7 +76,7 @@ function EmailInputSection({
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]"
className="px-4 py-2 rounded-sm bg-primary text-white hover:opacity-80 disabled:opacity-30 disabled:cursor-not-allowed disabled:text-[#0D0F11]"
data-testid="resend-verification-button"
>
{isResendingVerification
@@ -96,7 +96,7 @@ 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"
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-sm mt-4"
role="alert"
>
<p className="font-bold">{t("SETTINGS$EMAIL_VERIFICATION_REQUIRED")}</p>
@@ -205,7 +205,7 @@ function UserSettingsScreen() {
<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" />
<div className="animate-pulse h-8 w-64 bg-tertiary rounded-sm" />
) : (
<EmailInputSection
email={email}
+1 -1
View File
@@ -76,7 +76,7 @@ function VSCodeTab() {
<button
type="button"
onClick={handleOpenInNewTab}
className="px-4 py-2 bg-primary text-white rounded hover:bg-primary-dark transition-colors"
className="px-4 py-2 bg-primary text-white rounded-sm hover:bg-primary-dark transition-colors"
>
{t("VSCODE$OPEN_IN_NEW_TAB")}
</button>
+7 -4
View File
@@ -1,9 +1,12 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@plugin '../hero.ts';
@config "../tailwind.config.js";
@source '../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
.button-base {
@apply bg-tertiary border border-neutral-600 rounded;
@apply bg-tertiary border border-neutral-600 rounded-xs;
}
.skeleton {
@@ -0,0 +1 @@
export type ConversationStatus = "STARTING" | "RUNNING" | "STOPPED";
+4
View File
@@ -1,3 +1,7 @@
/**
* @deprecated This type is deprecated and will be removed in a future version.
* Use types in `frontend/src/types/core` instead.
*/
export interface ActionMessage {
id: number;
+8
View File
@@ -0,0 +1,8 @@
export type RuntimeStatus =
| "STATUS$STOPPED"
| "STATUS$BUILDING_RUNTIME"
| "STATUS$STARTING_RUNTIME"
| "STATUS$RUNTIME_STARTED"
| "STATUS$SETTING_UP_WORKSPACE"
| "STATUS$SETTING_UP_GIT_HOOKS"
| "STATUS$READY";
+4 -1
View File
@@ -5,7 +5,10 @@
* @returns The URL to redirect to for OAuth
*/
export const generateAuthUrl = (identityProvider: string, requestUrl: URL) => {
const redirectUri = `${requestUrl.origin}/oauth/keycloak/callback`;
// Use HTTPS protocol unless the host is localhost
const protocol =
requestUrl.hostname === "localhost" ? requestUrl.protocol : "https:";
const redirectUri = `${protocol}//${requestUrl.host}/oauth/keycloak/callback`;
let authUrl = requestUrl.hostname
.replace(/(^|\.)staging\.all-hands\.dev$/, "$1auth.staging.all-hands.dev")
.replace(/(^|\.)app\.all-hands\.dev$/, "auth.app.all-hands.dev")
+121
View File
@@ -0,0 +1,121 @@
import { WebSocketStatus } from "#/context/ws-client-provider";
import { I18nKey } from "#/i18n/declaration";
import { AgentState } from "#/types/agent-state";
import { ConversationStatus } from "#/types/conversation-status";
import { StatusMessage } from "#/types/message";
import { RuntimeStatus } from "#/types/runtime-status";
export enum IndicatorColor {
BLUE = "bg-blue-500",
GREEN = "bg-green-500",
ORANGE = "bg-orange-500",
YELLOW = "bg-yellow-500",
RED = "bg-red-500",
DARK_ORANGE = "bg-orange-800",
}
export const AGENT_STATUS_MAP: {
[k: string]: string;
} = {
[AgentState.INIT]: I18nKey.CHAT_INTERFACE$AGENT_INIT_MESSAGE,
[AgentState.RUNNING]: I18nKey.CHAT_INTERFACE$AGENT_RUNNING_MESSAGE,
[AgentState.AWAITING_USER_INPUT]:
I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE,
[AgentState.PAUSED]: I18nKey.CHAT_INTERFACE$AGENT_PAUSED_MESSAGE,
[AgentState.LOADING]:
I18nKey.CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE,
[AgentState.STOPPED]: I18nKey.CHAT_INTERFACE$AGENT_STOPPED_MESSAGE,
[AgentState.FINISHED]: I18nKey.CHAT_INTERFACE$AGENT_FINISHED_MESSAGE,
[AgentState.REJECTED]: I18nKey.CHAT_INTERFACE$AGENT_REJECTED_MESSAGE,
[AgentState.ERROR]: I18nKey.CHAT_INTERFACE$AGENT_ERROR_MESSAGE,
[AgentState.AWAITING_USER_CONFIRMATION]:
I18nKey.CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE,
[AgentState.USER_CONFIRMED]:
I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE,
[AgentState.USER_REJECTED]:
I18nKey.CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE,
[AgentState.RATE_LIMITED]: I18nKey.CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE,
};
export function getIndicatorColor(
webSocketStatus: WebSocketStatus,
conversationStatus: ConversationStatus | null,
runtimeStatus: RuntimeStatus | null,
agentState: AgentState | null,
) {
if (
webSocketStatus === "DISCONNECTED" ||
conversationStatus === "STOPPED" ||
runtimeStatus === "STATUS$STOPPED" ||
agentState === AgentState.STOPPED
) {
return IndicatorColor.RED;
}
// Display a yellow working icon while the runtime is starting
if (
conversationStatus === "STARTING" ||
!["STATUS$READY", null].includes(runtimeStatus) ||
(agentState != null &&
[
AgentState.LOADING,
AgentState.PAUSED,
AgentState.REJECTED,
AgentState.RATE_LIMITED,
].includes(agentState))
) {
return IndicatorColor.YELLOW;
}
if (agentState === AgentState.AWAITING_USER_CONFIRMATION) {
return IndicatorColor.ORANGE;
}
if (agentState === AgentState.AWAITING_USER_INPUT) {
return IndicatorColor.BLUE;
}
// All other agent states are green
return IndicatorColor.GREEN;
}
export function getStatusCode(
statusMessage: StatusMessage,
webSocketStatus: WebSocketStatus,
conversationStatus: ConversationStatus | null,
runtimeStatus: RuntimeStatus | null,
agentState: AgentState | null,
) {
if (conversationStatus === "STOPPED" || runtimeStatus === "STATUS$STOPPED") {
return I18nKey.CHAT_INTERFACE$STOPPED;
}
if (runtimeStatus === "STATUS$BUILDING_RUNTIME") {
return I18nKey.STATUS$BUILDING_RUNTIME;
}
if (runtimeStatus === "STATUS$STARTING_RUNTIME") {
return I18nKey.STATUS$STARTING_RUNTIME;
}
if (webSocketStatus === "DISCONNECTED") {
return I18nKey.CHAT_INTERFACE$DISCONNECTED;
}
if (webSocketStatus === "CONNECTING") {
return I18nKey.CHAT_INTERFACE$CONNECTING;
}
if (
agentState === AgentState.LOADING &&
statusMessage?.id &&
statusMessage.id !== "STATUS$READY"
) {
return statusMessage.id;
}
if (agentState) {
return AGENT_STATUS_MAP[agentState];
}
if (runtimeStatus && runtimeStatus !== "STATUS$READY" && !agentState) {
return runtimeStatus;
}
return "STATUS$ERROR"; // illegal state
}
+1 -24
View File
@@ -2,10 +2,6 @@
import { heroui } from "@heroui/react";
import typography from "@tailwindcss/typography";
export default {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
@@ -24,24 +20,5 @@ export default {
},
},
darkMode: "class",
plugins: [
heroui({
defaultTheme: "dark",
layout: {
radius: {
small: "5px",
large: "20px",
},
},
themes: {
dark: {
colors: {
primary: "#4465DB",
logo: "#CFB755",
},
},
},
}),
typography,
],
plugins: [typography],
};
+3 -1
View File
@@ -5,6 +5,7 @@ import viteTsconfigPaths from "vite-tsconfig-paths";
import svgr from "vite-plugin-svgr";
import { reactRouter } from "@react-router/dev/vite";
import { configDefaults } from "vitest/config";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig(({ mode }) => {
const {
@@ -28,6 +29,7 @@ export default defineConfig(({ mode }) => {
!process.env.VITEST && reactRouter(),
viteTsconfigPaths(),
svgr(),
tailwindcss(),
],
server: {
port: FE_PORT,
@@ -54,7 +56,7 @@ export default defineConfig(({ mode }) => {
},
},
watch: {
ignored: ['**/node_modules/**', '**/.git/**'],
ignored: ["**/node_modules/**", "**/.git/**"],
},
},
ssr: {
+54
View File
@@ -0,0 +1,54 @@
---
triggers:
- /codereview
---
PERSONA:
You are an expert software engineer and code reviewer with deep experience in modern programming best practices, secure coding, and clean code principles.
TASK:
Review the code changes in this pull request or merge request, and provide actionable feedback to help the author improve code quality, maintainability, and security. DO NOT modify the code; only provide specific feedback.
CONTEXT:
You have full context of the code being committed in the pull request or merge request, including the diff, surrounding files, and project structure. The code is written in a modern language and follows typical idioms and patterns for that language.
ROLE:
As an automated reviewer, your role is to analyze the code changes and produce structured comments, including line numbers, across the following scenarios:
CODE REVIEW SCENARIOS:
1. Style and Formatting
Check for:
- Inconsistent indentation, spacing, or bracket usage
- Unused imports or variables
- Non-standard naming conventions
- Missing or misformatted comments/docstrings
- Violations of common language-specific style guides (e.g., PEP8, Google Style Guide)
2. Clarity and Readability
Identify:
- Overly complex or deeply nested logic
- Functions doing too much (violating single responsibility)
- Poor naming that obscures intent
- Missing inline documentation for non-obvious logic
3. Security and Common Bug Patterns
Watch for:
- Unsanitized user input (e.g., in SQL, shell, or web contexts)
- Hardcoded secrets or credentials
- Incorrect use of cryptographic libraries
- Common pitfalls (null dereferencing, off-by-one errors, race conditions)
INSTRUCTIONS FOR RESPONSE:
Group the feedback by the scenarios above.
Then, for each issue you find:
- Provide a line number or line range
- Briefly explain why it's an issue
- Suggest a concrete improvement
Use the following structure in your output:
[Line 42] :hammer_and_wrench: Unused import: The 'os' module is imported but never used. Remove it to clean up the code.
[Lines 7885] :mag: Readability: This nested if-else block is hard to follow. Consider refactoring into smaller functions or using early returns.
[Line 102] :closed_lock_with_key: Security Risk: User input is directly concatenated into an SQL query. This could allow SQL injection. Use parameterized queries instead.
REMEMBER, DO NOT MODIFY THE CODE. ONLY PROVIDE FEEDBACK IN YOUR RESPONSE.
@@ -141,6 +141,9 @@ 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,10 +2,18 @@ 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 `# unchanged` 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 `# ... existing code ...` 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:
@@ -33,13 +41,12 @@ 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):
# no changes before
# ... existing code ...
self.y = 2
# self.z is removed
# MyClass().z is removed
print(MyClass().y)
```
@@ -58,6 +65,7 @@ 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)
```
@@ -93,9 +101,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=1001 end=1008
path="/path/to/file.txt" start=1002 end=1008
content=```
class MyClass:
#EDIT: I want to change the value of y to 2
def __init__(self):
# no changes before
self.y = 2
@@ -208,7 +208,7 @@ Note:
# for visualwebarena, webarena and miniwob++ eval, we need to retrieve the initial observation already in browser env
# initialize and retrieve the first observation by issuing an noop OP
# For non-benchmark browsing, the browser env starts with a blank page, and the agent is expected to first navigate to desired websites
return BrowseInteractiveAction(browser_actions='noop(1000)')
return BrowseInteractiveAction(browser_actions='noop(1000)', return_axtree=True)
for event in state.view:
if isinstance(event, BrowseInteractiveAction):
+10 -6
View File
@@ -158,8 +158,8 @@ async def modify_llm_settings_basic(
provider_completer = FuzzyWordCompleter(provider_list)
session = PromptSession(key_bindings=kb_cancel())
# Set default provider - use the first available provider from the list
provider = provider_list[0] if provider_list else 'openai'
# Set default provider - prefer 'anthropic' if available, otherwise use the first provider
provider = 'anthropic' if 'anthropic' in provider_list else provider_list[0]
model = None
api_key = None
@@ -196,9 +196,11 @@ async def modify_llm_settings_basic(
# Make sure the provider exists in organized_models
if provider not in organized_models:
# If the provider doesn't exist, use the first available provider
# If the provider doesn't exist, prefer 'anthropic' if available, otherwise use the first provider
provider = (
next(iter(organized_models.keys())) if organized_models else 'openai'
'anthropic'
if 'anthropic' in organized_models
else next(iter(organized_models.keys()))
)
provider_models = organized_models[provider]['models']
@@ -213,8 +215,10 @@ async def modify_llm_settings_basic(
]
provider_models = VERIFIED_ANTHROPIC_MODELS + provider_models
# Set default model to the first model in the list
default_model = provider_models[0] if provider_models else 'gpt-4'
# Set default model to the first model in the list (which will be a verified model if available)
default_model = (
provider_models[0] if provider_models else 'claude-sonnet-4-20250514'
)
# Show the default model but allow changing it
print_formatted_text(

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