diff --git a/.github/workflows/lint-fix.yml b/.github/workflows/lint-fix.yml new file mode 100644 index 000000000000..e3021b887c53 --- /dev/null +++ b/.github/workflows/lint-fix.yml @@ -0,0 +1,61 @@ +name: Lint Fix + +on: + pull_request: + types: [labeled] + +jobs: + lint-fix: + if: github.event.label.name == 'lint-fix' + name: Fix linting issues + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + # Frontend lint fixes + - name: Install Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install frontend dependencies + run: | + cd frontend + npm install --frozen-lockfile + - name: Fix frontend lint issues + run: | + cd frontend + npm run lint:fix + + # Python lint fixes + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + cache: 'pip' + - name: Install pre-commit + run: pip install pre-commit==3.7.0 + - name: Fix python lint issues + run: | + pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --config ./dev_config/python/.pre-commit-config.yaml + + # Commit and push changes if any + - name: Check for changes + id: git-check + run: | + git diff --quiet || echo "changes=true" >> $GITHUB_OUTPUT + - name: Commit and push if there are changes + if: steps.git-check.outputs.changes == 'true' + run: | + git config --local user.email "openhands@all-hands.dev" + git config --local user.name "OpenHands Bot" + git add -A + git commit -m "🤖 Auto-fix linting issues" + git push diff --git a/.github/workflows/openhands-resolver.yml b/.github/workflows/openhands-resolver.yml index 9ce516b15b43..44875b177ec5 100644 --- a/.github/workflows/openhands-resolver.yml +++ b/.github/workflows/openhands-resolver.yml @@ -72,7 +72,7 @@ jobs: run: | python -m pip index versions openhands-ai > openhands_versions.txt OPENHANDS_VERSION=$(head -n 1 openhands_versions.txt | awk '{print $2}' | tr -d '()') - echo "openhands-resolver==${OPENHANDS_VERSION}" >> requirements.txt + echo "openhands-ai==${OPENHANDS_VERSION}" >> requirements.txt cat requirements.txt - name: Cache pip dependencies @@ -135,7 +135,7 @@ jobs: issue_number: ${{ env.ISSUE_NUMBER }}, owner: context.repo.owner, repo: context.repo.repo, - body: `OpenHands started fixing the ${issueType}! You can monitor the progress [here](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).` + body: `[OpenHands](https://github.com/All-Hands-AI/OpenHands) started fixing the ${issueType}! You can monitor the progress [here](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).` }); - name: Install OpenHands @@ -181,6 +181,7 @@ jobs: retention-days: 30 # Keep the artifact for 30 days - name: Create draft PR or push branch + if: always() # Create PR or branch even if the previous steps fail env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} GITHUB_USERNAME: ${{ secrets.PAT_USERNAME }} @@ -204,6 +205,7 @@ jobs: - name: Comment on issue uses: actions/github-script@v7 + if: always() # Comment on issue even if the previous steps fail with: github-token: ${{secrets.GITHUB_TOKEN}} script: | @@ -262,6 +264,6 @@ jobs: issue_number: issueNumber, owner: context.repo.owner, repo: context.repo.repo, - body: `The workflow to fix this issue encountered an error. Please check the workflow logs for more information.` + body: `The workflow to fix this issue encountered an error. Please check the [workflow logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for more information.` }); } diff --git a/COMMUNITY.md b/COMMUNITY.md new file mode 100644 index 000000000000..ffebf7fd5ae5 --- /dev/null +++ b/COMMUNITY.md @@ -0,0 +1,40 @@ +# 🙌 The OpenHands Community + +The OpenHands community is built around the belief that (1) AI and AI agents are going to fundamentally change the way we build software, and (2) if this is true, we should do everything we can to make sure that the benefits provided by such powerful technology are accessible to everyone. + +If this resonates with you, we'd love to have you join us in our quest! + +## 🤝 How to Join + +We do most of our communication through Slack, so this is the best place to start, but we also are happy to have you contact us on Discord or Github. + +- [Join our Slack workspace](https://join.slack.com/t/openhands-ai/shared_invite/zt-2tom0er4l-JeNUGHt_AxpEfIBstbLPiw) - Here we talk about research, architecture, and future development. +- [Join our Discord server](https://discord.gg/ESHStjSjD4) - This is a community-run server for general discussion, questions, and feedback. +- [Read and post Github Issues](https://github.com/All-Hands-AI/OpenHands/issues) - Check out the issues we're working on, or add your own ideas. + +## 💪 Becoming a Contributor + +We welcome contributions from everyone! Whether you're a developer, a researcher, or simply enthusiastic about advancing the field of software engineering with AI, there are many ways to get involved: + +- **Code Contributions:** Help us develop new core functionality, improve our agents, improve the frontend and other interfaces, or anything else that would help make OpenHands better. +- **Research and Evaluation:** Contribute to our understanding of LLMs in software engineering, participate in evaluating the models, or suggest improvements. +- **Feedback and Testing:** Use the OpenHands toolset, report bugs, suggest features, or provide feedback on usability. + +For details, please check [CONTRIBUTING.md](./CONTRIBUTING.md). + +## Code of Conduct + +We have a [Code of Conduct](./CODE_OF_CONDUCT.md) that we expect all contributors to adhere to. +Long story short, we are aiming for an open, welcoming, diverse, inclusive, and healthy community. +All contributors are expected to contribute to building this sort of community. + +## 🛠️ Becoming a Maintainer + +For contributors who have made significant and sustained contributions to the project, there is a possibility of joining the maintainer team. +The process for this is as follows: + +1. Any contributor who has made sustained and high-quality contributions to the codebase can be nominated by any maintainer. If you feel that you may qualify you can reach out to any of the maintainers that have reviewed your PRs and ask if you can be nominated. +2. Once a maintainer nominates a new maintainer, there will be a discussion period among the maintainers for at least 3 days. +3. If no concerns are raised the nomination will be accepted by acclamation, and if concerns are raised there will be a discussion and possible vote. + +Note that just making many PRs does not immediately imply that you will become a maintainer. We will be looking at sustained high-quality contributions over a period of time, as well as good teamwork and adherence to our [Code of Conduct](./CODE_OF_CONDUCT.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index effb7662a123..e69538d21e0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,3 +92,32 @@ You may also check out previous PRs in the [PR list](https://github.com/All-Hand If your changes are user-facing (e.g. a new feature in the UI, a change in behavior, or a bugfix) please include a short message that we can add to our changelog. + +## How to Make Effective Contributions + +### Opening Issues + +If you notice any bugs or have any feature requests please open them via the [issues page](https://github.com/All-Hands-AI/OpenHands/issues). We will triage based on how critical the bug is or how potentially useful the improvement is, discuss, and implement the ones that the community has interest/effort for. + +Further, if you see an issue you like, please leave a "thumbs-up" or a comment, which will help us prioritize. + +### Making Pull Requests + +We're generally happy to consider all PRs, with the evaluation process varying based on the type of change: + +#### For Small Improvements + +Small improvements with few downsides are typically reviewed and approved quickly. +One thing to check when making changes is to ensure that all continuous integration tests pass, which you can check before getting a review. + +#### For Core Agent Changes + +We need to be more careful with changes to the core agent, as it is imperative to maintain high quality. These PRs are evaluated based on three key metrics: + +1. **Accuracy** +2. **Efficiency** +3. **Code Complexity** + +If it improves accuracy, efficiency, or both with only a minimal change to code quality, that's great we're happy to merge it in! +If there are bigger tradeoffs (e.g. helping efficiency a lot and hurting accuracy a little) we might want to put it behind a feature flag. +Either way, please feel free to discuss on github issues or slack, and we will give guidance and preliminary feedback. diff --git a/Development.md b/Development.md index 249d454ca49b..afc0bcd1479d 100644 --- a/Development.md +++ b/Development.md @@ -38,7 +38,9 @@ make build ``` ### 3. Configuring the Language Model -OpenHands supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. By default, we've chosen the mighty GPT-4 from OpenAI as our go-to model, but the world is your oyster! You can unleash the potential of Anthropic's suave Claude, the enigmatic Llama, or any other LM that piques your interest. +OpenHands supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. +By default, we've chosen Claude Sonnet 3.5 as our go-to model, but the world is your oyster! You can unleash the +potential of any other LM that piques your interest. To configure the LM of your choice, run: @@ -52,10 +54,7 @@ To configure the LM of your choice, run: Environment variables > config.toml variables > default variables **Note on Alternative Models:** -Some alternative models may prove more challenging to tame than others. Fear not, brave adventurer! We shall soon unveil LLM-specific documentation to guide you on your quest. -And if you've already mastered the art of wielding a model other than OpenAI's GPT, we encourage you to share your setup instructions with us by creating instructions and adding it [to our documentation](https://github.com/All-Hands-AI/OpenHands/tree/main/docs/modules/usage/llms). - -For a full list of the LM providers and models available, please consult the [litellm documentation](https://docs.litellm.ai/docs/providers). +See [our documentation](https://docs.all-hands.dev/modules/usage/llms) for recommended models. ### 4. Running the application #### Option A: Run the Full Application @@ -98,9 +97,10 @@ poetry run pytest ./tests/unit/test_*.py 2. Update the poetry.lock file via `poetry lock --no-update` ### 9. Use existing Docker image -To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker container image. Follow these steps: -1. Set the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image. -2. Example: export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.13-nikolaik +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.14-nikolaik` ## Develop inside Docker container diff --git a/ISSUE_TRIAGE.md b/ISSUE_TRIAGE.md index f251ad7263fb..1cb12cc9ea6e 100644 --- a/ISSUE_TRIAGE.md +++ b/ISSUE_TRIAGE.md @@ -6,9 +6,9 @@ These are the procedures and guidelines on how issues are triaged in this repo b * Issues may be tagged with what it relates to (**backend**, **frontend**, **agent quality**, etc.) ## Severity -* **Low**: Minor issues, single user report -* **Medium**: Affecting multiple users -* **Critical**: Affecting all users or potential security issues +* **Low**: Minor issues or affecting single user. +* **Medium**: Affecting multiple users. +* **Critical**: Affecting all users or potential security issues. ## Effort * Issues may be estimated with effort required (**small effort**, **medium effort**, **large effort**) @@ -17,9 +17,9 @@ These are the procedures and guidelines on how issues are triaged in this repo b * Issues with low implementation difficulty may be tagged with **good first issue** ## Not Enough Information -* User is asked to provide more information (logs, how to reproduce, etc.) when the issue is not clear -* If an issue is unclear and the author does not provide more information or respond to a request, the issue may be closed as **not planned** (Usually after a week) +* User is asked to provide more information (logs, how to reproduce, etc.) when the issue is not clear. +* If an issue is unclear and the author does not provide more information or respond to a request, the issue may be closed as **not planned** (Usually after a week). ## Multiple Requests/Fixes in One Issue -* These issues will be narrowed down to one request/fix so the issue is more easily tracked and fixed -* Issues may be broken down into multiple issues if required +* These issues will be narrowed down to one request/fix so the issue is more easily tracked and fixed. +* Issues may be broken down into multiple issues if required. diff --git a/README.md b/README.md index 42f193daf857..cd35635d56de 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,16 @@ See the [Installation](https://docs.all-hands.dev/modules/usage/installation) gu system requirements and more information. ```bash -docker pull docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik +docker pull docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik docker run -it --pull=always \ - -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik \ + -e LOG_ALL_EVENTS=true \ -v /var/run/docker.sock:/var/run/docker.sock \ -p 3000:3000 \ - -e LOG_ALL_EVENTS=true \ --add-host host.docker.internal:host-gateway \ --name openhands-app \ - docker.all-hands.dev/all-hands-ai/openhands:0.13 + docker.all-hands.dev/all-hands-ai/openhands:0.14 ``` You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)! @@ -77,25 +77,15 @@ To learn more about the project, and for tips on using OpenHands, There you'll find resources on how to use different LLM providers, troubleshooting resources, and advanced configuration options. -## 🤝 How to Contribute - -OpenHands is a community-driven project, and we welcome contributions from everyone. -Whether you're a developer, a researcher, or simply enthusiastic about advancing the field of -software engineering with AI, there are many ways to get involved: - -- **Code Contributions:** Help us develop new agents, core functionality, the frontend and other interfaces, or sandboxing solutions. -- **Research and Evaluation:** Contribute to our understanding of LLMs in software engineering, participate in evaluating the models, or suggest improvements. -- **Feedback and Testing:** Use the OpenHands toolset, report bugs, suggest features, or provide feedback on usability. - -For details, please check [CONTRIBUTING.md](./CONTRIBUTING.md). +## 🤝 How to Join the Community -## 🤖 Join Our Community +OpenHands is a community-driven project, and we welcome contributions from everyone. As a first step, you can: -Whether you're a developer, a researcher, or simply enthusiastic about OpenHands, we'd love to have you in our community. -Let's make software engineering better together! +- [Join our Slack workspace](https://join.slack.com/t/openhands-ai/shared_invite/zt-2tom0er4l-JeNUGHt_AxpEfIBstbLPiw) - Here we talk about research, architecture, and future development. +- [Join our Discord server](https://discord.gg/ESHStjSjD4) - This is a community-run server for general discussion, questions, and feedback. +- [Read or post Github Issues](https://github.com/All-Hands-AI/OpenHands/issues) - Check out the issues we're working on, or add your own ideas. -- [Slack workspace](https://join.slack.com/t/openhands-ai/shared_invite/zt-2tom0er4l-JeNUGHt_AxpEfIBstbLPiw) - Here we talk about research, architecture, and future development. -- [Discord server](https://discord.gg/ESHStjSjD4) - This is a community-run server for general discussion, questions, and feedback. +See more about the community in [COMMUNITY.md](./COMMUNITY.md) or find details on contributing in [CONTRIBUTING.md](./CONTRIBUTING.md). ## 📈 Progress diff --git a/compose.yml b/compose.yml index 7beec1157b5b..b54e270ab28e 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,7 @@ services: image: openhands:latest container_name: openhands-app-${DATE:-} environment: - - SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.13-nikolaik} + - SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.14-nikolaik} - SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} - WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace} ports: diff --git a/containers/dev/compose.yml b/containers/dev/compose.yml index f339cc654ac8..b6a9b37a064f 100644 --- a/containers/dev/compose.yml +++ b/containers/dev/compose.yml @@ -11,7 +11,7 @@ services: - BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"} - SANDBOX_API_HOSTNAME=host.docker.internal # - - SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.13-nikolaik} + - SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.14-nikolaik} - SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} - WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace} ports: diff --git a/docs/modules/usage/about.md b/docs/modules/usage/about.md index f8e2ecf7c7a5..93454bb4d9e8 100644 --- a/docs/modules/usage/about.md +++ b/docs/modules/usage/about.md @@ -1,6 +1,6 @@ -# 📚 Misc +# About OpenHands -## ⭐️ Research Strategy +## Research Strategy Achieving full replication of production-grade applications with LLMs is a complex endeavor. Our strategy involves: @@ -9,34 +9,11 @@ Achieving full replication of production-grade applications with LLMs is a compl 3. **Task Planning:** Developing capabilities for bug detection, codebase management, and optimization 4. **Evaluation:** Establishing comprehensive evaluation metrics to better understand and improve our models -## 🚧 Default Agent +## Default Agent Our default Agent is currently the [CodeActAgent](agents), which is capable of generating code and handling files. -## 🤝 How to Contribute - -OpenHands is a community-driven project, and we welcome contributions from everyone. Whether you're a developer, a researcher, or simply enthusiastic about advancing the field of software engineering with AI, there are many ways to get involved: - -- **Code Contributions:** Help us develop the core functionalities, frontend interface, or sandboxing solutions -- **Research and Evaluation:** Contribute to our understanding of LLMs in software engineering, participate in evaluating the models, or suggest improvements -- **Feedback and Testing:** Use the OpenHands toolset, report bugs, suggest features, or provide feedback on usability - -For details, please check [this document](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md). - -## 🤖 Join Our Community - -We have both Slack workspace for the collaboration on building OpenHands and Discord server for discussion about anything related, e.g., this project, LLM, agent, etc. - -- [Slack workspace](https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA) -- [Discord server](https://discord.gg/ESHStjSjD4) - -If you would love to contribute, feel free to join our community. Let's simplify software engineering together! - -🐚 **Code less, make more with OpenHands.** - -[![Star History Chart](https://api.star-history.com/svg?repos=All-Hands-AI/OpenHands&type=Date)](https://star-history.com/#All-Hands-AI/OpenHands&Date) - -## 🛠️ Built With +## Built With OpenHands is built using a combination of powerful frameworks and libraries, providing a robust foundation for its development. Here are the key technologies used in the project: @@ -44,6 +21,9 @@ OpenHands is built using a combination of powerful frameworks and libraries, pro Please note that the selection of these technologies is in progress, and additional technologies may be added or existing ones may be removed as the project evolves. We strive to adopt the most suitable and efficient tools to enhance the capabilities of OpenHands. -## 📜 License +## Licensing, Contributing, Community Servers + +Distributed under MIT [License](https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE). -Distributed under the MIT License. See [our license](https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE) for more information. +For guides on how to contribute to OpenHands, joining our Discord and Slack servers +[check out the OpenHands README.](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-how-to-contribute) diff --git a/docs/modules/usage/how-to/cli-mode.md b/docs/modules/usage/how-to/cli-mode.md index 0db6ea9292de..852b8f7010ef 100644 --- a/docs/modules/usage/how-to/cli-mode.md +++ b/docs/modules/usage/how-to/cli-mode.md @@ -50,7 +50,7 @@ LLM_API_KEY="sk_test_12345" ```bash docker run -it \ --pull=always \ - -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik \ -e SANDBOX_USER_ID=$(id -u) \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -e LLM_API_KEY=$LLM_API_KEY \ @@ -59,7 +59,7 @@ docker run -it \ -v /var/run/docker.sock:/var/run/docker.sock \ --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.13 \ + docker.all-hands.dev/all-hands-ai/openhands:0.14 \ python -m openhands.core.cli ``` diff --git a/docs/modules/usage/how-to/custom-sandbox-guide.md b/docs/modules/usage/how-to/custom-sandbox-guide.md index 7d1f333e4846..3708584a6a1a 100644 --- a/docs/modules/usage/how-to/custom-sandbox-guide.md +++ b/docs/modules/usage/how-to/custom-sandbox-guide.md @@ -62,25 +62,3 @@ Run OpenHands by running ```make run``` in the top level directory. ## Technical Explanation Please refer to [custom docker image section of the runtime documentation](https://docs.all-hands.dev/modules/usage/architecture/runtime#advanced-how-openhands-builds-and-maintains-od-runtime-images) for more details. - -## Troubleshooting / Errors - -### Error: ```useradd: UID 1000 is not unique``` - -If you see this error in the console output it is because OpenHands is trying to create the openhands user in the sandbox with a UID of 1000, however this UID is already being used in the image (for some reason). To fix this change the sandbox_user_id field in the config.toml file to a different value: - -```toml -[core] -workspace_base="./workspace" -run_as_openhands=true -sandbox_base_container_image="custom_image" -sandbox_user_id="1001" -``` - -### Port use errors - -If you see an error about a port being in use or unavailable, try deleting all running Docker Containers (run `docker ps` and `docker rm` relevant containers) and then re-running ```make run``` . - -## Discuss - -For other issues or questions join the [Slack](https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA) or [Discord](https://discord.gg/ESHStjSjD4) and ask! diff --git a/docs/modules/usage/how-to/github-action.md b/docs/modules/usage/how-to/github-action.md index d38092aa4329..70c43beea174 100644 --- a/docs/modules/usage/how-to/github-action.md +++ b/docs/modules/usage/how-to/github-action.md @@ -12,4 +12,5 @@ To use the OpenHands GitHub Action in the OpenHands repository, an OpenHands mai ## Installing the Action in a New Repository -To install the OpenHands GitHub Action in your own repository, follow the [directions in the OpenHands Resolver repo](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/resolver/README.md). +To install the OpenHands GitHub Action in your own repository, follow +the [README for the OpenHands Resolver](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/resolver/README.md). diff --git a/docs/modules/usage/how-to/headless-mode.md b/docs/modules/usage/how-to/headless-mode.md index adba43e47468..89ba9a5794b7 100644 --- a/docs/modules/usage/how-to/headless-mode.md +++ b/docs/modules/usage/how-to/headless-mode.md @@ -44,7 +44,7 @@ LLM_API_KEY="sk_test_12345" ```bash docker run -it \ --pull=always \ - -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik \ -e SANDBOX_USER_ID=$(id -u) \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -e LLM_API_KEY=$LLM_API_KEY \ @@ -54,6 +54,6 @@ docker run -it \ -v /var/run/docker.sock:/var/run/docker.sock \ --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.13 \ + docker.all-hands.dev/all-hands-ai/openhands:0.14 \ python -m openhands.core.main -t "write a bash script that prints hi" ``` diff --git a/docs/modules/usage/installation.mdx b/docs/modules/usage/installation.mdx index 7a7f2c352b6d..e33e4bcef907 100644 --- a/docs/modules/usage/installation.mdx +++ b/docs/modules/usage/installation.mdx @@ -11,16 +11,16 @@ The easiest way to run OpenHands is in Docker. ```bash -docker pull docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik +docker pull docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik docker run -it --rm --pull=always \ - -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.13-nikolaik \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.14-nikolaik \ + -e LOG_ALL_EVENTS=true \ -v /var/run/docker.sock:/var/run/docker.sock \ -p 3000:3000 \ - -e LOG_ALL_EVENTS=true \ --add-host host.docker.internal:host-gateway \ --name openhands-app \ - docker.all-hands.dev/all-hands-ai/openhands:0.13 + docker.all-hands.dev/all-hands-ai/openhands:0.14 ``` You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), or using the [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action). diff --git a/docs/modules/usage/runtimes.md b/docs/modules/usage/runtimes.md index 0ea88442c7d9..e6212d43055a 100644 --- a/docs/modules/usage/runtimes.md +++ b/docs/modules/usage/runtimes.md @@ -49,7 +49,7 @@ but seems to work well on most systems. ## All Hands Runtime The All Hands Runtime is currently in beta. You can request access by joining -the #remote-runtime-limited-beta channel on Slack (see the README for an invite). +the #remote-runtime-limited-beta channel on Slack ([see the README](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-join-our-community) for an invite). To use the All Hands Runtime, set the following environment variables when starting OpenHands: @@ -66,7 +66,7 @@ docker run # ... ## Modal Runtime Our partners at [Modal](https://modal.com/) have also provided a runtime for OpenHands. -To use the Modal Runtime, create an account, and then [create an API key](https://modal.com/settings) +To use the Modal Runtime, create an account, and then [create an API key.](https://modal.com/settings) You'll then need to set the following environment variables when starting OpenHands: ```bash diff --git a/evaluation/swe_bench/eval_infer.py b/evaluation/swe_bench/eval_infer.py index d6c4e6ba938d..d40f984fca9c 100644 --- a/evaluation/swe_bench/eval_infer.py +++ b/evaluation/swe_bench/eval_infer.py @@ -263,23 +263,29 @@ def process_instance( test_output_path = os.path.join(log_dir, 'test_output.txt') with open(test_output_path, 'w') as f: f.write(test_output) - - _report = get_eval_report( - test_spec=test_spec, - prediction={ - 'model_patch': model_patch, - 'instance_id': instance_id, - }, - log_path=test_output_path, - include_tests_status=True, - ) - report = _report[instance_id] - logger.info( - f"[{instance_id}] report: {report}\nResult for {instance_id}: resolved: {report['resolved']}" - ) - instance['test_result']['report']['resolved'] = report[ - 'resolved' - ] + try: + _report = get_eval_report( + test_spec=test_spec, + prediction={ + 'model_patch': model_patch, + 'instance_id': instance_id, + }, + log_path=test_output_path, + include_tests_status=True, + ) + report = _report[instance_id] + logger.info( + f"[{instance_id}] report: {report}\nResult for {instance_id}: resolved: {report['resolved']}" + ) + instance['test_result']['report']['resolved'] = report[ + 'resolved' + ] + except Exception as e: + logger.error( + f'[{instance_id}] Error when getting eval report: {e}' + ) + instance['test_result']['report']['resolved'] = False + instance['test_result']['report']['error_eval'] = True else: logger.info(f'[{instance_id}] Error when starting eval:\n{obs.content}') instance['test_result']['report']['error_eval'] = True @@ -355,7 +361,7 @@ def process_instance( if 'model_patch' not in predictions.columns: predictions['model_patch'] = predictions['test_result'].apply( - lambda x: x['git_patch'] + lambda x: x.get('git_patch', '') ) assert {'instance_id', 'model_patch'}.issubset( set(predictions.columns) diff --git a/evaluation/swe_bench/run_infer.py b/evaluation/swe_bench/run_infer.py index a5d3db08ef46..386c0dd19238 100644 --- a/evaluation/swe_bench/run_infer.py +++ b/evaluation/swe_bench/run_infer.py @@ -534,5 +534,10 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame: instances[col] = instances[col].apply(lambda x: str(x)) run_evaluation( - instances, metadata, output_file, args.eval_num_workers, process_instance + instances, + metadata, + output_file, + args.eval_num_workers, + process_instance, + timeout_seconds=120 * 60, # 2 hour PER instance should be more than enough ) diff --git a/frontend/__tests__/components/chat/chat-interface.test.tsx b/frontend/__tests__/components/chat/chat-interface.test.tsx index fc4c03e3f68c..f4e1915c4960 100644 --- a/frontend/__tests__/components/chat/chat-interface.test.tsx +++ b/frontend/__tests__/components/chat/chat-interface.test.tsx @@ -21,6 +21,11 @@ describe("Empty state", () => { })); beforeAll(() => { + vi.mock("@remix-run/react", async (importActual) => ({ + ...(await importActual()), + useRouteLoaderData: vi.fn(() => ({})), + })); + vi.mock("#/context/socket", async (importActual) => ({ ...(await importActual()), useWsClient: useWsClientMock, diff --git a/frontend/__tests__/hooks/use-rate.test.ts b/frontend/__tests__/hooks/use-rate.test.ts new file mode 100644 index 000000000000..5457a8a5959f --- /dev/null +++ b/frontend/__tests__/hooks/use-rate.test.ts @@ -0,0 +1,93 @@ +import { act, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { useRate } from "#/utils/use-rate"; + +describe("useRate", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should initialize", () => { + const { result } = renderHook(() => useRate()); + + expect(result.current.items).toHaveLength(0); + expect(result.current.rate).toBeNull(); + expect(result.current.lastUpdated).toBeNull(); + expect(result.current.isUnderThreshold).toBe(true); + }); + + it("should handle the case of a single element", () => { + const { result } = renderHook(() => useRate()); + + act(() => { + result.current.record(123); + }); + + expect(result.current.items).toHaveLength(1); + expect(result.current.lastUpdated).not.toBeNull(); + }); + + it("should return the difference between the last two elements", () => { + const { result } = renderHook(() => useRate()); + + vi.setSystemTime(500); + act(() => { + result.current.record(4); + }); + + vi.advanceTimersByTime(500); + act(() => { + result.current.record(9); + }); + + expect(result.current.items).toHaveLength(2); + expect(result.current.rate).toBe(5); + expect(result.current.lastUpdated).toBe(1000); + }); + + it("should update isUnderThreshold after [threshold]ms of no activity", () => { + const { result } = renderHook(() => useRate({ threshold: 500 })); + + expect(result.current.isUnderThreshold).toBe(true); + + act(() => { + // not sure if fake timers is buggy with intervals, + // but I need to call it twice to register + vi.advanceTimersToNextTimer(); + vi.advanceTimersToNextTimer(); + }); + + expect(result.current.isUnderThreshold).toBe(false); + }); + + it("should return an isUnderThreshold boolean", () => { + const { result } = renderHook(() => useRate({ threshold: 500 })); + + vi.setSystemTime(500); + act(() => { + result.current.record(400); + }); + act(() => { + result.current.record(1000); + }); + + expect(result.current.isUnderThreshold).toBe(false); + + act(() => { + result.current.record(1500); + }); + + expect(result.current.isUnderThreshold).toBe(true); + + act(() => { + vi.advanceTimersToNextTimer(); + vi.advanceTimersToNextTimer(); + }); + + expect(result.current.isUnderThreshold).toBe(false); + }); +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7a19eb1d65e7..2d45a56c6c11 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "openhands-frontend", - "version": "0.13.1", + "version": "0.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openhands-frontend", - "version": "0.13.1", + "version": "0.14.0", "dependencies": { "@monaco-editor/react": "^4.6.0", "@nextui-org/react": "^2.4.8", diff --git a/frontend/package.json b/frontend/package.json index 635daf05bda5..c1415b02bd5a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "openhands-frontend", - "version": "0.13.1", + "version": "0.14.0", "private": true, "type": "module", "engines": { @@ -120,4 +120,4 @@ "public" ] } -} \ No newline at end of file +} diff --git a/frontend/src/api/open-hands.ts b/frontend/src/api/open-hands.ts index b3ce52a566c5..072588ce476e 100644 --- a/frontend/src/api/open-hands.ts +++ b/frontend/src/api/open-hands.ts @@ -183,6 +183,13 @@ class OpenHands { static async getVSCodeUrl(): Promise { return request(`/api/vscode-url`, {}, false, false, 1); } + + static async getRuntimeId(): Promise<{ runtime_id: string }> { + const response = await request("/api/config"); + const data = await response.json(); + + return data; + } } export default OpenHands; diff --git a/frontend/src/components/chat-input.tsx b/frontend/src/components/chat-input.tsx index 2b3c69b21c59..96c27fc84d65 100644 --- a/frontend/src/components/chat-input.tsx +++ b/frontend/src/components/chat-input.tsx @@ -18,6 +18,7 @@ interface ChatInputProps { onBlur?: () => void; onImagePaste?: (files: File[]) => void; className?: React.HTMLAttributes["className"]; + buttonClassName?: React.HTMLAttributes["className"]; } export function ChatInput({ @@ -35,6 +36,7 @@ export function ChatInput({ onBlur, onImagePaste, className, + buttonClassName, }: ChatInputProps) { const textareaRef = React.useRef(null); const [isDraggingOver, setIsDraggingOver] = React.useState(false); @@ -100,7 +102,7 @@ export function ChatInput({ return (
{showButton && ( - <> +
{button === "submit" && ( )} - +
)}
); diff --git a/frontend/src/components/chat-interface.tsx b/frontend/src/components/chat-interface.tsx index 626166f46260..f0004bd749d4 100644 --- a/frontend/src/components/chat-interface.tsx +++ b/frontend/src/components/chat-interface.tsx @@ -1,6 +1,7 @@ import { useDispatch, useSelector } from "react-redux"; import React from "react"; import posthog from "posthog-js"; +import { useRouteLoaderData } from "@remix-run/react"; import { convertImageToBase64 } from "#/utils/convert-image-to-base-64"; import { ChatMessage } from "./chat-message"; import { FeedbackActions } from "./feedback-actions"; @@ -21,18 +22,27 @@ import { ScrollToBottomButton } from "./scroll-to-bottom-button"; import { Suggestions } from "./suggestions"; import { SUGGESTIONS } from "#/utils/suggestions"; import BuildIt from "#/icons/build-it.svg?react"; -import { useWsClient } from "#/context/ws-client-provider"; +import { + useWsClient, + WsClientProviderStatus, +} from "#/context/ws-client-provider"; +import OpenHands from "#/api/open-hands"; +import { clientLoader } from "#/routes/_oh"; +import { downloadWorkspace } from "#/utils/download-workspace"; +import { SuggestionItem } from "./suggestion-item"; const isErrorMessage = ( message: Message | ErrorMessage, ): message is ErrorMessage => "error" in message; export function ChatInterface() { - const { send } = useWsClient(); + const { send, status, isLoadingMessages } = useWsClient(); + const dispatch = useDispatch(); const scrollRef = React.useRef(null); const { scrollDomToBottom, onChatBodyScroll, hitBottom } = useScrollToBottom(scrollRef); + const rootLoaderData = useRouteLoaderData("routes/_oh"); const { messages } = useSelector((state: RootState) => state.chat); const { curAgentState } = useSelector((state: RootState) => state.agent); @@ -42,6 +52,24 @@ export function ChatInterface() { >("positive"); const [feedbackModalIsOpen, setFeedbackModalIsOpen] = React.useState(false); const [messageToSend, setMessageToSend] = React.useState(null); + const [isDownloading, setIsDownloading] = React.useState(false); + + React.useEffect(() => { + if (status === WsClientProviderStatus.ACTIVE) { + try { + OpenHands.getRuntimeId().then(({ runtime_id }) => { + // eslint-disable-next-line no-console + console.log( + "Runtime ID: %c%s", + "background: #444; color: #ffeb3b; font-weight: bold; padding: 2px 4px; border-radius: 4px;", + runtime_id, + ); + }); + } catch (e) { + console.warn("Runtime ID not available in this environment"); + } + } + }, [status]); const handleSendMessage = async (content: string, files: File[]) => { posthog.capture("user_message_sent", { @@ -72,6 +100,17 @@ export function ChatInterface() { setFeedbackPolarity(polarity); }; + const handleDownloadWorkspace = async () => { + setIsDownloading(true); + try { + await downloadWorkspace(); + } catch (error) { + // TODO: Handle error + } finally { + setIsDownloading(false); + } + }; + return (
{messages.length === 0 && ( @@ -101,29 +140,64 @@ export function ChatInterface() { onScroll={(e) => onChatBodyScroll(e.currentTarget)} className="flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2" > - {messages.map((message, index) => - isErrorMessage(message) ? ( - - ) : ( - - {message.imageUrls.length > 0 && ( - - )} - {messages.length - 1 === index && - message.sender === "assistant" && - curAgentState === AgentState.AWAITING_USER_CONFIRMATION && ( - + {isLoadingMessages && ( +
+
+
+ )} + + {!isLoadingMessages && + messages.map((message, index) => + isErrorMessage(message) ? ( + + ) : ( + + {message.imageUrls.length > 0 && ( + )} - - ), + {messages.length - 1 === index && + message.sender === "assistant" && + curAgentState === AgentState.AWAITING_USER_CONFIRMATION && ( + + )} + + ), + )} + + {(curAgentState === AgentState.AWAITING_USER_INPUT || + curAgentState === AgentState.FINISHED) && ( +
+ {rootLoaderData?.ghToken ? ( + { + handleSendMessage(value, []); + }} + /> + ) : ( + + )} +
)}
diff --git a/frontend/src/components/event-handler.tsx b/frontend/src/components/event-handler.tsx index 3ab50245e87d..930bbafc840f 100644 --- a/frontend/src/components/event-handler.tsx +++ b/frontend/src/components/event-handler.tsx @@ -20,6 +20,7 @@ import { } from "#/services/terminalService"; import { clearFiles, + clearInitialQuery, clearSelectedRepository, setImportedProjectZip, } from "#/state/initial-query-slice"; @@ -52,13 +53,10 @@ export function EventHandler({ children }: React.PropsWithChildren) { const runtimeActive = status === WsClientProviderStatus.ACTIVE; const fetcher = useFetcher(); const dispatch = useDispatch(); - const { files, importedProjectZip } = useSelector( + const { files, importedProjectZip, initialQuery } = useSelector( (state: RootState) => state.initalQuery, ); const { ghToken, repo } = useLoaderData(); - const initialQueryRef = React.useRef( - store.getState().initalQuery.initialQuery, - ); const sendInitialQuery = (query: string, base64Files: string[]) => { const timestamp = new Date().toISOString(); @@ -119,7 +117,6 @@ export function EventHandler({ children }: React.PropsWithChildren) { return; // This is a check because of strict mode - if the status did not change, don't do anything } statusRef.current = status; - const initialQuery = initialQueryRef.current; if (status === WsClientProviderStatus.ACTIVE) { let additionalInfo = ""; @@ -140,7 +137,7 @@ export function EventHandler({ children }: React.PropsWithChildren) { sendInitialQuery(initialQuery, files); } dispatch(clearFiles()); // reset selected files - initialQueryRef.current = null; + dispatch(clearInitialQuery()); // reset initial query } } diff --git a/frontend/src/components/github-repositories-suggestion-box.tsx b/frontend/src/components/github-repositories-suggestion-box.tsx index c39abe11a9ea..4886513dd487 100644 --- a/frontend/src/components/github-repositories-suggestion-box.tsx +++ b/frontend/src/components/github-repositories-suggestion-box.tsx @@ -10,32 +10,8 @@ import { GitHubRepositorySelector } from "#/routes/_oh._index/github-repo-select import ModalButton from "./buttons/ModalButton"; import GitHubLogo from "#/assets/branding/github-logo.svg?react"; -interface GitHubAuthProps { - onConnectToGitHub: () => void; - repositories: GitHubRepository[]; - isLoggedIn: boolean; -} - -function GitHubAuth({ - onConnectToGitHub, - repositories, - isLoggedIn, -}: GitHubAuthProps) { - if (isLoggedIn) { - return ; - } - - return ( - } - className="bg-[#791B80] w-full" - onClick={onConnectToGitHub} - /> - ); -} - interface GitHubRepositoriesSuggestionBoxProps { + handleSubmit: () => void; repositories: Awaited< ReturnType > | null; @@ -44,6 +20,7 @@ interface GitHubRepositoriesSuggestionBoxProps { } export function GitHubRepositoriesSuggestionBox({ + handleSubmit, repositories, gitHubAuthUrl, user, @@ -70,16 +47,26 @@ export function GitHubRepositoriesSuggestionBox({ ); } + const isLoggedIn = !!user && !isGitHubErrorReponse(user); + return ( <> + isLoggedIn ? ( + + ) : ( + } + className="bg-[#791B80] w-full" + onClick={handleConnectToGitHub} + /> + ) } /> {connectToGitHubModalOpen && ( diff --git a/frontend/src/components/interactive-chat-box.tsx b/frontend/src/components/interactive-chat-box.tsx index 3cb166cd7a4f..c3ab7945a64b 100644 --- a/frontend/src/components/interactive-chat-box.tsx +++ b/frontend/src/components/interactive-chat-box.tsx @@ -56,7 +56,7 @@ export function InteractiveChatBox({
diff --git a/frontend/src/components/modals/AccountSettingsModal.tsx b/frontend/src/components/modals/AccountSettingsModal.tsx index b2adc2d7bb32..558966777bda 100644 --- a/frontend/src/components/modals/AccountSettingsModal.tsx +++ b/frontend/src/components/modals/AccountSettingsModal.tsx @@ -1,7 +1,10 @@ import { useFetcher, useRouteLoaderData } from "@remix-run/react"; import React from "react"; import { useTranslation } from "react-i18next"; -import { BaseModalTitle } from "./confirmation-modals/BaseModal"; +import { + BaseModalDescription, + BaseModalTitle, +} from "./confirmation-modals/BaseModal"; import ModalBody from "./ModalBody"; import ModalButton from "../buttons/ModalButton"; import FormFieldset from "../form/FormFieldset"; @@ -87,6 +90,17 @@ function AccountSettingsModal({ type="password" defaultValue={data?.ghToken ?? ""} /> + + {t(I18nKey.CONNECT_TO_GITHUB_MODAL$GET_YOUR_TOKEN)}{" "} + + {t(I18nKey.CONNECT_TO_GITHUB_MODAL$HERE)} + + {gitHubError && (

{t(I18nKey.ACCOUNT_SETTINGS_MODAL$GITHUB_TOKEN_INVALID)} diff --git a/frontend/src/components/suggestion-item.tsx b/frontend/src/components/suggestion-item.tsx index 0631d582be4b..cb9b7fdcae6c 100644 --- a/frontend/src/components/suggestion-item.tsx +++ b/frontend/src/components/suggestion-item.tsx @@ -7,12 +7,12 @@ interface SuggestionItemProps { export function SuggestionItem({ suggestion, onClick }: SuggestionItemProps) { return ( -

  • +
  • diff --git a/frontend/src/components/upload-image-input.tsx b/frontend/src/components/upload-image-input.tsx index a9003b394a90..9919120f3b6f 100644 --- a/frontend/src/components/upload-image-input.tsx +++ b/frontend/src/components/upload-image-input.tsx @@ -11,7 +11,7 @@ export function UploadImageInput({ onUpload, label }: UploadImageInputProps) { }; return ( -