@@ -940,6 +940,76 @@ describe('ProjectView daemon reattach restore', () => {
940940 } ) ;
941941 } ) ;
942942
943+ it ( 'coalesces adjacent thinking events while saving a full reattach replay' , async ( ) => {
944+ const startedAt = Date . now ( ) ;
945+ listConversations . mockResolvedValue ( [ { id : 'conv-1' , title : 'Conversation' } ] ) ;
946+ listMessages . mockResolvedValue ( [
947+ {
948+ id : 'msg-reattach-full-replay-thinking' ,
949+ role : 'assistant' ,
950+ content : '' ,
951+ createdAt : startedAt ,
952+ startedAt,
953+ runId : 'run-full-replay-thinking' ,
954+ runStatus : 'running' ,
955+ preTurnFileNames : [ ] ,
956+ events : [ ] ,
957+ } satisfies ChatMessage ,
958+ ] ) ;
959+ fetchPreviewComments . mockResolvedValue ( [ ] ) ;
960+ loadTabs . mockResolvedValue ( { tabs : [ ] , activeTabId : null } ) ;
961+ fetchProjectFiles . mockResolvedValue ( [ ] ) ;
962+ fetchLiveArtifacts . mockResolvedValue ( [ ] ) ;
963+ fetchSkill . mockResolvedValue ( null ) ;
964+ fetchDesignSystem . mockResolvedValue ( null ) ;
965+ getTemplate . mockResolvedValue ( null ) ;
966+ fetchChatRunStatus . mockResolvedValue ( {
967+ id : 'run-full-replay-thinking' ,
968+ status : 'running' ,
969+ createdAt : startedAt ,
970+ updatedAt : startedAt ,
971+ exitCode : null ,
972+ signal : null ,
973+ } ) ;
974+ listActiveChatRuns . mockResolvedValue ( [ ] ) ;
975+
976+ let captured : {
977+ onAgentEvent : ( ev : unknown ) => void ;
978+ onDone : ( ) => void ;
979+ } | null = null ;
980+ reattachDaemonRun . mockImplementation ( async ( options : any ) => {
981+ captured = {
982+ onAgentEvent : options . handlers . onAgentEvent ,
983+ onDone : options . handlers . onDone ,
984+ } ;
985+ return new Promise < void > ( ( ) => { } ) ;
986+ } ) ;
987+
988+ renderProjectView ( ) ;
989+
990+ await waitFor ( ( ) => expect ( reattachDaemonRun ) . toHaveBeenCalledTimes ( 1 ) ) ;
991+ expect ( captured ) . not . toBeNull ( ) ;
992+ for ( let index = 0 ; index < 1_500 ; index += 1 ) {
993+ captured ! . onAgentEvent ( { kind : 'thinking' , text : 'thought ' } ) ;
994+ }
995+ captured ! . onDone ( ) ;
996+
997+ await waitFor ( ( ) => {
998+ const finalMessage = saveMessage . mock . calls
999+ . map ( ( call ) => call [ 2 ] as ChatMessage )
1000+ . filter (
1001+ ( message ) =>
1002+ message ?. id === 'msg-reattach-full-replay-thinking' &&
1003+ message . runStatus === 'succeeded' ,
1004+ )
1005+ . at ( - 1 ) ;
1006+ expect ( finalMessage ?. events ) . toHaveLength ( 1 ) ;
1007+ expect ( finalMessage ?. events ) . toEqual ( [
1008+ { kind : 'thinking' , text : 'thought ' . repeat ( 1_500 ) } ,
1009+ ] ) ;
1010+ } ) ;
1011+ } ) ;
1012+
9431013 it ( 'clears touched-file paths after a failed run before the next successful run finalizes' , async ( ) => {
9441014 listConversations . mockResolvedValue ( [ { id : 'conv-1' , title : 'Conversation' } ] ) ;
9451015 listMessages . mockResolvedValue ( [ ] ) ;
0 commit comments