From 94bd91388f46afd7f676e5bbde0be584aee5169e Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Fri, 13 Feb 2026 21:23:52 +0530 Subject: [PATCH] feat(library): enhance folder and agent management with error handling improvements - Added support for updating the folder ID in the `update_library_agent` function, allowing agents to be moved to different folders. - Implemented cleanup of schedules and webhooks for affected agents during folder deletion, ensuring proper resource management. - Improved error handling in various folder-related API endpoints, standardizing error messages to provide clearer feedback to users. - Updated the `LibraryFolderEditDialog` to handle API errors more effectively, enhancing user experience during folder operations. These changes improve the functionality and reliability of folder and agent management within the library, providing users with a smoother experience when organizing their agents. --- .../backend/api/features/library/db.py | 32 +++++++++++++++++-- .../api/features/library/routes/agents.py | 12 ++++--- .../api/features/library/routes/folders.py | 16 +++++----- .../LibraryFolderEditDialog.tsx | 27 ++++++++-------- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/library/db.py b/autogpt_platform/backend/backend/api/features/library/db.py index 1eb224521a..6561c341bd 100644 --- a/autogpt_platform/backend/backend/api/features/library/db.py +++ b/autogpt_platform/backend/backend/api/features/library/db.py @@ -688,6 +688,9 @@ async def update_library_agent( update_fields["isDeleted"] = is_deleted if settings is not None: update_fields["settings"] = SafeJson(settings.model_dump()) + if folder_id is not None: + # Empty string means "move to root" (no folder) + update_fields["folderId"] = None if folder_id == "" else folder_id try: # If graph_version is provided, update to that specific version @@ -1510,11 +1513,36 @@ async def delete_folder( if not existing: raise NotFoundError(f"Folder #{folder_id} not found") + # Collect all folder IDs (target + descendants) before the transaction async with transaction() as tx: - # Get all descendant folders recursively descendant_ids = await _get_descendant_folder_ids(folder_id, user_id, tx) - all_folder_ids = [folder_id] + descendant_ids + all_folder_ids = [folder_id] + descendant_ids + if soft_delete: + # Clean up schedules/webhooks for each affected agent before + # soft-deleting, matching what delete_library_agent() does. + affected_agents = await prisma.models.LibraryAgent.prisma().find_many( + where={ + "folderId": {"in": all_folder_ids}, + "userId": user_id, + "isDeleted": False, + }, + ) + for agent in affected_agents: + try: + await _cleanup_schedules_for_graph( + graph_id=agent.agentGraphId, user_id=user_id + ) + await _cleanup_webhooks_for_graph( + graph_id=agent.agentGraphId, user_id=user_id + ) + except Exception as e: + logger.warning( + f"Cleanup failed for agent {agent.id} " + f"(graph {agent.agentGraphId}): {e}" + ) + + async with transaction() as tx: if soft_delete: # Soft-delete all agents in these folders await prisma.models.LibraryAgent.prisma(tx).update_many( diff --git a/autogpt_platform/backend/backend/api/features/library/routes/agents.py b/autogpt_platform/backend/backend/api/features/library/routes/agents.py index 9a3f687abc..801a5a703b 100644 --- a/autogpt_platform/backend/backend/api/features/library/routes/agents.py +++ b/autogpt_platform/backend/backend/api/features/library/routes/agents.py @@ -194,16 +194,20 @@ async def update_library_agent( detail=str(e), ) from e except DatabaseError as e: - logger.error(f"Database error while updating library agent: {e}") + logger.error( + f"Database error while updating library agent: {e}", exc_info=True + ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail={"message": str(e), "hint": "Verify DB connection."}, + detail={"message": "Internal server error", "hint": "Contact support"}, ) from e except Exception as e: - logger.error(f"Unexpected error while updating library agent: {e}") + logger.error( + f"Unexpected error while updating library agent: {e}", exc_info=True + ) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail={"message": str(e), "hint": "Check server logs."}, + detail={"message": "Internal server error", "hint": "Contact support"}, ) from e diff --git a/autogpt_platform/backend/backend/api/features/library/routes/folders.py b/autogpt_platform/backend/backend/api/features/library/routes/folders.py index 324fbd5c08..053a785fe0 100644 --- a/autogpt_platform/backend/backend/api/features/library/routes/folders.py +++ b/autogpt_platform/backend/backend/api/features/library/routes/folders.py @@ -69,7 +69,7 @@ async def list_folders( logger.error(f"Could not list folders for user #{user_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -101,7 +101,7 @@ async def get_folder_tree( logger.error(f"Could not get folder tree for user #{user_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -140,7 +140,7 @@ async def get_folder( logger.error(f"Could not get folder #{folder_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -198,7 +198,7 @@ async def create_folder( logger.error(f"Database error creating folder: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -257,7 +257,7 @@ async def update_folder( logger.error(f"Database error updating folder #{folder_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -314,7 +314,7 @@ async def move_folder( logger.error(f"Database error moving folder #{folder_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -358,7 +358,7 @@ async def delete_folder( logger.error(f"Database error deleting folder #{folder_id}: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e @@ -404,5 +404,5 @@ async def bulk_move_agents( logger.error(f"Database error bulk moving agents: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=str(e), + detail="Internal server error", ) from e diff --git a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx index a0c73b3aca..ba5ec1df7c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/library/components/LibraryFolderEditDialog/LibraryFolderEditDialog.tsx @@ -25,6 +25,7 @@ import { import { useQueryClient } from "@tanstack/react-query"; import type { LibraryFolder } from "@/app/api/__generated__/models/libraryFolder"; import type { getV2ListLibraryFoldersResponseSuccess } from "@/app/api/__generated__/endpoints/folders/folders"; +import { ApiError } from "@/lib/autogpt-server-api/helpers"; const FOLDER_COLORS = [ { value: "#3B82F6", label: "Blue" }, @@ -106,25 +107,23 @@ export function LibraryFolderEditDialog({ folder, isOpen, setIsOpen }: Props) { return { previousData }; }, - onError: ( - error: { detail?: string; response?: { detail?: string } }, - _variables, - context, - ) => { + onError: (error: unknown, _variables, context) => { if (context?.previousData) { for (const [queryKey, data] of context.previousData) { queryClient.setQueryData(queryKey, data); } } - const detail = error.detail ?? error.response?.detail ?? ""; - if ( - typeof detail === "string" && - detail.toLowerCase().includes("already exists") - ) { - form.setError("folderName", { - message: "A folder with this name already exists", - }); - return; + if (error instanceof ApiError) { + const detail = (error.response as any)?.detail ?? ""; + if ( + typeof detail === "string" && + detail.toLowerCase().includes("already exists") + ) { + form.setError("folderName", { + message: "A folder with this name already exists", + }); + return; + } } toast({ title: "Error",