From f6097bda536eda214ec9a8d1542f5245dd99dbac Mon Sep 17 00:00:00 2001 From: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com> Date: Fri, 14 Aug 2026 17:46:35 +0200 Subject: [PATCH] chore: remove unreachable frontend modules and their tests (#16606) --- .../api/cloud/suggestions-service.test.ts | 54 ---- __tests__/api/suggestions-service.test.ts | 83 ------- .../automations/metadata-chip.test.tsx | 17 -- .../new-conversation-button-cloud.test.tsx | 231 ------------------ .../features/home/home-header.test.tsx | 50 ---- .../features/home/new-conversation.test.tsx | 99 -------- .../features/home/task-card.test.tsx | 190 -------------- .../features/home/task-suggestions.test.tsx | 167 ------------- .../mcp-settings/mcp-server-list.test.tsx | 152 ------------ __tests__/services/actions.test.ts | 105 -------- __tests__/services/actions.test.tsx | 89 ------- __tests__/services/observations.test.tsx | 24 -- __tests__/ui/card.test.tsx | 29 --- __tests__/use-suggested-tasks.test.ts | 59 ----- __tests__/utils/group-suggested-tasks.test.ts | 98 -------- src/api/backend-registry/index.ts | 5 - src/api/cloud/suggestions-service.api.ts | 36 --- .../suggestions-service.api.ts | 23 -- .../chat/event-message-components/index.ts | 1 - .../observation-pair-event-message.tsx | 37 --- src/components/conversation-events/index.ts | 1 - .../features/automations/metadata-chip.tsx | 16 -- .../chat/task-tracking/status-badge.tsx | 18 -- .../chat/task-tracking/status-icon.tsx | 9 - .../chat/task-tracking/task-list-section.tsx | 37 --- src/components/features/controls/tools.tsx | 108 -------- .../new-conversation-button.tsx | 100 -------- .../home/home-header/guide-message.tsx | 19 -- .../features/home/home-header/home-header.tsx | 11 - .../features/home/new-conversation.tsx | 61 ----- .../create-conversation-button.tsx | 46 ---- .../new-conversation/new-conversation.tsx | 23 -- .../features/home/tasks/task-card.tsx | 110 --------- .../features/home/tasks/task-group.tsx | 32 --- .../features/home/tasks/task-issue-number.tsx | 19 -- .../features/home/tasks/task-item-title.tsx | 7 - .../home/tasks/task-suggestions-skeleton.tsx | 42 ---- .../features/home/tasks/task-suggestions.tsx | 112 --------- .../settings/agent-server-onboarding.tsx | 164 ------------- .../mcp-settings/mcp-server-list-item.tsx | 100 -------- .../settings/mcp-settings/mcp-server-list.tsx | 62 ----- .../query/use-openhands-verified-models.ts | 16 -- src/hooks/query/use-suggested-tasks.ts | 15 -- src/hooks/use-should-show-user-features.ts | 13 - src/mocks/handlers.ts | 2 - src/mocks/task-suggestions-handlers.ts | 101 -------- src/mocks/workspace-handlers.ts | 89 ------- src/routes/index-redirect.tsx | 7 - src/services/actions.ts | 87 ------- src/services/observations.ts | 92 ------- src/stores/security-analyzer-store.ts | 75 ------ src/stores/status-store.ts | 20 -- src/types/message.tsx | 78 ------ src/types/observation-type.tsx | 48 ---- src/ui/card-title.tsx | 77 ------ src/ui/card.tsx | 66 ----- src/ui/context-menu-icon-text.tsx | 30 --- src/utils/group-suggested-tasks.ts | 27 -- 58 files changed, 3489 deletions(-) delete mode 100644 __tests__/api/cloud/suggestions-service.test.ts delete mode 100644 __tests__/api/suggestions-service.test.ts delete mode 100644 __tests__/components/automations/metadata-chip.test.tsx delete mode 100644 __tests__/components/features/conversation-panel/new-conversation-button-cloud.test.tsx delete mode 100644 __tests__/components/features/home/home-header.test.tsx delete mode 100644 __tests__/components/features/home/new-conversation.test.tsx delete mode 100644 __tests__/components/features/home/task-card.test.tsx delete mode 100644 __tests__/components/features/home/task-suggestions.test.tsx delete mode 100644 __tests__/components/features/settings/mcp-settings/mcp-server-list.test.tsx delete mode 100644 __tests__/services/actions.test.ts delete mode 100644 __tests__/services/actions.test.tsx delete mode 100644 __tests__/services/observations.test.tsx delete mode 100644 __tests__/ui/card.test.tsx delete mode 100644 __tests__/use-suggested-tasks.test.ts delete mode 100644 __tests__/utils/group-suggested-tasks.test.ts delete mode 100644 src/api/backend-registry/index.ts delete mode 100644 src/api/cloud/suggestions-service.api.ts delete mode 100644 src/api/suggestions-service/suggestions-service.api.ts delete mode 100644 src/components/conversation-events/chat/event-message-components/observation-pair-event-message.tsx delete mode 100644 src/components/conversation-events/index.ts delete mode 100644 src/components/features/automations/metadata-chip.tsx delete mode 100644 src/components/features/chat/task-tracking/status-badge.tsx delete mode 100644 src/components/features/chat/task-tracking/status-icon.tsx delete mode 100644 src/components/features/chat/task-tracking/task-list-section.tsx delete mode 100644 src/components/features/controls/tools.tsx delete mode 100644 src/components/features/conversation-panel/new-conversation-button.tsx delete mode 100644 src/components/features/home/home-header/guide-message.tsx delete mode 100644 src/components/features/home/home-header/home-header.tsx delete mode 100644 src/components/features/home/new-conversation.tsx delete mode 100644 src/components/features/home/new-conversation/create-conversation-button.tsx delete mode 100644 src/components/features/home/new-conversation/new-conversation.tsx delete mode 100644 src/components/features/home/tasks/task-card.tsx delete mode 100644 src/components/features/home/tasks/task-group.tsx delete mode 100644 src/components/features/home/tasks/task-issue-number.tsx delete mode 100644 src/components/features/home/tasks/task-item-title.tsx delete mode 100644 src/components/features/home/tasks/task-suggestions-skeleton.tsx delete mode 100644 src/components/features/home/tasks/task-suggestions.tsx delete mode 100644 src/components/features/settings/agent-server-onboarding.tsx delete mode 100644 src/components/features/settings/mcp-settings/mcp-server-list-item.tsx delete mode 100644 src/components/features/settings/mcp-settings/mcp-server-list.tsx delete mode 100644 src/hooks/query/use-openhands-verified-models.ts delete mode 100644 src/hooks/query/use-suggested-tasks.ts delete mode 100644 src/hooks/use-should-show-user-features.ts delete mode 100644 src/mocks/task-suggestions-handlers.ts delete mode 100644 src/mocks/workspace-handlers.ts delete mode 100644 src/routes/index-redirect.tsx delete mode 100644 src/services/actions.ts delete mode 100644 src/services/observations.ts delete mode 100644 src/stores/security-analyzer-store.ts delete mode 100644 src/stores/status-store.ts delete mode 100644 src/types/message.tsx delete mode 100644 src/types/observation-type.tsx delete mode 100644 src/ui/card-title.tsx delete mode 100644 src/ui/card.tsx delete mode 100644 src/ui/context-menu-icon-text.tsx delete mode 100644 src/utils/group-suggested-tasks.ts diff --git a/__tests__/api/cloud/suggestions-service.test.ts b/__tests__/api/cloud/suggestions-service.test.ts deleted file mode 100644 index 35c89acbae..0000000000 --- a/__tests__/api/cloud/suggestions-service.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - __resetActiveStoreForTests, - setActiveSelection, - setRegisteredBackends, -} from "#/api/backend-registry/active-store"; -import { getCloudSuggestedTasks } from "#/api/cloud/suggestions-service.api"; -import type { Backend } from "#/api/backend-registry/types"; -import { getFetchCall, mockJsonResponse } from "./fetch-test-utils"; - -const cloudBackend: Backend = { - id: "prod", - name: "Production", - host: "https://app.all-hands.dev", - apiKey: "bearer-token", - kind: "cloud", -}; - -const originalFetch = global.fetch; -const fetchMock = vi.fn(); - -beforeEach(() => { - __resetActiveStoreForTests(); - setRegisteredBackends([cloudBackend]); - setActiveSelection({ backendId: cloudBackend.id }); - fetchMock.mockReset(); - fetchMock.mockResolvedValue( - mockJsonResponse({ items: [], next_page_id: null }), - ); - global.fetch = fetchMock as typeof fetch; -}); - -afterEach(() => { - __resetActiveStoreForTests(); - fetchMock.mockReset(); - global.fetch = originalFetch; -}); - -describe("getCloudSuggestedTasks", () => { - it("forwards limit and pageId to the upstream /api/v1/git/suggested-tasks/search endpoint", async () => { - // Act - await getCloudSuggestedTasks({ limit: 10, pageId: "p2" }); - - // Assert - const [url, init] = getFetchCall(fetchMock); - expect(init).toMatchObject({ - method: "GET", - headers: { Authorization: "Bearer bearer-token" }, - }); - expect(url).toContain("/api/v1/git/suggested-tasks/search"); - expect(url).toContain("limit=10"); - expect(url).toContain("page_id=p2"); - }); -}); diff --git a/__tests__/api/suggestions-service.test.ts b/__tests__/api/suggestions-service.test.ts deleted file mode 100644 index 6cd904c501..0000000000 --- a/__tests__/api/suggestions-service.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - __resetActiveStoreForTests, - setActiveSelection, - setRegisteredBackends, -} from "#/api/backend-registry/active-store"; -import * as cloudSuggestions from "#/api/cloud/suggestions-service.api"; -import { SuggestionsService } from "#/api/suggestions-service/suggestions-service.api"; -import type { Backend } from "#/api/backend-registry/types"; -import type { SuggestedTask } from "#/utils/types"; - -vi.mock("#/api/cloud/suggestions-service.api", () => ({ - getCloudSuggestedTasks: vi.fn(), -})); - -const mockGetCloudSuggestedTasks = vi.mocked( - cloudSuggestions.getCloudSuggestedTasks, -); - -const localBackend: Backend = { - id: "local", - name: "Local", - host: "http://localhost", - apiKey: "local-key", - kind: "local", -}; - -const cloudBackend: Backend = { - id: "prod", - name: "Production", - host: "https://app.all-hands.dev", - apiKey: "bearer", - kind: "cloud", -}; - -beforeEach(() => { - __resetActiveStoreForTests(); - mockGetCloudSuggestedTasks.mockReset(); -}); - -afterEach(() => { - __resetActiveStoreForTests(); -}); - -describe("SuggestionsService.getSuggestedTasks", () => { - it("returns the cloud endpoint items when the active backend is cloud", async () => { - // Arrange - setRegisteredBackends([cloudBackend]); - setActiveSelection({ backendId: cloudBackend.id }); - const items: SuggestedTask[] = [ - { - issue_number: 1, - title: "Fix CI", - repo: "octo/foo", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - ]; - mockGetCloudSuggestedTasks.mockResolvedValueOnce({ - items, - next_page_id: null, - }); - - // Act - const result = await SuggestionsService.getSuggestedTasks(); - - // Assert - expect(result).toEqual(items); - }); - - it("returns an empty list without calling the cloud endpoint when the active backend is local", async () => { - // Arrange - setRegisteredBackends([localBackend]); - setActiveSelection({ backendId: localBackend.id }); - - // Act - const result = await SuggestionsService.getSuggestedTasks(); - - // Assert - expect(result).toEqual([]); - expect(mockGetCloudSuggestedTasks).not.toHaveBeenCalled(); - }); -}); diff --git a/__tests__/components/automations/metadata-chip.test.tsx b/__tests__/components/automations/metadata-chip.test.tsx deleted file mode 100644 index 8b0b3501c8..0000000000 --- a/__tests__/components/automations/metadata-chip.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, it, expect } from "vitest"; -import { MetadataChip } from "#/components/features/automations/metadata-chip"; - -describe("MetadataChip", () => { - it("renders the label and icon", () => { - render( - } - label="acme/frontend-app" - />, - ); - - expect(screen.getByText("acme/frontend-app")).toBeInTheDocument(); - expect(screen.getByTestId("chip-icon")).toBeInTheDocument(); - }); -}); diff --git a/__tests__/components/features/conversation-panel/new-conversation-button-cloud.test.tsx b/__tests__/components/features/conversation-panel/new-conversation-button-cloud.test.tsx deleted file mode 100644 index 81057c30a8..0000000000 --- a/__tests__/components/features/conversation-panel/new-conversation-button-cloud.test.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { screen, waitFor } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import { renderWithProviders } from "test-utils"; -import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api"; -import { NewConversationButton } from "#/components/features/conversation-panel/new-conversation-button"; -import { GitRepository } from "#/types/git"; - -const mockUseUserProviders = vi.fn(); -const mockUseActiveBackend = vi.fn(); -const mockUseGitRepositories = vi.fn(); -const mockUseSearchRepositories = vi.fn(); - -vi.mock("#/hooks/use-user-providers", () => ({ - useUserProviders: () => mockUseUserProviders(), -})); - -vi.mock("#/contexts/active-backend-context", () => ({ - useActiveBackend: () => mockUseActiveBackend(), -})); - -vi.mock("#/hooks/query/use-git-repositories", () => ({ - useGitRepositories: (...args: unknown[]) => mockUseGitRepositories(...args), -})); - -vi.mock("#/hooks/query/use-search-repositories", () => ({ - useSearchRepositories: (...args: unknown[]) => - mockUseSearchRepositories(...args), -})); - -vi.mock("#/hooks/use-tracking", () => ({ - useTracking: () => ({ - trackConversationCreated: vi.fn(), - }), -})); - -const makeRepo = ( - full_name: string, - overrides: Partial = {}, -): GitRepository => ({ - id: full_name, - full_name, - git_provider: "github", - is_public: true, - ...overrides, -}); - -const makeStartTask = (conversationId: string) => ({ - id: "task-id", - created_by_user_id: null, - status: "READY" as const, - detail: null, - app_conversation_id: conversationId, - agent_server_url: "http://agent-server.local", - request: { - initial_message: null, - processors: [], - llm_model: null, - selected_repository: null, - selected_branch: null, - git_provider: "github" as const, - suggested_task: null, - title: null, - trigger: null, - pr_number: [], - parent_conversation_id: null, - agent_type: "default" as const, - }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), -}); - -describe("NewConversationButton (cloud)", () => { - beforeEach(() => { - window.localStorage.clear(); - mockUseActiveBackend.mockReturnValue({ - backend: { id: "cloud-1", kind: "cloud" }, - orgId: null, - }); - mockUseUserProviders.mockReturnValue({ - providers: ["github"], - isLoadingSettings: false, - }); - mockUseGitRepositories.mockReturnValue({ - data: { - pages: [ - { - items: [ - makeRepo("octo/cat", { main_branch: "main" }), - makeRepo("octo/dog", { main_branch: "trunk" }), - ], - next_page_id: null, - }, - ], - }, - isLoading: false, - isError: false, - hasNextPage: false, - isFetchingNextPage: false, - fetchNextPage: vi.fn(), - onLoadMore: vi.fn(), - }); - mockUseSearchRepositories.mockReturnValue({ - data: [], - isLoading: false, - }); - }); - - afterEach(() => { - vi.restoreAllMocks(); - window.localStorage.clear(); - }); - - it("renders a list of repositories from the active provider", async () => { - const user = userEvent.setup(); - renderWithProviders(); - - await user.click(screen.getByTestId("new-conversation-button")); - - expect(screen.getByTestId("new-conversation-popover")).toBeInTheDocument(); - const items = screen.getAllByTestId("launch-repository"); - expect(items).toHaveLength(2); - expect(items[0]).toHaveAttribute("data-repo-name", "octo/cat"); - expect(items[1]).toHaveAttribute("data-repo-name", "octo/dog"); - - // No workspace footer in cloud mode. - expect( - screen.queryByTestId("add-workspaces-button"), - ).not.toBeInTheDocument(); - expect(screen.queryByTestId("launch-no-workspace")).not.toBeInTheDocument(); - }); - - it("launches a conversation against the repo's default branch", async () => { - const navigate = vi.fn(); - const createSpy = vi - .spyOn(AgentServerConversationService, "createConversation") - .mockResolvedValue(makeStartTask("conv-xyz")); - - const user = userEvent.setup(); - renderWithProviders(, { - navigation: { navigate, currentPath: "/conversations" }, - }); - - await user.click(screen.getByTestId("new-conversation-button")); - const items = screen.getAllByTestId("launch-repository"); - await user.click(items[1]); // octo/dog, main_branch = "trunk" - - await waitFor(() => { - expect(createSpy).toHaveBeenCalledWith({ - metadata: { - selected_repository: "octo/dog", - selected_branch: "trunk", - git_provider: "github", - }, - }); - }); - await waitFor(() => { - expect(navigate).toHaveBeenCalledWith("/conversations/conv-xyz"); - }); - }); - - it('falls back to "main" when the repository has no main_branch metadata', async () => { - mockUseGitRepositories.mockReturnValue({ - data: { - pages: [{ items: [makeRepo("octo/no-main")], next_page_id: null }], - }, - isLoading: false, - isError: false, - hasNextPage: false, - isFetchingNextPage: false, - fetchNextPage: vi.fn(), - onLoadMore: vi.fn(), - }); - const createSpy = vi - .spyOn(AgentServerConversationService, "createConversation") - .mockResolvedValue(makeStartTask("conv-abc")); - - const user = userEvent.setup(); - renderWithProviders(); - - await user.click(screen.getByTestId("new-conversation-button")); - await user.click(screen.getByTestId("launch-repository")); - - await waitFor(() => { - expect(createSpy).toHaveBeenCalledWith({ - metadata: { - selected_repository: "octo/no-main", - selected_branch: "main", - git_provider: "github", - }, - }); - }); - }); - - it("renders provider tabs when multiple providers are connected", async () => { - mockUseUserProviders.mockReturnValue({ - providers: ["github", "gitlab"], - isLoadingSettings: false, - }); - - const user = userEvent.setup(); - renderWithProviders(); - - await user.click(screen.getByTestId("new-conversation-button")); - - expect(screen.getByTestId("cloud-provider-tabs")).toBeInTheDocument(); - expect(screen.getByTestId("cloud-provider-tab-github")).toBeInTheDocument(); - expect(screen.getByTestId("cloud-provider-tab-gitlab")).toBeInTheDocument(); - }); - - it("shows an empty state when no repositories are returned", async () => { - mockUseGitRepositories.mockReturnValue({ - data: { pages: [{ items: [], next_page_id: null }] }, - isLoading: false, - isError: false, - hasNextPage: false, - isFetchingNextPage: false, - fetchNextPage: vi.fn(), - onLoadMore: vi.fn(), - }); - - const user = userEvent.setup(); - renderWithProviders(); - - await user.click(screen.getByTestId("new-conversation-button")); - - expect(screen.getByTestId("cloud-repo-empty")).toBeInTheDocument(); - expect(screen.queryAllByTestId("launch-repository")).toHaveLength(0); - }); -}); diff --git a/__tests__/components/features/home/home-header.test.tsx b/__tests__/components/features/home/home-header.test.tsx deleted file mode 100644 index 66ae4d1de7..0000000000 --- a/__tests__/components/features/home/home-header.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; -import { render, screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; -import { HomeHeader } from "#/components/features/home/home-header/home-header"; - -// Mock the translation function -vi.mock("react-i18next", async () => { - const actual = await vi.importActual("react-i18next"); - return { - ...actual, - useTranslation: () => ({ - t: (key: string) => { - // Return a mock translation for the test - const translations: Record = { - HOME$LETS_START_BUILDING: "Let's start building", - }; - return translations[key] || key; - }, - i18n: { language: "en" }, - }), - }; -}); - -const renderHomeHeader = () => { - return render(, { - wrapper: ({ children }) => ( - - {children} - - ), - }); -}; - -describe("HomeHeader", () => { - it("should render the header with the correct title", () => { - renderHomeHeader(); - - const title = screen.getByText("Let's start building"); - expect(title).toBeInTheDocument(); - }); - - it("should render the GuideMessage component", () => { - renderHomeHeader(); - - // The GuideMessage component should be rendered as part of the header - const header = screen.getByRole("banner"); - expect(header).toBeInTheDocument(); - }); - -}); diff --git a/__tests__/components/features/home/new-conversation.test.tsx b/__tests__/components/features/home/new-conversation.test.tsx deleted file mode 100644 index b2d39bfd0d..0000000000 --- a/__tests__/components/features/home/new-conversation.test.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { screen, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; -import userEvent from "@testing-library/user-event"; -import { renderWithProviders } from "test-utils"; -import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api"; -import { NewConversation } from "#/components/features/home/new-conversation/new-conversation"; - -vi.mock("#/hooks/query/use-settings", async () => { - const actual = await vi.importActual( - "#/hooks/query/use-settings", - ); - return { - ...actual, - getSettingsQueryFn: vi.fn().mockResolvedValue({}), - }; -}); - -// Mock the translation function -vi.mock("react-i18next", async () => { - const actual = await vi.importActual("react-i18next"); - return { - ...actual, - useTranslation: () => ({ - t: (key: string) => { - // Return a mock translation for the test - const translations: Record = { - COMMON$START_FROM_SCRATCH: "Start from Scratch", - HOME$NEW_PROJECT_DESCRIPTION: "Create a new project from scratch", - COMMON$NEW_CONVERSATION: "New Conversation", - HOME$LOADING: "Loading...", - }; - return translations[key] || key; - }, - i18n: { language: "en" }, - }), - }; -}); - -const renderNewConversation = (navigate = vi.fn()) => - renderWithProviders(, { - navigation: { navigate }, - }); - -describe("NewConversation", () => { - it("should create an empty conversation and navigate when pressing the launch from scratch button", async () => { - const navigate = vi.fn(); - const createConversationSpy = vi - .spyOn(AgentServerConversationService, "createConversation") - .mockResolvedValue({ - id: "task-id", - created_by_user_id: null, - status: "READY", - detail: null, - app_conversation_id: "conv-123", - agent_server_url: "http://agent-server.local", - request: { - initial_message: null, - processors: [], - llm_model: null, - selected_repository: null, - selected_branch: null, - git_provider: "github", - suggested_task: null, - title: null, - trigger: null, - pr_number: [], - parent_conversation_id: null, - agent_type: "default", - }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); - - renderNewConversation(navigate); - - const launchButton = screen.getByTestId("launch-new-conversation-button"); - await userEvent.click(launchButton); - - expect(createConversationSpy).toHaveBeenCalledOnce(); - await waitFor(() => { - expect(navigate).toHaveBeenCalledWith("/conversations/conv-123"); - }); - }); - - it("should change the launch button text to 'Loading...' when creating a conversation", async () => { - // Mock V1 API to never resolve, keeping the mutation in loading state - vi.spyOn(AgentServerConversationService, "createConversation").mockImplementation( - () => new Promise(() => {}), - ); - - renderNewConversation(); - - const launchButton = screen.getByTestId("launch-new-conversation-button"); - await userEvent.click(launchButton); - - expect(launchButton).toHaveTextContent(/Loading.../i); - expect(launchButton).toBeDisabled(); - }); -}); diff --git a/__tests__/components/features/home/task-card.test.tsx b/__tests__/components/features/home/task-card.test.tsx deleted file mode 100644 index af3c3cd63b..0000000000 --- a/__tests__/components/features/home/task-card.test.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { screen, waitFor } from "@testing-library/react"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import userEvent from "@testing-library/user-event"; -import { renderWithProviders } from "test-utils"; -import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api"; -import GitService from "#/api/git-service/git-service.api"; -import { TaskCard } from "#/components/features/home/tasks/task-card"; -import { GitRepository } from "#/types/git"; -import { SuggestedTask } from "#/utils/types"; - -vi.mock("#/hooks/query/use-settings", async () => { - const actual = await vi.importActual< - typeof import("#/hooks/query/use-settings") - >("#/hooks/query/use-settings"); - return { - ...actual, - getSettingsQueryFn: vi.fn().mockResolvedValue({}), - }; -}); - -const MOCK_TASK_1: SuggestedTask = { - issue_number: 123, - repo: "repo1", - title: "Task 1", - task_type: "MERGE_CONFLICTS", - git_provider: "github", -}; - -const MOCK_RESPOSITORIES: GitRepository[] = [ - { id: "1", full_name: "repo1", git_provider: "github", is_public: true }, - { id: "2", full_name: "repo2", git_provider: "github", is_public: true }, - { id: "3", full_name: "repo3", git_provider: "gitlab", is_public: true }, - { id: "4", full_name: "repo4", git_provider: "gitlab", is_public: true }, - { - id: "5", - full_name: "repo5", - git_provider: "azure_devops", - is_public: true, - }, -]; - -const renderTaskCard = (task = MOCK_TASK_1, navigate = vi.fn()) => - renderWithProviders(, { - navigation: { navigate }, - }); - -describe("TaskCard", () => { - it("format the issue id", async () => { - renderTaskCard(); - - const taskId = screen.getByTestId("task-id"); - expect(taskId).toHaveTextContent(/#123/i); - }); - - it("should call createConversation when clicking the launch button", async () => { - const createConversationSpy = vi - .spyOn(AgentServerConversationService, "createConversation") - .mockResolvedValue({ - id: "task-id", - created_by_user_id: null, - status: "READY", - detail: null, - app_conversation_id: "conv-123", - agent_server_url: "http://agent-server.local", - request: { - initial_message: null, - processors: [], - llm_model: null, - selected_repository: null, - selected_branch: null, - git_provider: "github", - suggested_task: null, - title: null, - trigger: null, - pr_number: [], - parent_conversation_id: null, - agent_type: "default", - }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); - - renderTaskCard(); - - const launchButton = screen.getByTestId("task-launch-button"); - await userEvent.click(launchButton); - - await waitFor(() => { - expect(createConversationSpy).toHaveBeenCalled(); - }); - }); - - describe("creating suggested task conversation", () => { - beforeEach(() => { - const retrieveUserGitRepositoriesSpy = vi.spyOn( - GitService, - "retrieveUserGitRepositories", - ); - retrieveUserGitRepositoriesSpy.mockResolvedValue({ - items: MOCK_RESPOSITORIES, - next_page_id: null, - }); - }); - - it("should call create conversation with suggest task trigger and selected suggested task", async () => { - const createConversationSpy = vi - .spyOn(AgentServerConversationService, "createConversation") - .mockResolvedValue({ - id: "task-id", - created_by_user_id: null, - status: "READY", - detail: null, - app_conversation_id: "conv-123", - agent_server_url: "http://agent-server.local", - request: { - initial_message: null, - processors: [], - llm_model: null, - selected_repository: MOCK_RESPOSITORIES[0].full_name, - selected_branch: null, - git_provider: "github", - suggested_task: null, - title: null, - trigger: null, - pr_number: [], - parent_conversation_id: null, - agent_type: "default", - }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); - - renderTaskCard(MOCK_TASK_1); - - const launchButton = screen.getByTestId("task-launch-button"); - await userEvent.click(launchButton); - - expect(createConversationSpy).toHaveBeenCalledWith({ - metadata: { - selected_repository: MOCK_TASK_1.repo, - selected_branch: null, - git_provider: MOCK_TASK_1.git_provider, - }, - }); - }); - }); - - it("should navigate to the conversation page after creating a conversation", async () => { - const navigate = vi.fn(); - - vi.spyOn( - AgentServerConversationService, - "createConversation", - ).mockResolvedValue({ - id: "task-id", - created_by_user_id: null, - status: "READY", - detail: null, - app_conversation_id: "test-conversation-id", - agent_server_url: "http://agent-server.local", - request: { - initial_message: null, - processors: [], - llm_model: null, - selected_repository: "repo1", - selected_branch: "main", - git_provider: "github", - suggested_task: null, - title: null, - trigger: null, - pr_number: [], - parent_conversation_id: null, - agent_type: "default", - }, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); - - renderTaskCard(MOCK_TASK_1, navigate); - - const launchButton = screen.getByTestId("task-launch-button"); - await userEvent.click(launchButton); - - await waitFor(() => { - expect(navigate).toHaveBeenCalledWith( - "/conversations/test-conversation-id", - ); - }); - }); -}); diff --git a/__tests__/components/features/home/task-suggestions.test.tsx b/__tests__/components/features/home/task-suggestions.test.tsx deleted file mode 100644 index f0dd90f25e..0000000000 --- a/__tests__/components/features/home/task-suggestions.test.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { render, screen, waitFor } from "@testing-library/react"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { createRoutesStub } from "react-router"; -import { TaskSuggestions } from "#/components/features/home/tasks/task-suggestions"; -import { SuggestionsService } from "#/api/suggestions-service/suggestions-service.api"; -import { MOCK_TASKS } from "#/mocks/task-suggestions-handlers"; -import { ActiveBackendProvider } from "#/contexts/active-backend-context"; -import { - __resetActiveStoreForTests, - setActiveSelection, - setRegisteredBackends, -} from "#/api/backend-registry/active-store"; -import type { Backend } from "#/api/backend-registry/types"; - -// Mock the translation function -vi.mock("react-i18next", async () => { - const actual = await vi.importActual("react-i18next"); - return { - ...(actual as object), - useTranslation: () => ({ - t: (key: string) => key, - i18n: { - changeLanguage: () => new Promise(() => {}), - }, - }), - }; -}); - -// Mock the dependencies for useShouldShowUserFeatures -vi.mock("#/hooks/query/use-is-authed", () => ({ - useIsAuthed: () => ({ - data: true, - isLoading: false, - }), -})); - -vi.mock("#/hooks/query/use-config", () => ({ - useConfig: () => ({ - data: {}, - isLoading: false, - }), -})); - -const { useUserProvidersMock } = vi.hoisted(() => ({ - useUserProvidersMock: vi.fn(() => ({ - providers: [{ id: "github", name: "GitHub" }], - isLoading: false, - })), -})); - -vi.mock("#/hooks/use-user-providers", () => ({ - useUserProviders: () => useUserProvidersMock(), -})); - -const cloudBackend: Backend = { - id: "prod", - name: "Production", - host: "https://app.all-hands.dev", - apiKey: "k", - kind: "cloud", -}; - -const renderTaskSuggestions = () => { - const RouterStub = createRoutesStub([ - { - Component: () => , - path: "/", - }, - { - Component: () =>
, - path: "/conversations/:conversationId", - }, - { - Component: () =>
, - path: "/settings", - }, - ]); - - return render(, { - wrapper: ({ children }) => ( - - {children} - - ), - }); -}; - -describe("TaskSuggestions", () => { - const getSuggestedTasksSpy = vi.spyOn( - SuggestionsService, - "getSuggestedTasks", - ); - - beforeEach(() => { - window.localStorage.clear(); - __resetActiveStoreForTests(); - setRegisteredBackends([cloudBackend]); - setActiveSelection({ backendId: cloudBackend.id }); - }); - - afterEach(() => { - vi.clearAllMocks(); - window.localStorage.clear(); - __resetActiveStoreForTests(); - }); - - it("should render the task suggestions section", () => { - renderTaskSuggestions(); - screen.getByTestId("task-suggestions"); - }); - - it("renders nothing when the active backend is local", () => { - setRegisteredBackends([]); - setActiveSelection(null); - - renderTaskSuggestions(); - - expect(screen.queryByTestId("task-suggestions")).not.toBeInTheDocument(); - }); - - it("should render an empty message if there are no tasks", async () => { - getSuggestedTasksSpy.mockResolvedValue([]); - renderTaskSuggestions(); - await screen.findByText("TASKS$NO_TASKS_AVAILABLE"); - }); - - it("should render the task groups with the correct titles", async () => { - getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); - renderTaskSuggestions(); - - await waitFor(() => { - // Check for repository names (grouped by repo) - only the first 3 tasks are shown - screen.getByText("octocat/hello-world"); - screen.getByText("octocat/earth"); - }); - }); - - it("should render the task cards with the correct task details", async () => { - getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); - renderTaskSuggestions(); - - await waitFor(() => { - // Only check for the first 3 tasks that are actually rendered - // The component limits to 3 tasks due to getLimitedTaskGroups function - screen.getByText("Fix merge conflicts"); // First task from octocat/hello-world - screen.getByText("Fix broken CI checks"); // First task from octocat/earth - screen.getByText("Fix issue"); // Second task from octocat/earth - }); - }); - - it("should render skeletons when loading", async () => { - getSuggestedTasksSpy.mockResolvedValue(MOCK_TASKS); - renderTaskSuggestions(); - - const skeletons = await screen.findAllByTestId("task-group-skeleton"); - expect(skeletons.length).toBeGreaterThan(0); - - await waitFor(() => { - // Check for repository names (grouped by repo) - only the first 3 tasks are shown - screen.getByText("octocat/hello-world"); - screen.getByText("octocat/earth"); - }); - - expect(screen.queryByTestId("task-group-skeleton")).not.toBeInTheDocument(); - }); -}); diff --git a/__tests__/components/features/settings/mcp-settings/mcp-server-list.test.tsx b/__tests__/components/features/settings/mcp-settings/mcp-server-list.test.tsx deleted file mode 100644 index 05261ab8b8..0000000000 --- a/__tests__/components/features/settings/mcp-settings/mcp-server-list.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, it, expect, vi } from "vitest"; -import { MCPServerList } from "#/components/features/settings/mcp-settings/mcp-server-list"; - -// Mock react-i18next -vi.mock("react-i18next", () => ({ - useTranslation: () => ({ - t: (key: string) => key, - }), -})); - -const mockServers = [ - { - id: "sse-0", - type: "sse" as const, - url: "https://very-long-url-that-could-cause-layout-overflow.example.com/api/v1/mcp/server/endpoint/with/many/path/segments", - }, - { - id: "stdio-0", - type: "stdio" as const, - name: "test-stdio-server", - command: "python", - args: ["-m", "test_server"], - }, -]; - -describe("MCPServerList", () => { - it("should render servers with proper layout structure", () => { - const mockOnEdit = vi.fn(); - const mockOnDelete = vi.fn(); - - render( - , - ); - - // Check that the table structure is rendered - const table = screen.getByRole("table"); - expect(table).toBeInTheDocument(); - - // Check that server items are rendered - const serverItems = screen.getAllByTestId("mcp-server-item"); - expect(serverItems).toHaveLength(2); - - // Check that action buttons are present for each server - const editButtons = screen.getAllByTestId("edit-mcp-server-button"); - const deleteButtons = screen.getAllByTestId("delete-mcp-server-button"); - expect(editButtons).toHaveLength(2); - expect(deleteButtons).toHaveLength(2); - }); - - it("should render empty state when no servers", () => { - const mockOnEdit = vi.fn(); - const mockOnDelete = vi.fn(); - - render( - , - ); - - expect(screen.getByText("SETTINGS$MCP_NO_SERVERS")).toBeInTheDocument(); - }); - - it("should handle long URLs without breaking layout", () => { - const longUrlServer = { - id: "sse-0", - type: "sse" as const, - url: "https://extremely-long-url-that-would-previously-cause-layout-overflow-and-push-action-buttons-out-of-view.example.com/api/v1/mcp/server/endpoint/with/many/path/segments/and/query/parameters?param1=value1¶m2=value2¶m3=value3", - }; - - const mockOnEdit = vi.fn(); - const mockOnDelete = vi.fn(); - - render( - , - ); - - // Check that action buttons are still present and accessible - const editButton = screen.getByTestId("edit-mcp-server-button"); - const deleteButton = screen.getByTestId("delete-mcp-server-button"); - - expect(editButton).toBeInTheDocument(); - expect(deleteButton).toBeInTheDocument(); - - // Check that the URL is properly displayed with title attribute for accessibility - const detailsCells = screen.getAllByTitle(longUrlServer.url); - expect(detailsCells).toHaveLength(2); // Name and Details columns both have the URL - }); - - it("should display command and arguments for STDIO servers", () => { - const stdioServer = { - id: "stdio-1", - type: "stdio" as const, - name: "test-server", - command: "python", - args: ["-m", "test_module", "--verbose"], - }; - - const mockOnEdit = vi.fn(); - const mockOnDelete = vi.fn(); - - render( - , - ); - - // Check that the server details show command + arguments - const expectedDetails = "python -m test_module --verbose"; - expect(screen.getByTitle(expectedDetails)).toBeInTheDocument(); - expect(screen.getByText(expectedDetails)).toBeInTheDocument(); - }); - - it("should fallback to server name for STDIO servers without command", () => { - const stdioServer = { - id: "stdio-2", - type: "stdio" as const, - name: "fallback-server", - }; - - const mockOnEdit = vi.fn(); - const mockOnDelete = vi.fn(); - - render( - , - ); - - // Check that the server details show the server name as fallback - // Both name and details columns will have the same value, so we expect 2 elements - const fallbackElements = screen.getAllByTitle("fallback-server"); - expect(fallbackElements).toHaveLength(2); - - const fallbackTextElements = screen.getAllByText("fallback-server"); - expect(fallbackTextElements).toHaveLength(2); - }); -}); diff --git a/__tests__/services/actions.test.ts b/__tests__/services/actions.test.ts deleted file mode 100644 index 8054b999d1..0000000000 --- a/__tests__/services/actions.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { handleStatusMessage } from "#/services/actions"; -import { StatusMessage } from "#/types/message"; -import { queryClient } from "#/query-client-config"; -import { useStatusStore } from "#/stores/status-store"; -import { trackError } from "#/utils/error-handler"; - -// Mock dependencies -vi.mock("#/query-client-config", () => ({ - queryClient: { - invalidateQueries: vi.fn(), - }, -})); - -vi.mock("#/stores/status-store", () => ({ - useStatusStore: { - getState: vi.fn(() => ({ - setCurStatusMessage: vi.fn(), - })), - }, -})); - -vi.mock("#/utils/error-handler", () => ({ - trackError: vi.fn(), -})); - -describe("handleStatusMessage", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it("should invalidate queries when receiving a conversation title update", () => { - // Create a status message with a conversation title - const statusMessage: StatusMessage = { - status_update: true, - type: "info", - message: "conversation-123", - conversation_title: "New Conversation Title", - }; - - // Call the function - handleStatusMessage(statusMessage); - - // Verify that queryClient.invalidateQueries was called with the correct parameters - expect(queryClient.invalidateQueries).toHaveBeenCalledWith({ - queryKey: ["user", "conversation", "conversation-123"], - }); - }); - - it("should call setCurStatusMessage for info messages without conversation_title", () => { - // Create a status message without a conversation title - const statusMessage: StatusMessage = { - status_update: true, - type: "info", - message: "Some info message", - }; - - const mockSetCurStatusMessage = vi.fn(); - vi.mocked(useStatusStore.getState).mockReturnValue({ - setCurStatusMessage: mockSetCurStatusMessage, - curStatusMessage: { - status_update: true, - type: "info", - id: "", - message: "", - }, - }); - - // Call the function - handleStatusMessage(statusMessage); - - // Verify that setCurStatusMessage was called with the correct message - expect(mockSetCurStatusMessage).toHaveBeenCalledWith(statusMessage); - - // Verify that queryClient.invalidateQueries was not called - expect(queryClient.invalidateQueries).not.toHaveBeenCalled(); - }); - - it("should call trackError for error messages", () => { - // Create an error status message - const statusMessage: StatusMessage = { - status_update: true, - type: "error", - id: "ERROR_ID", - message: "Some error message", - }; - - // Call the function - handleStatusMessage(statusMessage); - - // Verify that trackError was called with the correct parameters - expect(trackError).toHaveBeenCalledWith({ - message: "Some error message", - source: "chat", - metadata: { msgId: "ERROR_ID" }, - }); - - // Verify that queryClient.invalidateQueries was not called - expect(queryClient.invalidateQueries).not.toHaveBeenCalled(); - }); -}); diff --git a/__tests__/services/actions.test.tsx b/__tests__/services/actions.test.tsx deleted file mode 100644 index 555fd18caa..0000000000 --- a/__tests__/services/actions.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import ActionType from "#/types/action-type"; -import { ActionMessage } from "#/types/message"; -import { useCommandStore } from "#/stores/command-store"; - -const mockDispatch = vi.fn(); -const mockAppendInput = vi.fn(); - -vi.mock("#/store", () => ({ - default: { - dispatch: mockDispatch, - }, -})); - -describe("handleActionMessage", () => { - beforeEach(() => { - vi.clearAllMocks(); - useCommandStore.setState({ - appendInput: mockAppendInput, - }); - }); - - it("should handle RUN actions by adding input to terminal", async () => { - const { handleActionMessage } = await import("#/services/actions"); - - const runAction: ActionMessage = { - id: 1, - source: "agent", - action: ActionType.RUN, - args: { - command: "ls -la", - }, - message: "Running command: ls -la", - timestamp: "2023-01-01T00:00:00Z", - }; - - // Handle the action - handleActionMessage(runAction); - - // Check that appendInput was called with the command - expect(mockAppendInput).toHaveBeenCalledWith("ls -la"); - expect(mockDispatch).not.toHaveBeenCalled(); - }); - - it("should handle RUN_IPYTHON actions as no-op (Jupyter removed)", async () => { - const { handleActionMessage } = await import("#/services/actions"); - - const ipythonAction: ActionMessage = { - id: 2, - source: "agent", - action: ActionType.RUN_IPYTHON, - args: { - code: "print('Hello from Jupyter!')", - }, - message: - "Running Python code interactively: print('Hello from Jupyter!')", - timestamp: "2023-01-01T00:00:00Z", - }; - - // Handle the action - handleActionMessage(ipythonAction); - - // Jupyter functionality has been removed, so nothing should be called - expect(mockAppendInput).not.toHaveBeenCalled(); - }); - - it("should not process hidden actions", async () => { - const { handleActionMessage } = await import("#/services/actions"); - - const hiddenAction: ActionMessage = { - id: 3, - source: "agent", - action: ActionType.RUN, - args: { - command: "secret command", - hidden: "true", - }, - message: "Running command: secret command", - timestamp: "2023-01-01T00:00:00Z", - }; - - // Handle the action - handleActionMessage(hiddenAction); - - // Check that nothing was dispatched or called - expect(mockDispatch).not.toHaveBeenCalled(); - expect(mockAppendInput).not.toHaveBeenCalled(); - }); -}); diff --git a/__tests__/services/observations.test.tsx b/__tests__/services/observations.test.tsx deleted file mode 100644 index 4f43cfa16a..0000000000 --- a/__tests__/services/observations.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, it, vi, beforeEach, afterEach } from "vitest"; - -// Mock the store module -vi.mock("#/store", () => ({ - default: { - dispatch: vi.fn(), - }, -})); - -describe("handleObservationMessage", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it.todo("updates browser state when receiving a browse observation"); - - it.todo( - "updates browser state when receiving a browse_interactive observation", - ); -}); diff --git a/__tests__/ui/card.test.tsx b/__tests__/ui/card.test.tsx deleted file mode 100644 index 14162affef..0000000000 --- a/__tests__/ui/card.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import { describe, expect, it } from "vitest"; -import { Card } from "#/ui/card"; - -describe("Card", () => { - it("should render children", () => { - render(Card Content); - - expect(screen.getByText("Card Content")).toBeInTheDocument(); - }); - - it("should render with testId", () => { - render(Content); - - expect(screen.getByTestId("test-card")).toBeInTheDocument(); - }); - - // Prop-passthrough contract: consumer-supplied className must land on the - // element. - it("should apply custom className", () => { - render( - - Content - , - ); - - expect(screen.getByTestId("test-card")).toHaveClass("custom-class"); - }); -}); diff --git a/__tests__/use-suggested-tasks.test.ts b/__tests__/use-suggested-tasks.test.ts deleted file mode 100644 index 868ece2136..0000000000 --- a/__tests__/use-suggested-tasks.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { renderHook } from "@testing-library/react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import React from "react"; -import { useSuggestedTasks } from "../src/hooks/query/use-suggested-tasks"; -import { useShouldShowUserFeatures } from "../src/hooks/use-should-show-user-features"; - -// Mock the dependencies -vi.mock("../src/hooks/use-should-show-user-features"); -vi.mock("#/api/suggestions-service/suggestions-service.api", () => ({ - SuggestionsService: { - getSuggestedTasks: vi.fn().mockResolvedValue([]), - }, -})); - -const mockUseShouldShowUserFeatures = vi.mocked(useShouldShowUserFeatures); - -const createWrapper = () => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }); - - return ({ children }: { children: React.ReactNode }) => - React.createElement(QueryClientProvider, { client: queryClient }, children); -}; - -describe("useSuggestedTasks", () => { - beforeEach(() => { - vi.clearAllMocks(); - // Default to disabled - mockUseShouldShowUserFeatures.mockReturnValue(false); - }); - - it("should be disabled when useShouldShowUserFeatures returns false", () => { - mockUseShouldShowUserFeatures.mockReturnValue(false); - - const { result } = renderHook(() => useSuggestedTasks(), { - wrapper: createWrapper(), - }); - - expect(result.current.isLoading).toBe(false); - expect(result.current.isFetching).toBe(false); - }); - - it("should be enabled when useShouldShowUserFeatures returns true", () => { - mockUseShouldShowUserFeatures.mockReturnValue(true); - - const { result } = renderHook(() => useSuggestedTasks(), { - wrapper: createWrapper(), - }); - - // When enabled, the query should be loading/fetching - expect(result.current.isLoading).toBe(true); - }); -}); diff --git a/__tests__/utils/group-suggested-tasks.test.ts b/__tests__/utils/group-suggested-tasks.test.ts deleted file mode 100644 index ccfc7f18a7..0000000000 --- a/__tests__/utils/group-suggested-tasks.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { expect, test } from "vitest"; -import { SuggestedTask, SuggestedTaskGroup } from "#/utils/types"; -import { groupSuggestedTasks } from "#/utils/group-suggested-tasks"; - -const rawTasks: SuggestedTask[] = [ - { - issue_number: 1, - repo: "repo1", - title: "Task 1", - task_type: "MERGE_CONFLICTS", - git_provider: "github", - }, - { - issue_number: 2, - repo: "repo1", - title: "Task 2", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 3, - repo: "repo2", - title: "Task 3", - task_type: "UNRESOLVED_COMMENTS", - git_provider: "github", - }, - { - issue_number: 4, - repo: "repo2", - title: "Task 4", - task_type: "OPEN_ISSUE", - git_provider: "github", - }, - { - issue_number: 5, - repo: "repo3", - title: "Task 5", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, -]; - -const groupedTasks: SuggestedTaskGroup[] = [ - { - title: "repo1", - tasks: [ - { - issue_number: 1, - repo: "repo1", - title: "Task 1", - task_type: "MERGE_CONFLICTS", - git_provider: "github", - }, - { - issue_number: 2, - repo: "repo1", - title: "Task 2", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - ], - }, - { - title: "repo2", - tasks: [ - { - issue_number: 3, - repo: "repo2", - title: "Task 3", - task_type: "UNRESOLVED_COMMENTS", - git_provider: "github", - }, - { - issue_number: 4, - repo: "repo2", - title: "Task 4", - task_type: "OPEN_ISSUE", - git_provider: "github", - }, - ], - }, - { - title: "repo3", - tasks: [ - { - issue_number: 5, - repo: "repo3", - title: "Task 5", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - ], - }, -]; - -test("groupSuggestedTasks", () => { - expect(groupSuggestedTasks(rawTasks)).toEqual(groupedTasks); -}); diff --git a/src/api/backend-registry/index.ts b/src/api/backend-registry/index.ts deleted file mode 100644 index f3e94281c1..0000000000 --- a/src/api/backend-registry/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./types"; -export * from "./storage"; -export * from "./default-backend"; -export * from "./active-store"; -export * from "./auth"; diff --git a/src/api/cloud/suggestions-service.api.ts b/src/api/cloud/suggestions-service.api.ts deleted file mode 100644 index 425ee0e2bb..0000000000 --- a/src/api/cloud/suggestions-service.api.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SuggestedTask } from "#/utils/types"; -import { getActiveBackend } from "../backend-registry/active-store"; -import type { Backend } from "../backend-registry/types"; -import { callCloudProxy } from "./proxy"; - -function getActiveCloudBackend(): Backend { - const active = getActiveBackend().backend; - if (active.kind !== "cloud") { - throw new Error("Cloud suggested-tasks call requires a cloud backend."); - } - return active; -} - -export async function getCloudSuggestedTasks(args: { - pageId?: string; - limit?: number; -}): Promise<{ items: SuggestedTask[]; next_page_id: string | null }> { - const backend = getActiveCloudBackend(); - const params = new URLSearchParams(); - params.set("limit", String(args.limit ?? 30)); - if (args.pageId) params.set("page_id", args.pageId); - - const data = await callCloudProxy<{ - items: SuggestedTask[]; - next_page_id: string | null; - }>({ - backend, - method: "GET", - path: `/api/v1/git/suggested-tasks/search?${params.toString()}`, - }); - - return { - items: data?.items ?? [], - next_page_id: data?.next_page_id ?? null, - }; -} diff --git a/src/api/suggestions-service/suggestions-service.api.ts b/src/api/suggestions-service/suggestions-service.api.ts deleted file mode 100644 index 16e70c26c2..0000000000 --- a/src/api/suggestions-service/suggestions-service.api.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { SuggestedTask } from "#/utils/types"; -import { getActiveBackend } from "../backend-registry/active-store"; -import { getCloudSuggestedTasks } from "../cloud/suggestions-service.api"; - -export class SuggestionsService { - /** - * Aggregate suggested tasks for the calling user. Cloud exposes - * `/api/v1/git/suggested-tasks/search` which the proxy forwards to. - * Local agent-server has no equivalent endpoint, so we return an - * empty list there — the home page's `` already - * renders nothing when the active backend is local. - */ - static async getSuggestedTasks( - pageId?: string, - limit: number = 30, - ): Promise { - if (getActiveBackend().backend.kind !== "cloud") { - return []; - } - const page = await getCloudSuggestedTasks({ pageId, limit }); - return page.items; - } -} diff --git a/src/components/conversation-events/chat/event-message-components/index.ts b/src/components/conversation-events/chat/event-message-components/index.ts index 6644066fc8..4da5c75b3d 100644 --- a/src/components/conversation-events/chat/event-message-components/index.ts +++ b/src/components/conversation-events/chat/event-message-components/index.ts @@ -1,5 +1,4 @@ export { UserAssistantEventMessage } from "./user-assistant-event-message"; -export { ObservationPairEventMessage } from "./observation-pair-event-message"; export { ErrorEventMessage } from "./error-event-message"; export { FinishEventMessage } from "./finish-event-message"; export { GenericEventMessageWrapper } from "./generic-event-message-wrapper"; diff --git a/src/components/conversation-events/chat/event-message-components/observation-pair-event-message.tsx b/src/components/conversation-events/chat/event-message-components/observation-pair-event-message.tsx deleted file mode 100644 index 5f8a0eada5..0000000000 --- a/src/components/conversation-events/chat/event-message-components/observation-pair-event-message.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { ActionEvent } from "#/types/agent-server/core"; -import { isActionEvent } from "#/types/agent-server/type-guards"; -import { ChatMessage } from "../../../features/chat/chat-message"; - -interface ObservationPairEventMessageProps { - event: ActionEvent; -} - -export function ObservationPairEventMessage({ - event, -}: ObservationPairEventMessageProps) { - if (!isActionEvent(event)) { - return null; - } - - // Check if there's thought content to display - const thoughtContent = event.thought - .filter((t) => t.type === "text") - .map((t) => t.text) - .join("\n"); - - // Defensive check: ensure action exists and has kind property - if ( - thoughtContent && - event.action?.kind && - event.action.kind !== "ThinkAction" - ) { - return ( -
- -
- ); - } - - return null; -} diff --git a/src/components/conversation-events/index.ts b/src/components/conversation-events/index.ts deleted file mode 100644 index d27da0d970..0000000000 --- a/src/components/conversation-events/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./chat"; diff --git a/src/components/features/automations/metadata-chip.tsx b/src/components/features/automations/metadata-chip.tsx deleted file mode 100644 index 5b5e46f054..0000000000 --- a/src/components/features/automations/metadata-chip.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { cn } from "#/utils/utils"; -import { extensionModuleCardPillClassName } from "#/utils/extension-module-card-classes"; - -interface MetadataChipProps { - icon: React.ReactNode; - label: string; -} - -export function MetadataChip({ icon, label }: MetadataChipProps) { - return ( - - {icon} - {label} - - ); -} diff --git a/src/components/features/chat/task-tracking/status-badge.tsx b/src/components/features/chat/task-tracking/status-badge.tsx deleted file mode 100644 index 6ed812187b..0000000000 --- a/src/components/features/chat/task-tracking/status-badge.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { cn, getStatusClassName } from "#/utils/utils"; - -interface StatusBadgeProps { - status: string; -} - -export function StatusBadge({ status }: StatusBadgeProps) { - return ( - - {status.replace("_", " ")} - - ); -} diff --git a/src/components/features/chat/task-tracking/status-icon.tsx b/src/components/features/chat/task-tracking/status-icon.tsx deleted file mode 100644 index d3181ea3af..0000000000 --- a/src/components/features/chat/task-tracking/status-icon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { getStatusIcon } from "#/utils/utils"; - -interface StatusIconProps { - status: string; -} - -export function StatusIcon({ status }: StatusIconProps) { - return {getStatusIcon(status)}; -} diff --git a/src/components/features/chat/task-tracking/task-list-section.tsx b/src/components/features/chat/task-tracking/task-list-section.tsx deleted file mode 100644 index b14666da20..0000000000 --- a/src/components/features/chat/task-tracking/task-list-section.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { TaskItem } from "./task-item"; -import LessonPlanIcon from "#/icons/lesson-plan.svg?react"; -import { I18nKey } from "#/i18n/declaration"; -import { Typography } from "#/ui/typography"; - -interface TaskListSectionProps { - taskList: Array<{ - id: string; - title: string; - status: "todo" | "in_progress" | "done"; - notes?: string; - }>; -} - -export function TaskListSection({ taskList }: TaskListSectionProps) { - const { t } = useTranslation("openhands"); - - return ( -
- {/* Header Tabs */} -
- - - {t(I18nKey.COMMON$TASKS)} - -
- - {/* Task Items */} -
- {taskList.map((task) => ( - - ))} -
-
- ); -} diff --git a/src/components/features/controls/tools.tsx b/src/components/features/controls/tools.tsx deleted file mode 100644 index 035aaed704..0000000000 --- a/src/components/features/controls/tools.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from "react"; -import { Wrench } from "lucide-react"; -import { useTranslation } from "react-i18next"; -import { I18nKey } from "#/i18n/declaration"; -import { useOptionalConversationId } from "#/hooks/use-conversation-id"; -import { ComboboxCaretInline } from "#/ui/combobox-caret"; -import { chatInputPillButtonClassName } from "#/utils/form-control-classes"; -import { ToolsContextMenu } from "./tools-context-menu"; -import { useConversationNameContextMenu } from "#/hooks/use-conversation-name-context-menu"; -import { useActiveConversation } from "#/hooks/query/use-active-conversation"; -import { SystemMessageModal } from "../conversation-panel/system-message-modal"; -import { SkillsModal } from "../conversation-panel/skills-modal"; -import { PluginsModal } from "../conversation-panel/plugins-modal"; -import { HooksModal } from "../conversation-panel/hooks-modal"; -import { cn } from "#/utils/utils"; - -export function Tools() { - const { t } = useTranslation("openhands"); - // Optional because this control also renders inside the home-page chat - // input shell, before any conversation exists. - const { conversationId } = useOptionalConversationId(); - const { data: conversation } = useActiveConversation(); - const [contextMenuOpen, setContextMenuOpen] = React.useState(false); - - const { - handleShowAgentTools, - handleShowSkills, - handleShowPlugins, - handleShowHooks, - systemModalVisible, - setSystemModalVisible, - skillsModalVisible, - setSkillsModalVisible, - pluginsModalVisible, - setPluginsModalVisible, - hooksModalVisible, - setHooksModalVisible, - systemMessage, - shouldShowAgentTools, - shouldShowHooks, - shouldShowPlugins, - } = useConversationNameContextMenu({ - conversationId: conversationId ?? undefined, - executionStatus: conversation?.execution_status, - showOptions: true, - onContextMenuToggle: setContextMenuOpen, - }); - - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - setContextMenuOpen(!contextMenuOpen); - }; - - return ( -
- - {contextMenuOpen && ( - setContextMenuOpen(false)} - onShowSkills={handleShowSkills} - onShowPlugins={handleShowPlugins} - onShowHooks={handleShowHooks} - onShowAgentTools={handleShowAgentTools} - shouldShowAgentTools={shouldShowAgentTools} - shouldShowHooks={shouldShowHooks} - shouldShowPlugins={shouldShowPlugins} - /> - )} - - {/* System Message Modal */} - setSystemModalVisible(false)} - systemMessage={systemMessage || null} - /> - - {/* Skills Modal */} - {skillsModalVisible && ( - setSkillsModalVisible(false)} /> - )} - - {/* Plugins Modal */} - {pluginsModalVisible && ( - setPluginsModalVisible(false)} /> - )} - - {/* Hooks Modal */} - {hooksModalVisible && ( - setHooksModalVisible(false)} /> - )} -
- ); -} diff --git a/src/components/features/conversation-panel/new-conversation-button.tsx b/src/components/features/conversation-panel/new-conversation-button.tsx deleted file mode 100644 index dcea5ecebd..0000000000 --- a/src/components/features/conversation-panel/new-conversation-button.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Plus } from "lucide-react"; - -import { I18nKey } from "#/i18n/declaration"; -import { cn } from "#/utils/utils"; -import { StyledTooltip } from "#/components/shared/buttons/styled-tooltip"; -import { useActiveBackend } from "#/contexts/active-backend-context"; - -import { - CloudNewConversationMenu, - type CloudNewConversationMenuTriggerProps, -} from "./cloud-new-conversation-menu"; -import { - LocalNewConversationMenu, - type LocalNewConversationMenuTriggerProps, -} from "./local-new-conversation-menu"; - -interface NewConversationButtonProps { - /** - * Render the trigger as a "+" icon-only button (used by the collapsed - * sidebar). The popover content is unchanged; only the trigger pill - * collapses. - */ - compact?: boolean; -} - -/** - * Sidebar "+ New Conversation" trigger. - * - * The popover content depends on the active backend: local backends operate - * on workspace folders so we surface the workspace picker, while cloud - * backends operate on git repositories so we surface a repository picker. - * - * The trigger pill (the "+" button itself) is identical for both variants; - * only the menu component differs. Both `Cloud/LocalNewConversationMenu` - * accept structurally identical trigger props, so a single `renderTrigger` - * callback can satisfy either menu's type signature. - */ -export function NewConversationButton({ - compact = false, -}: NewConversationButtonProps = {}) { - const { t } = useTranslation("openhands"); - const newConversationLabel = t(I18nKey.SIDEBAR$NEW_CONVERSATION); - const isCloud = useActiveBackend().backend.kind === "cloud"; - - const renderTrigger = React.useCallback( - ( - tp: - | CloudNewConversationMenuTriggerProps - | LocalNewConversationMenuTriggerProps, - ) => { - const triggerButton = ( - - ); - - return compact ? ( - - {triggerButton} - - ) : ( - triggerButton - ); - }, - [compact, newConversationLabel], - ); - - const wrapperClassName = cn(compact && "flex justify-center"); - const popoverClassName = compact ? "left-0 w-[260px]" : "left-0 right-0"; - - return isCloud ? ( - - ) : ( - - ); -} diff --git a/src/components/features/home/home-header/guide-message.tsx b/src/components/features/home/home-header/guide-message.tsx deleted file mode 100644 index 2180225ad0..0000000000 --- a/src/components/features/home/home-header/guide-message.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { I18nKey } from "#/i18n/declaration"; - -export function GuideMessage() { - const { t } = useTranslation("openhands"); - - return ( -
- {t(I18nKey.HOME$GUIDE_MESSAGE_TITLE)} - - {t(I18nKey.COMMON$CLICK_HERE)} - -
- ); -} diff --git a/src/components/features/home/home-header/home-header.tsx b/src/components/features/home/home-header/home-header.tsx deleted file mode 100644 index a66ef1a8e8..0000000000 --- a/src/components/features/home/home-header/home-header.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { GuideMessage } from "./guide-message"; -import { HomeHeaderTitle } from "./home-header-title"; - -export function HomeHeader() { - return ( -
- - -
- ); -} diff --git a/src/components/features/home/new-conversation.tsx b/src/components/features/home/new-conversation.tsx deleted file mode 100644 index 4927ce6c15..0000000000 --- a/src/components/features/home/new-conversation.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { I18nKey } from "#/i18n/declaration"; -import { useNavigation } from "#/context/navigation-context"; -import { BrandButton } from "../settings/brand-button"; -import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; -import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; -import PlusIcon from "#/icons/u-plus.svg?react"; - -export function NewConversation() { - const { t } = useTranslation("openhands"); - - const { navigate } = useNavigation(); - const { - mutate: createConversation, - isPending, - isSuccess, - } = useCreateConversation(); - const isCreatingConversationElsewhere = useIsCreatingConversation(); - - // We check for isSuccess because the app might require time to render - // into the new conversation screen after the conversation is created. - const isCreatingConversation = - isPending || isSuccess || isCreatingConversationElsewhere; - - return ( -
-
-
- - - {t(I18nKey.COMMON$START_FROM_SCRATCH)} - -
-
-
- - {t(I18nKey.HOME$NEW_PROJECT_DESCRIPTION)} - -
- - createConversation( - { entryPoint: "home_start_from_scratch" }, - { - onSuccess: (data) => - navigate(`/conversations/${data.conversation_id}`), - }, - ) - } - isDisabled={isCreatingConversation} - className="w-auto absolute bottom-5 left-5 right-5" - > - {!isCreatingConversation && t(I18nKey.COMMON$NEW_CONVERSATION)} - {isCreatingConversation && t(I18nKey.HOME$LOADING)} - -
- ); -} diff --git a/src/components/features/home/new-conversation/create-conversation-button.tsx b/src/components/features/home/new-conversation/create-conversation-button.tsx deleted file mode 100644 index c198b7e726..0000000000 --- a/src/components/features/home/new-conversation/create-conversation-button.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { BrandButton } from "../../settings/brand-button"; -import { useNavigation } from "#/context/navigation-context"; -import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; -import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; -import { I18nKey } from "#/i18n/declaration"; - -export function CreateConversationButton() { - const { t } = useTranslation("openhands"); - const { navigate } = useNavigation(); - - const { - mutate: createConversation, - isPending, - isSuccess, - } = useCreateConversation(); - const isCreatingConversationElsewhere = useIsCreatingConversation(); - - // We check for isSuccess because the app might require time to render - // into the new conversation screen after the conversation is created. - const isCreatingConversation = - isPending || isSuccess || isCreatingConversationElsewhere; - - const handleCreateConversation = () => { - createConversation( - { entryPoint: "home_new_conversation_button" }, - { - onSuccess: (data) => navigate(`/conversations/${data.conversation_id}`), - }, - ); - }; - - return ( - - {!isCreatingConversation && t(I18nKey.COMMON$NEW_CONVERSATION)} - {isCreatingConversation && t(I18nKey.HOME$LOADING)} - - ); -} diff --git a/src/components/features/home/new-conversation/new-conversation.tsx b/src/components/features/home/new-conversation/new-conversation.tsx deleted file mode 100644 index 0b511d7d5d..0000000000 --- a/src/components/features/home/new-conversation/new-conversation.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { I18nKey } from "#/i18n/declaration"; -import PlusIcon from "#/icons/u-plus.svg?react"; -import { CardTitle } from "#/ui/card-title"; -import { Typography } from "#/ui/typography"; -import { CreateConversationButton } from "./create-conversation-button"; -import { Card } from "#/ui/card"; - -export function NewConversation() { - const { t } = useTranslation("openhands"); - - return ( - - }> - {t(I18nKey.COMMON$START_FROM_SCRATCH)} - - - {t(I18nKey.HOME$NEW_PROJECT_DESCRIPTION)} - - - - ); -} diff --git a/src/components/features/home/tasks/task-card.tsx b/src/components/features/home/tasks/task-card.tsx deleted file mode 100644 index cc199ff5e3..0000000000 --- a/src/components/features/home/tasks/task-card.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { SuggestedTask } from "#/utils/types"; -import { useIsCreatingConversation } from "#/hooks/use-is-creating-conversation"; -import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; -import { TaskIssueNumber } from "./task-issue-number"; -import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-store"; -import { useNavigation } from "#/context/navigation-context"; -import { cn } from "#/utils/utils"; -import { I18nKey } from "#/i18n/declaration"; - -const getTaskTypeMap = ( - t: (key: string) => string, -): Record => ({ - FAILING_CHECKS: t(I18nKey.HOME$FIX_FAILING_CHECKS), - MERGE_CONFLICTS: t(I18nKey.HOME$RESOLVE_MERGE_CONFLICTS), - OPEN_ISSUE: t(I18nKey.HOME$OPEN_ISSUE), - UNRESOLVED_COMMENTS: t(I18nKey.HOME$RESOLVE_UNRESOLVED_COMMENTS), -}); - -interface TaskCardProps { - task: SuggestedTask; -} - -export function TaskCard({ task }: TaskCardProps) { - const enqueuePendingMessage = useOptimisticUserMessageStore( - (state) => state.enqueuePendingMessage, - ); - const { mutate: createConversation } = useCreateConversation(); - const isCreatingConversation = useIsCreatingConversation(); - const { t } = useTranslation("openhands"); - const { navigate } = useNavigation(); - - const handleLaunchConversation = () => - createConversation( - { - repository: { - name: task.repo, - gitProvider: task.git_provider, - }, - suggestedTask: task, - entryPoint: "home_task_card", - }, - { - onSuccess: (data) => { - // Enqueue the pending message after the new conversation exists so - // it can be tagged with the conversation id and only show up in the - // target conversation's chat (not in whatever convo the user was - // looking at when they clicked the task). - enqueuePendingMessage({ - conversationId: data.conversation_id, - text: t(I18nKey.TASK$ADDRESSING_TASK), - }); - navigate(`/conversations/${data.conversation_id}`); - }, - }, - ); - - // Determine the correct URL format based on git provider - let href: string; - if (task.git_provider === "gitlab") { - const issueType = - task.task_type === "OPEN_ISSUE" ? "issues" : "merge_requests"; - href = `https://gitlab.com/${task.repo}/-/${issueType}/${task.issue_number}`; - } else if (task.git_provider === "bitbucket") { - const issueType = - task.task_type === "OPEN_ISSUE" ? "issues" : "pull-requests"; - href = `https://bitbucket.org/${task.repo}/${issueType}/${task.issue_number}`; - } else if (task.git_provider === "azure_devops") { - // Azure DevOps URL format: https://dev.azure.com/{organization}/{project}/_workitems/edit/{id} - // or https://dev.azure.com/{organization}/{project}/_git/{repo}/pullrequest/{id} - const azureDevOpsBaseUrl = "https://dev.azure.com"; - if (task.task_type === "OPEN_ISSUE") { - href = `${azureDevOpsBaseUrl}/${task.repo}/_workitems/edit/${task.issue_number}`; - } else { - href = `${azureDevOpsBaseUrl}/${task.repo}/_git/${task.repo.split("/")[1]}/pullrequest/${task.issue_number}`; - } - } else { - const hrefType = task.task_type === "OPEN_ISSUE" ? "issues" : "pull"; - href = `https://github.com/${task.repo}/${hrefType}/${task.issue_number}`; - } - - return ( - - ); -} diff --git a/src/components/features/home/tasks/task-group.tsx b/src/components/features/home/tasks/task-group.tsx deleted file mode 100644 index ad5b2fd429..0000000000 --- a/src/components/features/home/tasks/task-group.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FaBitbucket, FaGithub, FaGitlab } from "react-icons/fa6"; -import { TaskCard } from "./task-card"; -import { TaskItemTitle } from "./task-item-title"; -import { SuggestedTask } from "#/utils/types"; - -interface TaskGroupProps { - title: string; - tasks: SuggestedTask[]; -} - -export function TaskGroup({ title, tasks }: TaskGroupProps) { - const gitProvider = tasks.length > 0 ? tasks[0].git_provider : null; - - return ( -
-
- {gitProvider === "github" && } - {gitProvider === "gitlab" && } - {gitProvider === "bitbucket" && } - {title} -
- -
    - {tasks.map((task) => ( -
  • - -
  • - ))} -
-
- ); -} diff --git a/src/components/features/home/tasks/task-issue-number.tsx b/src/components/features/home/tasks/task-issue-number.tsx deleted file mode 100644 index 55c7454996..0000000000 --- a/src/components/features/home/tasks/task-issue-number.tsx +++ /dev/null @@ -1,19 +0,0 @@ -interface TaskIssueNumberProps { - issueNumber: number; - href: string; -} - -export function TaskIssueNumber({ href, issueNumber }: TaskIssueNumberProps) { - return ( - - - #{issueNumber} - - - ); -} diff --git a/src/components/features/home/tasks/task-item-title.tsx b/src/components/features/home/tasks/task-item-title.tsx deleted file mode 100644 index 92f3efb050..0000000000 --- a/src/components/features/home/tasks/task-item-title.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function TaskItemTitle({ children: title }: React.PropsWithChildren) { - return ( -
-

{title}

-
- ); -} diff --git a/src/components/features/home/tasks/task-suggestions-skeleton.tsx b/src/components/features/home/tasks/task-suggestions-skeleton.tsx deleted file mode 100644 index 2044c9a199..0000000000 --- a/src/components/features/home/tasks/task-suggestions-skeleton.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { cn } from "#/utils/utils"; - -function TaskCardSkeleton() { - return ( -
  • -
    - -
    -
    -
    -
    - -
    -
  • - ); -} - -interface TaskGroupSkeletonProps { - items?: number; -} - -function TaskGroupSkeleton({ items = 3 }: TaskGroupSkeletonProps) { - return ( -
    -
    -
    -
    - -
      - {Array.from({ length: items }).map((_, index) => ( - - ))} -
    - -
    -
    - ); -} - -export function TaskSuggestionsSkeleton() { - return ; -} diff --git a/src/components/features/home/tasks/task-suggestions.tsx b/src/components/features/home/tasks/task-suggestions.tsx deleted file mode 100644 index cdd6b254ab..0000000000 --- a/src/components/features/home/tasks/task-suggestions.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { TaskGroup } from "./task-group"; -import { useSuggestedTasks } from "#/hooks/query/use-suggested-tasks"; -import { TaskSuggestionsSkeleton } from "./task-suggestions-skeleton"; -import { cn, getDisplayedTaskGroups, getTotalTaskCount } from "#/utils/utils"; -import { I18nKey } from "#/i18n/declaration"; -import { GitRepository } from "#/types/git"; -import { Typography } from "#/ui/typography"; -import { useActiveBackend } from "#/contexts/active-backend-context"; - -interface TaskSuggestionsProps { - filterFor?: GitRepository | null; -} - -export function TaskSuggestions({ filterFor }: TaskSuggestionsProps) { - const { t } = useTranslation("openhands"); - const [isExpanded, setIsExpanded] = useState(false); - const isCloud = useActiveBackend().backend.kind === "cloud"; - - const { data: tasks, isLoading } = useSuggestedTasks(); - - if (!isCloud) return null; - - const suggestedTasks = filterFor - ? tasks?.filter( - (element) => - element.title === filterFor.full_name && - !!element.tasks.find( - (task) => task.git_provider === filterFor.git_provider, - ), - ) - : tasks; - - const hasSuggestedTasks = suggestedTasks && suggestedTasks.length > 0; - - // Get the task groups to display based on expanded state - const displayedTaskGroups = getDisplayedTaskGroups( - suggestedTasks, - isExpanded, - ); - - // Check if there are more individual tasks to show - const hasMoreTasks = getTotalTaskCount(suggestedTasks) > 3; - - const handleToggle = () => { - setIsExpanded((prev) => !prev); - }; - - return ( -
    -
    -

    - {t(I18nKey.TASKS$SUGGESTED_TASKS)} -

    -
    - -
    - {isLoading && ( -
    - -
    - )} - {!hasSuggestedTasks && !isLoading && ( - - {t(I18nKey.TASKS$NO_TASKS_AVAILABLE)} - - )} - - {!isLoading && - displayedTaskGroups && - displayedTaskGroups.length > 0 && ( -
    -
    -
    - {displayedTaskGroups.map((taskGroup, index) => ( - - ))} -
    -
    -
    - )} -
    - - {!isLoading && hasMoreTasks && ( -
    - -
    - )} -
    - ); -} diff --git a/src/components/features/settings/agent-server-onboarding.tsx b/src/components/features/settings/agent-server-onboarding.tsx deleted file mode 100644 index 855043c438..0000000000 --- a/src/components/features/settings/agent-server-onboarding.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React from "react"; -import { RefreshCw } from "lucide-react"; -import { useTranslation } from "react-i18next"; -import { getAgentServerFormDefaults } from "#/api/agent-server-config"; -import { - getRegisteredBackends, - setRegisteredBackends, -} from "#/api/backend-registry/active-store"; -import { - SEEDED_DEFAULT_BACKEND_ID, - DEFAULT_LOCAL_BACKEND_NAME, -} from "#/api/backend-registry/default-backend"; -import type { Backend } from "#/api/backend-registry/types"; -import { cn } from "#/utils/utils"; -import { buildAgentCanvasPath } from "#/utils/base-path"; -import { BrandButton } from "./brand-button"; -import { SettingsInput } from "./settings-input"; -import { I18nKey } from "#/i18n/declaration"; - -type AgentServerConnectionFormVariant = "settings" | "onboarding"; - -interface AgentServerConnectionFormProps { - className?: string; - formClassName?: string; - variant?: AgentServerConnectionFormVariant; - showSectionHeader?: boolean; -} - -export function AgentServerConnectionForm({ - className, - formClassName, - variant = "onboarding", - showSectionHeader, -}: AgentServerConnectionFormProps) { - const { t } = useTranslation("openhands"); - const defaults = React.useMemo(() => getAgentServerFormDefaults(), []); - const [baseUrl, setBaseUrl] = React.useState(defaults.baseUrl); - const [sessionApiKey, setSessionApiKey] = React.useState( - defaults.sessionApiKey, - ); - - const formIsClean = - baseUrl === defaults.baseUrl && sessionApiKey === defaults.sessionApiKey; - const isOnboarding = variant === "onboarding"; - const shouldShowSectionHeader = showSectionHeader ?? isOnboarding; - - const reconnect = () => { - window.location.assign(buildAgentCanvasPath("/")); - }; - - const syncDefaultBackendInRegistry = () => { - const trimmedHost = baseUrl.trim(); - if (!trimmedHost) return; - - const trimmedKey = sessionApiKey.trim(); - const current = getRegisteredBackends(); - const defaultEntry: Backend = { - id: SEEDED_DEFAULT_BACKEND_ID, - name: DEFAULT_LOCAL_BACKEND_NAME, - host: trimmedHost, - apiKey: trimmedKey, - kind: "local", - }; - - const existingIndex = current.findIndex( - (b) => b.id === SEEDED_DEFAULT_BACKEND_ID, - ); - if (existingIndex === -1) { - setRegisteredBackends([defaultEntry, ...current]); - return; - } - - const next = current.slice(); - next[existingIndex] = { - ...current[existingIndex], - host: trimmedHost, - apiKey: trimmedKey, - }; - setRegisteredBackends(next); - }; - - const onSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - syncDefaultBackendInRegistry(); - - reconnect(); - }; - - return ( -
    -
    - {shouldShowSectionHeader ? ( -
    -

    - {t(I18nKey.SETTINGS$AGENT_SERVER_CONNECTION_DETAILS_TITLE)} -

    -

    - {t(I18nKey.SETTINGS$AGENT_SERVER_CONNECTION_DETAILS_DESCRIPTION)} -

    -
    - ) : null} - - - - - -

    - {t(I18nKey.SETTINGS$AGENT_SERVER_BROWSER_ONLY_NOTE)} -

    -
    - -
    - } - > - {t(I18nKey.SETTINGS$AGENT_SERVER_RETRY_CONNECTION)} - - - {t(I18nKey.SETTINGS$SAVE_AND_RECONNECT)} - -
    -
    - ); -} diff --git a/src/components/features/settings/mcp-settings/mcp-server-list-item.tsx b/src/components/features/settings/mcp-settings/mcp-server-list-item.tsx deleted file mode 100644 index 8405076cdf..0000000000 --- a/src/components/features/settings/mcp-settings/mcp-server-list-item.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Pencil, Trash2 } from "lucide-react"; -import { useTranslation } from "react-i18next"; -import { I18nKey } from "#/i18n/declaration"; -import type { MCPServerConfig } from "#/types/mcp-server"; - -export function MCPServerListItem({ - server, - onEdit, - onDelete, -}: { - server: MCPServerConfig; - onEdit: () => void; - onDelete: () => void; -}) { - const { t } = useTranslation("openhands"); - - const getServerTypeLabel = (type: string) => { - switch (type) { - case "sse": - return t(I18nKey.SETTINGS$MCP_SERVER_TYPE_SSE); - case "stdio": - return t(I18nKey.SETTINGS$MCP_SERVER_TYPE_STDIO); - case "shttp": - return t(I18nKey.SETTINGS$MCP_SERVER_TYPE_SHTTP); - default: - return type.toUpperCase(); - } - }; - - const getServerDescription = (serverConfig: MCPServerConfig) => { - if (serverConfig.type === "stdio") { - if (serverConfig.command) { - const args = - serverConfig.args && serverConfig.args.length > 0 - ? ` ${serverConfig.args.join(" ")}` - : ""; - return `${serverConfig.command}${args}`; - } - return serverConfig.name || ""; - } - if ( - (serverConfig.type === "sse" || serverConfig.type === "shttp") && - serverConfig.url - ) { - return serverConfig.url; - } - return ""; - }; - - const serverName = server.type === "stdio" ? server.name : server.url; - const serverDescription = getServerDescription(server); - - return ( - - - {serverName} - - - - {getServerTypeLabel(server.type)} - - - - - {serverDescription} - - - - - - - - - ); -} diff --git a/src/components/features/settings/mcp-settings/mcp-server-list.tsx b/src/components/features/settings/mcp-settings/mcp-server-list.tsx deleted file mode 100644 index 384b398548..0000000000 --- a/src/components/features/settings/mcp-settings/mcp-server-list.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { MCPServerListItem } from "./mcp-server-list-item"; -import { I18nKey } from "#/i18n/declaration"; -import { extensionModuleEmptyStateClassName } from "#/utils/extension-module-card-classes"; -import type { MCPServerConfig } from "#/types/mcp-server"; - -interface MCPServerListProps { - servers: MCPServerConfig[]; - onEdit: (server: MCPServerConfig) => void; - onDelete: (serverId: string) => void; -} - -export function MCPServerList({ - servers, - onEdit, - onDelete, -}: MCPServerListProps) { - const { t } = useTranslation("openhands"); - - if (servers.length === 0) { - return ( -
    -

    - {t(I18nKey.SETTINGS$MCP_NO_SERVERS)} -

    -
    - ); - } - - return ( -
    - - - - - - - - - - - {servers.map((server) => ( - onEdit(server)} - onDelete={() => onDelete(server.id)} - /> - ))} - -
    - {t(I18nKey.SETTINGS$NAME)} - - {t(I18nKey.SETTINGS$MCP_SERVER_TYPE)} - - {t(I18nKey.SETTINGS$MCP_SERVER_DETAILS)} - - {t(I18nKey.SETTINGS$ACTIONS)} -
    -
    - ); -} diff --git a/src/hooks/query/use-openhands-verified-models.ts b/src/hooks/query/use-openhands-verified-models.ts deleted file mode 100644 index ab1827aacb..0000000000 --- a/src/hooks/query/use-openhands-verified-models.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { - VERIFIED_MODELS_GC_TIME, - VERIFIED_MODELS_QUERY_KEY, - VERIFIED_MODELS_STALE_TIME, - fetchVerifiedModelsByProvider, -} from "./use-verified-models"; - -export const useOpenhandsVerifiedModels = () => - useQuery({ - queryKey: VERIFIED_MODELS_QUERY_KEY, - queryFn: fetchVerifiedModelsByProvider, - select: (data) => data?.openhands ?? [], - staleTime: VERIFIED_MODELS_STALE_TIME, - gcTime: VERIFIED_MODELS_GC_TIME, - }); diff --git a/src/hooks/query/use-suggested-tasks.ts b/src/hooks/query/use-suggested-tasks.ts deleted file mode 100644 index e2e36b6dd4..0000000000 --- a/src/hooks/query/use-suggested-tasks.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { SuggestionsService } from "#/api/suggestions-service/suggestions-service.api"; -import { groupSuggestedTasks } from "#/utils/group-suggested-tasks"; -import { useShouldShowUserFeatures } from "../use-should-show-user-features"; - -export const useSuggestedTasks = () => { - const shouldShowUserFeatures = useShouldShowUserFeatures(); - - return useQuery({ - queryKey: ["tasks"], - queryFn: () => SuggestionsService.getSuggestedTasks(), - select: groupSuggestedTasks, - enabled: shouldShowUserFeatures, - }); -}; diff --git a/src/hooks/use-should-show-user-features.ts b/src/hooks/use-should-show-user-features.ts deleted file mode 100644 index a8c00bb57f..0000000000 --- a/src/hooks/use-should-show-user-features.ts +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { useIsAuthed } from "./query/use-is-authed"; -import { useUserProviders } from "./use-user-providers"; - -export const useShouldShowUserFeatures = (): boolean => { - const { data: isAuthed } = useIsAuthed(); - const { providers } = useUserProviders(); - - return React.useMemo(() => { - if (!isAuthed) return false; - return providers.length > 0; - }, [isAuthed, providers.length]); -}; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index aafcbc9f3f..efcba4f0a6 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,5 +1,4 @@ import { FILE_SERVICE_HANDLERS } from "./file-service-handlers"; -import { TASK_SUGGESTIONS_HANDLERS } from "./task-suggestions-handlers"; import { SECRETS_HANDLERS } from "./secrets-handlers"; import { GIT_REPOSITORY_HANDLERS } from "./git-repository-handlers"; import { @@ -23,7 +22,6 @@ import { export const handlers = [ ...FILE_SERVICE_HANDLERS, - ...TASK_SUGGESTIONS_HANDLERS, ...SECRETS_HANDLERS, ...GIT_REPOSITORY_HANDLERS, ...SETTINGS_HANDLERS, diff --git a/src/mocks/task-suggestions-handlers.ts b/src/mocks/task-suggestions-handlers.ts deleted file mode 100644 index 700e7f2ed9..0000000000 --- a/src/mocks/task-suggestions-handlers.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { http, HttpResponse } from "msw"; -import { SuggestedTask } from "#/utils/types"; - -const TASKS_1: SuggestedTask[] = [ - { - issue_number: 6968, - title: "Fix merge conflicts", - repo: "octocat/hello-world", - task_type: "MERGE_CONFLICTS", - git_provider: "github", - }, -]; - -const TASKS_2: SuggestedTask[] = [ - { - issue_number: 268, - title: "Fix broken CI checks", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 281, - title: "Fix issue", - repo: "octocat/earth", - task_type: "UNRESOLVED_COMMENTS", - git_provider: "github", - }, - { - issue_number: 293, - title: "Update documentation", - repo: "octocat/earth", - task_type: "OPEN_ISSUE", - git_provider: "github", - }, - { - issue_number: 305, - title: "Refactor user service", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 312, - title: "Fix styling bug", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 327, - title: "Add unit tests", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 331, - title: "Implement dark mode", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 345, - title: "Optimize build process", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, - { - issue_number: 352, - title: "Update dependencies", - repo: "octocat/earth", - task_type: "FAILING_CHECKS", - git_provider: "github", - }, -]; - -export const MOCK_TASKS = [...TASKS_1, ...TASKS_2]; - -export const TASK_SUGGESTIONS_HANDLERS = [ - // Current endpoint with pagination - http.get("*/api/v1/git/suggested-tasks/search", async ({ request }) => { - const url = new URL(request.url); - const limit = url.searchParams.get("limit"); - const pageId = url.searchParams.get("page_id"); - - // Simple pagination: return all items if no pagination params, otherwise apply limit - let tasks = [...MOCK_TASKS]; - if (pageId || limit) { - const limitNum = limit ? parseInt(limit, 10) : 30; - tasks = tasks.slice(0, limitNum); - } - - return HttpResponse.json({ - items: tasks, - next_page_id: null, // No pagination in mock data - }); - }), -]; diff --git a/src/mocks/workspace-handlers.ts b/src/mocks/workspace-handlers.ts deleted file mode 100644 index 7c8a6d9152..0000000000 --- a/src/mocks/workspace-handlers.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { http, HttpResponse } from "msw"; - -import { LocalWorkspace, LocalWorkspaceParent } from "#/types/workspace"; - -interface MockWorkspacesState { - workspaces: LocalWorkspace[]; - workspaceParents: LocalWorkspaceParent[]; -} - -const mockWorkspaces: MockWorkspacesState = { - workspaces: [], - workspaceParents: [], -}; - -function workspacesResponse() { - return { - workspaces: mockWorkspaces.workspaces, - workspaceParents: mockWorkspaces.workspaceParents, - }; -} - -function readPath(url: URL) { - return url.searchParams.get("path") ?? ""; -} - -export function resetWorkspaceMockData() { - mockWorkspaces.workspaces = []; - mockWorkspaces.workspaceParents = []; -} - -export const WORKSPACE_HANDLERS = [ - http.get("*/api/workspaces", () => HttpResponse.json(workspacesResponse())), - http.post("*/api/workspaces", async ({ request }) => { - const body = (await request.json()) as { workspaces?: LocalWorkspace[] }; - const nextWorkspaces = body.workspaces ?? []; - const existingPaths = new Set( - mockWorkspaces.workspaces.map((workspace) => workspace.path), - ); - - for (const workspace of nextWorkspaces) { - if (!existingPaths.has(workspace.path)) { - mockWorkspaces.workspaces.push(workspace); - existingPaths.add(workspace.path); - } - } - - return HttpResponse.json(workspacesResponse()); - }), - http.delete("*/api/workspaces", ({ request }) => { - const path = readPath(new URL(request.url)); - mockWorkspaces.workspaces = mockWorkspaces.workspaces.filter( - (workspace) => workspace.path !== path, - ); - return HttpResponse.json({ ok: true }); - }), - http.post("*/api/workspaces/parents", async ({ request }) => { - const body = (await request.json()) as { parents?: LocalWorkspaceParent[] }; - const nextParents = body.parents ?? []; - const existingPaths = new Set( - mockWorkspaces.workspaceParents.map((parent) => parent.path), - ); - - for (const parent of nextParents) { - if (!existingPaths.has(parent.path)) { - mockWorkspaces.workspaceParents.push(parent); - existingPaths.add(parent.path); - } - } - - return HttpResponse.json(workspacesResponse()); - }), - http.delete("*/api/workspaces/parents", ({ request }) => { - const path = readPath(new URL(request.url)); - mockWorkspaces.workspaceParents = mockWorkspaces.workspaceParents.filter( - (parent) => parent.path !== path, - ); - mockWorkspaces.workspaces = mockWorkspaces.workspaces.filter( - (workspace) => workspace.parentPath !== path, - ); - return HttpResponse.json({ ok: true }); - }), - http.post("*/api/auth/workspace-session", () => - HttpResponse.json({ base_url: "/api/conversations/mock/workspace/" }), - ), - http.delete( - "*/api/auth/workspace-session", - () => new HttpResponse(null, { status: 204 }), - ), -]; diff --git a/src/routes/index-redirect.tsx b/src/routes/index-redirect.tsx deleted file mode 100644 index 198695c213..0000000000 --- a/src/routes/index-redirect.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { redirect } from "react-router"; - -export const clientLoader = () => redirect("/conversations"); - -export default function IndexRedirect() { - return null; -} diff --git a/src/services/actions.ts b/src/services/actions.ts deleted file mode 100644 index 947db37161..0000000000 --- a/src/services/actions.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { trackError } from "#/utils/error-handler"; -import useMetricsStore from "#/stores/metrics-store"; -import { useStatusStore } from "#/stores/status-store"; -import ActionType from "#/types/action-type"; -import { - ActionMessage, - ObservationMessage, - StatusMessage, -} from "#/types/message"; -import { handleObservationMessage } from "./observations"; -import { useCommandStore } from "#/stores/command-store"; -import { queryClient } from "#/query-client-config"; -import { - ActionSecurityRisk, - useSecurityAnalyzerStore, -} from "#/stores/security-analyzer-store"; - -export function handleActionMessage(message: ActionMessage) { - if (message.args?.hidden) { - return; - } - - // Update metrics if available - if (message.llm_metrics) { - const metrics = { - cost: message.llm_metrics?.accumulated_cost ?? null, - max_budget_per_task: message.llm_metrics?.max_budget_per_task ?? null, - usage: message.llm_metrics?.accumulated_token_usage ?? null, - }; - useMetricsStore.getState().setMetrics(metrics); - } - - if (message.action === ActionType.RUN) { - useCommandStore.getState().appendInput(message.args.command); - } - - if ("args" in message && "security_risk" in message.args) { - useSecurityAnalyzerStore.getState().appendSecurityAnalyzerInput({ - id: message.id, - args: { - command: message.args.command, - code: message.args.code, - content: message.args.content, - security_risk: message.args - .security_risk as unknown as ActionSecurityRisk, - confirmation_state: message.args.confirmation_state as - | "awaiting_confirmation" - | "confirmed" - | "rejected" - | undefined, - }, - message: message.message, - }); - } -} - -export function handleStatusMessage(message: StatusMessage) { - // Info message with conversation_title indicates new title for conversation - if (message.type === "info" && message.conversation_title) { - const conversationId = message.message; - - // Invalidate the conversation query to trigger a refetch with the new title - queryClient.invalidateQueries({ - queryKey: ["user", "conversation", conversationId], - }); - } else if (message.type === "info") { - useStatusStore.getState().setCurStatusMessage({ - ...message, - }); - } else if (message.type === "error") { - trackError({ - message: message.message, - source: "chat", - metadata: { msgId: message.id }, - }); - } -} - -export function handleAssistantMessage(message: Record) { - if (message.action) { - handleActionMessage(message as unknown as ActionMessage); - } else if (message.observation) { - handleObservationMessage(message as unknown as ObservationMessage); - } else if (message.status_update) { - handleStatusMessage(message as unknown as StatusMessage); - } -} diff --git a/src/services/observations.ts b/src/services/observations.ts deleted file mode 100644 index 8f1d8d3b41..0000000000 --- a/src/services/observations.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ObservationMessage } from "#/types/message"; -import { useCommandStore } from "#/stores/command-store"; -import ObservationType from "#/types/observation-type"; -import { useBrowserStore } from "#/stores/browser-store"; -import { useAgentStore } from "#/stores/agent-store"; -import { AgentState } from "#/types/agent-state"; - -export function handleObservationMessage(message: ObservationMessage) { - switch (message.observation) { - case ObservationType.RUN: { - if (message.extras.hidden) break; - let { content } = message; - - if (content.length > 5000) { - const halfLength = 2500; - const head = content.slice(0, halfLength); - const tail = content.slice(content.length - halfLength); - content = `${head}\r\n\n... (truncated ${message.content.length - 5000} characters) ...\r\n\n${tail}`; - } - - useCommandStore.getState().appendOutput(content); - break; - } - case ObservationType.BROWSE: - case ObservationType.BROWSE_INTERACTIVE: - if ( - message.extras?.screenshot && - typeof message.extras.screenshot === "string" - ) { - useBrowserStore.getState().setScreenshotSrc(message.extras.screenshot); - } - if (message.extras?.url && typeof message.extras.url === "string") { - useBrowserStore.getState().setUrl(message.extras.url); - } - break; - case ObservationType.AGENT_STATE_CHANGED: - if (typeof message.extras.agent_state === "string") { - useAgentStore - .getState() - .setCurrentAgentState(message.extras.agent_state as AgentState); - } - break; - case ObservationType.DELEGATE: - case ObservationType.READ: - case ObservationType.EDIT: - case ObservationType.THINK: - case ObservationType.NULL: - case ObservationType.RECALL: - case ObservationType.ERROR: - case ObservationType.MCP: - case ObservationType.TASK_TRACKING: - break; // We don't display the default message for these observations - default: - break; - } - if (!message.extras?.hidden) { - // Convert the message to the appropriate observation type - const { observation } = message; - - switch (observation) { - case "browse": - if ( - message.extras?.screenshot && - typeof message.extras.screenshot === "string" - ) { - useBrowserStore - .getState() - .setScreenshotSrc(message.extras.screenshot); - } - if (message.extras?.url && typeof message.extras.url === "string") { - useBrowserStore.getState().setUrl(message.extras.url); - } - break; - case "browse_interactive": - if ( - message.extras?.screenshot && - typeof message.extras.screenshot === "string" - ) { - useBrowserStore - .getState() - .setScreenshotSrc(message.extras.screenshot); - } - if (message.extras?.url && typeof message.extras.url === "string") { - useBrowserStore.getState().setUrl(message.extras.url); - } - break; - default: - // For any unhandled observation types, just ignore them - break; - } - } -} diff --git a/src/stores/security-analyzer-store.ts b/src/stores/security-analyzer-store.ts deleted file mode 100644 index a8522da813..0000000000 --- a/src/stores/security-analyzer-store.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { create } from "zustand"; - -export enum ActionSecurityRisk { - UNKNOWN = -1, - LOW = 0, - MEDIUM = 1, - HIGH = 2, -} - -export type SecurityAnalyzerLog = { - id: number; - content: string; - security_risk: ActionSecurityRisk; - confirmation_state?: "awaiting_confirmation" | "confirmed" | "rejected"; - confirmed_changed: boolean; -}; - -interface SecurityAnalyzerState { - logs: SecurityAnalyzerLog[]; -} - -interface SecurityAnalyzerStore extends SecurityAnalyzerState { - appendSecurityAnalyzerInput: (message: { - id: number; - args: { - command?: string; - code?: string; - content?: string; - security_risk: ActionSecurityRisk; - confirmation_state?: "awaiting_confirmation" | "confirmed" | "rejected"; - }; - message?: string; - }) => void; - clearLogs: () => void; -} - -const initialLogs: SecurityAnalyzerLog[] = []; - -export const useSecurityAnalyzerStore = create( - (set) => ({ - logs: initialLogs, - appendSecurityAnalyzerInput: (message) => - set((state) => { - const log: SecurityAnalyzerLog = { - id: message.id, - content: - message.args.command || - message.args.code || - message.args.content || - message.message || - "", - security_risk: message.args.security_risk, - confirmation_state: message.args.confirmation_state, - confirmed_changed: false, - }; - - const existingLog = state.logs.find( - (stateLog) => - stateLog.id === log.id || - (stateLog.confirmation_state === "awaiting_confirmation" && - stateLog.content === log.content), - ); - - if (existingLog) { - if (existingLog.confirmation_state !== log.confirmation_state) { - existingLog.confirmation_state = log.confirmation_state; - existingLog.confirmed_changed = true; - } - return { logs: [...state.logs] }; // Return new array to trigger re-render - } - return { logs: [...state.logs, log] }; - }), - clearLogs: () => set({ logs: initialLogs }), - }), -); diff --git a/src/stores/status-store.ts b/src/stores/status-store.ts deleted file mode 100644 index b692c098a3..0000000000 --- a/src/stores/status-store.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { create } from "zustand"; -import { StatusMessage } from "#/types/message"; - -const initialStatusMessage: StatusMessage = { - status_update: true, - type: "info", - id: "", - message: "", -}; - -interface StatusState { - curStatusMessage: StatusMessage; - setCurStatusMessage: (message: StatusMessage) => void; -} - -export const useStatusStore = create((set) => ({ - curStatusMessage: initialStatusMessage, - setCurStatusMessage: (message: StatusMessage) => - set({ curStatusMessage: message }), -})); diff --git a/src/types/message.tsx b/src/types/message.tsx deleted file mode 100644 index 24d0f9909d..0000000000 --- a/src/types/message.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @deprecated This type is deprecated and will be removed in a future version. - * Use agent-server event types instead. - */ -export interface ActionMessage { - id: number; - - // Either 'agent' or 'user' - source: "agent" | "user"; - - // The action to be taken - action: string; - - // The arguments for the action - args: Record; - - // A friendly message that can be put in the chat log - message: string; - - // The timestamp of the message - timestamp: string; - - // LLM metrics information - llm_metrics?: { - accumulated_cost: number; - max_budget_per_task: number | null; - accumulated_token_usage: { - prompt_tokens: number; - completion_tokens: number; - cache_read_tokens: number; - cache_write_tokens: number; - context_window: number; - per_turn_token: number; - }; - }; - - // Tool call metadata - tool_call_metadata?: { - model_response?: { - usage: { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; - }; - }; - }; -} - -export interface ObservationMessage { - // The type of observation - observation: string; - - id: number; - cause: number; - - // The observed data - content: string; - - extras: { - metadata: Record; - error_id: string; - [key: string]: string | Record; - }; - - // A friendly message that can be put in the chat log - message: string; - - // The timestamp of the message - timestamp: string; -} - -export interface StatusMessage { - status_update: true; - type: string; - id?: string; - message: string; - conversation_title?: string; -} diff --git a/src/types/observation-type.tsx b/src/types/observation-type.tsx deleted file mode 100644 index 37fc4cb183..0000000000 --- a/src/types/observation-type.tsx +++ /dev/null @@ -1,48 +0,0 @@ -enum ObservationType { - // The contents of a file - READ = "read", - - // The diff of a file edit - EDIT = "edit", - - // The HTML contents of a URL - BROWSE = "browse", - - // Interactive browsing - BROWSE_INTERACTIVE = "browse_interactive", - - // The output of a command - RUN = "run", - - // The output of an IPython command - RUN_IPYTHON = "run_ipython", - - // A message from the user - CHAT = "chat", - - // Agent state has changed - AGENT_STATE_CHANGED = "agent_state_changed", - - // Delegate result - DELEGATE = "delegate", - - // A response to the agent's thought (usually a static message) - THINK = "think", - - // An observation that shows agent's context extension - RECALL = "recall", - - // A MCP tool call observation - MCP = "mcp", - - // An error observation - ERROR = "error", - - // A no-op observation - NULL = "null", - - // Result of a task tracking operation - TASK_TRACKING = "task_tracking", -} - -export default ObservationType; diff --git a/src/ui/card-title.tsx b/src/ui/card-title.tsx deleted file mode 100644 index 0ce8285c94..0000000000 --- a/src/ui/card-title.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { ReactNode } from "react"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "#/utils/utils"; - -const cardTitleVariants = cva("flex items-center", { - variants: { - gap: { - default: "gap-[10px]", - }, - textSize: { - default: "text-base", - }, - fontWeight: { - default: "font-medium", - }, - textColor: { - default: "text-white", - }, - lineHeight: { - default: "leading-5", - }, - }, - defaultVariants: { - gap: "default", - textSize: "default", - fontWeight: "default", - textColor: "default", - lineHeight: "default", - }, -}); - -interface CardTitleProps extends VariantProps { - icon?: ReactNode; - children: ReactNode; - className?: string; -} - -export function CardTitle({ - icon, - children, - className = "", - gap, - textSize, - fontWeight, - textColor, - lineHeight, -}: CardTitleProps) { - return ( -
    - {icon} - - {children} - -
    - ); -} diff --git a/src/ui/card.tsx b/src/ui/card.tsx deleted file mode 100644 index 392a2b28c6..0000000000 --- a/src/ui/card.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { ReactNode } from "react"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "#/utils/utils"; - -const cardVariants = cva("flex", { - variants: { - theme: { - default: - "relative bg-[var(--oh-surface)] border border-[var(--oh-border)] rounded-xl", - outlined: - "relative bg-transparent border border-[var(--oh-border)] rounded-xl", - dark: "relative bg-black border border-[var(--oh-border-subtle)] rounded-2xl", - }, - hover: { - none: "", - elevated: [ - "transition-all duration-200", - "hover:bg-[linear-gradient(180deg,var(--cool-grey-950)_0%,var(--cool-grey-975)_100%)]", - "hover:border-t-[color-mix(in srgb, var(--cool-grey-925) 80%, transparent)]", - "hover:shadow-[0px_4px_6px_-4px_rgba(0, 0, 0, 0.1),0px_10px_15px_-3px_rgba(0, 0, 0, 0.1)]", - "before:absolute before:inset-0 before:rounded-2xl before:opacity-0 before:transition-opacity before:duration-200", - "before:bg-[radial-gradient(98.4%_116.11%_at_50%_0%,rgba(255,255,255,0.08)_0%,rgba(0,0,0,0)_70%)]", - "hover:before:opacity-100", - "before:pointer-events-none", - ].join(" "), - }, - gradient: { - none: "", - standard: [ - "bg-[color-mix(in srgb, var(--cool-grey-975) 50%, transparent)] border-t-[color-mix(in srgb, var(--cool-grey-925) 60%, transparent)]", - "shadow-[0px_4px_6px_-4px_rgba(0, 0, 0, 0.1),0px_10px_15px_-3px_rgba(0, 0, 0, 0.1)]", - "before:absolute before:inset-0 before:rounded-2xl before:pointer-events-none", - "before:bg-[radial-gradient(144.32%_106.6%_at_50%_0%,rgba(255,255,255,0.14)_0%,rgba(0,0,0,0)_55%)]", - ].join(" "), - }, - }, - defaultVariants: { - theme: "default", - hover: "none", - gradient: "none", - }, -}); - -interface CardProps extends VariantProps { - children?: ReactNode; - className?: string; - testId?: string; -} - -export function Card({ - children, - className, - testId, - theme, - hover, - gradient, -}: CardProps) { - return ( -
    - {children} -
    - ); -} diff --git a/src/ui/context-menu-icon-text.tsx b/src/ui/context-menu-icon-text.tsx deleted file mode 100644 index b3fa3543b5..0000000000 --- a/src/ui/context-menu-icon-text.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { cn } from "#/utils/utils"; - -interface ContextMenuIconTextProps { - icon: React.ReactNode; - text: string; - rightIcon?: React.ReactNode; - className?: string; -} - -export function ContextMenuIconText({ - icon, - text, - rightIcon, - className, -}: ContextMenuIconTextProps) { - return ( -
    -
    - {icon} - {text} -
    - {rightIcon &&
    {rightIcon}
    } -
    - ); -} diff --git a/src/utils/group-suggested-tasks.ts b/src/utils/group-suggested-tasks.ts deleted file mode 100644 index 01adb72b69..0000000000 --- a/src/utils/group-suggested-tasks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SuggestedTask, SuggestedTaskGroup } from "#/utils/types"; - -/** - * Groups suggested tasks by their repository. - * @param tasks Array of suggested tasks - * @returns Array of suggested task groups - */ -export function groupSuggestedTasks( - tasks: SuggestedTask[], -): SuggestedTaskGroup[] { - const groupsMap: Record = {}; - - for (const task of tasks) { - const groupKey = `${task.repo}`; - - if (!groupsMap[groupKey]) { - groupsMap[groupKey] = { - title: groupKey, - tasks: [], - }; - } - - groupsMap[groupKey].tasks.push(task); - } - - return Object.values(groupsMap); -}