Compare commits

..

1 Commits

Author SHA1 Message Date
mamoodi
c97d66131d Release 1.2.0 2026-01-15 10:08:32 -05:00
155 changed files with 391 additions and 857 deletions

View File

@@ -161,7 +161,7 @@ poetry run pytest ./tests/unit/test_*.py
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/openhands/runtime:1.1-nikolaik`
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/openhands/runtime:1.2-nikolaik`
## Develop inside Docker container

View File

@@ -12,7 +12,7 @@ services:
- SANDBOX_API_HOSTNAME=host.docker.internal
- DOCKER_HOST_ADDR=host.docker.internal
#
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/openhands/runtime:1.1-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/openhands/runtime:1.2-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:

View File

@@ -7,7 +7,7 @@ services:
image: openhands:latest
container_name: openhands-app-${DATE:-}
environment:
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.openhands.dev/openhands/runtime:1.1-nikolaik}
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.openhands.dev/openhands/runtime:1.2-nikolaik}
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:

View File

@@ -29,9 +29,7 @@ class ResolverUserContext(UserContext):
return UserInfo(id=user_id)
async def get_authenticated_git_url(
self, repository: str, is_optional: bool = False
) -> str:
async def get_authenticated_git_url(self, repository: str) -> str:
# This would need to be implemented based on the git provider tokens
# For now, return a basic HTTPS URL
return f'https://github.com/{repository}.git'

125
enterprise/poetry.lock generated
View File

@@ -1178,7 +1178,7 @@ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"", dev = "os_name == \"nt\"", test = "platform_system == \"Windows\" or sys_platform == \"win32\""}
markers = {main = "platform_system == \"Windows\" or os_name == \"nt\" or sys_platform == \"win32\"", dev = "os_name == \"nt\"", test = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "comm"
@@ -2264,14 +2264,14 @@ files = [
[[package]]
name = "filelock"
version = "3.20.3"
version = "3.19.1"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.10"
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"},
{file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"},
{file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"},
{file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"},
]
[[package]]
@@ -5858,14 +5858,14 @@ llama = ["llama-index (>=0.12.29,<0.13.0)", "llama-index-core (>=0.12.29,<0.13.0
[[package]]
name = "openhands-agent-server"
version = "1.8.2"
version = "1.8.1"
description = "OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent"
optional = false
python-versions = ">=3.12"
groups = ["main"]
files = [
{file = "openhands_agent_server-1.8.2-py3-none-any.whl", hash = "sha256:e9abb2e0fe970715537d0e0fc1aea3dd64bb9e8b531f70cb72b3d4e486aaa46a"},
{file = "openhands_agent_server-1.8.2.tar.gz", hash = "sha256:43db2371ee84b100ac921396338dee74359fceeb5c9400c90530bcc5730144c3"},
{file = "openhands_agent_server-1.8.1-py3-none-any.whl", hash = "sha256:c0dfe620184633a173094ffaa77b0d13124ea7bf84e7b534b1641e5fc5fd0256"},
{file = "openhands_agent_server-1.8.1.tar.gz", hash = "sha256:08adfe26d867ff0cb0c1e87bb0ad6e058c9a97374964ba6a9860ea35d32764a0"},
]
[package.dependencies]
@@ -5891,89 +5891,89 @@ files = []
develop = true
[package.dependencies]
aiohttp = ">=3.9,<3.11.13 || >3.11.13"
aiohttp = ">=3.9.0,!=3.11.13"
anthropic = {version = "*", extras = ["vertex"]}
anyio = "4.9"
asyncpg = ">=0.30"
bashlex = ">=0.18"
anyio = "4.9.0"
asyncpg = "^0.30.0"
bashlex = "^0.18"
boto3 = "*"
browsergym-core = "0.13.3"
deprecated = "*"
deprecation = ">=2.1"
deprecation = "^2.1.0"
dirhash = "*"
docker = "*"
fastapi = "*"
fastmcp = ">=2.12.4"
google-api-python-client = ">=2.164"
fastmcp = "^2.12.4"
google-api-python-client = "^2.164.0"
google-auth-httplib2 = "*"
google-auth-oauthlib = "*"
google-cloud-aiplatform = "*"
google-genai = "*"
html2text = "*"
httpx-aiohttp = ">=0.1.8"
ipywidgets = ">=8.1.5"
jinja2 = ">=3.1.6"
httpx-aiohttp = "^0.1.8"
ipywidgets = "^8.1.5"
jinja2 = "^3.1.6"
joblib = "*"
json-repair = "*"
jupyter-kernel-gateway = "*"
kubernetes = ">=33.1"
jupyter_kernel_gateway = "*"
kubernetes = "^33.1.0"
libtmux = ">=0.46.2"
litellm = ">=1.74.3"
lmnr = ">=0.7.20"
memory-profiler = ">=0.61"
litellm = ">=1.74.3, !=1.64.4, !=1.67.*"
lmnr = "^0.7.20"
memory-profiler = "^0.61.0"
numpy = "*"
openai = "2.8"
openai = "2.8.0"
openhands-aci = "0.3.2"
openhands-agent-server = "1.8.2"
openhands-sdk = "1.8.2"
openhands-tools = "1.8.2"
opentelemetry-api = ">=1.33.1"
opentelemetry-exporter-otlp-proto-grpc = ">=1.33.1"
pathspec = ">=0.12.1"
openhands-agent-server = "1.8.1"
openhands-sdk = "1.8.1"
openhands-tools = "1.8.1"
opentelemetry-api = "^1.33.1"
opentelemetry-exporter-otlp-proto-grpc = "^1.33.1"
pathspec = "^0.12.1"
pexpect = "*"
pg8000 = ">=1.31.5"
pillow = ">=11.3"
playwright = ">=1.55"
poetry = ">=2.1.2"
prompt-toolkit = ">=3.0.50"
protobuf = ">=5,<6"
pg8000 = "^1.31.5"
pillow = "^11.3.0"
playwright = "^1.55.0"
poetry = "^2.1.2"
prompt-toolkit = "^3.0.50"
protobuf = "^5.0.0,<6.0.0"
psutil = "*"
pybase62 = ">=1"
pygithub = ">=2.5"
pyjwt = ">=2.9"
pybase62 = "^1.0.0"
pygithub = "^2.5.0"
pyjwt = "^2.9.0"
pylatexenc = "*"
pypdf = ">=6"
pypdf = "^6.0.0"
python-docx = "*"
python-dotenv = "*"
python-frontmatter = ">=1.1"
python-frontmatter = "^1.1.0"
python-jose = {version = ">=3.3", extras = ["cryptography"]}
python-json-logger = ">=3.2.1"
python-json-logger = "^3.2.1"
python-multipart = "*"
python-pptx = "*"
python-socketio = ">=5.11.4"
python-socketio = "^5.11.4"
pythonnet = "*"
pyyaml = ">=6.0.2"
qtconsole = ">=5.6.1"
rapidfuzz = ">=3.9"
redis = ">=5.2,<7"
requests = ">=2.32.5"
pyyaml = "^6.0.2"
qtconsole = "^5.6.1"
rapidfuzz = "^3.9.0"
redis = ">=5.2,<7.0"
requests = "^2.32.5"
setuptools = ">=78.1.1"
shellingham = ">=1.5.4"
sqlalchemy = {version = ">=2.0.40", extras = ["asyncio"]}
sse-starlette = ">=3.0.2"
starlette = ">=0.48"
tenacity = ">=8.5,<10"
shellingham = "^1.5.4"
sqlalchemy = {version = "^2.0.40", extras = ["asyncio"]}
sse-starlette = "^3.0.2"
starlette = "^0.48.0"
tenacity = ">=8.5,<10.0"
termcolor = "*"
toml = "*"
tornado = ">=6.5"
types-toml = "*"
urllib3 = ">=2.6.3"
urllib3 = "^2.6.3"
uvicorn = "*"
whatthepatch = ">=1.0.6"
whatthepatch = "^1.0.6"
zope-interface = "7.2"
[package.extras]
third-party-runtimes = ["daytona (==0.24.2)", "e2b-code-interpreter (>=2)", "modal (>=0.66.26,<1.2)", "runloop-api-client (==0.50)"]
third-party-runtimes = ["daytona (==0.24.2)", "e2b-code-interpreter (>=2.0.0,<3.0.0)", "modal (>=0.66.26,<1.2.0)", "runloop-api-client (==0.50.0)"]
[package.source]
type = "directory"
@@ -5981,20 +5981,19 @@ url = ".."
[[package]]
name = "openhands-sdk"
version = "1.8.2"
version = "1.8.1"
description = "OpenHands SDK - Core functionality for building AI agents"
optional = false
python-versions = ">=3.12"
groups = ["main"]
files = [
{file = "openhands_sdk-1.8.2-py3-none-any.whl", hash = "sha256:b4fad9581865ce222a3e6722384e4df56113db01bd34c2d2d408dfd9695365c0"},
{file = "openhands_sdk-1.8.2.tar.gz", hash = "sha256:5bfb17c8b9515210d121249deb1f3d0dc407c3737edc55b5e73330b4571d61e3"},
{file = "openhands_sdk-1.8.1-py3-none-any.whl", hash = "sha256:133275f56321585c016b4718d56c8fc7bb834f4ef7cab1ef66b0c71c49d47d1d"},
{file = "openhands_sdk-1.8.1.tar.gz", hash = "sha256:9e2baa6c512ac4c2bc1c2c0bf8b1dbdb0267d794a8b86b7306a4656fc0cb8b0b"},
]
[package.dependencies]
deprecation = ">=2.1.0"
fastmcp = ">=2.11.3"
filelock = ">=3.20.1"
httpx = ">=0.27.0"
litellm = ">=1.80.10"
lmnr = ">=0.7.24"
@@ -6009,14 +6008,14 @@ boto3 = ["boto3 (>=1.35.0)"]
[[package]]
name = "openhands-tools"
version = "1.8.2"
version = "1.8.1"
description = "OpenHands Tools - Runtime tools for AI agents"
optional = false
python-versions = ">=3.12"
groups = ["main"]
files = [
{file = "openhands_tools-1.8.2-py3-none-any.whl", hash = "sha256:283f0c1fdd316914559cd16ade792383715478a8f5a73f7166daffc34bf9e5af"},
{file = "openhands_tools-1.8.2.tar.gz", hash = "sha256:eae416e3867f7cb595129a33a4b9237886c4b8a075d2bc7618da55963f2747d5"},
{file = "openhands_tools-1.8.1-py3-none-any.whl", hash = "sha256:9404b17edb8960d4af3a4439e6f68e37c92c59d0705f13096e4a8ff9b6ffc472"},
{file = "openhands_tools-1.8.1.tar.gz", hash = "sha256:e59fcd9ca3baa6266e92020646c4c5f5266f57761f434770cf0cd458b1a33cb0"},
]
[package.dependencies]

View File

@@ -39,8 +39,6 @@ ROLE_CHECK_ENABLED = os.getenv('ROLE_CHECK_ENABLED', 'false').lower() in (
'on',
)
DUPLICATE_EMAIL_CHECK = os.getenv('DUPLICATE_EMAIL_CHECK', 'true') in ('1', 'true')
# reCAPTCHA Enterprise
RECAPTCHA_PROJECT_ID = os.getenv('RECAPTCHA_PROJECT_ID', '').strip()
RECAPTCHA_SITE_KEY = os.getenv('RECAPTCHA_SITE_KEY', '').strip()

View File

@@ -19,7 +19,6 @@ from keycloak.exceptions import (
from server.auth.constants import (
BITBUCKET_APP_CLIENT_ID,
BITBUCKET_APP_CLIENT_SECRET,
DUPLICATE_EMAIL_CHECK,
GITHUB_APP_CLIENT_ID,
GITHUB_APP_CLIENT_SECRET,
GITLAB_APP_CLIENT_ID,
@@ -647,10 +646,6 @@ class TokenManager:
if not email:
return False
# We have the option to skip the duplicate email check in test environments
if not DUPLICATE_EMAIL_CHECK:
return False
base_email = extract_base_email(email)
if not base_email:
logger.warning(f'Could not extract base email from: {email}')

View File

@@ -20,7 +20,7 @@ This is the frontend of the OpenHands project. It is a React application that pr
### Prerequisites
- Node.js 22.12.x or later
- Node.js 20.x or later
- `npm`, `bun`, or any other package manager that supports the `package.json` file
### Installation

View File

@@ -1,34 +0,0 @@
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(
<ErrorMessageBanner
message="Something went wrong"
onDismiss={onDismiss}
/>,
);
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(<ErrorMessageBanner message={longMessage} />);
const toggle = screen.getByTestId("error-message-banner-toggle");
expect(toggle).toHaveTextContent("COMMON$VIEW_MORE");
await user.click(toggle);
expect(toggle).toHaveTextContent("COMMON$VIEW_LESS");
});
});

View File

@@ -0,0 +1,82 @@
import { describe, it, expect, afterEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter, Route, Routes } from "react-router";
import { ConversationTabsContextMenu } from "#/components/features/conversation/conversation-tabs/conversation-tabs-context-menu";
function renderWithRouter(conversationId: string, onClose: () => void) {
return render(
<MemoryRouter initialEntries={[`/conversations/${conversationId}`]}>
<Routes>
<Route
path="/conversations/:conversationId"
element={<ConversationTabsContextMenu isOpen onClose={onClose} />}
/>
</Routes>
</MemoryRouter>,
);
}
describe("ConversationTabsContextMenu", () => {
afterEach(() => {
localStorage.clear();
});
it("should use per-conversation localStorage key for unpinned tabs", async () => {
const user = userEvent.setup();
const onClose = () => {};
// Render for conversation-1
const { unmount } = renderWithRouter("conversation-1", onClose);
// Unpin the terminal tab in conversation-1
const terminalItem = screen.getByText("COMMON$TERMINAL");
await user.click(terminalItem);
// Verify localStorage key is per-conversation
const stored1 = JSON.parse(
localStorage.getItem("conversation-unpinned-tabs-conversation-1") || "[]",
);
expect(stored1).toContain("terminal");
unmount();
// Switch to conversation-2
renderWithRouter("conversation-2", onClose);
// conversation-2 should have its own empty state
const stored2 = JSON.parse(
localStorage.getItem("conversation-unpinned-tabs-conversation-2") || "[]",
);
expect(stored2).toEqual([]);
// conversation-1 state should still have terminal unpinned
const stored1Again = JSON.parse(
localStorage.getItem("conversation-unpinned-tabs-conversation-1") || "[]",
);
expect(stored1Again).toContain("terminal");
});
it("should toggle tab pin state when clicked", async () => {
const user = userEvent.setup();
const onClose = () => {};
renderWithRouter("conversation-1", onClose);
const terminalItem = screen.getByText("COMMON$TERMINAL");
// Click to unpin
await user.click(terminalItem);
let stored = JSON.parse(
localStorage.getItem("conversation-unpinned-tabs-conversation-1") || "[]",
);
expect(stored).toContain("terminal");
// Click again to pin
await user.click(terminalItem);
stored = JSON.parse(
localStorage.getItem("conversation-unpinned-tabs-conversation-1") || "[]",
);
expect(stored).not.toContain("terminal");
});
});

View File

@@ -1,90 +0,0 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MemoryRouter } from "react-router";
import { ConversationTabs } from "#/components/features/conversation/conversation-tabs/conversation-tabs";
import { ConversationTabsContextMenu } from "#/components/features/conversation/conversation-tabs/conversation-tabs-context-menu";
const TASK_CONVERSATION_ID = "task-ec03fb2ab8604517b24af632b058c2fd";
const REAL_CONVERSATION_ID = "conv-abc123";
vi.mock("#/utils/feature-flags", () => ({
USE_PLANNING_AGENT: () => false,
}));
let mockConversationId = TASK_CONVERSATION_ID;
vi.mock("#/hooks/use-conversation-id", () => ({
useConversationId: () => ({ conversationId: mockConversationId }),
}));
const createWrapper = (conversationId: string) => {
return ({ children }: { children: React.ReactNode }) => (
<MemoryRouter initialEntries={[`/conversations/${conversationId}`]}>
<QueryClientProvider client={new QueryClient()}>
{children}
</QueryClientProvider>
</MemoryRouter>
);
};
describe("ConversationTabs localStorage behavior", () => {
beforeEach(() => {
localStorage.clear();
vi.resetAllMocks();
mockConversationId = TASK_CONVERSATION_ID;
});
describe("task-prefixed conversation IDs", () => {
it("should not create localStorage entries for task-prefixed conversation IDs", () => {
render(<ConversationTabs />, {
wrapper: createWrapper(TASK_CONVERSATION_ID),
});
expect(
localStorage.getItem(`conversation-state-${TASK_CONVERSATION_ID}`),
).toBeNull();
});
});
describe("consolidated localStorage key", () => {
it("should use a single consolidated key for tab state", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
render(<ConversationTabs />, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
const changesTab = screen.getByText("COMMON$CHANGES");
await user.click(changesTab);
const consolidatedKey = `conversation-state-${REAL_CONVERSATION_ID}`;
const storedState = localStorage.getItem(consolidatedKey);
expect(storedState).not.toBeNull();
const parsed = JSON.parse(storedState!);
expect(parsed).toHaveProperty("selectedTab");
expect(parsed).toHaveProperty("rightPanelShown");
expect(parsed).toHaveProperty("unpinnedTabs");
});
it("should store unpinned tabs in consolidated key via context menu", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
render(<ConversationTabsContextMenu isOpen={true} onClose={vi.fn()} />);
const terminalItem = screen.getByText("COMMON$TERMINAL");
await user.click(terminalItem);
const consolidatedKey = `conversation-state-${REAL_CONVERSATION_ID}`;
const storedState = localStorage.getItem(consolidatedKey);
expect(storedState).not.toBeNull();
const parsed = JSON.parse(storedState!);
expect(parsed.unpinnedTabs).toContain("terminal");
});
});
});

View File

@@ -1,38 +0,0 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import {
clearConversationLocalStorage,
LOCAL_STORAGE_KEYS,
} from "#/utils/conversation-local-storage";
describe("conversation localStorage utilities", () => {
beforeEach(() => {
localStorage.clear();
});
describe("clearConversationLocalStorage", () => {
it("removes the consolidated conversation-state localStorage entry", () => {
const conversationId = "conv-123";
// Set up the consolidated key
const consolidatedKey = `${LOCAL_STORAGE_KEYS.CONVERSATION_STATE}-${conversationId}`;
localStorage.setItem(
consolidatedKey,
JSON.stringify({
selectedTab: "editor",
rightPanelShown: true,
unpinnedTabs: [],
}),
);
clearConversationLocalStorage(conversationId);
expect(localStorage.getItem(consolidatedKey)).toBeNull();
});
it("does not throw if conversation keys do not exist", () => {
expect(() => {
clearConversationLocalStorage("non-existent-id");
}).not.toThrow();
});
});
});

View File

@@ -15,11 +15,10 @@ import { MemoryRouter, Route, Routes } from "react-router";
import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-store";
import { useBrowserStore } from "#/stores/browser-store";
import { useCommandStore } from "#/stores/command-store";
import { useErrorMessageStore } from "#/stores/error-message-store";
import {
createMockMessageEvent,
createMockUserMessageEvent,
createMockConversationErrorEvent,
createMockAgentErrorEvent,
createMockBrowserObservationEvent,
createMockBrowserNavigateActionEvent,
createMockExecuteBashActionEvent,
@@ -52,9 +51,6 @@ afterEach(() => {
mswServer.resetHandlers();
// Clean up any React components
cleanup();
// Reset stores to prevent state leakage between tests
useErrorMessageStore.getState().removeErrorMessage();
useEventStore.getState().clearEvents();
});
afterAll(async () => {
@@ -281,23 +277,16 @@ describe("Conversation WebSocket Handler", () => {
// 5. Error Handling Tests
describe("Error Handling & Recovery", () => {
beforeEach(() => {
// Clear stores before each error handling test to prevent state leakage
useErrorMessageStore.getState().removeErrorMessage();
useEventStore.getState().clearEvents();
});
it("should update error message store on ConversationErrorEvent", async () => {
// ConversationErrorEvent represents infrastructure/authentication errors
// that should be shown as a banner to the user.
const mockConversationErrorEvent = createMockConversationErrorEvent();
it("should update error message store on AgentErrorEvent", async () => {
// Create a mock AgentErrorEvent to send through WebSocket
const mockAgentErrorEvent = createMockAgentErrorEvent();
// Set up MSW to send the error event when connection is established
mswServer.use(
wsLink.addEventListener("connection", ({ client, server }) => {
server.connect();
// Send the mock error event after connection
client.send(JSON.stringify(mockConversationErrorEvent));
client.send(JSON.stringify(mockAgentErrorEvent));
}),
);
@@ -310,7 +299,7 @@ describe("Conversation WebSocket Handler", () => {
// Wait for connection and error event processing
await waitFor(() => {
expect(screen.getByTestId("error-message")).toHaveTextContent(
"Your session has expired. Please log in again.",
"Failed to execute command: Permission denied",
);
});
});
@@ -449,60 +438,6 @@ describe("Conversation WebSocket Handler", () => {
);
});
it("should clear error message when a successful event is received after a ConversationErrorEvent", async () => {
// This test verifies that error banners disappear when follow-up messages
// are sent and received. Only ConversationErrorEvent sets the error banner,
// and any non-error event should clear it.
const conversationId = "test-conversation-error-clear";
// Set up MSW to mock event count API and send events
mswServer.use(
http.get(
`http://localhost:3000/api/conversations/${conversationId}/events/count`,
() => HttpResponse.json(2),
),
wsLink.addEventListener("connection", ({ client, server }) => {
server.connect();
// Send a ConversationErrorEvent first (this sets the error banner)
const mockConversationErrorEvent = createMockConversationErrorEvent();
client.send(JSON.stringify(mockConversationErrorEvent));
// Send a successful (non-error) event immediately after
// This simulates the user sending a follow-up message and receiving a response
const mockSuccessEvent = createMockMessageEvent({
id: "success-event-after-error",
});
client.send(JSON.stringify(mockSuccessEvent));
}),
);
// Verify error message store is initially empty
expect(useErrorMessageStore.getState().errorMessage).toBeNull();
// Render with WebSocket context (minimal component just to trigger connection)
renderWithWebSocketContext(
<ConnectionStatusComponent />,
conversationId,
`http://localhost:3000/api/conversations/${conversationId}`,
);
// Wait for connection
await waitFor(() => {
expect(screen.getByTestId("connection-state")).toHaveTextContent(
"OPEN",
);
});
// Wait for both events to be received and error to be cleared
// The error was set by the first event (ConversationErrorEvent),
// then cleared by the second successful event (MessageEvent).
await waitFor(() => {
expect(useEventStore.getState().events.length).toBe(2);
expect(useErrorMessageStore.getState().errorMessage).toBeNull();
});
});
it("should not create duplicate events when WebSocket reconnects with resend_all=true", async () => {
const conversationId = "test-conversation-reconnect";
let connectionCount = 0;

View File

@@ -1,12 +1,12 @@
{
"name": "openhands-frontend",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openhands-frontend",
"version": "1.1.0",
"version": "1.2.0",
"dependencies": {
"@heroui/react": "2.8.7",
"@microlink/react-json-view": "^1.27.1",
@@ -89,7 +89,7 @@
"vitest": "^4.0.14"
},
"engines": {
"node": ">=22.12.0"
"node": ">=22.0.0"
}
},
"node_modules/@acemir/cssom": {
@@ -192,7 +192,6 @@
"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",
@@ -732,7 +731,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -779,7 +777,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -2348,7 +2345,6 @@
"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",
@@ -2428,7 +2424,6 @@
"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",
@@ -5431,7 +5426,6 @@
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -5890,7 +5884,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -6071,14 +6064,6 @@
"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",
@@ -6099,7 +6084,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -6140,7 +6124,6 @@
"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",
@@ -6198,7 +6181,6 @@
"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",
@@ -6737,7 +6719,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7132,52 +7113,6 @@
"@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",
@@ -7318,7 +7253,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -8007,8 +7941,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -8726,7 +8659,6 @@
"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",
@@ -8850,7 +8782,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -8931,7 +8862,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -9023,7 +8953,6 @@
"integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"aria-query": "^5.3.2",
"array-includes": "^3.1.8",
@@ -9118,7 +9047,6 @@
"integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"array-includes": "^3.1.8",
"array.prototype.findlast": "^1.2.5",
@@ -9152,7 +9080,6 @@
"integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -9420,7 +9347,6 @@
"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",
@@ -10415,7 +10341,6 @@
"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"
},
@@ -11177,7 +11102,6 @@
"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",
@@ -12884,7 +12808,6 @@
"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"
@@ -12977,7 +12900,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@inquirer/confirm": "^5.0.0",
"@mswjs/interceptors": "^0.40.0",
@@ -13702,7 +13624,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -13780,7 +13701,6 @@
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -14004,7 +13924,6 @@
"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"
}
@@ -14053,7 +13972,6 @@
"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"
},
@@ -14166,7 +14084,6 @@
"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"
@@ -14528,7 +14445,6 @@
"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"
},
@@ -15504,7 +15420,6 @@
"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"
@@ -15631,7 +15546,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -15933,7 +15847,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16238,7 +16151,6 @@
"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",
@@ -16409,7 +16321,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},

View File

@@ -1,10 +1,10 @@
{
"name": "openhands-frontend",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"type": "module",
"engines": {
"node": ">=22.12.0"
"node": ">=22.0.0"
},
"dependencies": {
"@heroui/react": "2.8.7",
@@ -121,7 +121,7 @@
},
"packageManager": "npm@10.5.0",
"volta": {
"node": "22.12.0"
"node": "22.0.0"
},
"msw": {
"workerDirectory": [

View File

@@ -66,7 +66,7 @@ export function ChatInterface() {
const posthog = usePostHog();
const { setMessageToSend } = useConversationStore();
const { data: conversation } = useActiveConversation();
const { errorMessage, removeErrorMessage } = useErrorMessageStore();
const { errorMessage } = useErrorMessageStore();
const { isLoadingMessages } = useWsClient();
const { isTask, taskStatus, taskDetail } = useTaskPolling();
const conversationWebSocket = useConversationWebSocket();
@@ -342,12 +342,7 @@ export function ChatInterface() {
{!hitBottom && <ScrollToBottomButton onClick={scrollDomToBottom} />}
</div>
{errorMessage && (
<ErrorMessageBanner
message={errorMessage}
onDismiss={removeErrorMessage}
/>
)}
{errorMessage && <ErrorMessageBanner message={errorMessage} />}
<InteractiveChatBox onSubmit={handleSendMessage} />
</div>

View File

@@ -1,87 +1,30 @@
import React from "react";
import { Trans, useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { Link } from "react-router";
import { X } from "lucide-react";
import { I18nKey } from "#/i18n/declaration";
import { cn } from "#/utils/utils";
import i18n from "#/i18n";
interface ErrorMessageBannerProps {
message: string;
onDismiss?: () => void;
}
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;
export function ErrorMessageBanner({ message }: ErrorMessageBannerProps) {
return (
<div
className="w-full rounded-lg p-2 border border-[#FF0006] bg-[#4A0709] flex gap-2 items-start text-white"
data-testid="error-message-banner"
>
<div className="min-w-0 flex-1">
<div
className={cn(
"whitespace-pre-wrap wrap-break-words",
isCollapsed && "line-clamp-3",
)}
data-testid="error-message-banner-content"
>
{isI18nKey ? (
<Trans
i18nKey={message}
components={{
a: (
<Link
className="underline font-bold cursor-pointer"
to="/settings/billing"
>
link
</Link>
),
}}
/>
) : (
message
)}
</div>
{shouldShowToggle && (
<button
type="button"
className="mt-1 text-xs underline font-semibold cursor-pointer"
onClick={() => setIsExpanded((prev) => !prev)}
data-testid="error-message-banner-toggle"
>
{isExpanded
? t(I18nKey.COMMON$VIEW_LESS)
: t(I18nKey.COMMON$VIEW_MORE)}
</button>
)}
</div>
{onDismiss && (
<button
type="button"
onClick={onDismiss}
className="shrink-0 rounded-md p-1 hover:bg-black/10 cursor-pointer"
aria-label={t(I18nKey.BUTTON$CLOSE)}
data-testid="error-message-banner-dismiss"
>
<X className="h-4 w-4" />
</button>
<div className="w-full rounded-lg p-2 text-black border border-red-800 bg-red-500">
{i18n.exists(message) ? (
<Trans
i18nKey={message}
components={{
a: (
<Link
className="underline font-bold cursor-pointer"
to="/settings/billing"
>
link
</Link>
),
}}
/>
) : (
message
)}
</div>
);

View File

@@ -1,9 +1,8 @@
import { useTranslation } from "react-i18next";
import { useLocalStorage } from "@uidotdev/usehooks";
import { ContextMenu } from "#/ui/context-menu";
import { ContextMenuListItem } from "../../context-menu/context-menu-list-item";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useConversationLocalStorageState } from "#/utils/conversation-local-storage";
import { I18nKey } from "#/i18n/declaration";
import TerminalIcon from "#/icons/terminal.svg?react";
import GlobeIcon from "#/icons/globe.svg?react";
@@ -14,6 +13,7 @@ import PillIcon from "#/icons/pill.svg?react";
import PillFillIcon from "#/icons/pill-fill.svg?react";
import { USE_PLANNING_AGENT } from "#/utils/feature-flags";
import LessonPlanIcon from "#/icons/lesson-plan.svg?react";
import { useConversationId } from "#/hooks/use-conversation-id";
interface ConversationTabsContextMenuProps {
isOpen: boolean;
@@ -27,8 +27,11 @@ export function ConversationTabsContextMenu({
const ref = useClickOutsideElement<HTMLUListElement>(onClose);
const { t } = useTranslation();
const { conversationId } = useConversationId();
const { state, setUnpinnedTabs } =
useConversationLocalStorageState(conversationId);
const [unpinnedTabs, setUnpinnedTabs] = useLocalStorage<string[]>(
`conversation-unpinned-tabs-${conversationId}`,
[],
);
const shouldUsePlanningAgent = USE_PLANNING_AGENT();
@@ -51,11 +54,11 @@ export function ConversationTabsContextMenu({
if (!isOpen) return null;
const handleTabClick = (tab: string) => {
if (state.unpinnedTabs.includes(tab)) {
setUnpinnedTabs(state.unpinnedTabs.filter((item) => item !== tab));
} else {
setUnpinnedTabs([...state.unpinnedTabs, tab]);
}
setUnpinnedTabs((prev) =>
prev.includes(tab)
? prev.filter((tabItem) => tabItem !== tab)
: [...prev, tab],
);
};
return (
@@ -66,7 +69,7 @@ export function ConversationTabsContextMenu({
className="mt-2 w-fit z-[9999]"
>
{tabConfig.map(({ tab, icon: Icon, i18nKey }) => {
const pinned = !state.unpinnedTabs.includes(tab);
const pinned = !unpinnedTabs.includes(tab);
return (
<ContextMenuListItem
key={tab}

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocalStorage } from "@uidotdev/usehooks";
import TerminalIcon from "#/icons/terminal.svg?react";
import GlobeIcon from "#/icons/globe.svg?react";
import ServerIcon from "#/icons/server.svg?react";
@@ -8,7 +9,6 @@ import VSCodeIcon from "#/icons/vscode.svg?react";
import ThreeDotsVerticalIcon from "#/icons/three-dots-vertical.svg?react";
import LessonPlanIcon from "#/icons/lesson-plan.svg?react";
import { cn } from "#/utils/utils";
import { useConversationLocalStorageState } from "#/utils/conversation-local-storage";
import { ConversationTabNav } from "./conversation-tab-nav";
import { ChatActionTooltip } from "../../chat/chat-action-tooltip";
import { I18nKey } from "#/i18n/declaration";
@@ -32,11 +32,23 @@ export function ConversationTabs() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const {
state: persistedState,
setSelectedTab: setPersistedSelectedTab,
setRightPanelShown: setPersistedRightPanelShown,
} = useConversationLocalStorageState(conversationId);
// Persist selectedTab and isRightPanelShown in localStorage per conversation
const [persistedSelectedTab, setPersistedSelectedTab] =
useLocalStorage<ConversationTab | null>(
`conversation-selected-tab-${conversationId}`,
"editor",
);
const [persistedIsRightPanelShown, setPersistedIsRightPanelShown] =
useLocalStorage<boolean>(
`conversation-right-panel-shown-${conversationId}`,
true,
);
const [persistedUnpinnedTabs] = useLocalStorage<string[]>(
`conversation-unpinned-tabs-${conversationId}`,
[],
);
const shouldUsePlanningAgent = USE_PLANNING_AGENT();
@@ -49,13 +61,13 @@ export function ConversationTabs() {
// Initialize Zustand state from localStorage on component mount
useEffect(() => {
// Initialize selectedTab from localStorage if available
setSelectedTab(persistedState.selectedTab);
setHasRightPanelToggled(persistedState.rightPanelShown);
setSelectedTab(persistedSelectedTab);
setHasRightPanelToggled(persistedIsRightPanelShown);
}, [
setSelectedTab,
setHasRightPanelToggled,
persistedState.selectedTab,
persistedState.rightPanelShown,
persistedSelectedTab,
persistedIsRightPanelShown,
]);
useEffect(() => {
@@ -77,13 +89,13 @@ export function ConversationTabs() {
if (selectedTab === tab && isRightPanelShown) {
// If clicking the same active tab, close the drawer
setHasRightPanelToggled(false);
setPersistedRightPanelShown(false);
setPersistedIsRightPanelShown(false);
} else {
// If clicking a different tab or drawer is closed, open drawer and select tab
onTabChange(tab);
if (!isRightPanelShown) {
setHasRightPanelToggled(true);
setPersistedRightPanelShown(true);
setPersistedIsRightPanelShown(true);
}
}
};
@@ -154,7 +166,7 @@ export function ConversationTabs() {
// Filter out unpinned tabs
const visibleTabs = tabs.filter(
(tab) => !persistedState.unpinnedTabs.includes(tab.tabValue),
(tab) => !persistedUnpinnedTabs.includes(tab.tabValue),
);
return (

View File

@@ -329,17 +329,16 @@ export function ConversationWebSocketProvider({
if (isV1Event(event)) {
addEvent(event);
// Handle ConversationErrorEvent specifically - show error banner
// AgentErrorEvent errors are displayed inline in the chat, not as banners
// Handle ConversationErrorEvent specifically
if (isConversationErrorEvent(event)) {
setErrorMessage(event.detail);
} else {
// Clear error message on any non-ConversationErrorEvent
removeErrorMessage();
}
// Track credit limit reached if AgentErrorEvent has budget-related error
// Handle AgentErrorEvent specifically
if (isAgentErrorEvent(event)) {
setErrorMessage(event.error);
// Track credit limit reached if the error is budget-related
if (isBudgetOrCreditError(event.error)) {
trackCreditLimitReached({
conversationId: conversationId || "unknown",
@@ -418,7 +417,6 @@ export function ConversationWebSocketProvider({
isLoadingHistoryMain,
expectedEventCountMain,
setErrorMessage,
removeErrorMessage,
removeOptimisticUserMessage,
queryClient,
conversationId,
@@ -426,7 +424,6 @@ export function ConversationWebSocketProvider({
appendInput,
appendOutput,
updateMetricsFromStats,
trackCreditLimitReached,
],
);

View File

@@ -1,6 +1,5 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import ConversationService from "#/api/conversation-service/conversation-service.api";
import { clearConversationLocalStorage } from "#/utils/conversation-local-storage";
export const useDeleteConversation = () => {
const queryClient = useQueryClient();
@@ -25,11 +24,6 @@ export const useDeleteConversation = () => {
return { previousConversations };
},
onSuccess: (_, variables) => {
clearConversationLocalStorage(variables.conversationId);
},
onError: (err, variables, context) => {
if (context?.previousConversations) {
queryClient.setQueryData(

View File

@@ -7,7 +7,6 @@ import {
import { AgentStateChangeObservation } from "#/types/core/observations";
import { MessageEvent } from "#/types/v1/core";
import { AgentErrorEvent } from "#/types/v1/core/events/observation-event";
import { ConversationErrorEvent } from "#/types/v1/core/events/conversation-state-event";
import { MockSessionMessaage } from "./session-history.mock";
export const generateAgentStateChangeObservation = (
@@ -237,19 +236,3 @@ export const createMockBrowserNavigateActionEvent = (
llm_response_id: "llm-response-789",
security_risk: { level: "low" },
});
/**
* Creates a mock ConversationErrorEvent for testing conversation-level error handling
* These are infrastructure/authentication errors that should show error banners
*/
export const createMockConversationErrorEvent = (
overrides: Partial<ConversationErrorEvent> = {},
): ConversationErrorEvent => ({
id: "conversation-error-123",
timestamp: new Date().toISOString(),
source: "environment",
kind: "ConversationErrorEvent",
code: "AuthenticationError",
detail: "Your session has expired. Please log in again.",
...overrides,
});

View File

@@ -62,11 +62,6 @@ export interface ConversationState {
}
interface ConversationStateUpdateEventBase extends BaseEvent {
/**
* Discriminator field for type guards
*/
kind: "ConversationStateUpdateEvent";
/**
* The source is always "environment" for conversation state update events
*/
@@ -110,11 +105,6 @@ export type ConversationStateUpdateEvent =
// Conversation error event - contains error information
export interface ConversationErrorEvent extends BaseEvent {
/**
* Discriminator field for type guards
*/
kind: "ConversationErrorEvent";
/**
* The source is always "environment" for conversation error events
*/

View File

@@ -1,110 +0,0 @@
import { useState } from "react";
import type { ConversationTab } from "#/stores/conversation-store";
export const LOCAL_STORAGE_KEYS = {
CONVERSATION_STATE: "conversation-state",
} as const;
/**
* Consolidated conversation state stored in a single localStorage key.
*/
export interface ConversationState {
selectedTab: ConversationTab | null;
rightPanelShown: boolean;
unpinnedTabs: string[];
}
const DEFAULT_CONVERSATION_STATE: ConversationState = {
selectedTab: "editor",
rightPanelShown: true,
unpinnedTabs: [],
};
/**
* Check if a conversation ID is a temporary task ID that should not be persisted.
* Task IDs have the format "task-{uuid}" and are used during V1 conversation initialization.
*/
export function isTaskConversationId(conversationId: string): boolean {
return conversationId.startsWith("task-");
}
/**
* Get the full conversation state from localStorage.
*/
export function getConversationState(
conversationId: string,
): ConversationState {
if (isTaskConversationId(conversationId)) {
return DEFAULT_CONVERSATION_STATE;
}
try {
const key = `${LOCAL_STORAGE_KEYS.CONVERSATION_STATE}-${conversationId}`;
const item = localStorage.getItem(key);
if (item !== null) {
return { ...DEFAULT_CONVERSATION_STATE, ...JSON.parse(item) };
}
return DEFAULT_CONVERSATION_STATE;
} catch {
return DEFAULT_CONVERSATION_STATE;
}
}
/**
* Set the conversation state in localStorage, merging with existing state.
*/
export function setConversationState(
conversationId: string,
updates: Partial<ConversationState>,
): void {
if (isTaskConversationId(conversationId)) {
return;
}
try {
const key = `${LOCAL_STORAGE_KEYS.CONVERSATION_STATE}-${conversationId}`;
const currentState = getConversationState(conversationId);
const newState = { ...currentState, ...updates };
localStorage.setItem(key, JSON.stringify(newState));
} catch (err) {
console.warn("Failed to set conversation localStorage", err);
}
}
export function clearConversationLocalStorage(conversationId: string) {
try {
const key = `${LOCAL_STORAGE_KEYS.CONVERSATION_STATE}-${conversationId}`;
localStorage.removeItem(key);
} catch (err) {
console.warn(
"Failed to clear conversation localStorage",
conversationId,
err,
);
}
}
/**
* React hook for conversation-scoped localStorage state.
* Returns the full state and individual setters for each property.
*/
export function useConversationLocalStorageState(conversationId: string): {
state: ConversationState;
setSelectedTab: (tab: ConversationTab | null) => void;
setRightPanelShown: (shown: boolean) => void;
setUnpinnedTabs: (tabs: string[]) => void;
} {
const [state, setState] = useState<ConversationState>(() =>
getConversationState(conversationId),
);
const updateState = (updates: Partial<ConversationState>) => {
setState((prev) => ({ ...prev, ...updates }));
setConversationState(conversationId, updates);
};
return {
state,
setSelectedTab: (tab) => updateState({ selectedTab: tab }),
setRightPanelShown: (shown) => updateState({ rightPanelShown: shown }),
setUnpinnedTabs: (tabs) => updateState({ unpinnedTabs: tabs }),
};
}

View File

@@ -43,6 +43,7 @@ export default defineConfig(({ mode }) => {
"i18next-browser-languagedetector",
"react-i18next",
"axios",
"date-fns",
"@uidotdev/usehooks",
"react-icons/fa6",
"react-icons/fa",
@@ -50,6 +51,8 @@ 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:

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -465,9 +465,7 @@ async def _get_org_repository_url(
Authenticated Git URL if successful, None otherwise
"""
try:
remote_url = await user_context.get_authenticated_git_url(
org_openhands_repo, is_optional=True
)
remote_url = await user_context.get_authenticated_git_url(org_openhands_repo)
return remote_url
except AuthenticationError as e:
_logger.debug(

View File

@@ -13,7 +13,7 @@ from openhands.sdk.utils.models import DiscriminatedUnionMixin
# The version of the agent server to use for deployments.
# Typically this will be the same as the values from the pyproject.toml
AGENT_SERVER_IMAGE = 'ghcr.io/openhands/agent-server:10fff69-python'
AGENT_SERVER_IMAGE = 'ghcr.io/openhands/agent-server:7c91cbe-python'
class SandboxSpecService(ABC):

View File

@@ -63,13 +63,9 @@ class AuthUserContext(UserContext):
self._provider_handler = provider_handler
return provider_handler
async def get_authenticated_git_url(
self, repository: str, is_optional: bool = False
) -> str:
async def get_authenticated_git_url(self, repository: str) -> str:
provider_handler = await self.get_provider_handler()
url = await provider_handler.get_authenticated_git_url(
repository, is_optional=is_optional
)
url = await provider_handler.get_authenticated_git_url(repository)
return url
async def get_latest_token(self, provider_type: ProviderType) -> str | None:

View File

@@ -21,9 +21,7 @@ class SpecifyUserContext(UserContext):
async def get_user_info(self) -> UserInfo:
raise NotImplementedError()
async def get_authenticated_git_url(
self, repository: str, is_optional: bool = False
) -> str:
async def get_authenticated_git_url(self, repository: str) -> str:
raise NotImplementedError()
async def get_provider_tokens(self) -> PROVIDER_TOKEN_TYPE | None:

View File

@@ -23,16 +23,8 @@ class UserContext(ABC):
"""Get the user info."""
@abstractmethod
async def get_authenticated_git_url(
self, repository: str, is_optional: bool = False
) -> str:
"""Get an authenticated git URL for a repository.
Args:
repository: Repository name (owner/repo)
is_optional: If True, logs at debug level instead of error level
when repository is not found. Use for optional repositories.
"""
async def get_authenticated_git_url(self, repository: str) -> str:
"""Get the provider tokens for the user"""
@abstractmethod
async def get_provider_tokens(self) -> PROVIDER_TOKEN_TYPE | None:

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -57,9 +57,7 @@ class ForgejoMixinBase(BaseGitService, HTTPClient):
self.base_url = self.BASE_URL # Backwards compatibility for existing usage
parsed = urlparse(self.BASE_URL)
self.base_domain = parsed.netloc or self.DEFAULT_DOMAIN
# Preserve the protocol from BASE_URL (http or https)
protocol = parsed.scheme or 'https'
self.web_base_url = f'{protocol}://{self.base_domain}'.rstrip('/')
self.web_base_url = f'https://{self.base_domain}'.rstrip('/')
@property
def provider(self) -> str:

View File

@@ -675,22 +675,6 @@ class ProviderHandler:
if provider != ProviderType.AZURE_DEVOPS:
domain = self.provider_tokens[provider].host or domain
# Detect protocol before normalizing domain
# Default to https, but preserve http if explicitly specified
protocol = 'https'
if domain and domain.strip().startswith('http://'):
# Check if insecure HTTP access is allowed
allow_insecure = os.environ.get(
'ALLOW_INSECURE_GIT_ACCESS', 'false'
).lower() in ('true', '1', 'yes')
if not allow_insecure:
raise ValueError(
'Attempting to connect to an insecure git repository over HTTP. '
"If you'd like to allow this nonetheless, set "
'ALLOW_INSECURE_GIT_ACCESS=true as an environment variable.'
)
protocol = 'http'
# Normalize domain to prevent double protocols or path segments
if domain:
domain = domain.strip()
@@ -706,18 +690,16 @@ class ProviderHandler:
token_value = git_token.get_secret_value()
if provider == ProviderType.GITLAB:
remote_url = (
f'{protocol}://oauth2:{token_value}@{domain}/{repo_name}.git'
f'https://oauth2:{token_value}@{domain}/{repo_name}.git'
)
elif provider == ProviderType.BITBUCKET:
# For Bitbucket, handle username:app_password format
if ':' in token_value:
# App token format: username:app_password
remote_url = (
f'{protocol}://{token_value}@{domain}/{repo_name}.git'
)
remote_url = f'https://{token_value}@{domain}/{repo_name}.git'
else:
# Access token format: use x-token-auth
remote_url = f'{protocol}://x-token-auth:{token_value}@{domain}/{repo_name}.git'
remote_url = f'https://x-token-auth:{token_value}@{domain}/{repo_name}.git'
elif provider == ProviderType.AZURE_DEVOPS:
# Azure DevOps uses PAT with Basic auth
# Format: https://{anything}:{PAT}@dev.azure.com/{org}/{project}/_git/{repo}
@@ -777,11 +759,11 @@ class ProviderHandler:
)
else:
# GitHub, Forgejo
remote_url = f'{protocol}://{token_value}@{domain}/{repo_name}.git'
remote_url = f'https://{token_value}@{domain}/{repo_name}.git'
else:
remote_url = f'{protocol}://{domain}/{repo_name}.git'
remote_url = f'https://{domain}/{repo_name}.git'
else:
remote_url = f'{protocol}://{domain}/{repo_name}.git'
remote_url = f'https://{domain}/{repo_name}.git'
return remote_url

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

View File

@@ -1,4 +1,4 @@
# IMPORTANT: LEGACY V0 CODE - Deprecated since version 1.0.0, scheduled for removal April 1, 2026
# IMPORTANT: LEGACY V0 CODE
# This file is part of the legacy (V0) implementation of OpenHands and will be removed soon as we complete the migration to V1.
# OpenHands V1 uses the Software Agent SDK for the agentic core and runs a new application server. Please refer to:
# - V1 agentic core (SDK): https://github.com/OpenHands/software-agent-sdk

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