Compare commits

..

2 Commits

Author SHA1 Message Date
Bentlybro
b00336b66b style: add language tag to fenced code block 2026-02-16 12:08:53 +00:00
Bentlybro
23ea6bd38c docs: add Podman compatibility warning
Podman and podman-compose are not supported as they handle relative
paths differently than Docker, causing 'Dockerfile does not exist'
errors on Windows.

Closes #11358
2026-02-16 11:54:44 +00:00
17 changed files with 225 additions and 570 deletions

View File

@@ -41,18 +41,13 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
rabbitmq: rabbitmq:
image: rabbitmq:4.1.4 image: rabbitmq:3.12-management
ports: ports:
- 5672:5672 - 5672:5672
- 15672:15672
env: env:
RABBITMQ_DEFAULT_USER: ${{ env.RABBITMQ_DEFAULT_USER }} RABBITMQ_DEFAULT_USER: ${{ env.RABBITMQ_DEFAULT_USER }}
RABBITMQ_DEFAULT_PASS: ${{ env.RABBITMQ_DEFAULT_PASS }} 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: clamav:
image: clamav/clamav-debian:latest image: clamav/clamav-debian:latest
ports: ports:

View File

@@ -6,16 +6,10 @@ on:
paths: paths:
- ".github/workflows/platform-frontend-ci.yml" - ".github/workflows/platform-frontend-ci.yml"
- "autogpt_platform/frontend/**" - "autogpt_platform/frontend/**"
- "autogpt_platform/backend/Dockerfile"
- "autogpt_platform/docker-compose.yml"
- "autogpt_platform/docker-compose.platform.yml"
pull_request: pull_request:
paths: paths:
- ".github/workflows/platform-frontend-ci.yml" - ".github/workflows/platform-frontend-ci.yml"
- "autogpt_platform/frontend/**" - "autogpt_platform/frontend/**"
- "autogpt_platform/backend/Dockerfile"
- "autogpt_platform/docker-compose.yml"
- "autogpt_platform/docker-compose.platform.yml"
merge_group: merge_group:
workflow_dispatch: workflow_dispatch:

View File

@@ -53,6 +53,63 @@ COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/parti
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./ COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
RUN poetry run prisma generate && poetry run gen-prisma-stub RUN poetry run prisma generate && poetry run gen-prisma-stub
# ============================== BACKEND SERVER ============================== #
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
# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
# for the bash_exec MCP tool.
# Using --no-install-recommends saves ~650MB by skipping unnecessary deps like llvm, mesa, etc.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.13 \
python3-pip \
ffmpeg \
imagemagick \
jq \
ripgrep \
tree \
bubblewrap \
&& rm -rf /var/lib/apt/lists/*
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
COPY --from=builder /usr/bin/node /usr/bin/node
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
COPY --from=builder /usr/bin/npm /usr/bin/npm
COPY --from=builder /usr/bin/npx /usr/bin/npx
COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries
WORKDIR /app/autogpt_platform/backend
# Copy only the .venv from builder (not the entire /app directory)
# The .venv includes the generated Prisma client
COPY --from=builder /app/autogpt_platform/backend/.venv ./.venv
ENV PATH="/app/autogpt_platform/backend/.venv/bin:$PATH"
# Copy dependency files + autogpt_libs (path dependency)
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.toml ./
# Copy backend code + docs (for Copilot docs search)
COPY autogpt_platform/backend ./
COPY docs /app/docs
RUN poetry install --no-ansi --only-root
ENV PORT=8000
CMD ["poetry", "run", "rest"]
# =============================== DB MIGRATOR =============================== # # =============================== DB MIGRATOR =============================== #
# Lightweight migrate stage - only needs Prisma CLI, not full Python environment # Lightweight migrate stage - only needs Prisma CLI, not full Python environment
@@ -84,59 +141,3 @@ COPY autogpt_platform/backend/schema.prisma ./
COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/partial_types.py COPY autogpt_platform/backend/backend/data/partial_types.py ./backend/data/partial_types.py
COPY autogpt_platform/backend/gen_prisma_types_stub.py ./ COPY autogpt_platform/backend/gen_prisma_types_stub.py ./
COPY autogpt_platform/backend/migrations ./migrations COPY autogpt_platform/backend/migrations ./migrations
# ============================== BACKEND SERVER ============================== #
FROM debian:13-slim AS server
WORKDIR /app
ENV DEBIAN_FRONTEND=noninteractive
# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
# for the bash_exec MCP tool.
# Using --no-install-recommends saves ~650MB by skipping unnecessary deps like llvm, mesa, etc.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.13 \
python3-pip \
ffmpeg \
imagemagick \
jq \
ripgrep \
tree \
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
COPY --from=builder /usr/bin/node /usr/bin/node
COPY --from=builder /usr/lib/node_modules /usr/lib/node_modules
COPY --from=builder /usr/bin/npm /usr/bin/npm
COPY --from=builder /usr/bin/npx /usr/bin/npx
COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries
WORKDIR /app/autogpt_platform/backend
# Copy only the .venv from builder (not the entire /app directory)
# The .venv includes the generated Prisma client
COPY --from=builder /app/autogpt_platform/backend/.venv ./.venv
ENV PATH="/app/autogpt_platform/backend/.venv/bin:$PATH"
# Copy dependency files + autogpt_libs (path dependency)
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.toml ./
# Copy backend code + docs (for Copilot docs search)
COPY autogpt_platform/backend ./
COPY docs /app/docs
# Install the project package to create entry point scripts in .venv/bin/
# (e.g., rest, executor, ws, db, scheduler, notification - see [tool.poetry.scripts])
RUN POETRY_VIRTUALENVS_CREATE=true POETRY_VIRTUALENVS_IN_PROJECT=true \
poetry install --no-ansi --only-root
ENV PORT=8000
CMD ["rest"]

View File

@@ -23,7 +23,6 @@ from .model import (
ChatSession, ChatSession,
append_and_save_message, append_and_save_message,
create_chat_session, create_chat_session,
delete_chat_session,
get_chat_session, get_chat_session,
get_user_sessions, get_user_sessions,
) )
@@ -212,43 +211,6 @@ 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( @router.get(
"/sessions/{session_id}", "/sessions/{session_id}",
) )

View File

@@ -187,11 +187,9 @@ class ClaudeCodeBlock(Block):
) )
files: list[SandboxFileOutput] = SchemaField( files: list[SandboxFileOutput] = SchemaField(
description=( description=(
"List of files created/modified by Claude Code during this execution. " "List of text files created/modified by Claude Code during this execution. "
"Includes text files and binary files (images, PDFs, etc.). "
"Each file has 'path', 'relative_path', 'name', 'content', and 'workspace_ref' fields. " "Each file has 'path', 'relative_path', 'name', 'content', and 'workspace_ref' fields. "
"workspace_ref contains a workspace:// URI for workspace storage. " "workspace_ref contains a workspace:// URI if the file was stored to workspace."
"For binary files, content contains a placeholder; use workspace_ref to access the file."
) )
) )
conversation_history: str = SchemaField( conversation_history: str = SchemaField(
@@ -455,14 +453,12 @@ class ClaudeCodeBlock(Block):
new_conversation_history = turn_entry new_conversation_history = turn_entry
# Extract files created/modified during this run and store to workspace # Extract files created/modified during this run and store to workspace
# Include binary files (images, PDFs, etc.) - they'll be stored via
# store_media_file which handles virus scanning and workspace storage
sandbox_files = await extract_and_store_sandbox_files( sandbox_files = await extract_and_store_sandbox_files(
sandbox=sandbox, sandbox=sandbox,
working_directory=working_directory, working_directory=working_directory,
execution_context=execution_context, execution_context=execution_context,
since_timestamp=start_timestamp, since_timestamp=start_timestamp,
text_only=False, # Extract both text and binary files text_only=True,
) )
return ( return (

View File

@@ -74,51 +74,8 @@ TEXT_EXTENSIONS = {
".tex", ".tex",
".csv", ".csv",
".log", ".log",
".svg", # SVG is XML-based text
} }
# Binary file extensions we explicitly support extracting
# These are common output formats that users expect to retrieve
BINARY_EXTENSIONS = {
# Images
".png",
".jpg",
".jpeg",
".gif",
".webp",
".ico",
".bmp",
".tiff",
".tif",
# Documents
".pdf",
# Archives
".zip",
".tar",
".gz",
".7z",
# Audio
".mp3",
".wav",
".ogg",
".flac",
# Video
".mp4",
".webm",
".mov",
".avi",
# Fonts
".woff",
".woff2",
".ttf",
".otf",
".eot",
}
# Maximum file size for binary extraction (50MB)
# Prevents OOM from accidentally extracting huge files
MAX_BINARY_FILE_SIZE = 50 * 1024 * 1024
class SandboxFileOutput(BaseModel): class SandboxFileOutput(BaseModel):
"""A file extracted from a sandbox and optionally stored in workspace.""" """A file extracted from a sandbox and optionally stored in workspace."""
@@ -163,8 +120,7 @@ async def extract_sandbox_files(
sandbox: The E2B sandbox instance sandbox: The E2B sandbox instance
working_directory: Directory to search for files working_directory: Directory to search for files
since_timestamp: ISO timestamp - only return files modified after this time since_timestamp: ISO timestamp - only return files modified after this time
text_only: If True, only extract text files. If False, also extract text_only: If True, only extract text files (default). If False, extract all files.
supported binary files (images, PDFs, etc.).
Returns: Returns:
List of ExtractedFile objects with path, content, and metadata List of ExtractedFile objects with path, content, and metadata
@@ -193,53 +149,14 @@ async def extract_sandbox_files(
if not file_path: if not file_path:
continue continue
# Check file type (case-insensitive for extensions) # Check if it's a text file
file_path_lower = file_path.lower() is_text = any(file_path.endswith(ext) for ext in TEXT_EXTENSIONS)
is_text = any(
file_path_lower.endswith(ext.lower()) for ext in TEXT_EXTENSIONS
)
is_binary = any(
file_path_lower.endswith(ext.lower()) for ext in BINARY_EXTENSIONS
)
# Determine if we should extract this file # Skip non-text files if text_only mode
if text_only: if text_only and not is_text:
# Only extract text files
if not is_text:
continue
else:
# Extract text files and supported binary files
if not is_text and not is_binary:
continue continue
try: try:
# For binary files, check size before reading to prevent OOM
if is_binary:
stat_result = await sandbox.commands.run(
f"stat -c %s {shlex.quote(file_path)} 2>/dev/null"
)
if stat_result.exit_code != 0 or not stat_result.stdout:
logger.debug(
f"Skipping {file_path}: could not determine file size"
)
continue
try:
file_size = int(stat_result.stdout.strip())
except ValueError:
logger.debug(
f"Skipping {file_path}: unexpected stat output "
f"{stat_result.stdout.strip()!r}"
)
continue
if file_size > MAX_BINARY_FILE_SIZE:
logger.info(
f"Skipping {file_path}: size {file_size} bytes "
f"exceeds limit {MAX_BINARY_FILE_SIZE}"
)
continue
# Read file content as bytes # Read file content as bytes
content = await sandbox.files.read(file_path, format="bytes") content = await sandbox.files.read(file_path, format="bytes")
if isinstance(content, str): if isinstance(content, str):

View File

@@ -53,7 +53,7 @@ services:
rabbitmq: rabbitmq:
<<: *agpt-services <<: *agpt-services
image: rabbitmq:4.1.4 image: rabbitmq:management
container_name: rabbitmq container_name: rabbitmq
healthcheck: healthcheck:
test: rabbitmq-diagnostics -q ping test: rabbitmq-diagnostics -q ping
@@ -66,6 +66,7 @@ services:
- RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7 - RABBITMQ_DEFAULT_PASS=k0VMxyIJF9S35f3x2uaw5IWAl6Y536O7
ports: ports:
- "5672:5672" - "5672:5672"
- "15672:15672"
clamav: clamav:
image: clamav/clamav-debian:latest image: clamav/clamav-debian:latest
ports: ports:

View File

@@ -75,7 +75,7 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
rabbitmq: rabbitmq:
image: rabbitmq:4.1.4 image: rabbitmq:management
container_name: rabbitmq container_name: rabbitmq
healthcheck: healthcheck:
test: rabbitmq-diagnostics -q ping test: rabbitmq-diagnostics -q ping
@@ -88,13 +88,14 @@ services:
<<: *backend-env <<: *backend-env
ports: ports:
- "5672:5672" - "5672:5672"
- "15672:15672"
rest_server: rest_server:
build: build:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["rest"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.rest"]
develop: develop:
watch: watch:
- path: ./ - path: ./
@@ -127,7 +128,7 @@ services:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["executor"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.exec"]
develop: develop:
watch: watch:
- path: ./ - path: ./
@@ -162,7 +163,7 @@ services:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["ws"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.ws"]
develop: develop:
watch: watch:
- path: ./ - path: ./
@@ -195,7 +196,7 @@ services:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["db"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.db"]
develop: develop:
watch: watch:
- path: ./ - path: ./
@@ -224,7 +225,7 @@ services:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["scheduler"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.scheduler"]
develop: develop:
watch: watch:
- path: ./ - path: ./
@@ -272,7 +273,7 @@ services:
context: ../ context: ../
dockerfile: autogpt_platform/backend/Dockerfile dockerfile: autogpt_platform/backend/Dockerfile
target: server target: server
command: ["notification"] # points to entry in [tool.poetry.scripts] in pyproject.toml command: ["python", "-m", "backend.notification"]
develop: develop:
watch: watch:
- path: ./ - path: ./

View File

@@ -1,8 +1,6 @@
"use client"; "use client";
import { SidebarProvider } from "@/components/ui/sidebar"; 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 { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar"; import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer"; import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
@@ -33,12 +31,6 @@ export function CopilotPage() {
handleDrawerOpenChange, handleDrawerOpenChange,
handleSelectSession, handleSelectSession,
handleNewChat, handleNewChat,
// Delete functionality
sessionToDelete,
isDeleting,
handleDeleteClick,
handleConfirmDelete,
handleCancelDelete,
} = useCopilotPage(); } = useCopilotPage();
if (isUserLoading || !isLoggedIn) { if (isUserLoading || !isLoggedIn) {
@@ -56,19 +48,7 @@ export function CopilotPage() {
> >
{!isMobile && <ChatSidebar />} {!isMobile && <ChatSidebar />}
<div className="relative flex h-full w-full flex-col overflow-hidden bg-[#f8f8f9] px-0"> <div className="relative flex h-full w-full flex-col overflow-hidden bg-[#f8f8f9] px-0">
{isMobile && ( {isMobile && <MobileHeader onOpenDrawer={handleOpenDrawer} />}
<MobileHeader
onOpenDrawer={handleOpenDrawer}
showDelete={!!sessionId}
isDeleting={isDeleting}
onDelete={() => {
const session = sessions.find((s) => s.id === sessionId);
if (session) {
handleDeleteClick(session.id, session.title);
}
}}
/>
)}
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<ChatContainer <ChatContainer
messages={messages} messages={messages}
@@ -95,16 +75,6 @@ export function CopilotPage() {
onOpenChange={handleDrawerOpenChange} onOpenChange={handleDrawerOpenChange}
/> />
)} )}
{/* Delete confirmation dialog - rendered at top level for proper z-index on mobile */}
{isMobile && (
<DeleteConfirmDialog
entityType="chat"
entityName={sessionToDelete?.title || "Untitled chat"}
open={!!sessionToDelete}
onOpenChange={(open) => !open && handleCancelDelete()}
onDoDelete={handleConfirmDelete}
/>
)}
</SidebarProvider> </SidebarProvider>
); );
} }

View File

@@ -1,15 +1,8 @@
"use client"; "use client";
import { import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
getGetV2ListSessionsQueryKey,
useDeleteV2DeleteSession,
useGetV2ListSessions,
} from "@/app/api/__generated__/endpoints/chat/chat";
import { Button } from "@/components/atoms/Button/Button"; import { Button } from "@/components/atoms/Button/Button";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner"; import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { Text } from "@/components/atoms/Text/Text"; 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 { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
@@ -19,52 +12,18 @@ import {
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { PlusCircleIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react"; import { PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
import { useQueryClient } from "@tanstack/react-query";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useState } from "react";
import { parseAsString, useQueryState } from "nuqs"; import { parseAsString, useQueryState } from "nuqs";
export function ChatSidebar() { export function ChatSidebar() {
const { state } = useSidebar(); const { state } = useSidebar();
const isCollapsed = state === "collapsed"; const isCollapsed = state === "collapsed";
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString); 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 } = const { data: sessionsResponse, isLoading: isLoadingSessions } =
useGetV2ListSessions({ limit: 50 }); 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 = const sessions =
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : []; sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
@@ -76,22 +35,6 @@ export function ChatSidebar() {
setSessionId(id); 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) { function formatDate(dateString: string) {
const date = new Date(dateString); const date = new Date(dateString);
const now = new Date(); const now = new Date();
@@ -118,7 +61,6 @@ export function ChatSidebar() {
} }
return ( return (
<>
<Sidebar <Sidebar
variant="inset" variant="inset"
collapsible="icon" collapsible="icon"
@@ -188,18 +130,15 @@ export function ChatSidebar() {
</p> </p>
) : ( ) : (
sessions.map((session) => ( sessions.map((session) => (
<div <button
key={session.id} key={session.id}
onClick={() => handleSelectSession(session.id)}
className={cn( className={cn(
"group relative w-full rounded-lg transition-colors", "w-full rounded-lg px-3 py-2.5 text-left transition-colors",
session.id === sessionId session.id === sessionId
? "bg-zinc-100" ? "bg-zinc-100"
: "hover:bg-zinc-50", : "hover:bg-zinc-50",
)} )}
>
<button
onClick={() => handleSelectSession(session.id)}
className="w-full px-3 py-2.5 pr-10 text-left"
> >
<div className="flex min-w-0 max-w-full flex-col overflow-hidden"> <div className="flex min-w-0 max-w-full flex-col overflow-hidden">
<div className="min-w-0 max-w-full"> <div className="min-w-0 max-w-full">
@@ -220,17 +159,6 @@ export function ChatSidebar() {
</Text> </Text>
</div> </div>
</button> </button>
<button
onClick={(e) =>
handleDeleteClick(e, session.id, session.title)
}
disabled={isDeleting}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1.5 text-zinc-400 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-100 hover:text-red-600 focus-visible:opacity-100 disabled:cursor-not-allowed disabled:opacity-50"
aria-label="Delete chat"
>
<TrashIcon className="h-4 w-4" />
</button>
</div>
)) ))
)} )}
</motion.div> </motion.div>
@@ -256,14 +184,5 @@ export function ChatSidebar() {
</SidebarFooter> </SidebarFooter>
)} )}
</Sidebar> </Sidebar>
<DeleteConfirmDialog
entityType="chat"
entityName={sessionToDelete?.title || "Untitled chat"}
open={!!sessionToDelete}
onOpenChange={(open) => !open && setSessionToDelete(null)}
onDoDelete={handleConfirmDelete}
/>
</>
); );
} }

View File

@@ -1,46 +1,22 @@
import { Button } from "@/components/atoms/Button/Button"; import { Button } from "@/components/atoms/Button/Button";
import { NAVBAR_HEIGHT_PX } from "@/lib/constants"; import { NAVBAR_HEIGHT_PX } from "@/lib/constants";
import { ListIcon, TrashIcon } from "@phosphor-icons/react"; import { ListIcon } from "@phosphor-icons/react";
interface Props { interface Props {
onOpenDrawer: () => void; onOpenDrawer: () => void;
showDelete?: boolean;
isDeleting?: boolean;
onDelete?: () => void;
} }
export function MobileHeader({ export function MobileHeader({ onOpenDrawer }: Props) {
onOpenDrawer,
showDelete,
isDeleting,
onDelete,
}: Props) {
return ( return (
<div
className="fixed z-50 flex gap-2"
style={{ left: "1rem", top: `${NAVBAR_HEIGHT_PX + 20}px` }}
>
<Button <Button
variant="icon" variant="icon"
size="icon" size="icon"
aria-label="Open sessions" aria-label="Open sessions"
onClick={onOpenDrawer} onClick={onOpenDrawer}
className="bg-white shadow-md" className="fixed z-50 bg-white shadow-md"
style={{ left: "1rem", top: `${NAVBAR_HEIGHT_PX + 20}px` }}
> >
<ListIcon width="1.25rem" height="1.25rem" /> <ListIcon width="1.25rem" height="1.25rem" />
</Button> </Button>
{showDelete && onDelete && (
<Button
variant="icon"
size="icon"
aria-label="Delete current chat"
onClick={onDelete}
disabled={isDeleting}
className="bg-white text-red-500 shadow-md hover:bg-red-50 hover:text-red-600 disabled:opacity-50"
>
<TrashIcon width="1.25rem" height="1.25rem" />
</Button>
)}
</div>
); );
} }

View File

@@ -1,15 +1,10 @@
import { import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
getGetV2ListSessionsQueryKey,
useDeleteV2DeleteSession,
useGetV2ListSessions,
} from "@/app/api/__generated__/endpoints/chat/chat";
import { toast } from "@/components/molecules/Toast/use-toast"; import { toast } from "@/components/molecules/Toast/use-toast";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint"; import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useChat } from "@ai-sdk/react"; import { useChat } from "@ai-sdk/react";
import { useQueryClient } from "@tanstack/react-query";
import { DefaultChatTransport } from "ai"; import { DefaultChatTransport } from "ai";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useChatSession } from "./useChatSession"; import { useChatSession } from "./useChatSession";
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling"; import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
@@ -19,11 +14,6 @@ export function useCopilotPage() {
const { isUserLoading, isLoggedIn } = useSupabase(); const { isUserLoading, isLoggedIn } = useSupabase();
const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [pendingMessage, setPendingMessage] = useState<string | null>(null); const [pendingMessage, setPendingMessage] = useState<string | null>(null);
const [sessionToDelete, setSessionToDelete] = useState<{
id: string;
title: string | null | undefined;
} | null>(null);
const queryClient = useQueryClient();
const { const {
sessionId, sessionId,
@@ -34,30 +24,6 @@ export function useCopilotPage() {
isCreatingSession, isCreatingSession,
} = useChatSession(); } = 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 breakpoint = useBreakpoint();
const isMobile = const isMobile =
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md"; breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
@@ -177,24 +143,6 @@ export function useCopilotPage() {
if (isMobile) setIsDrawerOpen(false); 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 { return {
sessionId, sessionId,
messages, messages,
@@ -217,11 +165,5 @@ export function useCopilotPage() {
handleDrawerOpenChange, handleDrawerOpenChange,
handleSelectSession, handleSelectSession,
handleNewChat, handleNewChat,
// Delete functionality
sessionToDelete,
isDeleting,
handleDeleteClick,
handleConfirmDelete,
handleCancelDelete,
}; };
} }

View File

@@ -1151,36 +1151,6 @@
} }
}, },
"/api/chat/sessions/{session_id}": { "/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": { "get": {
"tags": ["v2", "chat", "chat"], "tags": ["v2", "chat", "chat"],
"summary": "Get Session", "summary": "Get Session",

View File

@@ -115,7 +115,7 @@ const DialogFooter = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className, className,
)} )}
{...props} {...props}

View File

@@ -16,7 +16,7 @@ When activated, the block:
- Install dependencies (npm, pip, etc.) - Install dependencies (npm, pip, etc.)
- Run terminal commands - Run terminal commands
- Build and test applications - Build and test applications
5. Extracts all files created/modified during execution (text files and binary files like images, PDFs, etc.) 5. Extracts all text files created/modified during execution
6. Returns the response and files, optionally keeping the sandbox alive for follow-up tasks 6. Returns the response and files, optionally keeping the sandbox alive for follow-up tasks
The block supports conversation continuation through three mechanisms: The block supports conversation continuation through three mechanisms:
@@ -42,7 +42,7 @@ The block supports conversation continuation through three mechanisms:
| Output | Description | | Output | Description |
|--------|-------------| |--------|-------------|
| Response | The output/response from Claude Code execution | | Response | The output/response from Claude Code execution |
| Files | List of files (text and binary) created/modified during execution. Includes images, PDFs, and other supported formats. Each file has path, relative_path, name, content, and workspace_ref fields. Binary files are stored in workspace and accessible via workspace_ref | | Files | List of text files created/modified during execution. Each file includes path, relative_path, name, and content fields |
| Conversation History | Full conversation history including this turn. Use to restore context on a fresh sandbox | | Conversation History | Full conversation history including this turn. Use to restore context on a fresh sandbox |
| Session ID | Session ID for this conversation. Pass back with sandbox_id to continue the conversation | | Session ID | Session ID for this conversation. Pass back with sandbox_id to continue the conversation |
| Sandbox ID | ID of the sandbox instance (null if disposed). Pass back with session_id to continue the conversation | | Sandbox ID | ID of the sandbox instance (null if disposed). Pass back with session_id to continue the conversation |

View File

@@ -535,7 +535,7 @@ When activated, the block:
2. Installs the latest version of Claude Code in the sandbox 2. Installs the latest version of Claude Code in the sandbox
3. Optionally runs setup commands to prepare the environment 3. Optionally runs setup commands to prepare the environment
4. Executes your prompt using Claude Code, which can create/edit files, install dependencies, run terminal commands, and build applications 4. Executes your prompt using Claude Code, which can create/edit files, install dependencies, run terminal commands, and build applications
5. Extracts all files created/modified during execution (text files and binary files like images, PDFs, etc.) 5. Extracts all text files created/modified during execution
6. Returns the response and files, optionally keeping the sandbox alive for follow-up tasks 6. Returns the response and files, optionally keeping the sandbox alive for follow-up tasks
The block supports conversation continuation through three mechanisms: The block supports conversation continuation through three mechanisms:
@@ -563,7 +563,7 @@ The block supports conversation continuation through three mechanisms:
|--------|-------------|------| |--------|-------------|------|
| error | Error message if execution failed | str | | error | Error message if execution failed | str |
| response | The output/response from Claude Code execution | str | | response | The output/response from Claude Code execution | str |
| files | List of files created/modified by Claude Code during this execution. Includes text files and binary files (images, PDFs, etc.). Each file has 'path', 'relative_path', 'name', 'content', and 'workspace_ref' fields. workspace_ref contains a workspace:// URI for workspace storage. For binary files, content contains a placeholder; use workspace_ref to access the file. | List[SandboxFileOutput] | | files | List of text files created/modified by Claude Code during this execution. Each file has 'path', 'relative_path', 'name', 'content', and 'workspace_ref' fields. workspace_ref contains a workspace:// URI if the file was stored to workspace. | List[SandboxFileOutput] |
| conversation_history | Full conversation history including this turn. Pass this to conversation_history input to continue on a fresh sandbox if the previous sandbox timed out. | str | | conversation_history | Full conversation history including this turn. Pass this to conversation_history input to continue on a fresh sandbox if the previous sandbox timed out. | str |
| session_id | Session ID for this conversation. Pass this back along with sandbox_id to continue the conversation. | str | | session_id | Session ID for this conversation. Pass this back along with sandbox_id to continue the conversation. | str |
| sandbox_id | ID of the sandbox instance. Pass this back along with session_id to continue the conversation. This is None if dispose_sandbox was True (sandbox was disposed). | str | | sandbox_id | ID of the sandbox instance. Pass this back along with session_id to continue the conversation. This is None if dispose_sandbox was True (sandbox was disposed). | str |

View File

@@ -218,6 +218,17 @@ If you initially installed Docker with Hyper-V, you **dont need to reinstall*
For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/). For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/).
### ⚠️ Podman Not Supported
AutoGPT requires **Docker** (Docker Desktop or Docker Engine). **Podman and podman-compose are not supported** and may cause path resolution issues, particularly on Windows.
If you see errors like:
```text
Error: the specified Containerfile or Dockerfile does not exist, ..\..\autogpt_platform\backend\Dockerfile
```
This indicates you're using Podman instead of Docker. Please install [Docker Desktop](https://docs.docker.com/desktop/) and use `docker compose` instead of `podman-compose`.
## Development ## Development