@@ -462,7 +462,12 @@ AND ct.parent_ordinal IS NULL
462462 sessionByTitle . TryAdd ( summary , ( sessionId , summary , path ) ) ;
463463 }
464464
465- if ( sessionByOrdinal . Count == 0 ) return ;
465+ if ( sessionByOrdinal . Count == 0 )
466+ {
467+ // No history for this window — fall back to scanning all copilot sessions on disk
468+ EnrichFromCopilotSessionStore ( tabs ) ;
469+ return ;
470+ }
466471
467472 // Enrich tabs that are missing session IDs
468473 for ( var i = 0 ; i < tabs . Count ; i ++ )
@@ -507,6 +512,88 @@ AND ct.parent_ordinal IS NULL
507512 }
508513 }
509514 }
515+
516+ // Final fallback: scan disk for any unmatched tabs
517+ EnrichFromCopilotSessionStore ( tabs ) ;
518+ }
519+
520+ /// <summary>
521+ /// Last-resort enrichment: scan all copilot session-state directories on disk
522+ /// and match tab titles to session summaries. Useful for OLD windows captured
523+ /// before session ID tracking was reliable.
524+ /// </summary>
525+ static void EnrichFromCopilotSessionStore ( List < CaptureTab > tabs )
526+ {
527+ var sessionsDir = Path . Combine (
528+ Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
529+ ".copilot" , "session-state" ) ;
530+ if ( ! Directory . Exists ( sessionsDir ) ) return ;
531+
532+ // Check if any tabs need enrichment
533+ if ( ! tabs . Any ( t => t . CopilotSessionId == null && ! string . IsNullOrEmpty ( t . Title ) ) ) return ;
534+
535+ // Build summary → (sessionId, cwd, updated) lookup from disk
536+ var diskSessions = new List < ( string SessionId , string Summary , string Cwd , DateTime Updated ) > ( ) ;
537+ foreach ( var dir in Directory . EnumerateDirectories ( sessionsDir ) )
538+ {
539+ var ws = Path . Combine ( dir , "workspace.yaml" ) ;
540+ if ( ! File . Exists ( ws ) ) continue ;
541+ try
542+ {
543+ string ? id = Path . GetFileName ( dir ) ;
544+ string ? summary = null ;
545+ string ? cwd = null ;
546+ DateTime updated = File . GetLastWriteTime ( ws ) ;
547+ foreach ( var line in File . ReadLines ( ws ) )
548+ {
549+ var t = line . TrimStart ( ) ;
550+ if ( t . StartsWith ( "summary: " , StringComparison . Ordinal ) )
551+ summary = t [ "summary: " . Length ..] . Trim ( ) ;
552+ else if ( t . StartsWith ( "cwd: " , StringComparison . Ordinal ) )
553+ cwd = t [ "cwd: " . Length ..] . Trim ( ) ;
554+ }
555+ if ( ! string . IsNullOrEmpty ( id ) && ! string . IsNullOrEmpty ( summary ) && summary . Length >= 4 )
556+ diskSessions . Add ( ( id , summary , cwd ?? "" , updated ) ) ;
557+ }
558+ catch ( IOException ) { }
559+ }
560+
561+ if ( diskSessions . Count == 0 ) return ;
562+
563+ // Sort by most recent for tie-breaking
564+ diskSessions = [ .. diskSessions . OrderByDescending ( s => s . Updated ) ] ;
565+
566+ foreach ( var tab in tabs )
567+ {
568+ if ( tab . CopilotSessionId != null ) continue ;
569+ if ( string . IsNullOrEmpty ( tab . Title ) ) continue ;
570+
571+ var cleanTitle = tab . Title . Replace ( "🤖" , "" , StringComparison . Ordinal ) . Trim ( ) ;
572+ if ( cleanTitle . Length < 4 ) continue ;
573+
574+ // Try exact summary match first, then bidirectional contains
575+ var match = diskSessions . FirstOrDefault ( s =>
576+ s . Summary . Equals ( cleanTitle , StringComparison . OrdinalIgnoreCase ) ) ;
577+ if ( match . SessionId == null )
578+ {
579+ match = diskSessions . FirstOrDefault ( s =>
580+ cleanTitle . Contains ( s . Summary , StringComparison . OrdinalIgnoreCase )
581+ || s . Summary . Contains ( cleanTitle , StringComparison . OrdinalIgnoreCase ) ) ;
582+ }
583+
584+ if ( match . SessionId != null )
585+ {
586+ tab . HasCopilot = true ;
587+ tab . CopilotSessionId = match . SessionId ;
588+ tab . CopilotSummary = match . Summary ;
589+ if ( string . IsNullOrEmpty ( tab . Path ) && ! string . IsNullOrEmpty ( match . Cwd ) )
590+ {
591+ tab . Path = match . Cwd ;
592+ tab . Folder = Path . GetFileName ( match . Cwd ) ;
593+ tab . DirExists = Directory . Exists ( match . Cwd ) ;
594+ }
595+ }
596+ }
510597 }
511598
512599 /// <summary>
0 commit comments