@@ -462,6 +462,122 @@ describe('Generation Actions', () => {
462462 // Should complete operation
463463 expect ( mockCompleteOperation ) . toHaveBeenCalledWith ( 'test-op-id' ) ;
464464 } ) ;
465+
466+ it ( 'should delete message BEFORE regeneration to prevent message not found issue (LOBE-2533)' , async ( ) => {
467+ // This test verifies the fix for LOBE-2533:
468+ // When "delete and regenerate" is called, if regeneration happens first,
469+ // it switches to a new branch, causing the original message to no longer
470+ // appear in displayMessages. Then deleteMessage cannot find the message
471+ // and fails silently.
472+ //
473+ // The fix: delete first, then regenerate.
474+
475+ const callOrder : string [ ] = [ ] ;
476+
477+ // Re-setup mock to track call order
478+ const { useChatStore } = await import ( '@/store/chat' ) ;
479+ vi . mocked ( useChatStore . getState ) . mockReturnValue ( {
480+ messagesMap : { } ,
481+ operations : { } ,
482+ messageLoadingIds : [ ] ,
483+ cancelOperations : mockCancelOperations ,
484+ cancelOperation : mockCancelOperation ,
485+ deleteMessage : vi . fn ( ) . mockImplementation ( ( ) => {
486+ callOrder . push ( 'deleteMessage' ) ;
487+ return Promise . resolve ( ) ;
488+ } ) ,
489+ switchMessageBranch : vi . fn ( ) . mockImplementation ( ( ) => {
490+ callOrder . push ( 'switchMessageBranch' ) ;
491+ return Promise . resolve ( ) ;
492+ } ) ,
493+ startOperation : mockStartOperation ,
494+ completeOperation : mockCompleteOperation ,
495+ failOperation : mockFailOperation ,
496+ internal_execAgentRuntime : vi . fn ( ) . mockImplementation ( ( ) => {
497+ callOrder . push ( 'internal_execAgentRuntime' ) ;
498+ return Promise . resolve ( ) ;
499+ } ) ,
500+ } as any ) ;
501+
502+ const context : ConversationContext = {
503+ agentId : 'session-1' ,
504+ topicId : 'topic-1' ,
505+ threadId : null ,
506+ groupId : 'group-1' ,
507+ } ;
508+
509+ const store = createStore ( { context } ) ;
510+
511+ // Set displayMessages and dbMessages
512+ act ( ( ) => {
513+ store . setState ( {
514+ displayMessages : [
515+ { id : 'msg-1' , role : 'user' , content : 'Hello' } ,
516+ { id : 'msg-2' , role : 'assistant' , content : 'Hi there' , parentId : 'msg-1' } ,
517+ ] ,
518+ dbMessages : [
519+ { id : 'msg-1' , role : 'user' , content : 'Hello' } ,
520+ { id : 'msg-2' , role : 'assistant' , content : 'Hi there' , parentId : 'msg-1' } ,
521+ ] ,
522+ } as any ) ;
523+ } ) ;
524+
525+ await act ( async ( ) => {
526+ await store . getState ( ) . delAndRegenerateMessage ( 'msg-2' ) ;
527+ } ) ;
528+
529+ // CRITICAL: deleteMessage must be called BEFORE switchMessageBranch and internal_execAgentRuntime
530+ // If regeneration (which calls switchMessageBranch) happens first, the message
531+ // won't be found in displayMessages and deletion will fail silently.
532+ expect ( callOrder [ 0 ] ) . toBe ( 'deleteMessage' ) ;
533+ expect ( callOrder ) . toContain ( 'switchMessageBranch' ) ;
534+ expect ( callOrder ) . toContain ( 'internal_execAgentRuntime' ) ;
535+
536+ // Verify deleteMessage is called before any regeneration-related calls
537+ const deleteIndex = callOrder . indexOf ( 'deleteMessage' ) ;
538+ const switchIndex = callOrder . indexOf ( 'switchMessageBranch' ) ;
539+ const execIndex = callOrder . indexOf ( 'internal_execAgentRuntime' ) ;
540+
541+ expect ( deleteIndex ) . toBeLessThan ( switchIndex ) ;
542+ expect ( deleteIndex ) . toBeLessThan ( execIndex ) ;
543+ } ) ;
544+
545+ it ( 'should not proceed if assistant message has no parentId' , async ( ) => {
546+ const { useChatStore } = await import ( '@/store/chat' ) ;
547+ vi . mocked ( useChatStore . getState ) . mockReturnValue ( {
548+ messagesMap : { } ,
549+ operations : { } ,
550+ messageLoadingIds : [ ] ,
551+ startOperation : mockStartOperation ,
552+ completeOperation : mockCompleteOperation ,
553+ deleteMessage : mockDeleteMessage ,
554+ } as any ) ;
555+
556+ const context : ConversationContext = {
557+ agentId : 'session-1' ,
558+ topicId : null ,
559+ threadId : null ,
560+ } ;
561+
562+ const store = createStore ( { context } ) ;
563+
564+ // Set displayMessages with assistant message that has no parentId
565+ act ( ( ) => {
566+ store . setState ( {
567+ displayMessages : [
568+ { id : 'msg-1' , role : 'assistant' , content : 'Hi there' } , // no parentId
569+ ] ,
570+ } as any ) ;
571+ } ) ;
572+
573+ await act ( async ( ) => {
574+ await store . getState ( ) . delAndRegenerateMessage ( 'msg-1' ) ;
575+ } ) ;
576+
577+ // Should not proceed - no operation created, no delete called
578+ expect ( mockStartOperation ) . not . toHaveBeenCalled ( ) ;
579+ expect ( mockDeleteMessage ) . not . toHaveBeenCalled ( ) ;
580+ } ) ;
465581 } ) ;
466582
467583 describe ( 'delAndResendThreadMessage' , ( ) => {
0 commit comments