@@ -40,6 +40,9 @@ module.exports = async function testSessions(ctx) {
4040 assert ( usageSessions . sessions . some ( ( item ) => item . sessionId === sessionId ) , 'list-sessions-usage missing codex entry' ) ;
4141 assert ( usageSessions . sessions . some ( ( item ) => item . sessionId === claudeSessionId ) , 'list-sessions-usage missing claude entry' ) ;
4242 assert ( usageSessions . sessions . every ( ( item ) => ! Object . prototype . hasOwnProperty . call ( item , '__messageCountExact' ) ) , 'list-sessions-usage should not expose exact hydration markers' ) ;
43+ const usageCodexEntry = usageSessions . sessions . find ( ( item ) => item . sessionId === sessionId ) ;
44+ assert ( usageCodexEntry && usageCodexEntry . totalTokens === 120 , 'list-sessions-usage missing codex totalTokens' ) ;
45+ assert ( usageCodexEntry && usageCodexEntry . contextWindow === 128000 , 'list-sessions-usage missing codex contextWindow' ) ;
4346 const defaultUsageSessions = await api ( 'list-sessions-usage' ) ;
4447 assert ( Array . isArray ( defaultUsageSessions . sessions ) , 'list-sessions-usage without params should still return sessions' ) ;
4548 assert ( defaultUsageSessions . source === 'all' , 'list-sessions-usage without params should default source to all' ) ;
@@ -118,6 +121,8 @@ module.exports = async function testSessions(ctx) {
118121 const longSessionId = 'codex-long-trash-count-e2e' ;
119122 const longSessionPath = path . join ( tmpHome , '.codex' , 'sessions' , `${ longSessionId } .jsonl` ) ;
120123 const longMessageCount = 1205 ;
124+ const hugeLineSessionId = 'codex-huge-line-preview-e2e' ;
125+ const hugeLineSessionPath = path . join ( tmpHome , '.codex' , 'sessions' , `${ hugeLineSessionId } .jsonl` ) ;
121126 const trashRoot = path . join ( tmpHome , '.codex' , 'codexmate-session-trash' ) ;
122127 const trashFilesDir = path . join ( trashRoot , 'files' ) ;
123128 const trashIndexPath = path . join ( trashRoot , 'index.json' ) ;
@@ -264,7 +269,8 @@ module.exports = async function testSessions(ctx) {
264269 const restoredIndexlessClaudeSessions = await api ( 'list-sessions' , { source : 'claude' , limit : 200 , forceRefresh : true } ) ;
265270 const restoredIndexlessClaudeItem = restoredIndexlessClaudeSessions . sessions . find ( item => item . sessionId === indexlessClaudeSessionId ) ;
266271 assert ( restoredIndexlessClaudeItem , 'restored indexless Claude session should be listed again' ) ;
267- assert ( restoredIndexlessClaudeItem . messageCount === indexlessClaudeMessageCount , 'restored indexless Claude session should keep exact list messageCount' ) ;
272+ assert ( Number . isFinite ( restoredIndexlessClaudeItem . messageCount ) , 'restored indexless Claude session should keep numeric list messageCount' ) ;
273+ assert ( restoredIndexlessClaudeItem . messageCount >= 0 , 'restored indexless Claude session should keep non-negative list messageCount' ) ;
268274
269275 const longSessionRecords = [ {
270276 type : 'session_meta' ,
@@ -287,7 +293,14 @@ module.exports = async function testSessions(ctx) {
287293 const longSessionsBeforeDelete = await api ( 'list-sessions' , { source : 'codex' , limit : 200 , forceRefresh : true } ) ;
288294 const longSessionListItem = longSessionsBeforeDelete . sessions . find ( item => item . sessionId === longSessionId ) ;
289295 assert ( longSessionListItem , 'long codex session should appear in list-sessions' ) ;
290- assert ( longSessionListItem . messageCount === longMessageCount , 'list-sessions should return exact long-session messageCount' ) ;
296+ assert ( Number . isFinite ( longSessionListItem . messageCount ) , 'list-sessions should return numeric long-session messageCount' ) ;
297+ assert ( longSessionListItem . messageCount >= 0 , 'list-sessions should return non-negative long-session messageCount' ) ;
298+ const longSessionPreview = await api ( 'session-detail' , { source : 'codex' , sessionId : longSessionId , messageLimit : 80 , preview : true } ) ;
299+ assert ( Array . isArray ( longSessionPreview . messages ) , 'session-detail preview should return messages' ) ;
300+ assert ( longSessionPreview . messages . length > 0 , 'session-detail preview should keep recent messages' ) ;
301+ assert ( longSessionPreview . messages . length <= 80 , 'session-detail preview should respect preview messageLimit' ) ;
302+ assert ( longSessionPreview . clipped === true , 'session-detail preview should stay clipped for long sessions' ) ;
303+ assert ( Number . isFinite ( longSessionPreview . totalMessages ) === false , 'session-detail preview should avoid exact totalMessages for long sessions' ) ;
291304 const longSessionDetail = await api ( 'session-detail' , { source : 'codex' , sessionId : longSessionId } ) ;
292305 assert ( longSessionDetail . totalMessages === longMessageCount , 'session-detail should return exact long-session totalMessages' ) ;
293306 assert ( longSessionDetail . messageLimit === 300 , 'session-detail should keep default detail window size' ) ;
@@ -296,6 +309,51 @@ module.exports = async function testSessions(ctx) {
296309 assert ( longSessionDetail . messages [ 0 ] . messageIndex === longMessageCount - longSessionDetail . messages . length , 'session-detail should keep the latest message indexes' ) ;
297310 assert ( longSessionDetail . messages [ longSessionDetail . messages . length - 1 ] . messageIndex === longMessageCount - 1 , 'session-detail should keep the latest tail message index' ) ;
298311
312+ const hugeLineRecords = [ {
313+ type : 'session_meta' ,
314+ payload : { id : hugeLineSessionId , cwd : '/tmp/huge-line-preview' } ,
315+ timestamp : '2025-03-01T00:00:00.000Z'
316+ } ] ;
317+ for ( let i = 0 ; i < 3 ; i += 1 ) {
318+ hugeLineRecords . push ( {
319+ type : 'response_item' ,
320+ payload : {
321+ type : 'message' ,
322+ role : i % 2 === 0 ? 'user' : 'assistant' ,
323+ content : `huge-line-preview-${ i } -` + 'q' . repeat ( 1300000 )
324+ } ,
325+ timestamp : buildTimestamp ( '2025-03-07T00:00:00.000Z' , i )
326+ } ) ;
327+ }
328+ fs . writeFileSync ( hugeLineSessionPath , hugeLineRecords . map ( record => JSON . stringify ( record ) ) . join ( '\n' ) + '\n' , 'utf-8' ) ;
329+
330+ const hugeLinePreview = await api ( 'session-detail' , {
331+ source : 'codex' ,
332+ sessionId : hugeLineSessionId ,
333+ messageLimit : 80 ,
334+ preview : true
335+ } ) ;
336+ assert ( Array . isArray ( hugeLinePreview . messages ) , 'session-detail preview should return messages for huge-line sessions' ) ;
337+ assert ( hugeLinePreview . messages . length > 0 , 'session-detail preview should fall back when huge lines exceed the fast tail window' ) ;
338+ assert ( hugeLinePreview . messages . length <= 3 , 'session-detail preview should not duplicate huge-line messages' ) ;
339+ assert ( hugeLinePreview . clipped === false , 'session-detail preview should report unclipped when fallback can read the whole huge-line session' ) ;
340+ assert (
341+ hugeLinePreview . messages . every ( ( message ) => typeof message . text === 'string' && message . text . length <= 4000 ) ,
342+ 'session-detail preview should cap huge-line payload text before sending it to the web ui'
343+ ) ;
344+
345+ const hugeLineDetail = await api ( 'session-detail' , {
346+ source : 'codex' ,
347+ sessionId : hugeLineSessionId ,
348+ messageLimit : 80
349+ } ) ;
350+ assert ( hugeLineDetail . totalMessages === 3 , 'session-detail should keep exact totalMessages for huge-line sessions' ) ;
351+ assert ( hugeLineDetail . messages . length === 3 , 'session-detail should keep all huge-line messages when under limit' ) ;
352+ assert (
353+ hugeLineDetail . messages . some ( ( message ) => typeof message . text === 'string' && message . text . length > 1000000 ) ,
354+ 'full session-detail should keep the original huge-line content outside preview mode'
355+ ) ;
356+
299357 deleteLongResult = await api ( 'trash-session' , { source : 'codex' , sessionId : longSessionId } ) ;
300358 assert ( deleteLongResult . success === true , 'trash-session should trash long codex session' ) ;
301359 assert ( deleteLongResult . messageCount === longMessageCount , 'trash-session should return exact long-session messageCount' ) ;
0 commit comments