diff --git a/docs/modules/usage/cloud/cloud-github-resolver.md b/docs/modules/usage/cloud/cloud-github-resolver.md
new file mode 100644
index 000000000000..946bd1ba78d8
--- /dev/null
+++ b/docs/modules/usage/cloud/cloud-github-resolver.md
@@ -0,0 +1,21 @@
+# Cloud GitHub Resolver
+
+The GitHub Resolver automates code fixes and provides intelligent assistance for your repositories.
+
+## Setup
+
+The Cloud Github Resolver is available automatically when you
+[grant OpenHands Cloud repository access](./openhands-cloud.md#adding-repositories).
+
+## Usage
+
+### Issues
+
+On your repository, label an issue with `openhands`. OpenHands will attempt to fix the issue.
+
+### Pull Requests
+
+In order to get OpenHands to work on pull requests, use `@openhands` in top level or single inline comments to:
+ - Ask questions
+ - Request updates
+ - Get code explanations
diff --git a/docs/modules/usage/cloud/openhands-cloud.md b/docs/modules/usage/cloud/openhands-cloud.md
new file mode 100644
index 000000000000..f39773cb27a3
--- /dev/null
+++ b/docs/modules/usage/cloud/openhands-cloud.md
@@ -0,0 +1,39 @@
+# Openhands Cloud
+
+OpenHands Cloud is the cloud hosted version of OpenHands by All Hands AI.
+
+## Accessing OpenHands Cloud
+
+Currently, users are being admitted to access OpenHands Cloud in waves. To sign up,
+[join the waitlist](https://www.all-hands.dev/join-waitlist). Once you are approved, you will get an email with
+instructions on how to access it.
+
+## Getting Started
+
+After visiting OpenHands Cloud, you will be asked to connect with your GitHub account:
+1. After reading and accepting the terms of service, click `Connect to GitHub`.
+2. Review the permissions requested by OpenHands and then click `Authorize OpenHands by All Hands AI`.
+ - OpenHands will require some permissions from your GitHub account. To read more about these permissions,
+ you can click the `Learn more` link on the GitHub authorize page.
+
+## Adding Repositories
+
+You can grant OpenHands specific repository access:
+1. Under the `Select a GitHub project` dropdown, select `Add more repositories...`.
+2. Select the organization, then choose the specific repositories to grant OpenHands access to.
+ - Openhands requests short-lived tokens (8-hour expiry) with these permissions:
+ - Actions: Read and write
+ - Administration: Read-only
+ - Commit statuses: Read and write
+ - Contents: Read and write
+ - Issues: Read and write
+ - Metadata: Read-only
+ - Pull requests: Read and write
+ - Webhooks: Read and write
+ - Workflows: Read and write
+ - Repository access for a user is granted based on:
+ - Granted permission for the repository.
+ - User's GitHub permissions (owner/collaborator).
+
+You can manage repository access any time by following the above workflow or visiting the Settings page and selecting
+`Configure GitHub Repositories` under the `GitHub Settings` section.
diff --git a/docs/modules/usage/llms/llms.md b/docs/modules/usage/llms/llms.md
index 5e6a472d0c0a..f4fa118dd02e 100644
--- a/docs/modules/usage/llms/llms.md
+++ b/docs/modules/usage/llms/llms.md
@@ -63,22 +63,22 @@ We have a few guides for running OpenHands with specific model providers:
### API retries and rate limits
LLM providers typically have rate limits, sometimes very low, and may require retries. OpenHands will automatically
-retry requests if it receives a Rate Limit Error (429 error code), API connection error, or other transient errors.
+retry requests if it receives a Rate Limit Error (429 error code).
You can customize these options as you need for the provider you're using. Check their documentation, and set the
following environment variables to control the number of retries and the time between retries:
-- `LLM_NUM_RETRIES` (Default of 8)
-- `LLM_RETRY_MIN_WAIT` (Default of 15 seconds)
-- `LLM_RETRY_MAX_WAIT` (Default of 120 seconds)
+- `LLM_NUM_RETRIES` (Default of 4 times)
+- `LLM_RETRY_MIN_WAIT` (Default of 5 seconds)
+- `LLM_RETRY_MAX_WAIT` (Default of 30 seconds)
- `LLM_RETRY_MULTIPLIER` (Default of 2)
If you are running OpenHands in development mode, you can also set these options in the `config.toml` file:
```toml
[llm]
-num_retries = 8
-retry_min_wait = 15
-retry_max_wait = 120
+num_retries = 4
+retry_min_wait = 5
+retry_max_wait = 30
retry_multiplier = 2
```
diff --git a/docs/sidebars.ts b/docs/sidebars.ts
index a8d88d9dfca1..da416ac30b91 100644
--- a/docs/sidebars.ts
+++ b/docs/sidebars.ts
@@ -42,7 +42,7 @@ const sidebars: SidebarsConfig = {
id: 'usage/prompting/microagents-public',
},
],
- }
+ },
],
},
{
@@ -69,6 +69,23 @@ const sidebars: SidebarsConfig = {
label: 'Github Actions',
id: 'usage/how-to/github-action',
},
+ {
+ type: 'category',
+ label: 'Cloud',
+ items: [
+ {
+ type: 'doc',
+ label: 'Openhands Cloud',
+ id: 'usage/cloud/openhands-cloud',
+ },
+
+ {
+ type: 'doc',
+ label: 'Cloud GitHub Resolver',
+ id: 'usage/cloud/cloud-github-resolver',
+ },
+ ],
+ },
],
},
{
@@ -185,7 +202,7 @@ const sidebars: SidebarsConfig = {
type: 'doc',
label: 'About',
id: 'usage/about',
- }
+ },
],
};
diff --git a/frontend/__tests__/routes/_oh.app.test.tsx b/frontend/__tests__/routes/_oh.app.test.tsx
index 4fc96b25d3e5..d809b128ce08 100644
--- a/frontend/__tests__/routes/_oh.app.test.tsx
+++ b/frontend/__tests__/routes/_oh.app.test.tsx
@@ -5,7 +5,6 @@ import { screen, waitFor } from "@testing-library/react";
import toast from "react-hot-toast";
import App from "#/routes/_oh.app/route";
import OpenHands from "#/api/open-hands";
-import { MULTI_CONVERSATION_UI } from "#/utils/feature-flags";
describe("App", () => {
const RouteStub = createRoutesStub([
@@ -35,7 +34,7 @@ describe("App", () => {
await screen.findByTestId("app-route");
});
- it.skipIf(!MULTI_CONVERSATION_UI)(
+ it(
"should call endSession if the user does not have permission to view conversation",
async () => {
const errorToastSpy = vi.spyOn(toast, "error");
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 0115a8c6934b..a261adf741e7 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -23,12 +23,12 @@
"eslint-config-airbnb-typescript": "^18.0.0",
"framer-motion": "^12.4.2",
"i18next": "^24.2.2",
- "i18next-browser-languagedetector": "^8.0.2",
+ "i18next-browser-languagedetector": "^8.0.3",
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.22",
"jose": "^5.9.4",
"monaco-editor": "^0.52.2",
- "posthog-js": "^1.217.2",
+ "posthog-js": "^1.217.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-highlight": "^0.15.0",
@@ -58,7 +58,7 @@
"@testing-library/jest-dom": "^6.6.1",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
- "@types/node": "^22.13.2",
+ "@types/node": "^22.13.4",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react-highlight": "^0.12.8",
@@ -171,22 +171,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.8.tgz",
- "integrity": "sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
+ "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.8",
+ "@babel/generator": "^7.26.9",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.7",
- "@babel/parser": "^7.26.8",
- "@babel/template": "^7.26.8",
- "@babel/traverse": "^7.26.8",
- "@babel/types": "^7.26.8",
- "@types/gensync": "^1.0.0",
+ "@babel/helpers": "^7.26.9",
+ "@babel/parser": "^7.26.9",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.9",
+ "@babel/types": "^7.26.9",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -211,13 +210,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.8.tgz",
- "integrity": "sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
+ "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.26.8",
- "@babel/types": "^7.26.8",
+ "@babel/parser": "^7.26.9",
+ "@babel/types": "^7.26.9",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -265,18 +264,18 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz",
- "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz",
+ "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
"@babel/helper-member-expression-to-functions": "^7.25.9",
"@babel/helper-optimise-call-expression": "^7.25.9",
- "@babel/helper-replace-supers": "^7.25.9",
+ "@babel/helper-replace-supers": "^7.26.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
- "@babel/traverse": "^7.25.9",
+ "@babel/traverse": "^7.26.9",
"semver": "^6.3.1"
},
"engines": {
@@ -422,25 +421,25 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
- "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
+ "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7"
+ "@babel/template": "^7.26.9",
+ "@babel/types": "^7.26.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.8.tgz",
- "integrity": "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
+ "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.8"
+ "@babel/types": "^7.26.9"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -585,9 +584,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
- "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
+ "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -597,30 +596,30 @@
}
},
"node_modules/@babel/template": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.8.tgz",
- "integrity": "sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
+ "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
- "@babel/parser": "^7.26.8",
- "@babel/types": "^7.26.8"
+ "@babel/parser": "^7.26.9",
+ "@babel/types": "^7.26.9"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.8.tgz",
- "integrity": "sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
+ "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.8",
- "@babel/parser": "^7.26.8",
- "@babel/template": "^7.26.8",
- "@babel/types": "^7.26.8",
+ "@babel/generator": "^7.26.9",
+ "@babel/parser": "^7.26.9",
+ "@babel/template": "^7.26.9",
+ "@babel/types": "^7.26.9",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -629,9 +628,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz",
- "integrity": "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==",
+ "version": "7.26.9",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
+ "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@@ -3220,13 +3219,13 @@
"license": "BSD-3-Clause"
},
"node_modules/@inquirer/confirm": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.5.tgz",
- "integrity": "sha512-ZB2Cz8KeMINUvoeDi7IrvghaVkYT2RB0Zb31EaLWOE87u276w4wnApv0SH2qWaJ3r0VSUa3BIuz7qAV2ZvsZlg==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.6.tgz",
+ "integrity": "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@inquirer/core": "^10.1.6",
+ "@inquirer/core": "^10.1.7",
"@inquirer/type": "^3.0.4"
},
"engines": {
@@ -3242,9 +3241,9 @@
}
},
"node_modules/@inquirer/core": {
- "version": "10.1.6",
- "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.6.tgz",
- "integrity": "sha512-Bwh/Zk6URrHwZnSSzAZAKH7YgGYi0xICIBDFOqBQoXNNAzBHw/bgXgLmChfp+GyR3PnChcTbiCTZGC6YJNJkMA==",
+ "version": "10.1.7",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.7.tgz",
+ "integrity": "sha512-AA9CQhlrt6ZgiSy6qoAigiA1izOa751ugX6ioSjqgJ+/Gd+tEN/TORk5sUYNjXuHWfW0r1n/a6ak4u/NqHHrtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5613,9 +5612,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz",
- "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
+ "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
"cpu": [
"arm"
],
@@ -5626,9 +5625,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz",
- "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
+ "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
"cpu": [
"arm64"
],
@@ -5639,9 +5638,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz",
- "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
+ "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
"cpu": [
"arm64"
],
@@ -5652,9 +5651,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz",
- "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
+ "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
"cpu": [
"x64"
],
@@ -5665,9 +5664,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz",
- "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
+ "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
"cpu": [
"arm64"
],
@@ -5678,9 +5677,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz",
- "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
+ "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
"cpu": [
"x64"
],
@@ -5691,9 +5690,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz",
- "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
+ "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
"cpu": [
"arm"
],
@@ -5704,9 +5703,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz",
- "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
+ "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
"cpu": [
"arm"
],
@@ -5717,9 +5716,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz",
- "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
+ "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
"cpu": [
"arm64"
],
@@ -5730,9 +5729,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz",
- "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
+ "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
"cpu": [
"arm64"
],
@@ -5743,9 +5742,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz",
- "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
+ "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
"cpu": [
"loong64"
],
@@ -5756,9 +5755,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz",
- "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
+ "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
"cpu": [
"ppc64"
],
@@ -5769,9 +5768,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz",
- "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
+ "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
"cpu": [
"riscv64"
],
@@ -5782,9 +5781,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz",
- "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
+ "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
"cpu": [
"s390x"
],
@@ -5795,9 +5794,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz",
- "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
+ "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
"cpu": [
"x64"
],
@@ -5808,9 +5807,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz",
- "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
+ "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
"cpu": [
"x64"
],
@@ -5821,9 +5820,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz",
- "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
+ "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
"cpu": [
"arm64"
],
@@ -5834,9 +5833,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz",
- "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
+ "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
"cpu": [
"ia32"
],
@@ -5847,9 +5846,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz",
- "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
+ "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
"cpu": [
"x64"
],
@@ -6140,9 +6139,9 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.66.0",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.66.0.tgz",
- "integrity": "sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==",
+ "version": "5.66.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.66.3.tgz",
+ "integrity": "sha512-+2iDxH7UFdtwcry766aJszGmbByQDIzTltJ3oQAZF9bhCxHCIN3yDwHa6qDCZxcpMGvUphCRx/RYJvLbM8mucQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -6150,12 +6149,12 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.66.0",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.0.tgz",
- "integrity": "sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==",
+ "version": "5.66.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.3.tgz",
+ "integrity": "sha512-sWMvxZ5VugPDgD1CzP7f0s9yFvjcXP3FXO5IVV2ndXlYqUCwykU8U69Kk05Qn5UvGRqB/gtj4J7vcTC6vtLHtQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.66.0"
+ "@tanstack/query-core": "5.66.3"
},
"funding": {
"type": "github",
@@ -6374,12 +6373,6 @@
"@types/estree": "*"
}
},
- "node_modules/@types/gensync": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz",
- "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==",
- "license": "MIT"
- },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -6427,9 +6420,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.13.2",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.2.tgz",
- "integrity": "sha512-Z+r8y3XL9ZpI2EY52YYygAFmo2/oWfNSj4BCpAXE2McAexDk8VcnBMGC9Djn9gTKt4d2T/hhXqmPzo4hfIXtTg==",
+ "version": "22.13.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
+ "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -6437,18 +6430,18 @@
}
},
"node_modules/@types/react": {
- "version": "19.0.8",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
- "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
+ "version": "19.0.10",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
+ "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
- "version": "19.0.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
- "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
+ "version": "19.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
+ "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -7776,9 +7769,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001699",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
- "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==",
+ "version": "1.0.30001700",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
+ "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
"funding": [
{
"type": "opencollective",
@@ -7806,9 +7799,9 @@
}
},
"node_modules/chai": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
- "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
+ "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8753,9 +8746,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.98",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.98.tgz",
- "integrity": "sha512-bI/LbtRBxU2GzK7KK5xxFd2y9Lf9XguHooPYbcXWy6wUoT8NMnffsvRhPmSeUHLSDKAEtKuTaEtK4Ms15zkIEA==",
+ "version": "1.5.101",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz",
+ "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -9028,7 +9021,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -10160,13 +10152,14 @@
}
},
"node_modules/form-data": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
- "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
@@ -10205,9 +10198,9 @@
}
},
"node_modules/framer-motion": {
- "version": "12.4.2",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.2.tgz",
- "integrity": "sha512-pW307cQKjDqEuO1flEoIFf6TkuJRfKr+c7qsHAJhDo4368N/5U8/7WU8J+xhd9+gjmOgJfgp+46evxRRFM39dA==",
+ "version": "12.4.3",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.3.tgz",
+ "integrity": "sha512-rsMeO7w3dKyNG09o3cGwSH49iHU+VgDmfSSfsX+wfkO3zDA6WWkh4sUsMXd155YROjZP+7FTIhDrBYfgZeHjKQ==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.0.0",
@@ -10645,7 +10638,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -10970,9 +10962,9 @@
}
},
"node_modules/i18next-browser-languagedetector": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz",
- "integrity": "sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==",
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.3.tgz",
+ "integrity": "sha512-beOOLArattPBc2YZG5IXGJytdYFgUR7cS8Wd6HT4IczIoWKgmTspOQ2yasaGklelVo5seLPmnEKvLHR+E/MdWQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
@@ -14542,9 +14534,9 @@
"license": "MIT"
},
"node_modules/posthog-js": {
- "version": "1.217.4",
- "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.217.4.tgz",
- "integrity": "sha512-ZIOb75F1pdMZl6e7C4mgH2accKArLA2RG3zMEjeils+3J/cylwgcr2Iw0QtzSLqQVvR7AFRRbXMZXUWsiB2zyA==",
+ "version": "1.219.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.219.0.tgz",
+ "integrity": "sha512-RnjtcjI4UYTBsjfF4Fs1lICWmGjiqMU9H0fN2ab1BEcDOFL/2m9Fx/1viCxvMiQR8cmgWWpkipJXD0gY7czDOA==",
"license": "MIT",
"dependencies": {
"core-js": "^3.38.1",
@@ -14560,9 +14552,9 @@
"license": "Apache-2.0"
},
"node_modules/preact": {
- "version": "10.25.4",
- "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz",
- "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==",
+ "version": "10.26.0",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.0.tgz",
+ "integrity": "sha512-6ugi/Mb7lyV5RA6KlnijFyDLMU253i7L0RRiObIzDoqj59KT9iTeNJbA/YGw6M7jP4vxaab0DOA8DgodTOA6EQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -14874,9 +14866,9 @@
}
},
"node_modules/react-hot-toast": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.1.tgz",
- "integrity": "sha512-54Gq1ZD1JbmAb4psp9bvFHjS7lje+8ubboUmvKZkCsQBLH6AOpZ9JemfRvIdHcfb9AZXRaFLrb3qUobGYDJhFQ==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
+ "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
@@ -15086,9 +15078,9 @@
"license": "MIT"
},
"node_modules/readdirp": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz",
- "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -15537,9 +15529,9 @@
}
},
"node_modules/rollup": {
- "version": "4.34.6",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz",
- "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==",
+ "version": "4.34.8",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
+ "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.6"
@@ -15552,25 +15544,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.34.6",
- "@rollup/rollup-android-arm64": "4.34.6",
- "@rollup/rollup-darwin-arm64": "4.34.6",
- "@rollup/rollup-darwin-x64": "4.34.6",
- "@rollup/rollup-freebsd-arm64": "4.34.6",
- "@rollup/rollup-freebsd-x64": "4.34.6",
- "@rollup/rollup-linux-arm-gnueabihf": "4.34.6",
- "@rollup/rollup-linux-arm-musleabihf": "4.34.6",
- "@rollup/rollup-linux-arm64-gnu": "4.34.6",
- "@rollup/rollup-linux-arm64-musl": "4.34.6",
- "@rollup/rollup-linux-loongarch64-gnu": "4.34.6",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6",
- "@rollup/rollup-linux-riscv64-gnu": "4.34.6",
- "@rollup/rollup-linux-s390x-gnu": "4.34.6",
- "@rollup/rollup-linux-x64-gnu": "4.34.6",
- "@rollup/rollup-linux-x64-musl": "4.34.6",
- "@rollup/rollup-win32-arm64-msvc": "4.34.6",
- "@rollup/rollup-win32-ia32-msvc": "4.34.6",
- "@rollup/rollup-win32-x64-msvc": "4.34.6",
+ "@rollup/rollup-android-arm-eabi": "4.34.8",
+ "@rollup/rollup-android-arm64": "4.34.8",
+ "@rollup/rollup-darwin-arm64": "4.34.8",
+ "@rollup/rollup-darwin-x64": "4.34.8",
+ "@rollup/rollup-freebsd-arm64": "4.34.8",
+ "@rollup/rollup-freebsd-x64": "4.34.8",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
+ "@rollup/rollup-linux-arm-musleabihf": "4.34.8",
+ "@rollup/rollup-linux-arm64-gnu": "4.34.8",
+ "@rollup/rollup-linux-arm64-musl": "4.34.8",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
+ "@rollup/rollup-linux-riscv64-gnu": "4.34.8",
+ "@rollup/rollup-linux-s390x-gnu": "4.34.8",
+ "@rollup/rollup-linux-x64-gnu": "4.34.8",
+ "@rollup/rollup-linux-x64-musl": "4.34.8",
+ "@rollup/rollup-win32-arm64-msvc": "4.34.8",
+ "@rollup/rollup-win32-ia32-msvc": "4.34.8",
+ "@rollup/rollup-win32-x64-msvc": "4.34.8",
"fsevents": "~2.3.2"
}
},
@@ -17105,9 +17097,9 @@
}
},
"node_modules/type-fest": {
- "version": "4.34.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.34.1.tgz",
- "integrity": "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==",
+ "version": "4.35.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz",
+ "integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
diff --git a/frontend/package.json b/frontend/package.json
index 2781b75eabbc..6049777c09df 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,12 +22,12 @@
"eslint-config-airbnb-typescript": "^18.0.0",
"framer-motion": "^12.4.2",
"i18next": "^24.2.2",
- "i18next-browser-languagedetector": "^8.0.2",
+ "i18next-browser-languagedetector": "^8.0.3",
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.22",
"jose": "^5.9.4",
"monaco-editor": "^0.52.2",
- "posthog-js": "^1.217.2",
+ "posthog-js": "^1.217.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-highlight": "^0.15.0",
@@ -85,7 +85,7 @@
"@testing-library/jest-dom": "^6.6.1",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
- "@types/node": "^22.13.2",
+ "@types/node": "^22.13.4",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react-highlight": "^0.12.8",
diff --git a/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx b/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx
index 4dd7c183be09..a649dc4ef878 100644
--- a/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx
+++ b/frontend/src/components/features/conversation-panel/confirm-delete-modal.tsx
@@ -22,7 +22,10 @@ export function ConfirmDeleteModal({
-
+
event.stopPropagation()}
+ >
) => {
diff --git a/frontend/src/components/features/conversation-panel/conversation-panel.tsx b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
index a96c649a54d8..d91a70755ba1 100644
--- a/frontend/src/components/features/conversation-panel/conversation-panel.tsx
+++ b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
@@ -44,12 +44,16 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
const handleConfirmDelete = () => {
if (selectedConversationId) {
- deleteConversation({ conversationId: selectedConversationId });
- setConfirmDeleteModalVisible(false);
-
- if (cid === selectedConversationId) {
- endSession();
- }
+ deleteConversation(
+ { conversationId: selectedConversationId },
+ {
+ onSuccess: () => {
+ if (cid === selectedConversationId) {
+ endSession();
+ }
+ },
+ },
+ );
}
};
@@ -110,7 +114,10 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
{confirmDeleteModalVisible && (
{
+ handleConfirmDelete();
+ setConfirmDeleteModalVisible(false);
+ }}
onCancel={() => setConfirmDeleteModalVisible(false)}
/>
)}
diff --git a/frontend/src/components/features/sidebar/sidebar.tsx b/frontend/src/components/features/sidebar/sidebar.tsx
index 645543ac6fd2..d44ca00e7153 100644
--- a/frontend/src/components/features/sidebar/sidebar.tsx
+++ b/frontend/src/components/features/sidebar/sidebar.tsx
@@ -15,7 +15,6 @@ import { SettingsModal } from "#/components/shared/modals/settings/settings-moda
import { useCurrentSettings } from "#/context/settings-context";
import { useSettings } from "#/hooks/query/use-settings";
import { ConversationPanel } from "../conversation-panel/conversation-panel";
-import { MULTI_CONVERSATION_UI } from "#/utils/feature-flags";
import { useEndSession } from "#/hooks/use-end-session";
import { setCurrentAgentState } from "#/state/agent-slice";
import { AgentState } from "#/types/agent-state";
@@ -78,16 +77,14 @@ export function Sidebar() {
- {MULTI_CONVERSATION_UI && (
-
setConversationPanelIsOpen((prev) => !prev)}
- >
-
-
- )}
+
setConversationPanelIsOpen((prev) => !prev)}
+ >
+
+
diff --git a/frontend/src/hooks/mutation/use-delete-conversation.ts b/frontend/src/hooks/mutation/use-delete-conversation.ts
index b0e3d6c90e58..cedc5475caae 100644
--- a/frontend/src/hooks/mutation/use-delete-conversation.ts
+++ b/frontend/src/hooks/mutation/use-delete-conversation.ts
@@ -7,7 +7,32 @@ export const useDeleteConversation = () => {
return useMutation({
mutationFn: (variables: { conversationId: string }) =>
OpenHands.deleteUserConversation(variables.conversationId),
- onSuccess: () => {
+ onMutate: async (variables) => {
+ await queryClient.cancelQueries({ queryKey: ["user", "conversations"] });
+ const previousConversations = queryClient.getQueryData([
+ "user",
+ "conversations",
+ ]);
+
+ queryClient.setQueryData(
+ ["user", "conversations"],
+ (old: { conversation_id: string }[] | undefined) =>
+ old?.filter(
+ (conv) => conv.conversation_id !== variables.conversationId,
+ ),
+ );
+
+ return { previousConversations };
+ },
+ onError: (err, variables, context) => {
+ if (context?.previousConversations) {
+ queryClient.setQueryData(
+ ["user", "conversations"],
+ context.previousConversations,
+ );
+ }
+ },
+ onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["user", "conversations"] });
},
});
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index eaa0ccf43b8e..70b9974dcb3c 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -3249,7 +3249,7 @@
"ja": "非公開"
},
"STATUS$STARTING_RUNTIME": {
- "en": "Starting Runtime...",
+ "en": "Starting runtime...",
"zh-CN": "启动运行时...",
"zh-TW": "啟動執行時...",
"de": "Laufzeitumgebung wird gestartet...",
@@ -3803,6 +3803,37 @@
"pt": "Erro ao autenticar com o provedor LLM. Por favor, verifique sua chave API",
"tr": "LLM sağlayıcısı ile kimlik doğrulama hatası. Lütfen API anahtarınızı kontrol edin"
},
+ "STATUS$ERROR_LLM_SERVICE_UNAVAILABLE": {
+ "en": "The LLM provider is currently unavailable. Please try again later.",
+ "es": "El proveedor LLM no está actualmente disponible. Por favor, inténtelo de nuevo más tarde.",
+ "zh-CN": "LLM提供商当前不可用",
+ "zh-TW": "LLM提供商目前無法使用",
+ "ko-KR": "LLM 공급자가 현재 사용 불가능합니다",
+ "ja": "LLMプロバイダーが現在利用できません。後でもう一度試してください。",
+ "no": "LLM-leverandøren er nå ikke tilgjengelig. Vennligst prøv igjen senere.",
+ "ar": "المزود LLM غير متاح حالياً. يرجى المحاولة مرة أخرى لاحقًا.",
+ "de": "Der LLM-Anbieter ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut.",
+ "fr": "Le fournisseur LLM n'est actuellement pas disponible. Veuillez réessayer plus tard.",
+ "it": "Il provider LLM non è attualmente disponibile. Per favore, riprova più tardi.",
+ "pt": "O provedor LLM não está atualmente disponível. Por favor, tente novamente mais tarde.",
+ "tr": "LLM sağlayıcısı şu anda kullanılamıyor. Lütfen daha sonra tekrar deneyin."
+ },
+ "STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR": {
+ "en": "The request failed with an internal server error.",
+ "es": "La solicitud falló con un error del servidor interno.",
+ "zh-CN": "请求失败,请稍后再试",
+ "zh-TW": "請求失敗,請稍後再試",
+ "ko-KR": "요청이 실패했습니다. 나중에 다시 시도해주세요.",
+ "ja": "リクエストが内部サーバーエラーで失敗しました。後でもう一度試してください。",
+ "no": "Det oppstod en feil ved tilkobling til kjøretidsmiljøet. Vennligst oppdater siden.",
+ "ar": "حدث خطأ أثناء الاتصال بوقت التشغيل. يرجى تحديث الصفحة.",
+ "de": "Beim Verbinden mit der Laufzeitumgebung ist ein Fehler aufgetreten. Bitte aktualisieren Sie die Seite.",
+ "fr": "Une erreur s'est produite lors de la connexion à l'environnement d'exécution. Veuillez rafraîchir la page.",
+ "it": "Si è verificato un errore durante la connessione al runtime. Aggiorna la pagina.",
+ "pt": "Ocorreu um erro ao conectar ao ambiente de execução. Por favor, atualize a página.",
+ "tr": "Çalışma zamanına bağlanırken bir hata oluştu. Lütfen sayfayı yenileyin."
+ },
+
"STATUS$ERROR_RUNTIME_DISCONNECTED": {
"en": "There was an error while connecting to the runtime. Please refresh the page.",
"zh-CN": "运行时已断开连接",
@@ -3820,7 +3851,18 @@
},
"STATUS$LLM_RETRY": {
"en": "Retrying LLM request",
- "zh-TW": "重新嘗試 LLM 請求中"
+ "es": "Reintentando solicitud LLM",
+ "zh-CN": "重试LLM请求",
+ "zh-TW": "重試LLM請求",
+ "ko-KR": "LLM 요청 재시도",
+ "ja": "LLM リクエストを再試行中",
+ "no": "Gjenforsøker LLM-forespørsel",
+ "ar": "يتم إعادة تحميل الطلب LLM",
+ "de": "LLM-Anfrage erneut versuchen",
+ "fr": "Réessayer la requête LLM",
+ "it": "Ritenta la richiesta LLM",
+ "pt": "Reintentando a solicitação LLM",
+ "tr": "LLM isteğini yeniden deniyor"
},
"AGENT_ERROR$BAD_ACTION": {
"en": "Agent tried to execute a malformed action.",
diff --git a/frontend/src/routes/_oh.app/route.tsx b/frontend/src/routes/_oh.app/route.tsx
index 19450e09ed75..c605927a0aa9 100644
--- a/frontend/src/routes/_oh.app/route.tsx
+++ b/frontend/src/routes/_oh.app/route.tsx
@@ -34,7 +34,6 @@ import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { ServedAppLabel } from "#/components/layout/served-app-label";
import { TerminalStatusLabel } from "#/components/features/terminal/terminal-status-label";
import { useSettings } from "#/hooks/query/use-settings";
-import { MULTI_CONVERSATION_UI } from "#/utils/feature-flags";
import { clearFiles, clearInitialPrompt } from "#/state/initial-query-slice";
import { RootState } from "#/store";
@@ -66,7 +65,7 @@ function AppContent() {
);
React.useEffect(() => {
- if (MULTI_CONVERSATION_UI && isFetched && !conversation) {
+ if (isFetched && !conversation) {
toast.error(
"This conversation does not exist, or you do not have permission to access it.",
);
diff --git a/frontend/src/utils/feature-flags.ts b/frontend/src/utils/feature-flags.ts
index 154e88a59924..a5f32b1128e7 100644
--- a/frontend/src/utils/feature-flags.ts
+++ b/frontend/src/utils/feature-flags.ts
@@ -12,5 +12,4 @@ function loadFeatureFlag(
}
}
-export const MULTI_CONVERSATION_UI = loadFeatureFlag("MULTI_CONVERSATION_UI");
export const MEMORY_CONDENSER = loadFeatureFlag("MEMORY_CONDENSER");
diff --git a/frontend/tests/conversation-panel.test.ts b/frontend/tests/conversation-panel.test.ts
index a4ef6ca6ea33..6e3f58cd458f 100644
--- a/frontend/tests/conversation-panel.test.ts
+++ b/frontend/tests/conversation-panel.test.ts
@@ -31,9 +31,6 @@ const selectConversationCard = async (page: Page, index: number) => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
- await page.evaluate(() => {
- localStorage.setItem("FEATURE_MULTI_CONVERSATION_UI", "true");
- });
});
test("should only display the create new conversation button when in a conversation", async ({
diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py
index e5a0b24f9694..1e338810198a 100644
--- a/openhands/controller/agent_controller.py
+++ b/openhands/controller/agent_controller.py
@@ -214,6 +214,17 @@ async def _react_to_exception(
err_id = ''
if isinstance(e, litellm.AuthenticationError):
err_id = 'STATUS$ERROR_LLM_AUTHENTICATION'
+ elif isinstance(
+ e,
+ (
+ litellm.ServiceUnavailableError,
+ litellm.APIConnectionError,
+ litellm.APIError,
+ ),
+ ):
+ err_id = 'STATUS$ERROR_LLM_SERVICE_UNAVAILABLE'
+ elif isinstance(e, litellm.InternalServerError):
+ err_id = 'STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR'
elif isinstance(e, RateLimitError):
await self.set_agent_state_to(AgentState.RATE_LIMITED)
return
diff --git a/openhands/core/config/llm_config.py b/openhands/core/config/llm_config.py
index cb1581634da1..cee22766df14 100644
--- a/openhands/core/config/llm_config.py
+++ b/openhands/core/config/llm_config.py
@@ -59,10 +59,11 @@ class LLMConfig(BaseModel):
aws_region_name: str | None = Field(default=None)
openrouter_site_url: str = Field(default='https://docs.all-hands.dev/')
openrouter_app_name: str = Field(default='OpenHands')
- num_retries: int = Field(default=8)
+ # total wait time: 5 + 10 + 20 + 30 = 65 seconds
+ num_retries: int = Field(default=4)
retry_multiplier: float = Field(default=2)
- retry_min_wait: int = Field(default=15)
- retry_max_wait: int = Field(default=120)
+ retry_min_wait: int = Field(default=5)
+ retry_max_wait: int = Field(default=30)
timeout: int | None = Field(default=None)
max_message_chars: int = Field(
default=30_000
diff --git a/openhands/core/logger.py b/openhands/core/logger.py
index b384fedac1d8..2f830c655a00 100644
--- a/openhands/core/logger.py
+++ b/openhands/core/logger.py
@@ -217,7 +217,21 @@ def _flush(self):
class SensitiveDataFilter(logging.Filter):
def filter(self, record):
- # start with attributes
+ # Gather sensitive values which should not ever appear in the logs.
+ sensitive_values = []
+ for key, value in os.environ.items():
+ key_upper = key.upper()
+ if len(value) > 2 and any(
+ s in key_upper for s in ('SECRET', 'KEY', 'CODE', 'TOKEN')
+ ):
+ sensitive_values.append(value)
+
+ # Replace sensitive values from env!
+ msg = record.getMessage()
+ for sensitive_value in sensitive_values:
+ msg = msg.replace(sensitive_value, '******')
+
+ # Replace obvious sensitive values from log itself...
sensitive_patterns = [
'api_key',
'aws_access_key_id',
@@ -227,28 +241,22 @@ def filter(self, record):
'jwt_secret',
'modal_api_token_id',
'modal_api_token_secret',
+ 'llm_api_key',
+ 'sandbox_env_github_token',
]
# add env var names
env_vars = [attr.upper() for attr in sensitive_patterns]
sensitive_patterns.extend(env_vars)
- # and some special cases
- sensitive_patterns.append('JWT_SECRET')
- sensitive_patterns.append('LLM_API_KEY')
- sensitive_patterns.append('GITHUB_TOKEN')
- sensitive_patterns.append('SANDBOX_ENV_GITHUB_TOKEN')
-
- # this also formats the message with % args
- msg = record.getMessage()
- record.args = ()
-
for attr in sensitive_patterns:
pattern = rf"{attr}='?([\w-]+)'?"
msg = re.sub(pattern, f"{attr}='******'", msg)
- # passed with msg
+ # Update the record
record.msg = msg
+ record.args = ()
+
return True
diff --git a/openhands/events/stream.py b/openhands/events/stream.py
index 5e02c4c369dd..0fc547803f6d 100644
--- a/openhands/events/stream.py
+++ b/openhands/events/stream.py
@@ -282,6 +282,9 @@ def add_event(self, event: Event, source: EventSource):
def set_secrets(self, secrets: dict[str, str]):
self.secrets = secrets.copy()
+ def update_secrets(self, secrets: dict[str, str]):
+ self.secrets.update(secrets)
+
def _replace_secrets(self, data: dict) -> dict:
for key in data:
if isinstance(data[key], dict):
diff --git a/openhands/llm/llm.py b/openhands/llm/llm.py
index b5fe67943467..a9071b43bed3 100644
--- a/openhands/llm/llm.py
+++ b/openhands/llm/llm.py
@@ -18,11 +18,7 @@
from litellm import completion as litellm_completion
from litellm import completion_cost as litellm_completion_cost
from litellm.exceptions import (
- APIConnectionError,
- APIError,
- InternalServerError,
RateLimitError,
- ServiceUnavailableError,
)
from litellm.types.utils import CostPerToken, ModelResponse, Usage
from litellm.utils import create_pretrained_tokenizer
@@ -41,15 +37,7 @@
__all__ = ['LLM']
# tuple of exceptions to retry on
-LLM_RETRY_EXCEPTIONS: tuple[type[Exception], ...] = (
- APIConnectionError,
- # FIXME: APIError is useful on 502 from a proxy for example,
- # but it also retries on other errors that are permanent
- APIError,
- InternalServerError,
- RateLimitError,
- ServiceUnavailableError,
-)
+LLM_RETRY_EXCEPTIONS: tuple[type[Exception], ...] = (RateLimitError,)
# cache prompt supporting models
# remove this when we gemini and deepseek are supported
diff --git a/openhands/runtime/README.md b/openhands/runtime/README.md
index 5a4c1bd0f4fa..10df124e83a7 100644
--- a/openhands/runtime/README.md
+++ b/openhands/runtime/README.md
@@ -145,7 +145,7 @@ Key features:
- Support for cloud-based deployments
- Potential for improved security through isolation
-At the time of this writing, this is mostly used in parallel evaluation, such as this example for [SWE-Bench](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation/swe_bench#run-inference-on-remoteruntime-experimental).
+At the time of this writing, this is mostly used in parallel evaluation, such as this example for [SWE-Bench](https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation/benchmarks/swe_bench#run-inference-on-remoteruntime-experimental).
## Related Components
diff --git a/openhands/runtime/base.py b/openhands/runtime/base.py
index 4f1e37f471dc..983fc67fa898 100644
--- a/openhands/runtime/base.py
+++ b/openhands/runtime/base.py
@@ -225,6 +225,13 @@ async def _handle_action(self, event: Action) -> None:
export_cmd = CmdRunAction(
f"export GITHUB_TOKEN='{token.get_secret_value()}'"
)
+
+ self.event_stream.update_secrets(
+ {
+ 'github_token': token.get_secret_value(),
+ }
+ )
+
await call_sync_from_async(self.run, export_cmd)
observation: Observation = await call_sync_from_async(
diff --git a/openhands/runtime/utils/bash.py b/openhands/runtime/utils/bash.py
index 419573d7546d..09ac30d19cc3 100644
--- a/openhands/runtime/utils/bash.py
+++ b/openhands/runtime/utils/bash.py
@@ -189,13 +189,15 @@ def initialize(self):
if self.username in ['root', 'openhands']:
# This starts a non-login (new) shell for the given user
_shell_command = f'su {self.username} -'
- # otherwise, we are running as the CURRENT USER (e.g., when running LocalRuntime)
- if self.max_memory_mb is not None:
- window_command = (
- f'prlimit --as={self.max_memory_mb * 1024 * 1024} {_shell_command}'
- )
- else:
- window_command = _shell_command
+
+ # FIXME: we will introduce memory limit using sysbox-runc in coming PR
+ # # otherwise, we are running as the CURRENT USER (e.g., when running LocalRuntime)
+ # if self.max_memory_mb is not None:
+ # window_command = (
+ # f'prlimit --as={self.max_memory_mb * 1024 * 1024} {_shell_command}'
+ # )
+ # else:
+ window_command = _shell_command
logger.debug(f'Initializing bash session with command: {window_command}')
session_name = f'openhands-{self.username}-{uuid.uuid4()}'
diff --git a/openhands/runtime/utils/runtime_templates/Dockerfile.j2 b/openhands/runtime/utils/runtime_templates/Dockerfile.j2
index ef073a6a7a84..c0d915f75ec9 100644
--- a/openhands/runtime/utils/runtime_templates/Dockerfile.j2
+++ b/openhands/runtime/utils/runtime_templates/Dockerfile.j2
@@ -88,7 +88,7 @@ RUN \
# Set environment variables
echo "OH_INTERPRETER_PATH=$(/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print(sys.executable)")" >> /etc/environment && \
# Clear caches
- /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . && \
+ /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \
# Set permissions
chmod -R g+rws /openhands/poetry && \
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
diff --git a/openhands/utils/search_utils.py b/openhands/utils/search_utils.py
index 315d0775c0c4..b7714249f875 100644
--- a/openhands/utils/search_utils.py
+++ b/openhands/utils/search_utils.py
@@ -1,4 +1,5 @@
import base64
+from typing import AsyncIterator, Callable
def offset_to_page_id(offset: int, has_next: bool) -> str | None:
@@ -13,3 +14,16 @@ def page_id_to_offset(page_id: str | None) -> int:
return 0
offset = int(base64.b64decode(page_id).decode())
return offset
+
+
+async def iterate(fn: Callable, **kwargs) -> AsyncIterator:
+ """Iterate over paged result sets. Assumes that the results sets contain an array of result objects, and a next_page_id"""
+ kwargs = {**kwargs}
+ kwargs['page_id'] = None
+ while True:
+ result_set = await fn(**kwargs)
+ for result in result_set.results:
+ yield result
+ if result_set.next_page_id is None:
+ return
+ kwargs['page_id'] = result_set.next_page_id
diff --git a/poetry.lock b/poetry.lock
index 614cf5a81549..22bf75f5da69 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -1385,7 +1385,6 @@ files = [
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
@@ -1396,7 +1395,6 @@ files = [
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
@@ -5895,14 +5893,14 @@ realtime = ["websockets (>=13,<15)"]
[[package]]
name = "openhands-aci"
-version = "0.2.2"
+version = "0.2.3"
description = "An Agent-Computer Interface (ACI) designed for software development agents OpenHands."
optional = false
python-versions = "<4.0,>=3.12"
groups = ["main"]
files = [
- {file = "openhands_aci-0.2.2-py3-none-any.whl", hash = "sha256:fdcea74d5760b7f936e532dec2923f06d6ba67b13312e2d91d230e751aa255f1"},
- {file = "openhands_aci-0.2.2.tar.gz", hash = "sha256:947d6c42d4d439200d0bda4748ee8bf5f0c517e8ee554d1c819b82f1d38536c6"},
+ {file = "openhands_aci-0.2.3-py3-none-any.whl", hash = "sha256:6c5479c41f3bad460a0ac078418260851166a8d4e641e0072b26459a34ef4442"},
+ {file = "openhands_aci-0.2.3.tar.gz", hash = "sha256:6b8031751ec3e6d1da54969b2ec19dcbc3192676e5386cbb48d04458f8021148"},
]
[package.dependencies]
@@ -5916,7 +5914,11 @@ networkx = "*"
numpy = "*"
pandas = "*"
scipy = "*"
-tree-sitter = "0.21.3"
+tree-sitter = ">=0.24.0,<0.25.0"
+tree-sitter-javascript = ">=0.23.1,<0.24.0"
+tree-sitter-python = ">=0.23.6,<0.24.0"
+tree-sitter-ruby = ">=0.23.1,<0.24.0"
+tree-sitter-typescript = ">=0.23.2,<0.24.0"
whatthepatch = ">=1.0.6,<2.0.0"
[[package]]
@@ -9498,50 +9500,68 @@ vision = ["Pillow (>=10.0.1,<=15.0)"]
[[package]]
name = "tree-sitter"
-version = "0.21.3"
-description = "Python bindings for the Tree-Sitter parsing library"
+version = "0.24.0"
+description = "Python bindings to the Tree-sitter parsing library"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95"},
+ {file = "tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce"},
+ {file = "tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751"},
+ {file = "tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34"},
+ {file = "tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8"},
+]
+
+[package.extras]
+docs = ["sphinx (>=8.1,<9.0)", "sphinx-book-theme"]
+tests = ["tree-sitter-html (>=0.23.2)", "tree-sitter-javascript (>=0.23.1)", "tree-sitter-json (>=0.24.8)", "tree-sitter-python (>=0.23.6)", "tree-sitter-rust (>=0.23.2)"]
+
+[[package]]
+name = "tree-sitter-javascript"
+version = "0.23.1"
+description = "JavaScript grammar for tree-sitter"
+optional = false
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "tree-sitter-0.21.3.tar.gz", hash = "sha256:b5de3028921522365aa864d95b3c41926e0ba6a85ee5bd000e10dc49b0766988"},
- {file = "tree_sitter-0.21.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:351f302b6615230c9dac9829f0ba20a94362cd658206ca9a7b2d58d73373dfb0"},
- {file = "tree_sitter-0.21.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:766e79ae1e61271e7fdfecf35b6401ad9b47fc07a0965ad78e7f97fddfdf47a6"},
- {file = "tree_sitter-0.21.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c4d3d4d4b44857e87de55302af7f2d051c912c466ef20e8f18158e64df3542a"},
- {file = "tree_sitter-0.21.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84eedb06615461b9e2847be7c47b9c5f2195d7d66d31b33c0a227eff4e0a0199"},
- {file = "tree_sitter-0.21.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d33ea425df8c3d6436926fe2991429d59c335431bf4e3c71e77c17eb508be5a"},
- {file = "tree_sitter-0.21.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae1ee0ff6d85e2fd5cd8ceb9fe4af4012220ee1e4cbe813305a316caf7a6f63"},
- {file = "tree_sitter-0.21.3-cp310-cp310-win_amd64.whl", hash = "sha256:bb41be86a987391f9970571aebe005ccd10222f39c25efd15826583c761a37e5"},
- {file = "tree_sitter-0.21.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54b22c3c2aab3e3639a4b255d9df8455da2921d050c4829b6a5663b057f10db5"},
- {file = "tree_sitter-0.21.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab6e88c1e2d5e84ff0f9e5cd83f21b8e5074ad292a2cf19df3ba31d94fbcecd4"},
- {file = "tree_sitter-0.21.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3fd34ed4cd5db445bc448361b5da46a2a781c648328dc5879d768f16a46771"},
- {file = "tree_sitter-0.21.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fabc7182f6083269ce3cfcad202fe01516aa80df64573b390af6cd853e8444a1"},
- {file = "tree_sitter-0.21.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f874c3f7d2a2faf5c91982dc7d88ff2a8f183a21fe475c29bee3009773b0558"},
- {file = "tree_sitter-0.21.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ee61ee3b7a4eedf9d8f1635c68ba4a6fa8c46929601fc48a907c6cfef0cfbcb2"},
- {file = "tree_sitter-0.21.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b7256c723642de1c05fbb776b27742204a2382e337af22f4d9e279d77df7aa2"},
- {file = "tree_sitter-0.21.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:669b3e5a52cb1e37d60c7b16cc2221c76520445bb4f12dd17fd7220217f5abf3"},
- {file = "tree_sitter-0.21.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2aa2a5099a9f667730ff26d57533cc893d766667f4d8a9877e76a9e74f48f0d3"},
- {file = "tree_sitter-0.21.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3e06ae2a517cf6f1abb682974f76fa760298e6d5a3ecf2cf140c70f898adf0"},
- {file = "tree_sitter-0.21.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af992dfe08b4fefcfcdb40548d0d26d5d2e0a0f2d833487372f3728cd0772b48"},
- {file = "tree_sitter-0.21.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c7cbab1dd9765138505c4a55e2aa857575bac4f1f8a8b0457744a4fefa1288e6"},
- {file = "tree_sitter-0.21.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1e66aeb457d1529370fcb0997ae5584c6879e0e662f1b11b2f295ea57e22f54"},
- {file = "tree_sitter-0.21.3-cp312-cp312-win_amd64.whl", hash = "sha256:013c750252dc3bd0e069d82e9658de35ed50eecf31c6586d0de7f942546824c5"},
- {file = "tree_sitter-0.21.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4986a8cb4acebd168474ec2e5db440e59c7888819b3449a43ce8b17ed0331b07"},
- {file = "tree_sitter-0.21.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e217fee2e7be7dbce4496caa3d1c466977d7e81277b677f954d3c90e3272ec2"},
- {file = "tree_sitter-0.21.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32a88afff4f2bc0f20632b0a2aa35fa9ae7d518f083409eca253518e0950929"},
- {file = "tree_sitter-0.21.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3652ac9e47cdddf213c5d5d6854194469097e62f7181c0a9aa8435449a163a9"},
- {file = "tree_sitter-0.21.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:60b4df3298ff467bc01e2c0f6c2fb43aca088038202304bf8e41edd9fa348f45"},
- {file = "tree_sitter-0.21.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:00e4d0c99dff595398ef5e88a1b1ddd53adb13233fb677c1fd8e497fb2361629"},
- {file = "tree_sitter-0.21.3-cp38-cp38-win_amd64.whl", hash = "sha256:50c91353a26946e4dd6779837ecaf8aa123aafa2d3209f261ab5280daf0962f5"},
- {file = "tree_sitter-0.21.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b17b8648b296ccc21a88d72ca054b809ee82d4b14483e419474e7216240ea278"},
- {file = "tree_sitter-0.21.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f2f057fd01d3a95cbce6794c6e9f6db3d376cb3bb14e5b0528d77f0ec21d6478"},
- {file = "tree_sitter-0.21.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:839759de30230ffd60687edbb119b31521d5ac016749358e5285816798bb804a"},
- {file = "tree_sitter-0.21.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df40aa29cb7e323898194246df7a03b9676955a0ac1f6bce06bc4903a70b5f7"},
- {file = "tree_sitter-0.21.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1d9be27dde007b569fa78ff9af5fe40d2532c998add9997a9729e348bb78fa59"},
- {file = "tree_sitter-0.21.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c4ac87735e6f98fe085244c7c020f0177d13d4c117db72ba041faa980d25d69d"},
- {file = "tree_sitter-0.21.3-cp39-cp39-win_amd64.whl", hash = "sha256:fbbd137f7d9a5309fb4cb82e2c3250ba101b0dd08a8abdce815661e6cf2cbc19"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6ca583dad4bd79d3053c310b9f7208cd597fd85f9947e4ab2294658bb5c11e35"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:94100e491a6a247aa4d14caf61230c171b6376c863039b6d9cd71255c2d815ec"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6bc1055b061c5055ec58f39ee9b2e9efb8e6e0ae970838af74da0afb811f0a"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:056dc04fb6b24293f8c5fec43c14e7e16ba2075b3009c643abf8c85edc4c7c3c"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a11ca1c0f736da42967586b568dff8a465ee148a986c15ebdc9382806e0ce871"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:041fa22b34250ea6eb313d33104d5303f79504cb259d374d691e38bbdc49145b"},
+ {file = "tree_sitter_javascript-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:eb28130cd2fb30d702d614cbf61ef44d1c7f6869e7d864a9cc17111e370be8f7"},
+ {file = "tree_sitter_javascript-0.23.1.tar.gz", hash = "sha256:b2059ce8b150162cda05a457ca3920450adbf915119c04b8c67b5241cd7fcfed"},
]
+[package.extras]
+core = ["tree-sitter (>=0.22,<1.0)"]
+
[[package]]
name = "tree-sitter-languages"
version = "1.10.2"
@@ -9614,6 +9634,69 @@ files = [
[package.dependencies]
tree-sitter = "*"
+[[package]]
+name = "tree-sitter-python"
+version = "0.23.6"
+description = "Python grammar for tree-sitter"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:28fbec8f74eeb2b30292d97715e60fac9ccf8a8091ce19b9d93e9b580ed280fb"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:680b710051b144fedf61c95197db0094f2245e82551bf7f0c501356333571f7a"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a9dcef55507b6567207e8ee0a6b053d0688019b47ff7f26edc1764b7f4dc0a4"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29dacdc0cd2f64e55e61d96c6906533ebb2791972bec988450c46cce60092f5d"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7e048733c36f564b379831689006801feb267d8194f9e793fbb395ef1723335d"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-win_amd64.whl", hash = "sha256:a24027248399fb41594b696f929f9956828ae7cc85596d9f775e6c239cd0c2be"},
+ {file = "tree_sitter_python-0.23.6-cp39-abi3-win_arm64.whl", hash = "sha256:71334371bd73d5fe080aed39fbff49ed8efb9506edebe16795b0c7567ed6a272"},
+ {file = "tree_sitter_python-0.23.6.tar.gz", hash = "sha256:354bfa0a2f9217431764a631516f85173e9711af2c13dbd796a8815acfe505d9"},
+]
+
+[package.extras]
+core = ["tree-sitter (>=0.22,<1.0)"]
+
+[[package]]
+name = "tree-sitter-ruby"
+version = "0.23.1"
+description = "Ruby grammar for tree-sitter"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:39f391322d2210843f07081182dbf00f8f69cfbfa4687b9575cac6d324bae443"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:aa4ee7433bd42fac22e2dad4a3c0f332292ecf482e610316828c711a0bb7f794"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b36813a56006b7569db7868f6b762caa3f4e419bd0f8cf9ccbb4abb1b6254c"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7bcd93972b4ca2803856d4fe0fbd04123ff29c4592bbb9f12a27528bd252341"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66c65d6c2a629783ca4ab2bab539bd6f271ce6f77cacb62845831e11665b5bd3"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:02e2c19ebefe29226c14aa63e11e291d990f5b5c20a99940ab6e7eda44e744e5"},
+ {file = "tree_sitter_ruby-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:ed042007e89f2cceeb1cbdd8b0caa68af1e2ce54c7eb2053ace760f90657ac9f"},
+ {file = "tree_sitter_ruby-0.23.1.tar.gz", hash = "sha256:886ed200bfd1f3ca7628bf1c9fefd42421bbdba70c627363abda67f662caa21e"},
+]
+
+[package.extras]
+core = ["tree-sitter (>=0.22,<1.0)"]
+
+[[package]]
+name = "tree-sitter-typescript"
+version = "0.23.2"
+description = "TypeScript and TSX grammars for tree-sitter"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9"},
+ {file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7"},
+ {file = "tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d"},
+]
+
+[package.extras]
+core = ["tree-sitter (>=0.23,<1.0)"]
+
[[package]]
name = "triton"
version = "3.1.0"
@@ -10670,4 +10753,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.1"
python-versions = "^3.12"
-content-hash = "e9ae47ca13de911290b2cbf57c81ec74b84663d915e252159e42707f5fa9ed5b"
+content-hash = "b30dd43f84e986adb7c2f2ea0420ce1d7be12027de453309489c9cc74ccd79cc"
diff --git a/pyproject.toml b/pyproject.toml
index 1958e11b6058..2866e6d5407d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ pathspec = "^0.12.1"
google-cloud-aiplatform = "*"
anthropic = {extras = ["vertex"], version = "*"}
grep-ast = "0.3.3"
-tree-sitter = "0.21.3"
+tree-sitter = "^0.24.0"
bashlex = "^0.18"
pyjwt = "^2.9.0"
dirhash = "*"
@@ -67,7 +67,7 @@ runloop-api-client = "0.23.0"
libtmux = ">=0.37,<0.40"
pygithub = "^2.5.0"
joblib = "*"
-openhands-aci = "^0.2.2"
+openhands-aci = "^0.2.3"
python-socketio = "^5.11.4"
redis = "^5.2.0"
sse-starlette = "^2.1.3"
diff --git a/tests/runtime/test_runtime_resource.py b/tests/runtime/test_runtime_resource.py
index 2873939f132d..950f37a3f3b2 100644
--- a/tests/runtime/test_runtime_resource.py
+++ b/tests/runtime/test_runtime_resource.py
@@ -36,78 +36,78 @@ def test_stress_docker_runtime(temp_dir, runtime_cls, repeat=1):
_close_test_runtime(runtime)
-def test_stress_docker_runtime_hit_memory_limits(temp_dir, runtime_cls):
- """Test runtime behavior under resource constraints."""
- runtime, config = _load_runtime(
- temp_dir,
- runtime_cls,
- docker_runtime_kwargs={
- 'cpu_period': 100000, # 100ms
- 'cpu_quota': 100000, # Can use 100ms out of each 100ms period (1 CPU)
- 'mem_limit': '4G', # 4 GB of memory
- 'memswap_limit': '0', # No swap
- 'mem_swappiness': 0, # Disable swapping
- 'oom_kill_disable': False, # Enable OOM killer
- },
- runtime_startup_env_vars={
- 'RUNTIME_MAX_MEMORY_GB': '3',
- },
- )
-
- action = CmdRunAction(
- command='sudo apt-get update && sudo apt-get install -y stress-ng'
- )
- logger.info(action, extra={'msg_type': 'ACTION'})
- obs = runtime.run_action(action)
- logger.info(obs, extra={'msg_type': 'OBSERVATION'})
- assert obs.exit_code == 0
-
- action = CmdRunAction(
- command='stress-ng --vm 1 --vm-bytes 6G --timeout 30s --metrics'
- )
- action.set_hard_timeout(120)
- logger.info(action, extra={'msg_type': 'ACTION'})
- obs = runtime.run_action(action)
- logger.info(obs, extra={'msg_type': 'OBSERVATION'})
- assert 'aborted early, out of system resources' in obs.content
- assert obs.exit_code == 3 # OOM killed!
-
- _close_test_runtime(runtime)
-
-
-def test_stress_docker_runtime_within_memory_limits(temp_dir, runtime_cls):
- """Test runtime behavior under resource constraints."""
- runtime, config = _load_runtime(
- temp_dir,
- runtime_cls,
- docker_runtime_kwargs={
- 'cpu_period': 100000, # 100ms
- 'cpu_quota': 100000, # Can use 100ms out of each 100ms period (1 CPU)
- 'mem_limit': '4G', # 4 GB of memory
- 'memswap_limit': '0', # No swap
- 'mem_swappiness': 0, # Disable swapping
- 'oom_kill_disable': False, # Enable OOM killer
- },
- runtime_startup_env_vars={
- 'RUNTIME_MAX_MEMORY_GB': '7',
- },
- )
-
- action = CmdRunAction(
- command='sudo apt-get update && sudo apt-get install -y stress-ng'
- )
- logger.info(action, extra={'msg_type': 'ACTION'})
- obs = runtime.run_action(action)
- logger.info(obs, extra={'msg_type': 'OBSERVATION'})
- assert obs.exit_code == 0
-
- action = CmdRunAction(
- command='stress-ng --vm 1 --vm-bytes 6G --timeout 30s --metrics'
- )
- action.set_hard_timeout(120)
- logger.info(action, extra={'msg_type': 'ACTION'})
- obs = runtime.run_action(action)
- logger.info(obs, extra={'msg_type': 'OBSERVATION'})
- assert obs.exit_code == 0
-
- _close_test_runtime(runtime)
+# def test_stress_docker_runtime_hit_memory_limits(temp_dir, runtime_cls):
+# """Test runtime behavior under resource constraints."""
+# runtime, config = _load_runtime(
+# temp_dir,
+# runtime_cls,
+# docker_runtime_kwargs={
+# 'cpu_period': 100000, # 100ms
+# 'cpu_quota': 100000, # Can use 100ms out of each 100ms period (1 CPU)
+# 'mem_limit': '4G', # 4 GB of memory
+# 'memswap_limit': '0', # No swap
+# 'mem_swappiness': 0, # Disable swapping
+# 'oom_kill_disable': False, # Enable OOM killer
+# },
+# runtime_startup_env_vars={
+# 'RUNTIME_MAX_MEMORY_GB': '3',
+# },
+# )
+
+# action = CmdRunAction(
+# command='sudo apt-get update && sudo apt-get install -y stress-ng'
+# )
+# logger.info(action, extra={'msg_type': 'ACTION'})
+# obs = runtime.run_action(action)
+# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
+# assert obs.exit_code == 0
+
+# action = CmdRunAction(
+# command='stress-ng --vm 1 --vm-bytes 6G --timeout 30s --metrics'
+# )
+# action.set_hard_timeout(120)
+# logger.info(action, extra={'msg_type': 'ACTION'})
+# obs = runtime.run_action(action)
+# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
+# assert 'aborted early, out of system resources' in obs.content
+# assert obs.exit_code == 3 # OOM killed!
+
+# _close_test_runtime(runtime)
+
+
+# def test_stress_docker_runtime_within_memory_limits(temp_dir, runtime_cls):
+# """Test runtime behavior under resource constraints."""
+# runtime, config = _load_runtime(
+# temp_dir,
+# runtime_cls,
+# docker_runtime_kwargs={
+# 'cpu_period': 100000, # 100ms
+# 'cpu_quota': 100000, # Can use 100ms out of each 100ms period (1 CPU)
+# 'mem_limit': '4G', # 4 GB of memory
+# 'memswap_limit': '0', # No swap
+# 'mem_swappiness': 0, # Disable swapping
+# 'oom_kill_disable': False, # Enable OOM killer
+# },
+# runtime_startup_env_vars={
+# 'RUNTIME_MAX_MEMORY_GB': '7',
+# },
+# )
+
+# action = CmdRunAction(
+# command='sudo apt-get update && sudo apt-get install -y stress-ng'
+# )
+# logger.info(action, extra={'msg_type': 'ACTION'})
+# obs = runtime.run_action(action)
+# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
+# assert obs.exit_code == 0
+
+# action = CmdRunAction(
+# command='stress-ng --vm 1 --vm-bytes 6G --timeout 30s --metrics'
+# )
+# action.set_hard_timeout(120)
+# logger.info(action, extra={'msg_type': 'ACTION'})
+# obs = runtime.run_action(action)
+# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
+# assert obs.exit_code == 0
+
+# _close_test_runtime(runtime)
diff --git a/tests/unit/test_file_conversation_store.py b/tests/unit/test_file_conversation_store.py
index 323f20de7780..80c391dacafb 100644
--- a/tests/unit/test_file_conversation_store.py
+++ b/tests/unit/test_file_conversation_store.py
@@ -40,3 +40,125 @@ async def test_load_int_user_id():
)
found = await store.get_metadata('some-conversation-id')
assert found.github_user_id == '12345'
+
+
+@pytest.mark.asyncio
+async def test_search_empty():
+ store = FileConversationStore(InMemoryFileStore({}))
+ result = await store.search()
+ assert len(result.results) == 0
+ assert result.next_page_id is None
+
+
+@pytest.mark.asyncio
+async def test_search_basic():
+ # Create test data with 3 conversations at different dates
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ 'sessions/conv1/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv1',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'First conversation',
+ 'created_at': '2025-01-16T19:51:04Z',
+ }
+ ),
+ 'sessions/conv2/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv2',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'Second conversation',
+ 'created_at': '2025-01-17T19:51:04Z',
+ }
+ ),
+ 'sessions/conv3/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv3',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'Third conversation',
+ 'created_at': '2025-01-15T19:51:04Z',
+ }
+ ),
+ }
+ )
+ )
+
+ result = await store.search()
+ assert len(result.results) == 3
+ # Should be sorted by date, newest first
+ assert result.results[0].conversation_id == 'conv2'
+ assert result.results[1].conversation_id == 'conv1'
+ assert result.results[2].conversation_id == 'conv3'
+ assert result.next_page_id is None
+
+
+@pytest.mark.asyncio
+async def test_search_pagination():
+ # Create test data with 5 conversations
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ f'sessions/conv{i}/metadata.json': json.dumps(
+ {
+ 'conversation_id': f'conv{i}',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': f'Conversation {i}',
+ 'created_at': f'2025-01-{15+i}T19:51:04Z',
+ }
+ )
+ for i in range(1, 6)
+ }
+ )
+ )
+
+ # Test with limit of 2
+ result = await store.search(limit=2)
+ assert len(result.results) == 2
+ assert result.results[0].conversation_id == 'conv5' # newest first
+ assert result.results[1].conversation_id == 'conv4'
+ assert result.next_page_id is not None
+
+ # Get next page using the next_page_id
+ result2 = await store.search(page_id=result.next_page_id, limit=2)
+ assert len(result2.results) == 2
+ assert result2.results[0].conversation_id == 'conv3'
+ assert result2.results[1].conversation_id == 'conv2'
+ assert result2.next_page_id is not None
+
+ # Get last page
+ result3 = await store.search(page_id=result2.next_page_id, limit=2)
+ assert len(result3.results) == 1
+ assert result3.results[0].conversation_id == 'conv1'
+ assert result3.next_page_id is None
+
+
+@pytest.mark.asyncio
+async def test_search_with_invalid_conversation():
+ # Test handling of invalid conversation data
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ 'sessions/conv1/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv1',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'Valid conversation',
+ 'created_at': '2025-01-16T19:51:04Z',
+ }
+ ),
+ 'sessions/conv2/metadata.json': 'invalid json', # Invalid conversation
+ }
+ )
+ )
+
+ result = await store.search()
+ # Should return only the valid conversation
+ assert len(result.results) == 1
+ assert result.results[0].conversation_id == 'conv1'
+ assert result.next_page_id is None
diff --git a/tests/unit/test_llm.py b/tests/unit/test_llm.py
index 98783c050d0a..1bfee8550698 100644
--- a/tests/unit/test_llm.py
+++ b/tests/unit/test_llm.py
@@ -3,10 +3,7 @@
import pytest
from litellm.exceptions import (
- APIConnectionError,
- InternalServerError,
RateLimitError,
- ServiceUnavailableError,
)
from openhands.core.config import LLMConfig
@@ -187,21 +184,6 @@ def test_completion_with_mocked_logger(
@pytest.mark.parametrize(
'exception_class,extra_args,expected_retries',
[
- (
- APIConnectionError,
- {'llm_provider': 'test_provider', 'model': 'test_model'},
- 2,
- ),
- (
- InternalServerError,
- {'llm_provider': 'test_provider', 'model': 'test_model'},
- 2,
- ),
- (
- ServiceUnavailableError,
- {'llm_provider': 'test_provider', 'model': 'test_model'},
- 2,
- ),
(RateLimitError, {'llm_provider': 'test_provider', 'model': 'test_model'}, 2),
],
)
@@ -254,22 +236,6 @@ def test_completion_rate_limit_wait_time(mock_litellm_completion, default_config
), f'Expected wait time between {default_config.retry_min_wait} and {default_config.retry_max_wait} seconds, but got {wait_time}'
-@patch('openhands.llm.llm.litellm_completion')
-def test_completion_exhausts_retries(mock_litellm_completion, default_config):
- mock_litellm_completion.side_effect = APIConnectionError(
- 'Persistent error', llm_provider='test_provider', model='test_model'
- )
-
- llm = LLM(config=default_config)
- with pytest.raises(APIConnectionError):
- llm.completion(
- messages=[{'role': 'user', 'content': 'Hello!'}],
- stream=False,
- )
-
- assert mock_litellm_completion.call_count == llm.config.num_retries
-
-
@patch('openhands.llm.llm.litellm_completion')
def test_completion_operation_cancelled(mock_litellm_completion, default_config):
mock_litellm_completion.side_effect = OperationCancelled('Operation cancelled')
diff --git a/tests/unit/test_llm_config.py b/tests/unit/test_llm_config.py
index 342112a44316..fd11deb98580 100644
--- a/tests/unit/test_llm_config.py
+++ b/tests/unit/test_llm_config.py
@@ -188,7 +188,7 @@ def test_load_from_toml_llm_missing_generic(
assert custom_only.model == 'custom-only-model'
assert custom_only.api_key.get_secret_value() == 'custom-only-api-key'
assert custom_only.embedding_model == 'local' # default value
- assert custom_only.num_retries == 8 # default value
+ assert custom_only.num_retries == 4 # default value
def test_load_from_toml_llm_invalid_config(
diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py
new file mode 100644
index 000000000000..78c5661bc823
--- /dev/null
+++ b/tests/unit/test_logger.py
@@ -0,0 +1,117 @@
+import logging
+from unittest.mock import patch
+
+from openhands.core.logger import SensitiveDataFilter
+
+
+@patch.dict(
+ 'os.environ',
+ {
+ 'API_SECRET': 'super-secret-123',
+ 'AUTH_TOKEN': 'auth-token-456',
+ 'NORMAL_VAR': 'normal-value',
+ },
+ clear=True,
+)
+def test_sensitive_data_filter_basic():
+ # Create a filter instance
+ filter = SensitiveDataFilter()
+
+ # Create a log record with sensitive data
+ record = logging.LogRecord(
+ name='test_logger',
+ level=logging.INFO,
+ pathname='test.py',
+ lineno=1,
+ msg='API Secret: super-secret-123, Token: auth-token-456, Normal: normal-value',
+ args=(),
+ exc_info=None,
+ )
+
+ # Apply the filter
+ filter.filter(record)
+
+ # Check that sensitive data is masked but normal data isn't
+ assert '******' in record.msg
+ assert 'super-secret-123' not in record.msg
+ assert 'auth-token-456' not in record.msg
+ assert 'normal-value' in record.msg
+
+
+@patch.dict('os.environ', {}, clear=True)
+def test_sensitive_data_filter_empty_values():
+ # Test with empty environment variables
+ filter = SensitiveDataFilter()
+
+ record = logging.LogRecord(
+ name='test_logger',
+ level=logging.INFO,
+ pathname='test.py',
+ lineno=1,
+ msg='No sensitive data here',
+ args=(),
+ exc_info=None,
+ )
+
+ # Apply the filter
+ filter.filter(record)
+
+ # Message should remain unchanged
+ assert record.msg == 'No sensitive data here'
+
+
+@patch.dict('os.environ', {'API_KEY': 'secret-key-789'}, clear=True)
+def test_sensitive_data_filter_multiple_occurrences():
+ # Test with multiple occurrences of the same sensitive data
+ filter = SensitiveDataFilter()
+
+ # Create a message with multiple occurrences of the same sensitive data
+ record = logging.LogRecord(
+ name='test_logger',
+ level=logging.INFO,
+ pathname='test.py',
+ lineno=1,
+ msg='Key1: secret-key-789, Key2: secret-key-789',
+ args=(),
+ exc_info=None,
+ )
+
+ # Apply the filter
+ filter.filter(record)
+
+ # Check that all occurrences are masked
+ assert record.msg.count('******') == 2
+ assert 'secret-key-789' not in record.msg
+
+
+@patch.dict(
+ 'os.environ',
+ {
+ 'secret_KEY': 'secret-value-1',
+ 'API_secret': 'secret-value-2',
+ 'TOKEN_code': 'secret-value-3',
+ },
+ clear=True,
+)
+def test_sensitive_data_filter_case_sensitivity():
+ # Test with different case variations in environment variable names
+ filter = SensitiveDataFilter()
+
+ record = logging.LogRecord(
+ name='test_logger',
+ level=logging.INFO,
+ pathname='test.py',
+ lineno=1,
+ msg='Values: secret-value-1, secret-value-2, secret-value-3',
+ args=(),
+ exc_info=None,
+ )
+
+ # Apply the filter
+ filter.filter(record)
+
+ # Check that all sensitive values are masked regardless of case
+ assert 'secret-value-1' not in record.msg
+ assert 'secret-value-2' not in record.msg
+ assert 'secret-value-3' not in record.msg
+ assert record.msg.count('******') == 3
diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py
index 5f5ef0b57974..e225313a0710 100644
--- a/tests/unit/test_logging.py
+++ b/tests/unit/test_logging.py
@@ -1,5 +1,6 @@
import logging
from io import StringIO
+from unittest.mock import patch
import pytest
@@ -26,7 +27,6 @@ def test_openai_api_key_masking(test_handler):
message = f"OpenAI API key: api_key='{api_key}'and there's some stuff here"
logger.info(message)
log_output = stream.getvalue()
- assert "api_key='******'" in log_output
assert api_key not in log_output
@@ -36,7 +36,6 @@ def test_azure_api_key_masking(test_handler):
message = f"Azure API key: api_key='{api_key}' and chatty chat with ' and \" and '"
logger.info(message)
log_output = stream.getvalue()
- assert "api_key='******'" in log_output
assert api_key not in log_output
@@ -46,7 +45,6 @@ def test_google_vertex_api_key_masking(test_handler):
message = f"Google Vertex API key: api_key='{api_key}' or not"
logger.info(message)
log_output = stream.getvalue()
- assert "api_key='******'" in log_output
assert api_key not in log_output
@@ -56,7 +54,6 @@ def test_anthropic_api_key_masking(test_handler):
message = f"Anthropic API key: api_key='{api_key}' and there's some 'stuff' here"
logger.info(message)
log_output = stream.getvalue()
- assert "api_key='******'" in log_output
assert api_key not in log_output
@@ -69,9 +66,6 @@ def test_llm_config_attributes_masking(test_handler):
)
logger.info(f'LLM Config: {llm_config}')
log_output = stream.getvalue()
- assert "api_key='******'" in log_output
- assert "aws_access_key_id='******'" in log_output
- assert "aws_secret_access_key='******'" in log_output
assert 'sk-abc123' not in log_output
assert 'AKIAIOSFODNN7EXAMPLE' not in log_output
assert 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' not in log_output
@@ -82,7 +76,6 @@ def test_app_config_attributes_masking(test_handler):
app_config = AppConfig(e2b_api_key='e2b-xyz789')
logger.info(f'App Config: {app_config}')
log_output = stream.getvalue()
- assert "e2b_api_key='******'" in log_output
assert 'github_token' not in log_output
assert 'e2b-xyz789' not in log_output
assert 'ghp_abcdefghijklmnopqrstuvwxyz' not in log_output
@@ -90,7 +83,7 @@ def test_app_config_attributes_masking(test_handler):
def test_sensitive_env_vars_masking(test_handler):
logger, stream = test_handler
- sensitive_data = {
+ environ = {
'API_KEY': 'API_KEY_VALUE',
'AWS_ACCESS_KEY_ID': 'AWS_ACCESS_KEY_ID_VALUE',
'AWS_SECRET_ACCESS_KEY': 'AWS_SECRET_ACCESS_KEY_VALUE',
@@ -99,31 +92,29 @@ def test_sensitive_env_vars_masking(test_handler):
'JWT_SECRET': 'JWT_SECRET_VALUE',
}
- log_message = ' '.join(
- f"{attr}='{value}'" for attr, value in sensitive_data.items()
- )
- logger.info(log_message)
+ with patch.dict('openhands.core.logger.os.environ', environ, clear=True):
+ log_message = ' '.join(f"{attr}='{value}'" for attr, value in environ.items())
+ logger.info(log_message)
- log_output = stream.getvalue()
- for attr, value in sensitive_data.items():
- assert f"{attr}='******'" in log_output
- assert value not in log_output
+ log_output = stream.getvalue()
+ for _, value in environ.items():
+ assert value not in log_output
def test_special_cases_masking(test_handler):
logger, stream = test_handler
- sensitive_data = {
+ environ = {
'LLM_API_KEY': 'LLM_API_KEY_VALUE',
'SANDBOX_ENV_GITHUB_TOKEN': 'SANDBOX_ENV_GITHUB_TOKEN_VALUE',
}
- log_message = ' '.join(
- f"{attr}={value} with no single quotes' and something"
- for attr, value in sensitive_data.items()
- )
- logger.info(log_message)
+ with patch.dict('openhands.core.logger.os.environ', environ, clear=True):
+ log_message = ' '.join(
+ f"{attr}={value} with no single quotes' and something"
+ for attr, value in environ.items()
+ )
+ logger.info(log_message)
- log_output = stream.getvalue()
- for attr, value in sensitive_data.items():
- assert f"{attr}='******'" in log_output
- assert value not in log_output
+ log_output = stream.getvalue()
+ for attr, value in environ.items():
+ assert value not in log_output
diff --git a/tests/unit/test_search_utils.py b/tests/unit/test_search_utils.py
index 3e68dbfab79f..d9e2830b6bf1 100644
--- a/tests/unit/test_search_utils.py
+++ b/tests/unit/test_search_utils.py
@@ -1,4 +1,10 @@
-from openhands.utils.search_utils import offset_to_page_id, page_id_to_offset
+import json
+
+import pytest
+
+from openhands.storage.conversation.file_conversation_store import FileConversationStore
+from openhands.storage.memory import InMemoryFileStore
+from openhands.utils.search_utils import iterate, offset_to_page_id, page_id_to_offset
def test_offset_to_page_id():
@@ -22,3 +28,110 @@ def test_bidirectional_conversion():
for offset in test_offsets:
page_id = offset_to_page_id(offset, True)
assert page_id_to_offset(page_id) == offset
+
+
+@pytest.mark.asyncio
+async def test_iterate_empty():
+ store = FileConversationStore(InMemoryFileStore({}))
+ results = []
+ async for result in iterate(store.search):
+ results.append(result)
+ assert len(results) == 0
+
+
+@pytest.mark.asyncio
+async def test_iterate_single_page():
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ 'sessions/conv1/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv1',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'First conversation',
+ 'created_at': '2025-01-16T19:51:04Z',
+ }
+ ),
+ 'sessions/conv2/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv2',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'Second conversation',
+ 'created_at': '2025-01-17T19:51:04Z',
+ }
+ ),
+ }
+ )
+ )
+
+ results = []
+ async for result in iterate(store.search):
+ results.append(result)
+
+ assert len(results) == 2
+ assert results[0].conversation_id == 'conv2' # newest first
+ assert results[1].conversation_id == 'conv1'
+
+
+@pytest.mark.asyncio
+async def test_iterate_multiple_pages():
+ # Create test data with 5 conversations
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ f'sessions/conv{i}/metadata.json': json.dumps(
+ {
+ 'conversation_id': f'conv{i}',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': f'Conversation {i}',
+ 'created_at': f'2025-01-{15+i}T19:51:04Z',
+ }
+ )
+ for i in range(1, 6)
+ }
+ )
+ )
+
+ results = []
+ async for result in iterate(store.search, limit=2):
+ results.append(result)
+
+ assert len(results) == 5
+ # Should be sorted by date, newest first
+ assert [r.conversation_id for r in results] == [
+ 'conv5',
+ 'conv4',
+ 'conv3',
+ 'conv2',
+ 'conv1',
+ ]
+
+
+@pytest.mark.asyncio
+async def test_iterate_with_invalid_conversation():
+ store = FileConversationStore(
+ InMemoryFileStore(
+ {
+ 'sessions/conv1/metadata.json': json.dumps(
+ {
+ 'conversation_id': 'conv1',
+ 'github_user_id': '123',
+ 'selected_repository': 'repo1',
+ 'title': 'Valid conversation',
+ 'created_at': '2025-01-16T19:51:04Z',
+ }
+ ),
+ 'sessions/conv2/metadata.json': 'invalid json', # Invalid conversation
+ }
+ )
+ )
+
+ results = []
+ async for result in iterate(store.search):
+ results.append(result)
+
+ assert len(results) == 1
+ assert results[0].conversation_id == 'conv1'