diff --git a/.github/workflows/platform-backend-ci.yml b/.github/workflows/platform-backend-ci.yml
index 1f0c6da3dd..22d1e91ead 100644
--- a/.github/workflows/platform-backend-ci.yml
+++ b/.github/workflows/platform-backend-ci.yml
@@ -41,13 +41,18 @@ jobs:
ports:
- 6379:6379
rabbitmq:
- image: rabbitmq:3.12-management
+ image: rabbitmq:4.1.4
ports:
- 5672:5672
- - 15672:15672
env:
RABBITMQ_DEFAULT_USER: ${{ env.RABBITMQ_DEFAULT_USER }}
RABBITMQ_DEFAULT_PASS: ${{ env.RABBITMQ_DEFAULT_PASS }}
+ options: >-
+ --health-cmd "rabbitmq-diagnostics -q ping"
+ --health-interval 30s
+ --health-timeout 10s
+ --health-retries 5
+ --health-start-period 10s
clamav:
image: clamav/clamav-debian:latest
ports:
diff --git a/.github/workflows/platform-frontend-ci.yml b/.github/workflows/platform-frontend-ci.yml
index 4bf8a2b80c..e788696f9b 100644
--- a/.github/workflows/platform-frontend-ci.yml
+++ b/.github/workflows/platform-frontend-ci.yml
@@ -6,10 +6,16 @@ on:
paths:
- ".github/workflows/platform-frontend-ci.yml"
- "autogpt_platform/frontend/**"
+ - "autogpt_platform/backend/Dockerfile"
+ - "autogpt_platform/docker-compose.yml"
+ - "autogpt_platform/docker-compose.platform.yml"
pull_request:
paths:
- ".github/workflows/platform-frontend-ci.yml"
- "autogpt_platform/frontend/**"
+ - "autogpt_platform/backend/Dockerfile"
+ - "autogpt_platform/docker-compose.yml"
+ - "autogpt_platform/docker-compose.platform.yml"
merge_group:
workflow_dispatch:
diff --git a/autogpt_platform/backend/Dockerfile b/autogpt_platform/backend/Dockerfile
index 05a8d4858b..6037ed656f 100644
--- a/autogpt_platform/backend/Dockerfile
+++ b/autogpt_platform/backend/Dockerfile
@@ -59,12 +59,7 @@ FROM debian:13-slim AS server
WORKDIR /app
-ENV POETRY_HOME=/opt/poetry \
- POETRY_NO_INTERACTION=1 \
- POETRY_VIRTUALENVS_CREATE=true \
- POETRY_VIRTUALENVS_IN_PROJECT=true \
- DEBIAN_FRONTEND=noninteractive
-ENV PATH=/opt/poetry/bin:$PATH
+ENV DEBIAN_FRONTEND=noninteractive
# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
@@ -81,6 +76,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
bubblewrap \
&& rm -rf /var/lib/apt/lists/*
+# Copy poetry (build-time only, for `poetry install --only-root` to create entry points)
COPY --from=builder /usr/local/lib/python3* /usr/local/lib/python3*
COPY --from=builder /usr/local/bin/poetry /usr/local/bin/poetry
# Copy Node.js installation for Prisma
@@ -104,11 +100,12 @@ COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.tom
# Copy backend code + docs (for Copilot docs search)
COPY autogpt_platform/backend ./
COPY docs /app/docs
-RUN poetry install --no-ansi --only-root
+RUN POETRY_VIRTUALENVS_CREATE=true POETRY_VIRTUALENVS_IN_PROJECT=true \
+ poetry install --no-ansi --only-root
ENV PORT=8000
-CMD ["poetry", "run", "rest"]
+CMD ["rest"]
# =============================== DB MIGRATOR =============================== #
diff --git a/autogpt_platform/backend/backend/api/features/chat/routes.py b/autogpt_platform/backend/backend/api/features/chat/routes.py
index 0f0568a349..2fd7d29319 100644
--- a/autogpt_platform/backend/backend/api/features/chat/routes.py
+++ b/autogpt_platform/backend/backend/api/features/chat/routes.py
@@ -24,6 +24,7 @@ from backend.copilot.model import (
ChatSession,
append_and_save_message,
create_chat_session,
+ delete_chat_session,
get_chat_session,
get_user_sessions,
)
@@ -212,6 +213,43 @@ async def create_session(
)
+@router.delete(
+ "/sessions/{session_id}",
+ dependencies=[Security(auth.requires_user)],
+ status_code=204,
+ responses={404: {"description": "Session not found or access denied"}},
+)
+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/backend/docker-compose.test.yaml b/autogpt_platform/backend/docker-compose.test.yaml
index 259d52c497..5944bf37ee 100644
--- a/autogpt_platform/backend/docker-compose.test.yaml
+++ b/autogpt_platform/backend/docker-compose.test.yaml
@@ -53,7 +53,7 @@ services:
rabbitmq:
<<: *agpt-services
- image: rabbitmq:management
+ image: rabbitmq:4.1.4
container_name: rabbitmq
healthcheck:
test: rabbitmq-diagnostics -q ping
@@ -66,7 +66,6 @@ services:
- RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
ports:
- "5672:5672"
- - "15672:15672"
clamav:
image: clamav/clamav-debian:latest
ports:
diff --git a/autogpt_platform/docker-compose.platform.yml b/autogpt_platform/docker-compose.platform.yml
index 16b7843dc0..906cbb40a7 100644
--- a/autogpt_platform/docker-compose.platform.yml
+++ b/autogpt_platform/docker-compose.platform.yml
@@ -75,7 +75,7 @@ services:
timeout: 5s
retries: 5
rabbitmq:
- image: rabbitmq:management
+ image: rabbitmq:4.1.4
container_name: rabbitmq
healthcheck:
test: rabbitmq-diagnostics -q ping
@@ -88,14 +88,13 @@ services:
<<: *backend-env
ports:
- "5672:5672"
- - "15672:15672"
rest_server:
build:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.rest"]
+ command: ["rest"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
@@ -128,7 +127,7 @@ services:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.exec"]
+ command: ["executor"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
@@ -198,7 +197,7 @@ services:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.ws"]
+ command: ["ws"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
@@ -231,7 +230,7 @@ services:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.db"]
+ command: ["db"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
@@ -260,7 +259,7 @@ services:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.scheduler"]
+ command: ["scheduler"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
@@ -308,7 +307,7 @@ services:
context: ../
dockerfile: autogpt_platform/backend/Dockerfile
target: server
- command: ["python", "-m", "backend.notification"]
+ command: ["notification"] # points to entry in [tool.poetry.scripts] in pyproject.toml
develop:
watch:
- path: ./
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
index 0d403b1a79..35b34890ce 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
@@ -1,6 +1,8 @@
"use client";
import { SidebarProvider } from "@/components/ui/sidebar";
+// TODO: Replace with modern Dialog component when available
+import DeleteConfirmDialog from "@/components/__legacy__/delete-confirm-dialog";
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
@@ -31,6 +33,12 @@ export function CopilotPage() {
handleDrawerOpenChange,
handleSelectSession,
handleNewChat,
+ // Delete functionality
+ sessionToDelete,
+ isDeleting,
+ handleDeleteClick,
+ handleConfirmDelete,
+ handleCancelDelete,
} = useCopilotPage();
if (isUserLoading || !isLoggedIn) {
@@ -48,7 +56,19 @@ export function CopilotPage() {
>
{!isMobile && }
- {isMobile &&
}
+ {isMobile && (
+
{
+ const session = sessions.find((s) => s.id === sessionId);
+ if (session) {
+ handleDeleteClick(session.id, session.title);
+ }
+ }}
+ />
+ )}
)}
+ {/* Delete confirmation dialog - rendered at top level for proper z-index on mobile */}
+ {isMobile && (
+
!open && handleCancelDelete()}
+ onDoDelete={handleConfirmDelete}
+ />
+ )}
);
}
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..8e785dd9d3 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,15 @@
"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 { toast } from "@/components/molecules/Toast/use-toast";
+// TODO: Replace with modern Dialog component when available
+import DeleteConfirmDialog from "@/components/__legacy__/delete-confirm-dialog";
import {
Sidebar,
SidebarContent,
@@ -12,18 +19,52 @@ 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 | undefined;
+ } | 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) => {
+ toast({
+ title: "Failed to delete chat",
+ description:
+ error instanceof Error ? error.message : "An error occurred",
+ variant: "destructive",
+ });
+ setSessionToDelete(null);
+ },
+ },
+ });
+
const sessions =
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
@@ -35,6 +76,22 @@ export function ChatSidebar() {
setSessionId(id);
}
+ function handleDeleteClick(
+ e: React.MouseEvent,
+ id: string,
+ title: string | null | undefined,
+ ) {
+ e.stopPropagation(); // Prevent session selection
+ if (isDeleting) return; // Prevent double-click during deletion
+ 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 +118,152 @@ 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/(platform)/copilot/components/MobileHeader/MobileHeader.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
index e0d6161744..b4b7636c81 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
@@ -1,22 +1,46 @@
import { Button } from "@/components/atoms/Button/Button";
import { NAVBAR_HEIGHT_PX } from "@/lib/constants";
-import { ListIcon } from "@phosphor-icons/react";
+import { ListIcon, TrashIcon } from "@phosphor-icons/react";
interface Props {
onOpenDrawer: () => void;
+ showDelete?: boolean;
+ isDeleting?: boolean;
+ onDelete?: () => void;
}
-export function MobileHeader({ onOpenDrawer }: Props) {
+export function MobileHeader({
+ onOpenDrawer,
+ showDelete,
+ isDeleting,
+ onDelete,
+}: Props) {
return (
-
+
+ {showDelete && onDelete && (
+
+ )}
+
);
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
index 28e9ba7cfb..444e745ec6 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
@@ -1,10 +1,15 @@
-import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
+import {
+ getGetV2ListSessionsQueryKey,
+ useDeleteV2DeleteSession,
+ useGetV2ListSessions,
+} from "@/app/api/__generated__/endpoints/chat/chat";
import { toast } from "@/components/molecules/Toast/use-toast";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useChat } from "@ai-sdk/react";
+import { useQueryClient } from "@tanstack/react-query";
import { DefaultChatTransport } from "ai";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useChatSession } from "./useChatSession";
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
@@ -14,6 +19,11 @@ export function useCopilotPage() {
const { isUserLoading, isLoggedIn } = useSupabase();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [pendingMessage, setPendingMessage] = useState(null);
+ const [sessionToDelete, setSessionToDelete] = useState<{
+ id: string;
+ title: string | null | undefined;
+ } | null>(null);
+ const queryClient = useQueryClient();
const {
sessionId,
@@ -24,6 +34,30 @@ export function useCopilotPage() {
isCreatingSession,
} = useChatSession();
+ const { mutate: deleteSessionMutation, isPending: isDeleting } =
+ useDeleteV2DeleteSession({
+ mutation: {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: getGetV2ListSessionsQueryKey(),
+ });
+ if (sessionToDelete?.id === sessionId) {
+ setSessionId(null);
+ }
+ setSessionToDelete(null);
+ },
+ onError: (error) => {
+ toast({
+ title: "Failed to delete chat",
+ description:
+ error instanceof Error ? error.message : "An error occurred",
+ variant: "destructive",
+ });
+ setSessionToDelete(null);
+ },
+ },
+ });
+
const breakpoint = useBreakpoint();
const isMobile =
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
@@ -143,6 +177,24 @@ export function useCopilotPage() {
if (isMobile) setIsDrawerOpen(false);
}
+ const handleDeleteClick = useCallback(
+ (id: string, title: string | null | undefined) => {
+ if (isDeleting) return;
+ setSessionToDelete({ id, title });
+ },
+ [isDeleting],
+ );
+
+ const handleConfirmDelete = useCallback(() => {
+ if (sessionToDelete) {
+ deleteSessionMutation({ sessionId: sessionToDelete.id });
+ }
+ }, [sessionToDelete, deleteSessionMutation]);
+
+ const handleCancelDelete = useCallback(() => {
+ setSessionToDelete(null);
+ }, []);
+
return {
sessionId,
messages,
@@ -165,5 +217,11 @@ export function useCopilotPage() {
handleDrawerOpenChange,
handleSelectSession,
handleNewChat,
+ // Delete functionality
+ sessionToDelete,
+ isDeleting,
+ handleDeleteClick,
+ handleConfirmDelete,
+ handleCancelDelete,
};
}
diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json
index 63a8a856b9..feabc9b51d 100644
--- a/autogpt_platform/frontend/src/app/api/openapi.json
+++ b/autogpt_platform/frontend/src/app/api/openapi.json
@@ -1151,6 +1151,36 @@
}
},
"/api/chat/sessions/{session_id}": {
+ "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.\n\nRaises:\n HTTPException: 404 if session not found or not owned by user.",
+ "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" }
+ }
+ }
+ }
+ }
+ },
"get": {
"tags": ["v2", "chat", "chat"],
"summary": "Get Session",
diff --git a/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx b/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
index 4ce998b6f6..10af4fa3c8 100644
--- a/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
+++ b/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
@@ -115,7 +115,7 @@ const DialogFooter = ({
}: React.HTMLAttributes) => (