@@ -29,6 +29,7 @@ const listSessions = vi.hoisted(() => vi.fn());
2929const deleteSession = vi . hoisted ( ( ) => vi . fn ( ) ) ;
3030const deleteSessionIfEmpty = vi . hoisted ( ( ) => vi . fn ( ) ) ;
3131const appendMessage = vi . hoisted ( ( ) => vi . fn ( ) ) ;
32+ const replaceMessages = vi . hoisted ( ( ) => vi . fn ( ) ) ;
3233const updateSessionModel = vi . hoisted ( ( ) => vi . fn ( ) ) ;
3334const saveConfig = vi . hoisted ( ( ) => vi . fn ( ) ) ;
3435const checkHealth = vi . hoisted ( ( ) => vi . fn ( ) ) ;
@@ -62,6 +63,7 @@ vi.mock('@/utils', async () => ({
6263 deleteSessionIfEmpty,
6364 listSessions,
6465 loadSession,
66+ replaceMessages,
6567 updateSessionModel,
6668 } ,
6769 terminal : {
@@ -85,6 +87,9 @@ const capturedCallbacks = vi.hoisted(() => ({
8587 onMessagesChange : null as
8688 | ( ( messages : { role : string ; content : string } [ ] ) => void )
8789 | null ,
90+ onMessagesReplace : null as
91+ | ( ( messages : { role : string ; content : string } [ ] ) => void )
92+ | null ,
8893} ) ) ;
8994
9095vi . mock ( '@/components/Header' , ( ) => ( {
@@ -97,18 +102,21 @@ vi.mock('@/components/Chat', () => ({
97102 Chat : ( {
98103 onCommand,
99104 onMessagesChange,
105+ onMessagesReplace,
100106 onModeChange,
101107 sessionId,
102108 } : {
103109 model : string ;
104110 onCommand : ( command : string ) => void ;
105111 onMessagesChange ?: ( messages : { role : string ; content : string } [ ] ) => void ;
112+ onMessagesReplace ?: ( messages : { role : string ; content : string } [ ] ) => void ;
106113 mode : string ;
107114 onModeChange : ( mode : string ) => void ;
108115 sessionId : string ;
109116 } ) => {
110117 capturedCallbacks . onCommand = onCommand ;
111118 capturedCallbacks . onMessagesChange = onMessagesChange ?? null ;
119+ capturedCallbacks . onMessagesReplace = onMessagesReplace ?? null ;
112120 capturedCallbacks . onModeChange = onModeChange ;
113121 return < Text > { `> session:${ sessionId } ` } </ Text > ;
114122 } ,
@@ -266,6 +274,7 @@ describe('App', () => {
266274 capturedCallbacks . onDeleteSession = null ;
267275 capturedCallbacks . onNewSession = null ;
268276 capturedCallbacks . onMessagesChange = null ;
277+ capturedCallbacks . onMessagesReplace = null ;
269278
270279 resetSystemMessage . mockClear ( ) ;
271280 clearScreen . mockClear ( ) ;
@@ -278,6 +287,7 @@ describe('App', () => {
278287 deleteSession . mockReset ( ) ;
279288 deleteSessionIfEmpty . mockReset ( ) ;
280289 appendMessage . mockReset ( ) ;
290+ replaceMessages . mockReset ( ) ;
281291 updateSessionModel . mockReset ( ) ;
282292 saveConfig . mockReset ( ) ;
283293 checkHealth . mockReset ( ) ;
@@ -329,6 +339,17 @@ describe('App', () => {
329339 directory : process . cwd ( ) ,
330340 } ) ) ;
331341
342+ replaceMessages . mockImplementation (
343+ ( _sessionId , _messages , model : string ) => ( {
344+ id : 'session-0' ,
345+ createdAt : '2026-05-11T00:00:00.000Z' ,
346+ updatedAt : '2026-05-11T00:00:01.000Z' ,
347+ title : 'Session 0' ,
348+ model,
349+ directory : process . cwd ( ) ,
350+ } ) ,
351+ ) ;
352+
332353 updateSessionModel . mockImplementation (
333354 ( sessionId : string , model : string ) => ( {
334355 id : sessionId ,
@@ -731,6 +752,27 @@ describe('App', () => {
731752 expect ( appendMessage ) . not . toHaveBeenCalled ( ) ;
732753 } ) ;
733754
755+ it ( 'replaces persisted session messages when chat compacts history' , async ( ) => {
756+ await renderApp ( ) ;
757+
758+ capturedCallbacks . onMessagesReplace ?.( [
759+ { role : 'system' , content : 'Compacted conversation context' } ,
760+ { role : 'user' , content : TURN_ABORTED_MESSAGE } ,
761+ { role : 'assistant' , content : 'latest reply' } ,
762+ ] ) ;
763+
764+ await time . tick ( ) ;
765+
766+ expect ( replaceMessages ) . toHaveBeenCalledWith (
767+ 'session-0' ,
768+ [
769+ { role : 'system' , content : 'Compacted conversation context' } ,
770+ { role : 'assistant' , content : 'latest reply' } ,
771+ ] ,
772+ 'gemma4' ,
773+ ) ;
774+ } ) ;
775+
734776 it ( 'updates the active session model when the manager saves one' , async ( ) => {
735777 const { rerender } = await renderApp ( ) ;
736778 capturedCallbacks . onCommand ?.( '/model' ) ;
0 commit comments