Skip to content

Commit 410b5b8

Browse files
fix: enrich closed-window tabs from copilot session-state on disk
For windows captured before reliable session ID tracking, fall back to scanning ~/.copilot/session-state/* workspace.yaml files and matching tab titles against session summaries. Recovers session IDs and CWDs for old closed windows that have no session history in the DB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 17b2006 commit 410b5b8

2 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/TSG/TSG.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<PackAsTool>true</PackAsTool>
2525
<ToolCommandName>tsg</ToolCommandName>
2626
<PackageId>TerminalStateGuard</PackageId>
27-
<Version>2.0.2</Version>
27+
<Version>2.0.3</Version>
2828
<Authors>sbay-dev</Authors>
2929
<Company>sbay-dev</Company>
3030
<Description>TSG — Terminal State Guard: Complete terminal intelligence platform with real-time window tracking (COM IUIAutomation), interactive process manager, SQLite state database, session recovery, and Copilot performance optimization.</Description>

src/TSG/TerminalDatabase.cs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)