diff --git a/autogpt_platform/backend/backend/api/features/chat/routes.py b/autogpt_platform/backend/backend/api/features/chat/routes.py
index aa565ca891..aa95373257 100644
--- a/autogpt_platform/backend/backend/api/features/chat/routes.py
+++ b/autogpt_platform/backend/backend/api/features/chat/routes.py
@@ -23,6 +23,7 @@ from .model import (
ChatSession,
append_and_save_message,
create_chat_session,
+ delete_chat_session,
get_chat_session,
get_user_sessions,
)
@@ -211,6 +212,42 @@ async def create_session(
)
+@router.delete(
+ "/sessions/{session_id}",
+ dependencies=[Security(auth.requires_user)],
+ status_code=204,
+)
+async def delete_session(
+ session_id: str,
+ user_id: Annotated[str, Security(auth.get_user_id)],
+) -> Response:
+ """
+ Delete a chat session.
+
+ Permanently removes a chat session and all its messages.
+ Only the owner can delete their sessions.
+
+ Args:
+ session_id: The session ID to delete.
+ user_id: The authenticated user's ID.
+
+ Returns:
+ 204 No Content on success.
+
+ Raises:
+ HTTPException: 404 if session not found or not owned by user.
+ """
+ deleted = await delete_chat_session(session_id, user_id)
+
+ if not deleted:
+ raise HTTPException(
+ status_code=404,
+ detail=f"Session {session_id} not found or access denied",
+ )
+
+ return Response(status_code=204)
+
+
@router.get(
"/sessions/{session_id}",
)
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
index 6b7398b4ba..2cfad63f0f 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
@@ -1,8 +1,13 @@
"use client";
-import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
+import {
+ getGetV2ListSessionsQueryKey,
+ useDeleteV2DeleteSession,
+ useGetV2ListSessions,
+} from "@/app/api/__generated__/endpoints/chat/chat";
import { Button } from "@/components/atoms/Button/Button";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { Text } from "@/components/atoms/Text/Text";
+import DeleteConfirmDialog from "@/components/__legacy__/delete-confirm-dialog";
import {
Sidebar,
SidebarContent,
@@ -12,18 +17,47 @@ import {
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
-import { PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
+import { PlusCircleIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react";
+import { useQueryClient } from "@tanstack/react-query";
import { motion } from "framer-motion";
+import { useState } from "react";
import { parseAsString, useQueryState } from "nuqs";
export function ChatSidebar() {
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
+ const [sessionToDelete, setSessionToDelete] = useState<{
+ id: string;
+ title: string | null;
+ } | null>(null);
+
+ const queryClient = useQueryClient();
const { data: sessionsResponse, isLoading: isLoadingSessions } =
useGetV2ListSessions({ limit: 50 });
+ const { mutate: deleteSession, isPending: isDeleting } =
+ useDeleteV2DeleteSession({
+ mutation: {
+ onSuccess: () => {
+ // Invalidate sessions list to refetch
+ queryClient.invalidateQueries({
+ queryKey: getGetV2ListSessionsQueryKey(),
+ });
+ // If we deleted the current session, clear selection
+ if (sessionToDelete?.id === sessionId) {
+ setSessionId(null);
+ }
+ setSessionToDelete(null);
+ },
+ onError: (error) => {
+ console.error("Failed to delete session:", error);
+ setSessionToDelete(null);
+ },
+ },
+ });
+
const sessions =
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
@@ -35,6 +69,21 @@ export function ChatSidebar() {
setSessionId(id);
}
+ function handleDeleteClick(
+ e: React.MouseEvent,
+ id: string,
+ title: string | null,
+ ) {
+ e.stopPropagation(); // Prevent session selection
+ setSessionToDelete({ id, title });
+ }
+
+ function handleConfirmDelete() {
+ if (sessionToDelete) {
+ deleteSession({ sessionId: sessionToDelete.id });
+ }
+ }
+
function formatDate(dateString: string) {
const date = new Date(dateString);
const now = new Date();
@@ -61,128 +110,151 @@ export function ChatSidebar() {
}
return (
-
- {isCollapsed && (
-
-
-
-
-
- )}
-
- {!isCollapsed && (
-
-
- Your chats
-
-
-
-
-
- )}
-
- {!isCollapsed && (
-
- {isLoadingSessions ? (
-
-
-
- ) : sessions.length === 0 ? (
-
- No conversations yet
-
- ) : (
- sessions.map((session) => (
-
- ))
+ <>
+
+ {isCollapsed && (
+
- )}
-
- {!isCollapsed && sessionId && (
-
-
- }
+
- New Chat
-
-
-
- )}
-
+
+
+
+ )}
+
+ {!isCollapsed && (
+
+
+ Your chats
+
+
+
+
+
+ )}
+
+ {!isCollapsed && (
+
+ {isLoadingSessions ? (
+
+
+
+ ) : sessions.length === 0 ? (
+
+ No conversations yet
+
+ ) : (
+ sessions.map((session) => (
+
+
+
+
+ ))
+ )}
+
+ )}
+
+ {!isCollapsed && sessionId && (
+
+
+ }
+ >
+ New Chat
+
+
+
+ )}
+
+
+ !open && setSessionToDelete(null)}
+ onDoDelete={handleConfirmDelete}
+ />
+ >
);
}
diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json
index 63a8a856b9..02d55b4b90 100644
--- a/autogpt_platform/frontend/src/app/api/openapi.json
+++ b/autogpt_platform/frontend/src/app/api/openapi.json
@@ -1188,6 +1188,40 @@
}
}
}
+ },
+ "delete": {
+ "tags": ["v2", "chat", "chat"],
+ "summary": "Delete Session",
+ "description": "Delete a chat session.\n\nPermanently removes a chat session and all its messages.\nOnly the owner can delete their sessions.\n\nArgs:\n session_id: The session ID to delete.\n user_id: The authenticated user's ID.\n\nReturns:\n 204 No Content on success.",
+ "operationId": "deleteV2DeleteSession",
+ "security": [{ "HTTPBearerJWT": [] }],
+ "parameters": [
+ {
+ "name": "session_id",
+ "in": "path",
+ "required": true,
+ "schema": { "type": "string", "title": "Session Id" }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful Response"
+ },
+ "401": {
+ "$ref": "#/components/responses/HTTP401NotAuthenticatedError"
+ },
+ "404": {
+ "description": "Session not found or access denied"
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/HTTPValidationError" }
+ }
+ }
+ }
+ }
}
},
"/api/chat/sessions/{session_id}/assign-user": {
diff --git a/notes/plan-SECRT-1928-delete-chat-sessions.md b/notes/plan-SECRT-1928-delete-chat-sessions.md
new file mode 100644
index 0000000000..fb23f008e3
--- /dev/null
+++ b/notes/plan-SECRT-1928-delete-chat-sessions.md
@@ -0,0 +1,235 @@
+# Implementation Plan: SECRT-1928 - Delete Chat Sessions
+
+**Ticket:** [SECRT-1928](https://linear.app/autogpt/issue/SECRT-1928)
+**Author:** Otto
+**Date:** 2026-02-14
+
+## Summary
+
+Add the ability for users to delete chat sessions from the CoPilot interface. The backend logic already exists (`delete_chat_session` in `model.py` and `db.py`), it just needs a route and frontend UI.
+
+## Current State
+
+### Backend (already exists)
+- `backend/api/features/chat/db.py:delete_chat_session()` - DB deletion with user ownership validation
+- `backend/api/features/chat/model.py:delete_chat_session()` - Handles cache cleanup and lock removal
+- **Missing:** No DELETE route in `routes.py`
+
+### Frontend
+- `ChatSidebar.tsx` displays session list with no delete option
+- `MobileDrawer.tsx` also needs delete option
+- **Missing:** Delete button, confirmation dialog, API call
+
+## Implementation
+
+### Phase 1: Backend Route (15 min)
+
+**File:** `autogpt_platform/backend/backend/api/features/chat/routes.py`
+
+Add after line ~200 (after `create_session`):
+
+```python
+@router.delete(
+ "/sessions/{session_id}",
+ dependencies=[Security(auth.requires_user)],
+ status_code=204,
+)
+async def delete_session(
+ session_id: str,
+ user_id: Annotated[str, Security(auth.get_user_id)],
+) -> Response:
+ """
+ Delete a chat session.
+
+ Permanently removes a chat session and all its messages.
+ Only the owner can delete their sessions.
+
+ Args:
+ session_id: The session ID to delete.
+ user_id: The authenticated user's ID.
+
+ Returns:
+ 204 No Content on success.
+
+ Raises:
+ 404: Session not found or not owned by user.
+ """
+ from .model import delete_chat_session
+
+ deleted = await delete_chat_session(session_id, user_id)
+
+ if not deleted:
+ raise HTTPException(
+ status_code=404,
+ detail=f"Session {session_id} not found or access denied"
+ )
+
+ return Response(status_code=204)
+```
+
+**Add import at top:**
+```python
+from fastapi import Response # add to existing imports
+```
+
+### Phase 2: Frontend API Hook (auto-generated)
+
+After adding the backend route, run the OpenAPI generator to create the hook:
+```bash
+cd autogpt_platform/frontend
+pnpm generate:api
+```
+
+This will generate `useDeleteV2Session` or similar.
+
+### Phase 3: Frontend UI (30 min)
+
+**File:** `autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx`
+
+1. Add imports:
+```tsx
+import { TrashIcon } from "@phosphor-icons/react";
+import { useDeleteV2Session } from "@/app/api/__generated__/endpoints/chat/chat";
+import { useQueryClient } from "@tanstack/react-query";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+```
+
+2. Add state and mutation hook inside component:
+```tsx
+const [sessionToDelete, setSessionToDelete] = useState(null);
+const queryClient = useQueryClient();
+
+const { mutate: deleteSession, isPending: isDeleting } = useDeleteV2Session({
+ mutation: {
+ onSuccess: () => {
+ // Invalidate sessions list to refetch
+ queryClient.invalidateQueries({ queryKey: ['v2', 'sessions'] });
+ // If we deleted the current session, clear selection
+ if (sessionToDelete === sessionId) {
+ setSessionId(null);
+ }
+ setSessionToDelete(null);
+ },
+ onError: (error) => {
+ console.error("Failed to delete session:", error);
+ setSessionToDelete(null);
+ },
+ },
+});
+
+function handleDeleteClick(e: React.MouseEvent, id: string) {
+ e.stopPropagation(); // Prevent session selection
+ setSessionToDelete(id);
+}
+
+function handleConfirmDelete() {
+ if (sessionToDelete) {
+ deleteSession({ sessionId: sessionToDelete });
+ }
+}
+```
+
+3. Add delete button to each session item (inside the session button, after the date):
+```tsx
+
+```
+
+4. Add confirmation dialog before the closing ``:
+```tsx
+ !open && setSessionToDelete(null)}>
+
+
+ Delete this chat?
+
+ This will permanently delete this conversation and all its messages. This action cannot be undone.
+
+
+
+ Cancel
+
+ {isDeleting ? "Deleting..." : "Delete"}
+
+
+
+
+```
+
+5. Update session button to have `group` class for hover effects:
+```tsx
+