diff --git a/frontend/README.md b/frontend/README.md
index f11e38bca7..0ab14b8528 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -20,7 +20,7 @@ This is the frontend of the OpenHands project. It is a React application that pr
### Prerequisites
-- Node.js 20.x or later
+- Node.js 22.12.x or later
- `npm`, `bun`, or any other package manager that supports the `package.json` file
### Installation
diff --git a/frontend/__tests__/components/chat/error-message-banner.test.tsx b/frontend/__tests__/components/chat/error-message-banner.test.tsx
new file mode 100644
index 0000000000..f1ea8eaa17
--- /dev/null
+++ b/frontend/__tests__/components/chat/error-message-banner.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { ErrorMessageBanner } from "#/components/features/chat/error-message-banner";
+
+describe("ErrorMessageBanner", () => {
+ it("calls onDismiss when the close button is clicked", async () => {
+ const user = userEvent.setup();
+ const onDismiss = vi.fn();
+
+ render(
+ ,
+ );
+
+ await user.click(screen.getByLabelText("BUTTON$CLOSE"));
+ expect(onDismiss).toHaveBeenCalledTimes(1);
+ });
+
+ it("shows a View More / View Less toggle for long messages", async () => {
+ const user = userEvent.setup();
+ const longMessage = "a".repeat(400);
+
+ render();
+
+ const toggle = screen.getByTestId("error-message-banner-toggle");
+ expect(toggle).toHaveTextContent("COMMON$VIEW_MORE");
+
+ await user.click(toggle);
+ expect(toggle).toHaveTextContent("COMMON$VIEW_LESS");
+ });
+});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 99887b5e20..b476f24e99 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -89,7 +89,7 @@
"vitest": "^4.0.14"
},
"engines": {
- "node": ">=22.0.0"
+ "node": ">=22.12.0"
}
},
"node_modules/@acemir/cssom": {
@@ -192,6 +192,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -731,6 +732,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -777,6 +779,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -2345,6 +2348,7 @@
"version": "2.4.25",
"resolved": "https://registry.npmjs.org/@heroui/system/-/system-2.4.25.tgz",
"integrity": "sha512-F6UUoGTQ+Qas5wYkCzLjXE7u74Z9ygO0u0+dkTW7zCaY7ds65CcmvZ/ahKz2ES3Tk6TNks1MJSyaQ9rFLs8AqA==",
+ "peer": true,
"dependencies": {
"@heroui/react-utils": "2.1.14",
"@heroui/system-rsc": "2.3.21",
@@ -2424,6 +2428,7 @@
"version": "2.4.25",
"resolved": "https://registry.npmjs.org/@heroui/theme/-/theme-2.4.25.tgz",
"integrity": "sha512-nTptYhO1V9rMoh9SJDnMfaSmFuoXvbem1UuwgHcraRtqy/TIVBPqv26JEGzSoUCL194TDGOJpqrpMuab/PdXcw==",
+ "peer": true,
"dependencies": {
"@heroui/shared-utils": "2.1.12",
"color": "^4.2.3",
@@ -5426,6 +5431,7 @@
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -5884,6 +5890,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -6064,6 +6071,14 @@
"undici-types": "~7.16.0"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/prismjs": {
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
@@ -6084,6 +6099,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -6124,6 +6140,7 @@
"integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.18.0",
@@ -6181,6 +6198,7 @@
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.18.0",
@@ -6719,6 +6737,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7113,6 +7132,52 @@
"@babel/types": "^7.23.6"
}
},
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -7253,6 +7318,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -7941,7 +8007,8 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -8659,6 +8726,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -8782,6 +8850,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -8862,6 +8931,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -8953,6 +9023,7 @@
"integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"aria-query": "^5.3.2",
"array-includes": "^3.1.8",
@@ -9047,6 +9118,7 @@
"integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"array-includes": "^3.1.8",
"array.prototype.findlast": "^1.2.5",
@@ -9080,6 +9152,7 @@
"integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -9347,6 +9420,7 @@
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -10341,6 +10415,7 @@
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4"
},
@@ -11102,6 +11177,7 @@
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6",
@@ -12808,6 +12884,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -12900,6 +12977,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@inquirer/confirm": "^5.0.0",
"@mswjs/interceptors": "^0.40.0",
@@ -13624,6 +13702,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -13701,6 +13780,7 @@
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -13924,6 +14004,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13972,6 +14053,7 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -14084,6 +14166,7 @@
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
+ "peer": true,
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@@ -14445,6 +14528,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -15420,6 +15504,7 @@
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@@ -15546,6 +15631,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -15847,6 +15933,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16151,6 +16238,7 @@
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -16321,6 +16409,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
diff --git a/frontend/package.json b/frontend/package.json
index 30a60413c2..fa1ca4a09b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
- "node": ">=22.0.0"
+ "node": ">=22.12.0"
},
"dependencies": {
"@heroui/react": "2.8.7",
@@ -121,7 +121,7 @@
},
"packageManager": "npm@10.5.0",
"volta": {
- "node": "22.0.0"
+ "node": "22.12.0"
},
"msw": {
"workerDirectory": [
diff --git a/frontend/src/components/features/chat/chat-interface.tsx b/frontend/src/components/features/chat/chat-interface.tsx
index d57b5acc3e..d1a431e2ed 100644
--- a/frontend/src/components/features/chat/chat-interface.tsx
+++ b/frontend/src/components/features/chat/chat-interface.tsx
@@ -66,7 +66,7 @@ export function ChatInterface() {
const posthog = usePostHog();
const { setMessageToSend } = useConversationStore();
const { data: conversation } = useActiveConversation();
- const { errorMessage } = useErrorMessageStore();
+ const { errorMessage, removeErrorMessage } = useErrorMessageStore();
const { isLoadingMessages } = useWsClient();
const { isTask, taskStatus, taskDetail } = useTaskPolling();
const conversationWebSocket = useConversationWebSocket();
@@ -342,7 +342,12 @@ export function ChatInterface() {
{!hitBottom && }
- {errorMessage && }
+ {errorMessage && (
+
+ )}
diff --git a/frontend/src/components/features/chat/error-message-banner.tsx b/frontend/src/components/features/chat/error-message-banner.tsx
index 36876ab7e3..85086791d2 100644
--- a/frontend/src/components/features/chat/error-message-banner.tsx
+++ b/frontend/src/components/features/chat/error-message-banner.tsx
@@ -1,30 +1,87 @@
-import { Trans } from "react-i18next";
+import React from "react";
+import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router";
-import i18n from "#/i18n";
+import { X } from "lucide-react";
+import { I18nKey } from "#/i18n/declaration";
+import { cn } from "#/utils/utils";
interface ErrorMessageBannerProps {
message: string;
+ onDismiss?: () => void;
}
-export function ErrorMessageBanner({ message }: ErrorMessageBannerProps) {
+const DEFAULT_MAX_COLLAPSED_CHARS = 220;
+
+export function ErrorMessageBanner({
+ message,
+ onDismiss,
+}: ErrorMessageBannerProps) {
+ const { t, i18n } = useTranslation();
+ const [isExpanded, setIsExpanded] = React.useState(false);
+
+ const isI18nKey = i18n.exists(message);
+ const displayTextForLength = isI18nKey ? String(t(message)) : message;
+ const shouldShowToggle =
+ displayTextForLength.length > DEFAULT_MAX_COLLAPSED_CHARS;
+
+ const isCollapsed = shouldShowToggle && !isExpanded;
+
return (
-
- {i18n.exists(message) ? (
-
- link
-
- ),
- }}
- />
- ) : (
- message
+
+
+
+ {isI18nKey ? (
+
+ link
+
+ ),
+ }}
+ />
+ ) : (
+ message
+ )}
+
+
+ {shouldShowToggle && (
+
+ )}
+
+
+ {onDismiss && (
+
)}
);
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index e75428e6d6..4c721f9ff9 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -43,7 +43,6 @@ export default defineConfig(({ mode }) => {
"i18next-browser-languagedetector",
"react-i18next",
"axios",
- "date-fns",
"@uidotdev/usehooks",
"react-icons/fa6",
"react-icons/fa",
@@ -51,8 +50,6 @@ export default defineConfig(({ mode }) => {
"tailwind-merge",
"@heroui/react",
"lucide-react",
- "react-select",
- "react-select/async",
"@microlink/react-json-view",
"socket.io-client",
// These are discovered when launching conversations: