@@ -2264,24 +2264,30 @@ export default function CodexFlowManagerUI() {
22642264 logs : [ ] ,
22652265 createdAt : Date . now ( ) ,
22662266 } ;
2267- registerTabProject ( tab . id , project . id ) ;
2268- setTabsByProject ( ( m ) => ( { ...m , [ project . id ] : [ ...( m [ project . id ] || [ ] ) , tab ] } ) ) ;
2269- setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
2267+ let ptyId : string | undefined ;
22702268 try {
22712269 const startupCmd = injectTraceEnv ( codexCmd ) ;
22722270 const { id } = await window . host . pty . openWSLConsole ( { distro : wslDistro , wslPath : project . wslPath , winPath : project . winPath , cols : 80 , rows : 24 , startupCmd } ) ;
2273- ptyByTabRef . current [ tab . id ] = id ;
2274- setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : id } ) ) ;
2275- ptyAliveRef . current [ tab . id ] = true ;
2276- setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
2277- registerPtyForTab ( tab . id , id ) ;
2278- try { tm . setPty ( tab . id , id ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
2279- try { window . host . projects . touch ( project . id ) ; } catch { }
2280- // 打开控制台后,立即在内存中更新最近使用时间,保证"最近使用优先"实时生效
2281- markProjectUsed ( project . id ) ;
2271+ ptyId = id ;
22822272 } catch ( e ) {
22832273 console . error ( 'Failed to open PTY for project' , e ) ;
2274+ alert ( String ( t ( 'terminal:openFailed' , { error : String ( ( e as any ) ?. message || e ) } ) ) ) ;
2275+ return ;
2276+ }
2277+ registerTabProject ( tab . id , project . id ) ;
2278+ setTabsByProject ( ( m ) => ( { ...m , [ project . id ] : [ ...( m [ project . id ] || [ ] ) , tab ] } ) ) ;
2279+ setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
2280+ if ( ptyId ) {
2281+ ptyByTabRef . current [ tab . id ] = ptyId ;
2282+ setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : ptyId } ) ) ;
2283+ ptyAliveRef . current [ tab . id ] = true ;
2284+ setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
2285+ registerPtyForTab ( tab . id , ptyId ) ;
2286+ try { tm . setPty ( tab . id , ptyId ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
22842287 }
2288+ try { window . host . projects . touch ( project . id ) ; } catch { }
2289+ // 打开控制台后,立即在内存中更新最近使用时间,保证"最近使用优先"实时生效
2290+ markProjectUsed ( project . id ) ;
22852291 // 确保视图停留在控制台
22862292 try { setCenterMode ( 'console' ) ; } catch { }
22872293 }
@@ -2335,10 +2341,7 @@ export default function CodexFlowManagerUI() {
23352341 logs : [ ] ,
23362342 createdAt : Date . now ( ) ,
23372343 } ;
2338- registerTabProject ( tab . id , selectedProject . id ) ;
2339- setTabsByProject ( ( m ) => ( { ...m , [ selectedProject . id ] : [ ...( m [ selectedProject . id ] || [ ] ) , tab ] } ) ) ;
2340- setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
2341-
2344+ let ptyId : string | undefined ;
23422345 // Open PTY in main (WSL)
23432346 try {
23442347 try { await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] openNewConsole start project=${ selectedProject ?. name } ` ) ; } catch { }
@@ -2352,21 +2355,30 @@ export default function CodexFlowManagerUI() {
23522355 startupCmd,
23532356 } ) ;
23542357 try { await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] openNewConsole pty=${ id } ` ) ; } catch { }
2355- ptyByTabRef . current [ tab . id ] = id ;
2356- setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : id } ) ) ;
2357- ptyAliveRef . current [ tab . id ] = true ;
2358- setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
2359- registerPtyForTab ( tab . id , id ) ;
2360- // inform manager about PTY so it can wire bridges
2361- try { tm . setPty ( tab . id , id ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
2362- // touch project lastOpenedAt
2363- try { window . host . projects . touch ( selectedProject . id ) ; } catch { }
2364- // 同步更新内存,触发排序刷新;并抑制历史面板自动切换
2365- markProjectUsed ( selectedProject . id ) ;
2358+ ptyId = id ;
23662359 } catch ( e ) {
23672360 console . error ( 'Failed to open PTY' , e ) ;
23682361 try { await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] openNewConsole error ${ String ( ( e as any ) ?. stack || e ) } ` ) ; } catch { }
2362+ alert ( String ( t ( 'terminal:openFailed' , { error : String ( ( e as any ) ?. message || e ) } ) ) ) ;
2363+ return ;
2364+ }
2365+
2366+ registerTabProject ( tab . id , selectedProject . id ) ;
2367+ setTabsByProject ( ( m ) => ( { ...m , [ selectedProject . id ] : [ ...( m [ selectedProject . id ] || [ ] ) , tab ] } ) ) ;
2368+ setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
2369+ if ( ptyId ) {
2370+ ptyByTabRef . current [ tab . id ] = ptyId ;
2371+ setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : ptyId } ) ) ;
2372+ ptyAliveRef . current [ tab . id ] = true ;
2373+ setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
2374+ registerPtyForTab ( tab . id , ptyId ) ;
2375+ // inform manager about PTY so it can wire bridges
2376+ try { tm . setPty ( tab . id , ptyId ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
23692377 }
2378+ // touch project lastOpenedAt
2379+ try { window . host . projects . touch ( selectedProject . id ) ; } catch { }
2380+ // 同步更新内存,触发排序刷新;并抑制历史面板自动切换
2381+ markProjectUsed ( selectedProject . id ) ;
23702382 }
23712383
23722384 // 当项目变更时,加载历史(项目范围)
@@ -3332,38 +3344,48 @@ export default function CodexFlowManagerUI() {
33323344 logs : [ ] ,
33333345 createdAt : Date . now ( ) ,
33343346 } ;
3335- registerTabProject ( tab . id , selectedProject . id ) ;
3336- setTabsByProject ( ( m ) => ( { ...m , [ selectedProject . id ] : [ ...( m [ selectedProject . id ] || [ ] ) , tab ] } ) ) ;
3337- setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
3338- try {
3339- setCenterMode ( 'console' ) ;
3340- requestAnimationFrame ( ( ) => {
3341- try { scheduleFocusForTab ( tab . id , { immediate : true , allowDuringRename : true } ) ; } catch { }
3342- } ) ;
3343- } catch { }
3347+ let ptyId : string | undefined ;
33443348 try {
33453349 await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume openWSLConsole start tab=${ tab . id } ` ) ;
33463350 } catch { }
3347- const { id } = await window . host . pty . openWSLConsole ( {
3351+ try {
3352+ const { id } = await window . host . pty . openWSLConsole ( {
33483353 distro : wslDistro ,
33493354 wslPath : selectedProject . wslPath ,
33503355 winPath : selectedProject . winPath ,
33513356 cols : 80 ,
33523357 rows : 24 ,
33533358 startupCmd,
33543359 } ) ;
3360+ try {
3361+ await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume pty=${ id } tab=${ tab . id } - registering listener` ) ;
3362+ } catch { }
3363+ ptyId = id ;
3364+ } catch ( err ) {
3365+ console . warn ( 'executeResume failed' , err ) ;
3366+ alert ( String ( t ( 'history:resumeFailed' , { error : String ( ( err as any ) ?. message || err ) } ) ) ) ;
3367+ return false ;
3368+ }
3369+ registerTabProject ( tab . id , selectedProject . id ) ;
3370+ setTabsByProject ( ( m ) => ( { ...m , [ selectedProject . id ] : [ ...( m [ selectedProject . id ] || [ ] ) , tab ] } ) ) ;
3371+ setActiveTab ( tab . id , { focusMode : 'immediate' , allowDuringRename : true , delay : 0 } ) ;
33553372 try {
3356- await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume pty=${ id } tab=${ tab . id } - registering listener` ) ;
3357- } catch { }
3358- ptyByTabRef . current [ tab . id ] = id ;
3359- setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : id } ) ) ;
3360- ptyAliveRef . current [ tab . id ] = true ;
3361- setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
3362- registerPtyForTab ( tab . id , id ) ;
3363- try {
3364- await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume pty=${ id } tab=${ tab . id } - listener registered` ) ;
3373+ setCenterMode ( 'console' ) ;
3374+ requestAnimationFrame ( ( ) => {
3375+ try { scheduleFocusForTab ( tab . id , { immediate : true , allowDuringRename : true } ) ; } catch { }
3376+ } ) ;
33653377 } catch { }
3366- try { tm . setPty ( tab . id , id ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
3378+ if ( ptyId ) {
3379+ ptyByTabRef . current [ tab . id ] = ptyId ;
3380+ setPtyByTab ( ( m ) => ( { ...m , [ tab . id ] : ptyId } ) ) ;
3381+ ptyAliveRef . current [ tab . id ] = true ;
3382+ setPtyAlive ( ( m ) => ( { ...m , [ tab . id ] : true } ) ) ;
3383+ registerPtyForTab ( tab . id , ptyId ) ;
3384+ try {
3385+ await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume pty=${ ptyId } tab=${ tab . id } - listener registered` ) ;
3386+ } catch { }
3387+ try { tm . setPty ( tab . id , ptyId ) ; } catch ( err ) { console . warn ( 'tm.setPty failed' , err ) ; }
3388+ }
33673389 try { window . host . projects . touch ( selectedProject . id ) ; } catch { }
33683390 // 内存也更新最近使用时间,并抑制历史面板自动切换
33693391 markProjectUsed ( selectedProject . id ) ;
@@ -3382,6 +3404,7 @@ export default function CodexFlowManagerUI() {
33823404 try {
33833405 await ( window as any ) . host ?. utils ?. perfLog ?.( `[ui] history.resume ${ mode } error ${ String ( ( err as any ) ?. stack || err ) } ` ) ;
33843406 } catch { }
3407+ alert ( String ( t ( 'history:resumeFailed' , { error : String ( ( err as any ) ?. message || err ) } ) ) ) ;
33853408 return false ;
33863409 }
33873410 } ;
@@ -4566,9 +4589,24 @@ function filterHistoryMessages(session: HistorySession, typeFilter: Record<strin
45664589
45674590function HistoryDetail ( { sessions, selectedHistoryId, onBack, onResume, onResumeExternal, terminalMode } : { sessions : HistorySession [ ] ; selectedHistoryId : string | null ; onBack ?: ( ) => void ; onResume ?: ( filePath ?: string ) => void ; onResumeExternal ?: ( filePath ?: string ) => void ; terminalMode : 'wsl' | 'windows' } ) {
45684591 const { t } = useTranslation ( [ 'history' , 'common' ] ) ;
4592+ const MAX_HISTORY_MESSAGE_CACHE = 5 ;
45694593 const [ loaded , setLoaded ] = useState ( false ) ;
45704594 const [ skipped , setSkipped ] = useState ( 0 ) ;
4571- const [ localSessions , setLocalSessions ] = useState < HistorySession [ ] > ( sessions ) ;
4595+ const [ localSessions , setLocalSessions ] = useState < HistorySession [ ] > ( ( ) => sessions . map ( ( s ) => ( { ...s , messages : [ ] } ) ) ) ;
4596+ const messageCacheIdsRef = useRef < string [ ] > ( [ ] ) ;
4597+ const pruneMessages = useCallback ( ( list : HistorySession [ ] , allowed : Set < string > ) => {
4598+ if ( ! Array . isArray ( list ) || list . length === 0 ) return list ;
4599+ if ( allowed . size === 0 ) return list . map ( ( s ) => ( { ...s , messages : [ ] } ) ) ;
4600+ return list . map ( ( s ) => ( allowed . has ( s . id ) ? s : { ...s , messages : [ ] } ) ) ;
4601+ } , [ ] ) ;
4602+ const touchMessageCache = useCallback ( ( id ?: string | null ) => {
4603+ if ( ! id ) return messageCacheIdsRef . current ;
4604+ const next = messageCacheIdsRef . current . filter ( ( x ) => x !== id ) ;
4605+ next . unshift ( id ) ;
4606+ if ( next . length > MAX_HISTORY_MESSAGE_CACHE ) next . length = MAX_HISTORY_MESSAGE_CACHE ;
4607+ messageCacheIdsRef . current = next ;
4608+ return messageCacheIdsRef . current ;
4609+ } , [ MAX_HISTORY_MESSAGE_CACHE ] ) ;
45724610 const [ typeFilter , setTypeFilter ] = useState < Record < string , boolean > > ( { } ) ;
45734611 const [ detailSearch , setDetailSearch ] = useState ( "" ) ;
45744612 const reqSeq = useRef ( 0 ) ;
@@ -4594,18 +4632,23 @@ function HistoryDetail({ sessions, selectedHistoryId, onBack, onResume, onResume
45944632
45954633 // 刷新列表时保留已加载的消息内容,避免详情面板闪烁
45964634 useEffect ( ( ) => {
4635+ const filteredIds = messageCacheIdsRef . current . filter ( ( id ) => sessions . some ( ( s ) => s . id === id ) ) ;
4636+ messageCacheIdsRef . current = filteredIds ;
4637+ const allowed = new Set ( filteredIds ) ;
45974638 setLocalSessions ( ( cur ) => {
45984639 const prevMap = new Map ( cur . map ( ( x ) => [ x . id , x ] ) ) ;
4599- return sessions . map ( ( s ) => {
4640+ const merged = sessions . map ( ( s ) => {
46004641 const prev = prevMap . get ( s . id ) ;
4601- if ( ! prev ) return s ;
4642+ if ( ! prev ) return allowed . has ( s . id ) ? s : { ... s , messages : [ ] } ;
46024643 const prevMsgs = Array . isArray ( prev . messages ) ? prev . messages : [ ] ;
46034644 const nextMsgs = Array . isArray ( s . messages ) ? s . messages : [ ] ;
4604- if ( nextMsgs . length === 0 && prevMsgs . length > 0 ) return { ...s , messages : prevMsgs } ;
4645+ if ( allowed . has ( s . id ) && nextMsgs . length === 0 && prevMsgs . length > 0 ) return { ...s , messages : prevMsgs } ;
4646+ if ( ! allowed . has ( s . id ) ) return { ...s , messages : [ ] } ;
46054647 return s ;
46064648 } ) ;
4649+ return pruneMessages ( merged , allowed ) ;
46074650 } ) ;
4608- } , [ sessions ] ) ;
4651+ } , [ sessions , pruneMessages ] ) ;
46094652
46104653 useEffect ( ( ) => {
46114654 setDetailSearch ( "" ) ;
@@ -4725,7 +4768,11 @@ function HistoryDetail({ sessions, selectedHistoryId, onBack, onResume, onResume
47254768 const res : any = await window . host . history . read ( { filePath : String ( selectedSession . filePath || '' ) } ) ;
47264769 const msgs = ( res . messages || [ ] ) . map ( ( m : any ) => ( { role : m . role as any , content : m . content } ) ) ;
47274770 if ( seq === reqSeq . current ) {
4728- setLocalSessions ( ( cur ) => cur . map ( ( x ) => ( x . id === selectedHistoryId ? { ...x , messages : msgs } : x ) ) ) ;
4771+ const allowedIds = new Set ( touchMessageCache ( selectedHistoryId ) ) ;
4772+ setLocalSessions ( ( cur ) => {
4773+ const next = cur . map ( ( x ) => ( x . id === selectedHistoryId ? { ...x , messages : msgs } : x ) ) ;
4774+ return pruneMessages ( next , allowedIds ) ;
4775+ } ) ;
47294776 setSkipped ( res . skippedLines || 0 ) ;
47304777 setLoaded ( true ) ;
47314778 lastLoadedFingerprintRef . current = signature ;
@@ -4757,7 +4804,7 @@ function HistoryDetail({ sessions, selectedHistoryId, onBack, onResume, onResume
47574804 if ( seq === reqSeq . current ) setLoaded ( true ) ;
47584805 }
47594806 } ) ( ) ;
4760- } , [ selectedHistoryId , selectedSession , selectedSessionFingerprint , selectedLocalSession ] ) ;
4807+ } , [ selectedHistoryId , selectedSession , selectedSessionFingerprint , selectedLocalSession , pruneMessages , touchMessageCache ] ) ;
47614808
47624809 function buildFilteredText ( ) : string {
47634810 if ( ! selectedHistoryId ) return '' ;
0 commit comments