Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 71 additions & 31 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2111,8 +2111,12 @@ class CopilotTokenTracker implements vscode.Disposable {
const foundPaths: string[] = [];
for (let i = 0; i < allVSCodePaths.length; i++) {
const codeUserPath = allVSCodePaths[i];
if (fs.existsSync(codeUserPath)) {
foundPaths.push(codeUserPath);
try {
if (fs.existsSync(codeUserPath)) {
foundPaths.push(codeUserPath);
}
} catch (checkError) {
this.warn(`Could not check path ${codeUserPath}: ${checkError}`);
}
// Update progress
if ((i + 1) % 5 === 0 || i === allVSCodePaths.length - 1) {
Expand All @@ -2130,53 +2134,89 @@ class CopilotTokenTracker implements vscode.Disposable {

// Workspace storage sessions
const workspaceStoragePath = path.join(codeUserPath, 'workspaceStorage');
if (fs.existsSync(workspaceStoragePath)) {
const workspaceDirs = fs.readdirSync(workspaceStoragePath);

for (const workspaceDir of workspaceDirs) {
const chatSessionsPath = path.join(workspaceStoragePath, workspaceDir, 'chatSessions');
if (fs.existsSync(chatSessionsPath)) {
const sessionFiles2 = fs.readdirSync(chatSessionsPath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(chatSessionsPath, file));
if (sessionFiles2.length > 0) {
this.log(`📄 Found ${sessionFiles2.length} session files in ${pathName}/workspaceStorage/${workspaceDir}`);
sessionFiles.push(...sessionFiles2);
try {
if (fs.existsSync(workspaceStoragePath)) {
try {
const workspaceDirs = fs.readdirSync(workspaceStoragePath);

for (const workspaceDir of workspaceDirs) {
const chatSessionsPath = path.join(workspaceStoragePath, workspaceDir, 'chatSessions');
try {
if (fs.existsSync(chatSessionsPath)) {
try {
const sessionFiles2 = fs.readdirSync(chatSessionsPath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(chatSessionsPath, file));
if (sessionFiles2.length > 0) {
this.log(`📄 Found ${sessionFiles2.length} session files in ${pathName}/workspaceStorage/${workspaceDir}`);
sessionFiles.push(...sessionFiles2);
}
} catch (readError) {
this.warn(`Could not read chat sessions in ${chatSessionsPath}: ${readError}`);
}
}
} catch (checkError) {
this.warn(`Could not check chat sessions path ${chatSessionsPath}: ${checkError}`);
}
}
} catch (readError) {
this.warn(`Could not read workspace storage in ${workspaceStoragePath}: ${readError}`);
}
}
} catch (checkError) {
this.warn(`Could not check workspace storage path ${workspaceStoragePath}: ${checkError}`);
}

// Global storage sessions (legacy emptyWindowChatSessions)
const globalStoragePath = path.join(codeUserPath, 'globalStorage', 'emptyWindowChatSessions');
if (fs.existsSync(globalStoragePath)) {
const globalSessionFiles = fs.readdirSync(globalStoragePath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(globalStoragePath, file));
if (globalSessionFiles.length > 0) {
this.log(`📄 Found ${globalSessionFiles.length} session files in ${pathName}/globalStorage/emptyWindowChatSessions`);
sessionFiles.push(...globalSessionFiles);
try {
if (fs.existsSync(globalStoragePath)) {
try {
const globalSessionFiles = fs.readdirSync(globalStoragePath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(globalStoragePath, file));
if (globalSessionFiles.length > 0) {
this.log(`📄 Found ${globalSessionFiles.length} session files in ${pathName}/globalStorage/emptyWindowChatSessions`);
sessionFiles.push(...globalSessionFiles);
}
} catch (readError) {
this.warn(`Could not read global storage in ${globalStoragePath}: ${readError}`);
}
}
} catch (checkError) {
this.warn(`Could not check global storage path ${globalStoragePath}: ${checkError}`);
}

// GitHub Copilot Chat extension global storage
const copilotChatGlobalPath = path.join(codeUserPath, 'globalStorage', 'github.copilot-chat');
if (fs.existsSync(copilotChatGlobalPath)) {
this.log(`📄 Scanning ${pathName}/globalStorage/github.copilot-chat`);
this.scanDirectoryForSessionFiles(copilotChatGlobalPath, sessionFiles);
try {
if (fs.existsSync(copilotChatGlobalPath)) {
this.log(`📄 Scanning ${pathName}/globalStorage/github.copilot-chat`);
this.scanDirectoryForSessionFiles(copilotChatGlobalPath, sessionFiles);
}
} catch (checkError) {
this.warn(`Could not check Copilot Chat global storage path ${copilotChatGlobalPath}: ${checkError}`);
}
}

// Check for Copilot CLI session-state directory (new location for agent mode sessions)
const copilotCliSessionPath = path.join(os.homedir(), '.copilot', 'session-state');
if (fs.existsSync(copilotCliSessionPath)) {
const cliSessionFiles = fs.readdirSync(copilotCliSessionPath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(copilotCliSessionPath, file));
if (cliSessionFiles.length > 0) {
this.log(`📄 Found ${cliSessionFiles.length} session files in Copilot CLI directory`);
sessionFiles.push(...cliSessionFiles);
try {
if (fs.existsSync(copilotCliSessionPath)) {
try {
const cliSessionFiles = fs.readdirSync(copilotCliSessionPath)
.filter(file => file.endsWith('.json') || file.endsWith('.jsonl'))
.map(file => path.join(copilotCliSessionPath, file));
if (cliSessionFiles.length > 0) {
this.log(`📄 Found ${cliSessionFiles.length} session files in Copilot CLI directory`);
sessionFiles.push(...cliSessionFiles);
}
} catch (readError) {
this.warn(`Could not read Copilot CLI session path in ${copilotCliSessionPath}: ${readError}`);
}
}
} catch (checkError) {
this.warn(`Could not check Copilot CLI session path ${copilotCliSessionPath}: ${checkError}`);
}

// Log summary
Expand Down
42 changes: 42 additions & 0 deletions src/webview/logviewer/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,15 @@ function renderLayout(data: SessionLogData): void {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.4), 0 2px 4px rgba(0,0,0,0.2);
}
.filename-link {
cursor: pointer;
color: #60a5fa;
text-decoration: underline;
transition: color 0.2s;
}
.filename-link:hover {
color: #93c5fd;
}
.summary-label {
font-size: 14px;
color: #b8b8c0;
Expand Down Expand Up @@ -678,6 +687,36 @@ function renderLayout(data: SessionLogData): void {
<div class="summary-value">${usageContextTotal}</div>
<div class="summary-sub">#file ${usageContextRefs.file || 0} · @vscode ${usageContextRefs.vscode || 0} · @workspace ${usageContextRefs.workspace || 0}</div>
</div>
<div class="summary-card">
<div class="summary-label">📁 File Name</div>
<div class="summary-value" style="font-size: 16px;"><span class="filename-link" id="open-file-link">${escapeHtml(getFileName(data.file))}</span></div>
<div class="summary-sub">Click to open in editor</div>
</div>
<div class="summary-card">
<div class="summary-label">💻 Editor</div>
<div class="summary-value" style="font-size: 20px;">${escapeHtml(data.editorName)}</div>
<div class="summary-sub">Source editor</div>
</div>
<div class="summary-card">
<div class="summary-label">📦 File Size</div>
<div class="summary-value">${formatFileSize(data.size)}</div>
<div class="summary-sub">Total size on disk</div>
</div>
<div class="summary-card">
<div class="summary-label">🕒 Modified</div>
<div class="summary-value" style="font-size: 14px;">${formatDate(data.modified)}</div>
<div class="summary-sub">Last file modification</div>
</div>
<div class="summary-card">
<div class="summary-label">▶️ First Interaction</div>
<div class="summary-value" style="font-size: 14px;">${formatDate(data.firstInteraction)}</div>
<div class="summary-sub">Session started</div>
</div>
<div class="summary-card">
<div class="summary-label">⏹️ Last Interaction</div>
<div class="summary-value" style="font-size: 14px;">${formatDate(data.lastInteraction)}</div>
<div class="summary-sub">Most recent activity</div>
</div>
</div>

<div class="turns-header">
Expand Down Expand Up @@ -713,6 +752,9 @@ function renderLayout(data: SessionLogData): void {
e.preventDefault();
vscode.postMessage({ command: 'openRawFile' });
});
document.getElementById('open-file-link')?.addEventListener('click', () => {
vscode.postMessage({ command: 'openRawFile' });
});

// Wire tool call clicks after DOM render so listeners bind correctly
document.querySelectorAll('.tool-call-link').forEach(link => {
Expand Down
Loading