@@ -85,6 +85,22 @@ test('extractSessionDetailPreviewFromFileFast preserves clipped previews after f
8585 return content ;
8686 }
8787 return '' ;
88+ } ,
89+ removeLeadingSystemMessage ( messages ) {
90+ if ( ! Array . isArray ( messages ) ) {
91+ return [ ] ;
92+ }
93+ let startIndex = 0 ;
94+ while ( startIndex < messages . length ) {
95+ const item = messages [ startIndex ] ;
96+ const role = item && typeof item . role === 'string' ? item . role : '' ;
97+ if ( role === 'system' ) {
98+ startIndex += 1 ;
99+ continue ;
100+ }
101+ break ;
102+ }
103+ return startIndex > 0 ? messages . slice ( startIndex ) : messages ;
88104 }
89105 } ) ;
90106
@@ -98,3 +114,97 @@ test('extractSessionDetailPreviewFromFileFast preserves clipped previews after f
98114 fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
99115 }
100116} ) ;
117+
118+ test ( 'extractSessionDetailPreviewFromFileFast normalizes full-scan previews before returning' , ( ) => {
119+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'codexmate-fast-preview-full-' ) ) ;
120+ const filePath = path . join ( tmpDir , 'session.jsonl' ) ;
121+
122+ try {
123+ const records = [
124+ {
125+ type : 'response_item' ,
126+ payload : {
127+ type : 'message' ,
128+ role : 'system' ,
129+ content : 'bootstrap'
130+ } ,
131+ timestamp : '2025-04-12T00:00:00.000Z'
132+ } ,
133+ {
134+ type : 'response_item' ,
135+ payload : {
136+ type : 'message' ,
137+ role : 'user' ,
138+ content : 'hello'
139+ } ,
140+ timestamp : '2025-04-12T00:00:01.000Z'
141+ } ,
142+ {
143+ type : 'response_item' ,
144+ payload : {
145+ type : 'message' ,
146+ role : 'assistant' ,
147+ content : 'world'
148+ } ,
149+ timestamp : '2025-04-12T00:00:02.000Z'
150+ }
151+ ] ;
152+ fs . writeFileSync ( filePath , records . map ( ( record ) => JSON . stringify ( record ) ) . join ( '\n' ) + '\n' , 'utf-8' ) ;
153+
154+ const extractSessionDetailPreviewFromFileFast = instantiateExtractSessionDetailPreviewFromFileFast ( {
155+ fs,
156+ getFileStatSafe ( targetPath ) {
157+ try {
158+ return fs . statSync ( targetPath ) ;
159+ } catch ( _ ) {
160+ return null ;
161+ }
162+ } ,
163+ FAST_SESSION_DETAIL_PREVIEW_FILE_BYTES : 32 ,
164+ FAST_SESSION_DETAIL_PREVIEW_MAX_BYTES : 4096 ,
165+ FAST_SESSION_DETAIL_PREVIEW_CHUNK_BYTES : 256 ,
166+ DEFAULT_SESSION_DETAIL_MESSAGES : 300 ,
167+ toIsoTime ( value , fallback = '' ) {
168+ return typeof value === 'string' && value ? value : fallback ;
169+ } ,
170+ normalizeRole ( role ) {
171+ const normalized = String ( role || '' ) . trim ( ) . toLowerCase ( ) ;
172+ return normalized === 'user' || normalized === 'assistant' || normalized === 'system'
173+ ? normalized
174+ : '' ;
175+ } ,
176+ extractMessageText ( content ) {
177+ return typeof content === 'string' ? content : '' ;
178+ } ,
179+ removeLeadingSystemMessage ( messages ) {
180+ if ( ! Array . isArray ( messages ) ) {
181+ return [ ] ;
182+ }
183+ let startIndex = 0 ;
184+ while ( startIndex < messages . length ) {
185+ const item = messages [ startIndex ] ;
186+ const role = item && typeof item . role === 'string' ? item . role : '' ;
187+ if ( role === 'system' ) {
188+ startIndex += 1 ;
189+ continue ;
190+ }
191+ break ;
192+ }
193+ return startIndex > 0 ? messages . slice ( startIndex ) : messages ;
194+ }
195+ } ) ;
196+
197+ const preview = extractSessionDetailPreviewFromFileFast ( filePath , 'codex' , 5 ) ;
198+
199+ assert ( preview , 'full-scan fast preview should still return a preview' ) ;
200+ assert . deepStrictEqual (
201+ preview . messages . map ( ( item ) => item . role ) ,
202+ [ 'user' , 'assistant' ] ,
203+ 'full-scan previews should strip leading system/bootstrap messages'
204+ ) ;
205+ assert . strictEqual ( preview . totalMessages , 2 , 'full-scan previews should restore an exact total' ) ;
206+ assert . strictEqual ( preview . clipped , false , 'full-scan previews should not stay clipped when all messages fit' ) ;
207+ } finally {
208+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
209+ }
210+ } ) ;
0 commit comments