From 670a9ab16618e94ae4a767815dc83cf1daeb4a03 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Mon, 27 Apr 2026 16:49:48 +0200 Subject: [PATCH] Bump version to 0.10.17 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 29 ++++++++++++++ README.md | 8 +++- package-lock.json | 4 +- package.json | 2 +- src/binlogDocumentProvider.ts | 46 ++++++++++++++-------- src/binlogTreeView.ts | 40 +++++++++++++------ src/chatParticipant.ts | 8 +++- src/extension.ts | 72 +++++++++++++++++++++-------------- 8 files changed, 147 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02405e3..8610688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## 0.10.17 (Preview) + +### Added +- **Language Model Tools** — 5 LM-tool wrappers (`binlog_lm_overview`, `binlog_lm_errors`, `binlog_lm_search`, `binlog_lm_perf`, `binlog_lm_compare`) registered via `vscode.lm.registerTool` so any agent (@workspace, agent mode, custom chat modes) can analyze loaded binlogs — not just `@binlog` +- **"Ask @binlog" CodeAction** — click a build error/warning squiggle in the editor → Quick Fix → "Ask @binlog about this error" or "Fix with @binlog" +- **Auto-fix diagnostic** — right-click any error/warning in the Binlog Explorer → "Auto-fix with Copilot" opens agent mode to edit the source file directly +- **Diagnostic context menu** — right-click error/warning tree items for "Ask @binlog about this issue" and "Auto-fix with Copilot" +- **Fix All: before/after comparison** — "Fix All Issues" now writes to a new `{name}_fixed_{N}.binlog` (preserving the original) and auto-loads both for `/compare` +- **`/timeline` command playbook** — `/timeline` now has its own per-command instruction file +- **esbuild bundling** — extension ships as a single `dist/extension.js` (~476 KB) instead of raw TypeScript output +- **CI matrix** — GitHub Actions workflow runs on both Ubuntu and Windows + +### Changed +- **Prompt refactor** — SYSTEM_PROMPT and COMMAND_PROMPTS moved to lazy-loaded markdown playbooks (`resources/playbooks/`). Total prompt budget ~250 tokens baseline vs ~1500 before +- **Auto-greeting** — loading a binlog now fires `@binlog /summary` automatically instead of asking "What would you like to analyze?" +- **Multi-binlog support** — all MCP tool calls (tree view, document provider, timeline, extension commands) now auto-inject `binlog_file` for the primary binlog when multiple are loaded + +### Fixed +- **MCP startup race** — auto-greeting now waits for both MCP config AND tree client initialization before firing `/summary` +- **LM-tool readiness** — wrappers wait up to 10s for MCP client to become ready instead of immediately returning "No binlog loaded" +- **Cross-machine file navigation** — diagnostics and tree view items from binlogs built on other machines (CI, coworkers) no longer show "file not found" — paths are resolved against workspace folders +- **Multi-root workspace path resolution** — `resolveFilePath` now checks each workspace folder for file existence instead of blindly using the first +- **MCP timer leak** — `setTimeout` in `sendRequest` is now cleared on resolve/reject +- **Spawn error handler** — missing `error` event listener on MCP subprocess no longer crashes the extension host +- **processResponse error retry** — recursive tool-call retry now checks all 5 error patterns (was missing `role 'tool'`, `tool_call_id`, `400`) +- **CodeAction cap** — max 5 diagnostics per `provideCodeActions` call to prevent unusable Quick Fix menus +- **Cross-platform tests** — `workspaceMatchesBinlog` and `getSourceLabel` no longer depend on `path.sep` (fixes CI on Ubuntu) +- **Stale tool names in prompts** — `analyzeInChat` prompts no longer reference non-existent tools (`binlog_search_targets`, etc.) + ## 0.10.16 (Preview) ### Fixed diff --git a/README.md b/README.md index e4db7ac..d7cba1a 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,17 @@ The [BinlogInsights.Mcp](https://www.nuget.org/packages/BinlogInsights.Mcp) serv | Feature | Description | |---------|-------------| -| **@binlog Chat** | Ask Copilot about errors, performance, targets, imports, NuGet issues — with slash commands like `/errors`, `/perf`, `/timeline`, `/compare` | +| **@binlog Chat** | Ask Copilot about errors, performance, targets, imports, NuGet issues — with slash commands like `/errors`, `/perf`, `/timeline`, `/compare`, `/summary`, `/incremental`, `/buildcheck`, `/search`, `/targets`, `/properties`, `/items`, `/propertyhistory` | | **Build & Collect** | Build a project and capture a `.binlog` in one step | | **Binlog Explorer** | Sidebar tree with project → target → task hierarchy, errors, warnings, performance | | **Build Timeline** | Visual bar charts of target/task durations with click-to-analyze in Copilot | +| **Fix All Issues** | Copilot fixes all build errors/warnings, rebuilds, and loads before/after for comparison | +| **Auto-fix Diagnostic** | Right-click any error/warning in the tree → "Auto-fix with Copilot" to fix it directly | | **Optimize Build** | Pick optimizations, Copilot applies changes, verify with A/B comparison | +| **Build Analysis Mode** | Chat mode pre-configured with BinlogInsights MCP tools — works with any agent | +| **Language Model Tools** | `binlog_lm_overview`, `binlog_lm_errors`, `binlog_lm_search`, `binlog_lm_perf`, `binlog_lm_compare` — available to @workspace, agent mode, and custom chat modes | | **CI/CD Integration** | Download binlogs from Azure DevOps Pipelines and GitHub Actions — filter by branch or PR | -| **Problems Panel** | Build diagnostics as native VS Code errors/warnings with per-project CodeLens | +| **Problems Panel** | Build diagnostics as native VS Code errors/warnings with per-project CodeLens and "Ask @binlog" CodeActions | | **Search** | Search across all build events — targets, tasks, messages, properties | ## Configuration diff --git a/package-lock.json b/package-lock.json index 9700c47..7455c0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "binlog-analyzer", - "version": "0.10.30", + "version": "0.10.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "binlog-analyzer", - "version": "0.10.30", + "version": "0.10.17", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.0" diff --git a/package.json b/package.json index a4ab1fb..76c7182 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "binlog-analyzer", "displayName": "MSBuild Binlog Analyzer", "description": "Analyze MSBuild binary logs with Copilot Chat and MCP tools", - "version": "0.10.30", + "version": "0.10.17", "preview": true, "publisher": "dotutils", "license": "MIT", diff --git a/src/binlogDocumentProvider.ts b/src/binlogDocumentProvider.ts index 3cc496b..155a1ed 100644 --- a/src/binlogDocumentProvider.ts +++ b/src/binlogDocumentProvider.ts @@ -20,6 +20,20 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide this.cache.clear(); } + /** + * Wrapper around mcpClient.callTool that auto-injects binlog_file + * for the primary (first) loaded binlog. Without this, multi-binlog + * mode causes "requires explicit binlog_file" errors in every + * document render call. + */ + private async call(tool: string, args: Record = {}): Promise<{ text: string }> { + if (!this.mcpClient) { throw new Error('MCP server not connected'); } + if (!args.binlog_file && this.mcpClient.loadedBinlogs.length > 1) { + args.binlog_file = this.mcpClient.loadedBinlogs[0]; + } + return this.mcpClient.callTool(tool, args); + } + invalidate(uri: vscode.Uri) { this.cache.delete(uri.toString()); this._onDidChange.fire(uri); @@ -82,7 +96,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Build overview try { - const overviewResult = await this.mcpClient!.callTool('binlog_overview'); + const overviewResult = await this.call('binlog_overview'); const ov = JSON.parse(overviewResult.text); const status = ov.succeeded ? '✅ BUILD SUCCEEDED' : '❌ BUILD FAILED'; const dur = ov.duration || ''; @@ -110,7 +124,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Projects try { - const projResult = await this.mcpClient!.callTool('binlog_projects'); + const projResult = await this.call('binlog_projects'); const projData = JSON.parse(projResult.text); // Handle both formats: array (BinlogInsights) and object (baronfel) @@ -141,8 +155,8 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide let diagsByProject: Map | undefined; try { const [errResult, warnResult] = await Promise.allSettled([ - this.mcpClient!.callTool('binlog_errors'), - this.mcpClient!.callTool('binlog_warnings'), + this.call('binlog_errors'), + this.call('binlog_warnings'), ]); diagsByProject = new Map(); for (const result of [errResult, warnResult]) { @@ -188,8 +202,8 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Diagnostics try { const [errResult, warnResult] = await Promise.allSettled([ - this.mcpClient!.callTool('binlog_errors'), - this.mcpClient!.callTool('binlog_warnings'), + this.call('binlog_errors'), + this.call('binlog_warnings'), ]); const parseItems = (r: PromiseSettledResult) => { @@ -237,7 +251,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Performance try { - const targetsResult = await this.mcpClient!.callTool('binlog_expensive_targets', { top_number: 10 }); + const targetsResult = await this.call('binlog_expensive_targets', { top_number: 10 }); const targetsData = JSON.parse(targetsResult.text); lines.push('🔥 SLOWEST TARGETS'); lines.push('─────────────────────────────────────────────────────'); @@ -250,7 +264,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide lines.push(''); try { - const tasksResult = await this.mcpClient!.callTool('binlog_expensive_tasks', { top_number: 10 }); + const tasksResult = await this.call('binlog_expensive_tasks', { top_number: 10 }); const tasksData = JSON.parse(tasksResult.text); lines.push('🔧 SLOWEST TASKS'); lines.push('─────────────────────────────────────────────────────'); @@ -263,7 +277,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Analyzers try { - const analyzersResult = await this.mcpClient!.callTool('binlog_expensive_analyzers', { limit: 10 }); + const analyzersResult = await this.call('binlog_expensive_analyzers', { limit: 10 }); const analyzersData = JSON.parse(analyzersResult.text); const entries = this.parsePerfEntries(analyzersData); if (entries.length > 0) { @@ -300,7 +314,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Get per-project target times try { - const targetTimesResult = await this.mcpClient!.callTool('binlog_project_target_times', { + const targetTimesResult = await this.call('binlog_project_target_times', { project: projectName, }); const targetData = JSON.parse(targetTimesResult.text); @@ -330,7 +344,7 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide } catch { // Try fallback: binlog_expensive_projects for at least a total time try { - const buildTimeResult = await this.mcpClient!.callTool('binlog_expensive_projects'); + const buildTimeResult = await this.call('binlog_expensive_projects'); const data = JSON.parse(buildTimeResult.text); const projects = Array.isArray(data) ? data : []; const match = projects.find((p: any) => @@ -348,8 +362,8 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Get diagnostics and filter for this project try { const [errResult, warnResult] = await Promise.allSettled([ - this.mcpClient!.callTool('binlog_errors'), - this.mcpClient!.callTool('binlog_warnings'), + this.call('binlog_errors'), + this.call('binlog_warnings'), ]); const parseItems = (r: PromiseSettledResult) => { @@ -414,18 +428,18 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide } private async renderProjects(): Promise { - const result = await this.mcpClient!.callTool('binlog_projects'); + const result = await this.call('binlog_projects'); return this.formatSection('PROJECTS', result.text); } private async renderDiagnostics(type: 'errors' | 'warnings'): Promise { const tool = type === 'errors' ? 'binlog_errors' : 'binlog_warnings'; - const result = await this.mcpClient!.callTool(tool); + const result = await this.call(tool); return this.formatSection(type.toUpperCase(), result.text); } private async renderExpensive(tool: string, title: string): Promise { - const result = await this.mcpClient!.callTool(tool, { top_number: 20 }); + const result = await this.call(tool, { top_number: 20 }); return this.formatSection(title.toUpperCase(), result.text); } diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index 5129ca8..8ebe5b7 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -825,7 +825,7 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider 0) { this.analyzersCache = items; @@ -842,7 +842,7 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider ({ name, ...(info as object) })) : []); @@ -1152,7 +1152,7 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider 1) { + args.binlog_file = this.binlogPaths[0]; + } + this.loadingSet.add(parentKind); try { - const result = await this.mcpClient.callTool(toolName, args); + const result = await this.mcpCall(toolName, args); this.loadingSet.delete(parentKind); const data = parser(result.text); if (data.length === 0) { @@ -1575,6 +1581,18 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = {}): Promise<{ text: string }> { + if (!this.mcpClient) { throw new Error('MCP server not connected'); } + if (!args.binlog_file && this.binlogPaths.length > 1) { + args.binlog_file = this.binlogPaths[0]; + } + return this.mcpClient.callTool(tool, args); + } + // --- Helpers --- private dataToItem(data: TreeNodeData): BinlogTreeItem { diff --git a/src/chatParticipant.ts b/src/chatParticipant.ts index 636f2a3..6c48d78 100644 --- a/src/chatParticipant.ts +++ b/src/chatParticipant.ts @@ -363,7 +363,13 @@ export class BinlogChatParticipant { await this.processResponse(nextRequest, messages, model, tools, stream, token, depth + 1); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('invalid_request_body') || msg.includes('tool_calls')) { + if ( + msg.includes('invalid_request_body') || + msg.includes('tool_calls') || + msg.includes("role 'tool'") || + msg.includes('tool_call_id') || + msg.includes('400') + ) { const retry = await model.sendRequest(messages, {}, token); for await (const part of retry.stream) { if (part instanceof vscode.LanguageModelTextPart) { diff --git a/src/extension.ts b/src/extension.ts index 8c06790..8eb0ae7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -35,6 +35,19 @@ function escapeHtml(s: string): string { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } +/** + * Call an MCP tool, auto-injecting `binlog_file` for the current primary + * binlog when multiple are loaded. This prevents "requires explicit + * binlog_file" errors throughout extension.ts call sites. + */ +function callMcpTool(tool: string, args: Record = {}): Promise<{ text: string }> { + if (!mcpClient) { throw new Error('MCP server not connected'); } + if (!args.binlog_file && allBinlogPaths.length > 1) { + args.binlog_file = currentBinlogPath || allBinlogPaths[0]; + } + return mcpClient.callTool(tool, args); +} + /** Returns a globalState key scoped to the current workspace folder, or a fallback for no-workspace. */ function binlogStateKey(): string { const ws = vscode.workspace.workspaceFolders?.[0]?.uri.toString(); @@ -889,16 +902,16 @@ export async function activate(context: vscode.ExtensionContext) { const isKnown = /^(ResolveAssemblyReferences|CoreCompile|Csc|Copy|RAR|Restore|ResolvePackageAssets|GenerateNuspec)/i.test(name); if (isKnown) { prompt = `@binlog The MSBuild target ${itemCtx} is a known build bottleneck. ` + - `First use binlog_expensive_targets to confirm its timing, then explain what specific MSBuild properties ` + - `can reduce its time. Show the exact XML to add to Directory.Build.props and any trade-offs.${binlogPath}`; + `Use binlog_lm_search to find which projects invoke it and binlog_expensive_targets to confirm its timing. ` + + `Explain what specific MSBuild properties can reduce its time. Show the exact XML to add to Directory.Build.props and any trade-offs.${binlogPath}`; } else { prompt = `@binlog Investigate the MSBuild target ${itemCtx}. ` + - `Use binlog_search_targets to find where it runs and which projects invoke it. ` + + `Use binlog_lm_search with the target name to find where it runs and which projects invoke it. ` + `Is it running too many times? Can it be skipped with proper Inputs/Outputs?${binlogPath}`; } } else if (category === 'task') { prompt = `@binlog Investigate the MSBuild task ${itemCtx}. ` + - `Use binlog_search_tasks to find it, then binlog_task_details for its parameters and output. ` + + `Use binlog_lm_search with the task name to find it and understand its parameters and output. ` + `What does it do and can it be optimized?${binlogPath}`; } else if (category === 'property-item') { prompt = `@binlog Explain the MSBuild property ${name} = "${detail}". ` + @@ -910,11 +923,11 @@ export async function activate(context: vscode.ExtensionContext) { `Are there any version conflicts or redundancies?${binlogPath}`; } else if (category === 'project') { prompt = `@binlog Analyze the project ${itemCtx}. ` + - `Use binlog_project_targets to show target breakdown and timing. ` + + `Use binlog_lm_search with the project name to show its targets and timing. ` + `What are its slowest targets? Does it have unnecessary dependencies?${binlogPath}`; } else { prompt = `@binlog Analyze the MSBuild target ${itemCtx}. ` + - `Use binlog_search_targets to find where it runs and binlog_expensive_targets for timing. ` + + `Use binlog_lm_search with the target name to find where it runs and binlog_expensive_targets for timing. ` + `What does it do and how does it affect the build?${binlogPath}`; } @@ -990,7 +1003,7 @@ export async function activate(context: vscode.ExtensionContext) { { location: vscode.ProgressLocation.Notification, title: `Searching binlog for "${query}"...` }, async () => { try { - const result = await mcpClient!.callTool('binlog_search', { + const result = await callMcpTool('binlog_search', { query: query.trim(), limit: 200, }); @@ -1044,7 +1057,7 @@ export async function activate(context: vscode.ExtensionContext) { const pageSize = 500; const maxTotal = 10000; while (offset < maxTotal) { - const result = await mcpClient!.callTool('binlog_search', { + const result = await callMcpTool('binlog_search', { query: query.trim(), limit: pageSize, offset, @@ -1102,7 +1115,7 @@ export async function activate(context: vscode.ExtensionContext) { // Always include overview try { - const overview = await mcpClient!.callTool('binlog_overview', {}); + const overview = await callMcpTool('binlog_overview', {}); sections.push('BUILD OVERVIEW'); sections.push('─'.repeat(40)); sections.push(overview.text); @@ -1111,7 +1124,7 @@ export async function activate(context: vscode.ExtensionContext) { // Always include errors try { - const errors = await mcpClient!.callTool('binlog_errors', {}); + const errors = await callMcpTool('binlog_errors', {}); sections.push('ERRORS'); sections.push('─'.repeat(40)); sections.push(errors.text || 'None'); @@ -1120,7 +1133,7 @@ export async function activate(context: vscode.ExtensionContext) { // Always include warnings try { - const warnings = await mcpClient!.callTool('binlog_warnings', {}); + const warnings = await callMcpTool('binlog_warnings', {}); sections.push('WARNINGS'); sections.push('─'.repeat(40)); sections.push(warnings.text || 'None'); @@ -1130,7 +1143,7 @@ export async function activate(context: vscode.ExtensionContext) { if (verbosity.value !== 'minimal') { // Normal+: include projects and properties try { - const projects = await mcpClient!.callTool('binlog_projects', {}); + const projects = await callMcpTool('binlog_projects', {}); sections.push('PROJECTS'); sections.push('─'.repeat(40)); sections.push(projects.text); @@ -1138,7 +1151,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const props = await mcpClient!.callTool('binlog_properties', {}); + const props = await callMcpTool('binlog_properties', {}); sections.push('PROPERTIES'); sections.push('─'.repeat(40)); sections.push(props.text); @@ -1149,7 +1162,7 @@ export async function activate(context: vscode.ExtensionContext) { if (verbosity.value === 'detailed' || verbosity.value === 'diagnostic') { // Detailed+: include targets and tasks try { - const targets = await mcpClient!.callTool('binlog_expensive_targets', { top_number: 30 }); + const targets = await callMcpTool('binlog_expensive_targets', { top_number: 30 }); sections.push('TARGETS (by duration)'); sections.push('─'.repeat(40)); sections.push(targets.text); @@ -1157,7 +1170,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const tasks = await mcpClient!.callTool('binlog_expensive_tasks', { top_number: 30 }); + const tasks = await callMcpTool('binlog_expensive_tasks', { top_number: 30 }); sections.push('TASKS (by duration)'); sections.push('─'.repeat(40)); sections.push(tasks.text); @@ -1165,7 +1178,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const itemTypes = await mcpClient!.callTool('binlog_item_types', {}); + const itemTypes = await callMcpTool('binlog_item_types', {}); sections.push('ITEM TYPES'); sections.push('─'.repeat(40)); sections.push(itemTypes.text); @@ -1176,7 +1189,7 @@ export async function activate(context: vscode.ExtensionContext) { if (verbosity.value === 'diagnostic') { // Diagnostic: include imports, NuGet, compiler, analyzers try { - const imports = await mcpClient!.callTool('binlog_imports', {}); + const imports = await callMcpTool('binlog_imports', {}); sections.push('IMPORTS'); sections.push('─'.repeat(40)); sections.push(imports.text); @@ -1184,7 +1197,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const nuget = await mcpClient!.callTool('binlog_nuget', {}); + const nuget = await callMcpTool('binlog_nuget', {}); sections.push('NUGET'); sections.push('─'.repeat(40)); sections.push(nuget.text); @@ -1192,7 +1205,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const analyzers = await mcpClient!.callTool('binlog_expensive_analyzers', { limit: 20 }); + const analyzers = await callMcpTool('binlog_expensive_analyzers', { limit: 20 }); sections.push('ANALYZERS (by duration)'); sections.push('─'.repeat(40)); sections.push(analyzers.text); @@ -1200,7 +1213,7 @@ export async function activate(context: vscode.ExtensionContext) { } catch { /* non-fatal */ } try { - const compiler = await mcpClient!.callTool('binlog_compiler', {}); + const compiler = await callMcpTool('binlog_compiler', {}); sections.push('COMPILER COMMAND LINE'); sections.push('─'.repeat(40)); sections.push(compiler.text); @@ -1587,7 +1600,7 @@ async function handleBinlogOpen(binlogPaths: string[], context: vscode.Extension // Fallback: older VS Code versions may not support chat.new vscode.commands.executeCommand('workbench.action.chat.open', chatMessage); }); - }, 500); + }, 1000); } // If workspace doesn't match binlog location, suggest updating it @@ -2335,8 +2348,8 @@ async function optimizeBuildFlow(context: vscode.ExtensionContext) { // Step 1: Get perf data from MCP const [targetsResult, tasksResult] = await Promise.allSettled([ - mcpClient.callTool('binlog_expensive_targets', { top_number: 10 }), - mcpClient.callTool('binlog_expensive_tasks', { top_number: 10 }), + callMcpTool('binlog_expensive_targets', { top_number: 10 }), + callMcpTool('binlog_expensive_tasks', { top_number: 10 }), ]); const targetsText = targetsResult.status === 'fulfilled' ? targetsResult.value.text : ''; @@ -2614,10 +2627,10 @@ async function showTimelineWebview(context: vscode.ExtensionContext) { try { const [targetsResult, tasksResult, projectsResult, allProjectsResult] = await Promise.all([ - mcpClient.callTool('binlog_expensive_targets', { top_number: 20 }), - mcpClient.callTool('binlog_expensive_tasks', { top_number: 20 }), - mcpClient.callTool('binlog_expensive_projects', { limit: 20 }), - mcpClient.callTool('binlog_projects'), + callMcpTool('binlog_expensive_targets', { top_number: 20 }), + callMcpTool('binlog_expensive_tasks', { top_number: 20 }), + callMcpTool('binlog_expensive_projects', { limit: 20 }), + callMcpTool('binlog_projects'), ]); targetsData = JSON.parse(targetsResult.text); tasksData = JSON.parse(tasksResult.text); @@ -2630,8 +2643,9 @@ async function showTimelineWebview(context: vscode.ExtensionContext) { return String(fp).split(/[/\\]/).pop()?.toLowerCase() || ''; }).filter(Boolean)); totalProjectCount = uniqueNames.size || projectList.length; - } catch { - panel.webview.html = '

Failed to load timeline data

'; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + panel.webview.html = `

Failed to load timeline data

${escapeHtml(msg)}

`; return; }