From 897cc5018772caa18d19a5a43852eb4b4120dfb1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 18 May 2026 09:44:01 -0400 Subject: [PATCH] fix: Ignore programmatic agent form resets --- .../SidePanel/Agents/AgentPanel.tsx | 18 ++++++++-- .../__tests__/AgentPanel.drafts.spec.tsx | 36 +++++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/client/src/components/SidePanel/Agents/AgentPanel.tsx b/client/src/components/SidePanel/Agents/AgentPanel.tsx index f4bc022bb3..05f4c0a5d1 100644 --- a/client/src/components/SidePanel/Agents/AgentPanel.tsx +++ b/client/src/components/SidePanel/Agents/AgentPanel.tsx @@ -11,7 +11,7 @@ import { PermissionBits, isAssistantsEndpoint, } from 'librechat-data-provider'; -import type { FieldNamesMarkedBoolean } from 'react-hook-form'; +import type { FieldNamesMarkedBoolean, FieldPath } from 'react-hook-form'; import type { Agent } from 'librechat-data-provider'; import type { TranslationKeys } from '~/hooks/useLocalize'; import type { AgentForm, StringOption } from '~/common'; @@ -280,6 +280,7 @@ export default function AgentPanel() { reset, watch, getValues, + getFieldState, setValue, formState: { dirtyFields }, } = methods; @@ -316,6 +317,17 @@ export default function AgentPanel() { const shouldPersistDraft = hasPersistableDirtyFields(dirtyFields); + const isDirtyPersistableDraftField = useCallback( + (name?: string): boolean => { + if (!isPersistableDraftField(name)) { + return false; + } + + return getFieldState(name as FieldPath).isDirty; + }, + [getFieldState], + ); + useEffect(() => { if (currentAgentIdRef.current === current_agent_id) { return; @@ -348,7 +360,7 @@ export default function AgentPanel() { useEffect(() => { const subscription = watch((_, { name }) => { - if (!shouldPersistDraftRef.current && !isPersistableDraftField(name)) { + if (!shouldPersistDraftRef.current && !isDirtyPersistableDraftField(name)) { return; } @@ -362,7 +374,7 @@ export default function AgentPanel() { } subscription.unsubscribe(); }; - }, [getValues, persistAgentDraft, watch]); + }, [getValues, isDirtyPersistableDraftField, persistAgentDraft, watch]); const uploadAvatarMutation = useUploadAgentAvatarMutation({ onSuccess: (updatedAgent) => { diff --git a/client/src/components/SidePanel/Agents/__tests__/AgentPanel.drafts.spec.tsx b/client/src/components/SidePanel/Agents/__tests__/AgentPanel.drafts.spec.tsx index 6e2d38ba81..a1418411ab 100644 --- a/client/src/components/SidePanel/Agents/__tests__/AgentPanel.drafts.spec.tsx +++ b/client/src/components/SidePanel/Agents/__tests__/AgentPanel.drafts.spec.tsx @@ -2,7 +2,7 @@ * @jest-environment jsdom */ import * as React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import type { AgentForm, NavLink } from '~/common'; import { ActivePanelProvider, useActivePanel } from '~/Providers/ActivePanelContext'; @@ -21,6 +21,7 @@ const SAVE_AGENT_LABEL = 'Save Agent'; const AGENTS_BUTTON_LABEL = 'Agents'; const FILES_BUTTON_LABEL = 'Files'; const FILES_PANEL_LABEL = 'Files panel'; +const PROGRAMMATIC_UPDATE_LABEL = 'Programmatic agent update'; type MockButtonProps = React.ButtonHTMLAttributes & { size?: string; @@ -136,9 +137,24 @@ jest.mock('~/common', () => { jest.mock('../AgentSelect', () => ({ __esModule: true, - default: ({ hasDraft }: MockAgentSelectProps) => { + default: function MockAgentSelect({ hasDraft }: MockAgentSelectProps) { + const { useFormContext } = jest.requireActual( + 'react-hook-form', + ) as typeof import('react-hook-form'); + const { setValue } = useFormContext(); + mockLastAgentSelectHasDraft = hasDraft; - return
{AGENT_SELECT_LABEL}
; + return ( + <> +
{AGENT_SELECT_LABEL}
+ + + ); }, })); @@ -283,4 +299,18 @@ describe('AgentPanel draft preservation', () => { expect(getAgentDraft(undefined)).toBeUndefined(); expect(mockLastAgentSelectHasDraft).toBe(false); }); + + it('does not save a draft for programmatic form updates', async () => { + mockCurrentAgentId = 'agent-123'; + + render(); + + fireEvent.click(screen.getByRole('button', { name: PROGRAMMATIC_UPDATE_LABEL })); + + await waitFor(() => { + expect(screen.getByLabelText('Draft name')).toHaveValue('Saved from API'); + }); + expect(getAgentDraft('agent-123')).toBeUndefined(); + expect(mockLastAgentSelectHasDraft).toBe(false); + }); });