From 1be1fef9d1ba7698b5b33cb4abb45bb65d4b2c4e Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Wed, 3 Sep 2025 15:49:58 +0800 Subject: [PATCH 01/38] Add AI triage workflow and LLM documentation (#1577) - Add GitHub Actions workflow for automated issue triage using AI - Include LLM documentation with labeling guidelines for Java development - Configure workflow for vscode-java-debug repository - Enable automatic labeling and commenting on new issues --- .github/llms.md | 43 ++++++++++ .github/workflows/triage-agent.yml | 123 +++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 .github/llms.md create mode 100644 .github/workflows/triage-agent.yml diff --git a/.github/llms.md b/.github/llms.md new file mode 100644 index 00000000..f0c0521e --- /dev/null +++ b/.github/llms.md @@ -0,0 +1,43 @@ +# Extension Pack for Java + +Extension Pack for Java is a collection of popular extensions that can help write, test and debug Java applications in Visual Studio Code. By installing Extension Pack for Java, the following extensions are installed: + +- [📦 Language Support for Java™ by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) + - Code Navigation + - Auto Completion + - Refactoring + - Code Snippets +- [📦 Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) + - Debugging +- [📦 Test Runner for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test) + - Run & Debug JUnit/TestNG Test Cases +- [📦 Maven for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-maven) + - Project Scaffolding + - Custom Goals +- [📦 Gradle for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle) + - View Gradle tasks and project dependencies + - Gradle file authoring + - Import Gradle projects via [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle) +- [📦 Project Manager for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency) + - Manage Java projects, referenced libraries, resource files, packages, classes, and class members +- [📦 Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode) + - AI-assisted development + - Completion list ranked by AI + +## Label + +When labeling an issue, follow the rules below per label category: + +### General Rules + +- Analyze if the issue is related with the scope of using extensions for Java development. If not, STOP labelling IMMEDIATELY. +- Assign label per category. +- If a category is not applicable or you're unsure, you may skip it. +- Do not assign multiple labels within the same category, unless explicitly allowed as an exception. + +### Issue Type Labels + +- [bug]: Primary label for real bug issues +- [enhancement]: Primary label for enhancement issues +- [documentation]: Primary label for documentation issues +- [question]: Primary label for question issues diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml new file mode 100644 index 00000000..66496528 --- /dev/null +++ b/.github/workflows/triage-agent.yml @@ -0,0 +1,123 @@ +name: AI Triage - Label and Comment on New Issues + +on: + issues: + types: [opened] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to triage (manual run). e.g. 123' + required: true + +permissions: + issues: write + contents: read + +jobs: + label_and_comment: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Get issue data + id: get_issue + uses: actions/github-script@v6 + with: + script: | + const eventName = context.eventName; + let issue; + if (eventName === 'workflow_dispatch') { + const inputs = context.payload.inputs || {}; + const issueNumber = inputs.issue_number || inputs.issueNumber; + if (!issueNumber) core.setFailed('Input issue_number is required for manual run.'); + const { data } = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt(issueNumber, 10), + }); + issue = data; + } else if (context.payload.issue) { + issue = context.payload.issue; + } else { + core.setFailed('No issue information found in the event payload.'); + } + core.setOutput('id', String(issue.number)); + core.setOutput('user', String((issue.user && issue.user.login) || '')); + core.setOutput('title', String(issue.title || '')); + core.setOutput('body', String(issue.body || '')); + const labelNames = (issue.labels || []).map(label => label.name); + core.setOutput('labels', JSON.stringify(labelNames)); + + - name: Call Azure Function + id: call_azure_function + env: + PAYLOAD: >- + { + "authToken": "${{ secrets.GITHUB_TOKEN }}", + "repoId": "microsoft/vscode-java-debug", + "issueData": { + "id": ${{ steps.get_issue.outputs.id }}, + "user": ${{ toJson(steps.get_issue.outputs.user) }}, + "title": ${{ toJson(steps.get_issue.outputs.title) }}, + "body": ${{ toJson(steps.get_issue.outputs.body) }}, + "labels": ${{ steps.get_issue.outputs.labels }} + }, + "mode": "DirectUpdate" + } + + run: | + # Make the HTTP request with improved error handling and timeouts + echo "Making request to triage agent..." + + # Add timeout handling and better error detection + set +e # Don't exit on curl failure + response=$(timeout ${{ vars.TRIAGE_AGENT_TIMEOUT }} curl \ + --max-time 0 \ + --connect-timeout 30 \ + --fail-with-body \ + --silent \ + --show-error \ + --write-out "HTTPSTATUS:%{http_code}" \ + --header "Content-Type: application/json" \ + --request POST \ + --data "$PAYLOAD" \ + ${{ secrets.TRIAGE_FUNCTION_LINK }} 2>&1) + + curl_exit_code=$? + set -e # Re-enable exit on error + + echo "Curl exit code: $curl_exit_code" + + # Check if curl command timed out or failed + if [ $curl_exit_code -eq 124 ]; then + echo "❌ Request timed out after 650 seconds" + exit 1 + elif [ $curl_exit_code -ne 0 ]; then + echo "❌ Curl command failed with exit code: $curl_exit_code" + echo "Response: $response" + exit 1 + fi + + # Extract HTTP status code and response body + http_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2) + response_body=$(echo "$response" | sed 's/HTTPSTATUS:[0-9]*$//') + + echo "HTTP Status Code: $http_code" + + # Validate HTTP status code + if [ -z "$http_code" ]; then + echo "❌ Failed to extract HTTP status code from response" + echo "Raw response: $response" + exit 1 + fi + + # Check if the request was successful + if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then + echo "✅ Azure Function call succeeded" + else + echo "❌ Azure Function call failed with status code: $http_code" + echo "Response: $response_body" + exit 1 + fi From 295079a0faf010667598a8a161649f603c725afb Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:13:18 +0800 Subject: [PATCH 02/38] feat: no config debug (#1578) * feat: no config debug * feat: no config debug support * feat: update * ci: remove useless file * fix: fix lint error * fix: update name as debugjava * fix: update name as debugjava * fix: resolve comments in the pr * feat: java home set --- bundled/scripts/noConfigScripts/README.md | 102 ++++++++ bundled/scripts/noConfigScripts/debugjava | 16 ++ bundled/scripts/noConfigScripts/debugjava.bat | 13 + .../scripts/noConfigScripts/debugjava.fish | 16 ++ bundled/scripts/noConfigScripts/debugjava.ps1 | 15 ++ .../scripts/noConfigScripts/jdwp-wrapper.js | 130 +++++++++ src/extension.ts | 9 + src/noConfigDebugInit.ts | 247 ++++++++++++++++++ 8 files changed, 548 insertions(+) create mode 100644 bundled/scripts/noConfigScripts/README.md create mode 100644 bundled/scripts/noConfigScripts/debugjava create mode 100644 bundled/scripts/noConfigScripts/debugjava.bat create mode 100644 bundled/scripts/noConfigScripts/debugjava.fish create mode 100644 bundled/scripts/noConfigScripts/debugjava.ps1 create mode 100644 bundled/scripts/noConfigScripts/jdwp-wrapper.js create mode 100644 src/noConfigDebugInit.ts diff --git a/bundled/scripts/noConfigScripts/README.md b/bundled/scripts/noConfigScripts/README.md new file mode 100644 index 00000000..021f0553 --- /dev/null +++ b/bundled/scripts/noConfigScripts/README.md @@ -0,0 +1,102 @@ +# Java No-Config Debug + +This feature enables configuration-less debugging for Java applications, similar to the JavaScript Debug Terminal in VS Code. + +## How It Works + +When you open a terminal in VS Code with this extension installed, the following environment variables are automatically set: + +- `VSCODE_JDWP_ADAPTER_ENDPOINTS`: Path to a communication file for port exchange +- `PATH`: Includes the `debugjava` command wrapper + +Note: `JAVA_TOOL_OPTIONS` is NOT set globally to avoid affecting other Java tools (javac, maven, gradle). Instead, it's set only when you run the `debugjava` command. + +## Usage + +### Basic Usage + +Instead of running: +```bash +java -cp . com.example.Main +``` + +Simply run: +```bash +debugjava -cp . com.example.Main +``` + +The debugger will automatically attach, and breakpoints will work without any launch.json configuration! + +### Maven Projects + +```bash +debugjava -jar target/myapp.jar +``` + +### Gradle Projects + +```bash +debugjava -jar build/libs/myapp.jar +``` + +### With Arguments + +```bash +debugjava -cp . com.example.Main arg1 arg2 --flag=value +``` + +### Spring Boot + +```bash +debugjava -jar myapp.jar --spring.profiles.active=dev +``` + +## Advantages + +1. **No Configuration Required**: No need to create or maintain launch.json +2. **Rapid Prototyping**: Perfect for quick debugging sessions +3. **Script Debugging**: Debug applications launched by complex shell scripts +4. **Environment Consistency**: Inherits all terminal environment variables +5. **Parameter Flexibility**: Easy to change arguments using terminal history (↑ key) + +## How It Works Internally + +1. When you run `debugjava`, the wrapper script temporarily sets `JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0` +2. The wrapper determines which Java executable to use (priority order): + - First: `JAVA_HOME/bin/java` if JAVA_HOME environment variable is set (user's explicit choice) + - Second: `VSCODE_JAVA_EXEC` environment variable (Java path from VS Code's Java Language Server) + - Third: `java` command from system PATH +3. The wrapper launches the Java process with JDWP enabled +4. JVM starts and outputs: "Listening for transport dt_socket at address: 12345" +5. The wrapper captures the JDWP port from this output +6. The port is written to a communication file +7. VS Code's file watcher detects the file and automatically starts an attach debug session + +## Troubleshooting + +### Port Already in Use + +If you see "Address already in use", another Java debug session is running. Terminate it first. + +### No Breakpoints Hit + +1. Ensure you're running with `debugjava` command (not plain `java`) +2. Check that the `debugjava` command is available: `which debugjava` (Unix) or `Get-Command debugjava` (PowerShell) +3. Verify the terminal was opened AFTER the extension activated +4. Check the Debug Console for error messages + +### Node.js Not Found + +The wrapper script requires Node.js to be installed and available in PATH. + +## Limitations + +- Requires Node.js to be installed and available in PATH +- Only works in terminals opened within VS Code +- Requires using the `debugjava` command instead of `java` +- The Java process will suspend (hang) until the debugger attaches + +## See Also + +- [Debugger for Java Documentation](https://github.com/microsoft/vscode-java-debug) +- [JDWP Documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/jdwp-spec.html) diff --git a/bundled/scripts/noConfigScripts/debugjava b/bundled/scripts/noConfigScripts/debugjava new file mode 100644 index 00000000..e0861d16 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava @@ -0,0 +1,16 @@ +#!/bin/bash +# Java No-Config Debug Wrapper Script for Unix/Linux/macOS +# This script intercepts java commands and automatically enables JDWP debugging + +# Export the endpoint file path for JDWP port communication +export JDWP_ADAPTER_ENDPOINTS=$VSCODE_JDWP_ADAPTER_ENDPOINTS + +# Set JDWP options only for this debugjava invocation +# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes +export JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" + +# Get the directory of this script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Use Node.js wrapper to capture JDWP port +exec node "$SCRIPT_DIR/jdwp-wrapper.js" "$@" diff --git a/bundled/scripts/noConfigScripts/debugjava.bat b/bundled/scripts/noConfigScripts/debugjava.bat new file mode 100644 index 00000000..828c656f --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.bat @@ -0,0 +1,13 @@ +@echo off +REM Java No-Config Debug Wrapper Script for Windows +REM This script intercepts java commands and automatically enables JDWP debugging + +REM Export the endpoint file path for JDWP port communication +set JDWP_ADAPTER_ENDPOINTS=%VSCODE_JDWP_ADAPTER_ENDPOINTS% + +REM Set JDWP options only for this debugjava invocation +REM This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes +set JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0 + +REM Use Node.js wrapper to capture JDWP port +node "%~dp0jdwp-wrapper.js" %* diff --git a/bundled/scripts/noConfigScripts/debugjava.fish b/bundled/scripts/noConfigScripts/debugjava.fish new file mode 100644 index 00000000..b745bc18 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.fish @@ -0,0 +1,16 @@ +#!/usr/bin/env fish +# Java No-Config Debug Wrapper Script for Fish Shell +# This script intercepts java commands and automatically enables JDWP debugging + +# Export the endpoint file path for JDWP port communication +set -x JDWP_ADAPTER_ENDPOINTS $VSCODE_JDWP_ADAPTER_ENDPOINTS + +# Set JDWP options only for this debugjava invocation +# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes +set -x JAVA_TOOL_OPTIONS "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" + +# Get the directory of this script +set script_dir (dirname (status -f)) + +# Use Node.js wrapper to capture JDWP port +exec node "$script_dir/jdwp-wrapper.js" $argv diff --git a/bundled/scripts/noConfigScripts/debugjava.ps1 b/bundled/scripts/noConfigScripts/debugjava.ps1 new file mode 100644 index 00000000..931c0cad --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.ps1 @@ -0,0 +1,15 @@ +# Java No-Config Debug Wrapper Script for PowerShell +# This script intercepts java commands and automatically enables JDWP debugging + +# Export the endpoint file path for JDWP port communication +$env:JDWP_ADAPTER_ENDPOINTS = $env:VSCODE_JDWP_ADAPTER_ENDPOINTS + +# Set JDWP options only for this debugjava invocation +# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes +$env:JAVA_TOOL_OPTIONS = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" + +# Get the directory of this script +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Use Node.js wrapper to capture JDWP port +& node (Join-Path $scriptDir "jdwp-wrapper.js") $args diff --git a/bundled/scripts/noConfigScripts/jdwp-wrapper.js b/bundled/scripts/noConfigScripts/jdwp-wrapper.js new file mode 100644 index 00000000..de8a4d92 --- /dev/null +++ b/bundled/scripts/noConfigScripts/jdwp-wrapper.js @@ -0,0 +1,130 @@ +#!/usr/bin/env node +/** + * JDWP Port Listener and Communication Wrapper + * + * This script wraps Java process execution and captures the JDWP port + * from the JVM output, then writes it to the endpoint file for VS Code + * to pick up and attach the debugger. + * + * JDWP Output Format: + * "Listening for transport dt_socket at address: 12345" + */ + +const { spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +// Get environment variables +const endpointFile = process.env.JDWP_ADAPTER_ENDPOINTS || process.env.VSCODE_JDWP_ADAPTER_ENDPOINTS; +const javaToolOptions = process.env.JAVA_TOOL_OPTIONS || ''; + +// Check if debugging is enabled +const isDebugEnabled = javaToolOptions.includes('jdwp') && endpointFile; + +// Helper function to find java command +function getJavaCommand() { + // Priority 1: Try JAVA_HOME environment variable first (user's explicit choice) + const javaHome = process.env.JAVA_HOME; + if (javaHome) { + const javaPath = path.join(javaHome, 'bin', 'java'); + const javaPathExe = process.platform === 'win32' ? `${javaPath}.exe` : javaPath; + + // Check if the file exists + if (fs.existsSync(javaPathExe)) { + return javaPath; + } + if (fs.existsSync(javaPath)) { + return javaPath; + } + + console.warn(`[Java Debug] JAVA_HOME is set to '${javaHome}', but java command not found there. Falling back to VS Code's Java.`); + } + + // Priority 2: Use VSCODE_JAVA_EXEC if provided by VS Code (from Java Language Server) + const vscodeJavaExec = process.env.VSCODE_JAVA_EXEC; + if (vscodeJavaExec && fs.existsSync(vscodeJavaExec)) { + return vscodeJavaExec; + } + + // Priority 3: Fall back to 'java' in PATH + return 'java'; +} + +const javaCmd = getJavaCommand(); + +if (!isDebugEnabled) { + // No debugging, just run java normally + const child = spawn(javaCmd, process.argv.slice(2), { + stdio: 'inherit', + shell: false + }); + child.on('exit', (code) => process.exit(code || 0)); + child.on('error', (err) => { + console.error(`[Java Debug] Failed to start java: ${err.message}`); + console.error(`[Java Debug] Make sure Java is installed and either JAVA_HOME is set correctly or 'java' is in your PATH.`); + process.exit(1); + }); +} else { + // Debugging enabled, capture JDWP port + const child = spawn(javaCmd, process.argv.slice(2), { + stdio: ['inherit', 'pipe', 'pipe'], + shell: false + }); + + let portCaptured = false; + const jdwpPortRegex = /Listening for transport dt_socket at address:\s*(\d+)/; + + // Shared function to capture JDWP port from output + const capturePort = (output) => { + if (portCaptured) return; + + const match = output.match(jdwpPortRegex); + if (match && match[1]) { + const port = parseInt(match[1], 10); + + // Validate port range + if (port < 1 || port > 65535) { + console.error(`[Java Debug] Invalid port number: ${port}`); + return; + } + + console.log(`[Java Debug] Captured JDWP port: ${port}`); + + // Write port to endpoint file + const endpointData = JSON.stringify({ + client: { + host: 'localhost', + port: port + } + }); + + try { + fs.writeFileSync(endpointFile, endpointData, 'utf8'); + console.log(`[Java Debug] Wrote endpoint file: ${endpointFile}`); + portCaptured = true; + } catch (err) { + console.error(`[Java Debug] Failed to write endpoint file: ${err}`); + } + } + }; + + // Monitor stdout for JDWP port + child.stdout.on('data', (data) => { + const output = data.toString(); + process.stdout.write(data); + capturePort(output); + }); + + // Monitor stderr for JDWP port (it might appear on stderr) + child.stderr.on('data', (data) => { + const output = data.toString(); + process.stderr.write(data); + capturePort(output); + }); + + child.on('exit', (code) => process.exit(code || 0)); + child.on('error', (err) => { + console.error(`[Java Debug] Failed to start java: ${err}`); + process.exit(1); + }); +} diff --git a/src/extension.ts b/src/extension.ts index 6d8c2623..867b2c02 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,6 +13,7 @@ import { HCR_EVENT, JAVA_LANGID, TELEMETRY_EVENT, USER_NOTIFICATION_EVENT } from import { NotificationBar } from "./customWidget"; import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvider"; import { initExpService } from "./experimentationService"; +import { registerNoConfigDebug } from "./noConfigDebugInit"; import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace, NO_BUTTON, YES_BUTTON } from "./hotCodeReplace"; import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory"; import { JavaInlineValuesProvider } from "./JavaInlineValueProvider"; @@ -31,6 +32,14 @@ import { promisify } from "util"; export async function activate(context: vscode.ExtensionContext): Promise { await initializeFromJsonFile(context.asAbsolutePath("./package.json")); await initExpService(context); + + // Register No-Config Debug functionality + const noConfigDisposable = await registerNoConfigDebug( + context.environmentVariableCollection, + context.extensionPath + ); + context.subscriptions.push(noConfigDisposable); + return instrumentOperation("activation", initializeExtension)(context); } diff --git a/src/noConfigDebugInit.ts b/src/noConfigDebugInit.ts new file mode 100644 index 00000000..b0da4f01 --- /dev/null +++ b/src/noConfigDebugInit.ts @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; +import * as vscode from 'vscode'; + +import { sendInfo, sendError } from "vscode-extension-telemetry-wrapper"; +import { getJavaHome } from "./utility"; + +/** + * Registers the configuration-less debugging setup for the extension. + * + * This function sets up environment variables and a file system watcher to + * facilitate debugging without requiring a pre-configured launch.json file. + * + * @param envVarCollection - The collection of environment variables to be modified. + * @param extPath - The path to the extension directory. + * + * Environment Variables: + * - `VSCODE_JDWP_ADAPTER_ENDPOINTS`: Path to the file containing the debugger adapter endpoint. + * - `JAVA_TOOL_OPTIONS`: JDWP configuration for automatic debugging. + * - `PATH`: Appends the path to the noConfigScripts directory. + */ +export async function registerNoConfigDebug( + envVarCollection: vscode.EnvironmentVariableCollection, + extPath: string, +): Promise { + const collection = envVarCollection; + + // create a temp directory for the noConfigDebugAdapterEndpoints + // file path format: extPath/.noConfigDebugAdapterEndpoints/endpoint-stableWorkspaceHash.txt + let workspaceString = vscode.workspace.workspaceFile?.fsPath; + if (!workspaceString) { + workspaceString = vscode.workspace.workspaceFolders?.map((e) => e.uri.fsPath).join(';'); + } + if (!workspaceString) { + const error: Error = { + name: "NoConfigDebugError", + message: '[Java Debug] No workspace folder found', + }; + sendError(error); + return Promise.resolve(new vscode.Disposable(() => { })); + } + + // create a stable hash for the workspace folder, reduce terminal variable churn + const hash = crypto.createHash('sha256'); + hash.update(workspaceString.toString()); + const stableWorkspaceHash = hash.digest('hex').slice(0, 16); + + const tempDirPath = path.join(extPath, '.noConfigDebugAdapterEndpoints'); + const tempFilePath = path.join(tempDirPath, `endpoint-${stableWorkspaceHash}.txt`); + + // create the temp directory if it doesn't exist + if (!fs.existsSync(tempDirPath)) { + fs.mkdirSync(tempDirPath, { recursive: true }); + } else { + // remove endpoint file in the temp directory if it exists (async to avoid blocking) + if (fs.existsSync(tempFilePath)) { + fs.promises.unlink(tempFilePath).catch((err) => { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] Failed to cleanup old endpoint file: ${err}`, + }; + sendError(error); + }); + } + } + + // clear the env var collection to remove any existing env vars + collection.clear(); + + // Add env var for VSCODE_JDWP_ADAPTER_ENDPOINTS + // Note: We do NOT set JAVA_TOOL_OPTIONS globally to avoid affecting all Java processes + // (javac, maven, gradle, language server, etc.). Instead, JAVA_TOOL_OPTIONS is set + // only in the debugjava wrapper scripts (debugjava.ps1, debugjava.bat, debugjava) + collection.replace('VSCODE_JDWP_ADAPTER_ENDPOINTS', tempFilePath); + + // Try to get Java executable from Java Language Server + // This ensures we use the same Java version as the project is compiled with + try { + const javaHome = await getJavaHome(); + if (javaHome) { + const javaExec = path.join(javaHome, 'bin', 'java'); + collection.replace('VSCODE_JAVA_EXEC', javaExec); + } + } catch (error) { + // If we can't get Java from Language Server, that's okay + // The wrapper script will fall back to JAVA_HOME or PATH + } + + const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); + const pathSeparator = process.platform === 'win32' ? ';' : ':'; + + // Check if the current PATH already ends with a path separator to avoid double separators + const currentPath = process.env.PATH || ''; + const needsSeparator = currentPath.length > 0 && !currentPath.endsWith(pathSeparator); + const pathValueToAppend = needsSeparator ? `${pathSeparator}${noConfigScriptsDir}` : noConfigScriptsDir; + + collection.append('PATH', pathValueToAppend); + + // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written + const fileSystemWatcher = vscode.workspace.createFileSystemWatcher( + new vscode.RelativePattern(tempDirPath, '**/*.txt') + ); + + // Track active debug sessions to prevent duplicates + const activeDebugSessions = new Set(); + + // Handle both file creation and modification to support multiple runs + const handleEndpointFile = async (uri: vscode.Uri) => { + const filePath = uri.fsPath; + + // Add a small delay to ensure file is fully written + // File system events can fire before write is complete + await new Promise(resolve => setTimeout(resolve, 100)); + + fs.readFile(filePath, (err, data) => { + if (err) { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: file_read_error - ${err}`, + }; + sendError(error); + return; + } + try { + // parse the client port + const dataParse = data.toString(); + const jsonData = JSON.parse(dataParse); + + // Validate JSON structure + if (!jsonData || typeof jsonData !== 'object' || !jsonData.client) { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: invalid_format - ${dataParse}`, + }; + sendError(error); + return; + } + + const clientPort = jsonData.client.port; + + // Validate port number + if (!clientPort || typeof clientPort !== 'number' || clientPort < 1 || clientPort > 65535) { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: invalid_port - ${clientPort}`, + }; + sendError(error); + return; + } + + // Check if we already have an active session for this port + if (activeDebugSessions.has(clientPort)) { + // Skip duplicate session silently - this is expected behavior + return; + } + + // Mark this port as active + activeDebugSessions.add(clientPort); + + const options: vscode.DebugSessionOptions = { + noDebug: false, + }; + + // start debug session with the client port + vscode.debug.startDebugging( + undefined, + { + type: 'java', + request: 'attach', + name: 'Attach to Java (No-Config)', + hostName: 'localhost', + port: clientPort, + }, + options, + ).then( + (started) => { + if (started) { + // Send telemetry only on successful session start with port info + sendInfo('', { message: '[Java Debug] No-config debug session started', port: clientPort }); + // Clean up the endpoint file after successful debug session start (async) + if (fs.existsSync(filePath)) { + fs.promises.unlink(filePath).catch((cleanupErr) => { + // Cleanup failure is non-critical, just log for debugging + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: cleanup_error - ${cleanupErr}`, + }; + sendError(error); + }); + } + } else { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: attach_failed - port ${clientPort}`, + }; + sendError(error); + // Remove from active sessions on failure + activeDebugSessions.delete(clientPort); + } + }, + (error) => { + const attachError: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: attach_error - port ${clientPort} - ${error}`, + }; + sendError(attachError); + // Remove from active sessions on error + activeDebugSessions.delete(clientPort); + }, + ); + } catch (parseErr) { + const error: Error = { + name: "NoConfigDebugError", + message: `[Java Debug] No-config debug failed: parse_error - ${parseErr}`, + }; + sendError(error); + } + }); + }; + + // Listen for both file creation and modification events + const fileCreationEvent = fileSystemWatcher.onDidCreate(handleEndpointFile); + const fileChangeEvent = fileSystemWatcher.onDidChange(handleEndpointFile); + + // Clean up active sessions when debug session ends + const debugSessionEndListener = vscode.debug.onDidTerminateDebugSession((session) => { + if (session.name === 'Attach to Java (No-Config)' && session.configuration.port) { + const port = session.configuration.port; + activeDebugSessions.delete(port); + // Session end is normal operation, no telemetry needed + } + }); + + return Promise.resolve( + new vscode.Disposable(() => { + fileSystemWatcher.dispose(); + fileCreationEvent.dispose(); + fileChangeEvent.dispose(); + debugSessionEndListener.dispose(); + activeDebugSessions.clear(); + }), + ); +} From 5ce8f5a5ef3ce5075611ffe26778f3d2ef9d153c Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:18:08 +0800 Subject: [PATCH 03/38] docs: code owner update (#1579) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b11684f5..0fffee03 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @testforstephen @jdneo \ No newline at end of file +* @testforstephen @jdneo @wenytang-ms @chagong \ No newline at end of file From 52bd41881d9577aee12fa5daf6a14855da9fae7f Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:27:56 +0800 Subject: [PATCH 04/38] docs: update changelog to ship 0.58.3 (#1580) --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 810a2c50..8e851960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the "vscode-java-debugger" extension will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.58.3 - 2025-11-03 +### Added +- No config debug [[#1530](https://github.com/microsoft/vscode-java-debug/issues/1530)] + ## 0.58.2 - 2025-04-28 ### Fixed - Provide graceful shutdown on debug stop action. [#1274](https://github.com/microsoft/vscode-java-debug/issues/1274). Thanks to [Dave Syer](https://github.com/dsyer) for contribution. diff --git a/package-lock.json b/package-lock.json index 290a7dec..0db08cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-java-debug", - "version": "0.58.2", + "version": "0.58.3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index d5b58868..9cadc3c8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-debug", "displayName": "Debugger for Java", "description": "A lightweight Java debugger for Visual Studio Code", - "version": "0.58.2", + "version": "0.58.3", "publisher": "vscjava", "preview": false, "aiKey": "67d4461e-ccba-418e-8082-1bd0acfe8516", From 86fff7f694ebafca0e30906f90719351c77adc31 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:44:23 +0800 Subject: [PATCH 05/38] fix: fix java_tool_option in same terminal (#1581) --- bundled/scripts/noConfigScripts/debugjava | 12 +++---- bundled/scripts/noConfigScripts/debugjava.bat | 6 ++++ .../scripts/noConfigScripts/debugjava.fish | 14 +++----- bundled/scripts/noConfigScripts/debugjava.ps1 | 35 ++++++++++++++----- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/bundled/scripts/noConfigScripts/debugjava b/bundled/scripts/noConfigScripts/debugjava index e0861d16..881a9211 100644 --- a/bundled/scripts/noConfigScripts/debugjava +++ b/bundled/scripts/noConfigScripts/debugjava @@ -2,15 +2,11 @@ # Java No-Config Debug Wrapper Script for Unix/Linux/macOS # This script intercepts java commands and automatically enables JDWP debugging -# Export the endpoint file path for JDWP port communication -export JDWP_ADAPTER_ENDPOINTS=$VSCODE_JDWP_ADAPTER_ENDPOINTS - -# Set JDWP options only for this debugjava invocation -# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes -export JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" - # Get the directory of this script SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -# Use Node.js wrapper to capture JDWP port +# Set environment variables only for the node process, not the current shell +# This ensures JAVA_TOOL_OPTIONS doesn't affect subsequent commands in the terminal +JDWP_ADAPTER_ENDPOINTS=$VSCODE_JDWP_ADAPTER_ENDPOINTS \ +JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" \ exec node "$SCRIPT_DIR/jdwp-wrapper.js" "$@" diff --git a/bundled/scripts/noConfigScripts/debugjava.bat b/bundled/scripts/noConfigScripts/debugjava.bat index 828c656f..3606188c 100644 --- a/bundled/scripts/noConfigScripts/debugjava.bat +++ b/bundled/scripts/noConfigScripts/debugjava.bat @@ -2,6 +2,9 @@ REM Java No-Config Debug Wrapper Script for Windows REM This script intercepts java commands and automatically enables JDWP debugging +REM Use setlocal to ensure environment variables don't persist after this script exits +setlocal + REM Export the endpoint file path for JDWP port communication set JDWP_ADAPTER_ENDPOINTS=%VSCODE_JDWP_ADAPTER_ENDPOINTS% @@ -11,3 +14,6 @@ set JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,addr REM Use Node.js wrapper to capture JDWP port node "%~dp0jdwp-wrapper.js" %* + +REM endlocal is implicit at script exit, but we can call it explicitly for clarity +endlocal diff --git a/bundled/scripts/noConfigScripts/debugjava.fish b/bundled/scripts/noConfigScripts/debugjava.fish index b745bc18..47d7cf75 100644 --- a/bundled/scripts/noConfigScripts/debugjava.fish +++ b/bundled/scripts/noConfigScripts/debugjava.fish @@ -2,15 +2,11 @@ # Java No-Config Debug Wrapper Script for Fish Shell # This script intercepts java commands and automatically enables JDWP debugging -# Export the endpoint file path for JDWP port communication -set -x JDWP_ADAPTER_ENDPOINTS $VSCODE_JDWP_ADAPTER_ENDPOINTS - -# Set JDWP options only for this debugjava invocation -# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes -set -x JAVA_TOOL_OPTIONS "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" - # Get the directory of this script set script_dir (dirname (status -f)) -# Use Node.js wrapper to capture JDWP port -exec node "$script_dir/jdwp-wrapper.js" $argv +# Set environment variables only for the node process, not the current shell +# This ensures JAVA_TOOL_OPTIONS doesn't affect subsequent commands in the terminal +env JDWP_ADAPTER_ENDPOINTS=$VSCODE_JDWP_ADAPTER_ENDPOINTS \ + JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" \ + node "$script_dir/jdwp-wrapper.js" $argv diff --git a/bundled/scripts/noConfigScripts/debugjava.ps1 b/bundled/scripts/noConfigScripts/debugjava.ps1 index 931c0cad..0404a860 100644 --- a/bundled/scripts/noConfigScripts/debugjava.ps1 +++ b/bundled/scripts/noConfigScripts/debugjava.ps1 @@ -1,15 +1,32 @@ # Java No-Config Debug Wrapper Script for PowerShell # This script intercepts java commands and automatically enables JDWP debugging -# Export the endpoint file path for JDWP port communication -$env:JDWP_ADAPTER_ENDPOINTS = $env:VSCODE_JDWP_ADAPTER_ENDPOINTS - -# Set JDWP options only for this debugjava invocation -# This overrides the global JAVA_TOOL_OPTIONS to avoid affecting other Java processes -$env:JAVA_TOOL_OPTIONS = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" - # Get the directory of this script $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -# Use Node.js wrapper to capture JDWP port -& node (Join-Path $scriptDir "jdwp-wrapper.js") $args +# Save current environment variables to restore later +$oldJavaToolOptions = $env:JAVA_TOOL_OPTIONS +$oldJdwpAdapterEndpoints = $env:JDWP_ADAPTER_ENDPOINTS + +try { + # Set environment variables only for this debugjava invocation + $env:JDWP_ADAPTER_ENDPOINTS = $env:VSCODE_JDWP_ADAPTER_ENDPOINTS + $env:JAVA_TOOL_OPTIONS = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0" + + # Use Node.js wrapper to capture JDWP port + & node (Join-Path $scriptDir "jdwp-wrapper.js") $args +} +finally { + # Restore original environment variables to avoid affecting subsequent commands + if ($null -eq $oldJavaToolOptions) { + Remove-Item Env:\JAVA_TOOL_OPTIONS -ErrorAction SilentlyContinue + } else { + $env:JAVA_TOOL_OPTIONS = $oldJavaToolOptions + } + + if ($null -eq $oldJdwpAdapterEndpoints) { + Remove-Item Env:\JDWP_ADAPTER_ENDPOINTS -ErrorAction SilentlyContinue + } else { + $env:JDWP_ADAPTER_ENDPOINTS = $oldJdwpAdapterEndpoints + } +} From 97be3904f37671c88a932a7f7e0e0c53ca25b696 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Mon, 10 Nov 2025 15:55:43 +0800 Subject: [PATCH 06/38] chore: add triage open issues workflow (#1583) --- .github/workflows/triage-agent.yml | 5 +- .github/workflows/triage-all-open-issues.yml | 145 +++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/triage-all-open-issues.yml diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml index 66496528..0e0cb45e 100644 --- a/.github/workflows/triage-agent.yml +++ b/.github/workflows/triage-agent.yml @@ -1,4 +1,4 @@ -name: AI Triage - Label and Comment on New Issues +name: AI Triage on: issues: @@ -9,6 +9,9 @@ on: description: 'Issue number to triage (manual run). e.g. 123' required: true +run-name: >- + AI Triage for Issue #${{ github.event.issue.number || github.event.inputs.issue_number }} + permissions: issues: write contents: read diff --git a/.github/workflows/triage-all-open-issues.yml b/.github/workflows/triage-all-open-issues.yml new file mode 100644 index 00000000..f2600deb --- /dev/null +++ b/.github/workflows/triage-all-open-issues.yml @@ -0,0 +1,145 @@ +name: AI Triage - Process All Open Issues +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run mode - only list issues without processing' + required: false + default: false + type: boolean + max_issues: + description: 'Maximum number of issues to process (0 = all)' + required: false + default: '0' + type: string + +permissions: + issues: write + contents: read + actions: write + +jobs: + get_open_issues: + runs-on: ubuntu-latest + outputs: + issue_numbers: ${{ steps.get_issues.outputs.issue_numbers }} + total_count: ${{ steps.get_issues.outputs.total_count }} + + steps: + - name: Get all open issues + id: get_issues + uses: actions/github-script@v6 + with: + script: | + // Use Search API to filter issues at API level + const { data } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open -label:ai-triaged -label:invalid`, + sort: 'created', + order: 'desc', + per_page: 100 + }); + + const actualIssues = data.items; + + let issuesToProcess = actualIssues; + const maxIssues = parseInt('${{ inputs.max_issues }}' || '0'); + + if (maxIssues > 0 && actualIssues.length > maxIssues) { + issuesToProcess = actualIssues.slice(0, maxIssues); + console.log(`Limiting to first ${maxIssues} issues out of ${actualIssues.length} total`); + } + + const issueNumbers = issuesToProcess.map(issue => issue.number); + const totalCount = issuesToProcess.length; + + console.log(`Found ${actualIssues.length} open issues, processing ${totalCount}:`); + issuesToProcess.forEach(issue => { + console.log(` #${issue.number}: ${issue.title}`); + }); + + core.setOutput('issue_numbers', JSON.stringify(issueNumbers)); + core.setOutput('total_count', totalCount); + + process_issues: + runs-on: ubuntu-latest + needs: get_open_issues + if: needs.get_open_issues.outputs.total_count > 0 + + strategy: + # Process issues one by one (max-parallel: 1) + max-parallel: 1 + matrix: + issue_number: ${{ fromJSON(needs.get_open_issues.outputs.issue_numbers) }} + + steps: + - name: Log current issue being processed + run: | + echo "🔄 Processing issue #${{ matrix.issue_number }}" + echo "Total issues to process: ${{ needs.get_open_issues.outputs.total_count }}" + + - name: Check if dry run mode + if: inputs.dry_run == true + run: | + echo "🔍 DRY RUN MODE: Would process issue #${{ matrix.issue_number }}" + echo "Skipping actual triage processing" + + - name: Trigger triage workflow for issue + if: inputs.dry_run != true + uses: actions/github-script@v6 + with: + script: | + const issueNumber = '${{ matrix.issue_number }}'; + + try { + console.log(`Triggering triage workflow for issue #${issueNumber}`); + + const response = await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'triage-agent.yml', + ref: 'main', + inputs: { + issue_number: issueNumber + } + }); + + console.log(`✅ Successfully triggered triage workflow for issue #${issueNumber}`); + + } catch (error) { + console.error(`❌ Failed to trigger triage workflow for issue #${issueNumber}:`, error); + core.setFailed(`Failed to process issue #${issueNumber}: ${error.message}`); + } + + - name: Wait for workflow completion + if: inputs.dry_run != true + run: | + echo "⏳ Waiting for triage workflow to complete for issue #${{ matrix.issue_number }}..." + echo "Timeout: ${{ vars.TRIAGE_AGENT_TIMEOUT }} seconds" + sleep ${{ vars.TRIAGE_AGENT_TIMEOUT }} # Wait for triage workflow completion + + summary: + runs-on: ubuntu-latest + needs: [get_open_issues, process_issues] + if: always() + + steps: + - name: Print summary + run: | + echo "## Triage Processing Summary" + echo "Total open issues found: ${{ needs.get_open_issues.outputs.total_count }}" + + if [ "${{ inputs.dry_run }}" == "true" ]; then + echo "Mode: DRY RUN (no actual processing performed)" + else + echo "Mode: FULL PROCESSING" + fi + + if [ "${{ needs.process_issues.result }}" == "success" ]; then + echo "✅ All issues processed successfully" + elif [ "${{ needs.process_issues.result }}" == "failure" ]; then + echo "❌ Some issues failed to process" + elif [ "${{ needs.process_issues.result }}" == "skipped" ]; then + echo "⏭️ Processing was skipped (no open issues found)" + else + echo "⚠️ Processing completed with status: ${{ needs.process_issues.result }}" + fi From 486f9150c6424248f54e753649caf638e85f4b0f Mon Sep 17 00:00:00 2001 From: Roland Schaer Date: Tue, 25 Nov 2025 02:57:42 +0100 Subject: [PATCH 07/38] Add command to manage breakpoint exception types in command palette (#1566) --- package.json | 4 ++ src/breakpointCommands.ts | 124 ++++++++++++++++++++++++++++++++++++++ src/extension.ts | 2 + 3 files changed, 130 insertions(+) create mode 100644 src/breakpointCommands.ts diff --git a/package.json b/package.json index 9cadc3c8..51247986 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,10 @@ "command": "java.debug.pauseOthers", "title": "Pause Others" }, + { + "command": "java.debug.breakpoints.exceptionTypes", + "title": "Manage Java Breakpoint Exception Types" + }, { "command": "java.debug.variables.showHex", "title": "Show as Hex" diff --git a/src/breakpointCommands.ts b/src/breakpointCommands.ts new file mode 100644 index 00000000..84ee9e22 --- /dev/null +++ b/src/breakpointCommands.ts @@ -0,0 +1,124 @@ +import * as vscode from "vscode"; + +export function registerBreakpointCommands(context: vscode.ExtensionContext): void { + context.subscriptions.push(vscode.commands.registerCommand('java.debug.breakpoints.exceptionTypes', exceptionTypes)); +} + +async function exceptionTypes() { + const config = vscode.workspace.getConfiguration('java.debug.settings.exceptionBreakpoint'); + let currentTypes = config.get('exceptionTypes', []); + const addExceptionTypeItem: vscode.QuickPickItem = { + label: '$(add) Add Exception Types...', + alwaysShow: true, + }; + const removeExceptionTypeItem: any = (type: string): any => ({ + label: type, + buttons: [{ + iconPath: new vscode.ThemeIcon('close'), + tooltip: 'Remove this Exception Type' + }] + }); + + // Step 1: Show Breakpoint Exception Types + const pickStep = async (state: any) => { + return new Promise((resolve) => { + const items: vscode.QuickPickItem[] = [ + addExceptionTypeItem, + ...currentTypes.map(type => removeExceptionTypeItem(type)) + ]; + const quickPick = vscode.window.createQuickPick(); + quickPick.items = items; + quickPick.title = 'Breakpoint Exception Types'; + quickPick.canSelectMany = false; + quickPick.matchOnDescription = false; + quickPick.matchOnDetail = false; + + quickPick.onDidAccept(() => { + const selected = quickPick.selectedItems[0]; + if (selected.label.includes('Add Exception Types')) { + quickPick.hide(); + // go to next step + resolve(state); + } + }); + + quickPick.onDidTriggerItemButton(async (e) => { + const typeToRemove = e.item.label; + currentTypes = currentTypes.filter(type => type !== typeToRemove); + await config.update('exceptionTypes', currentTypes, vscode.ConfigurationTarget.Global); + quickPick.items = [ + addExceptionTypeItem, + ...currentTypes.map(type => removeExceptionTypeItem(type)) + ]; + }); + quickPick.onDidHide(() => { + quickPick.dispose(); + }); + quickPick.show(); + }); + }; + + // Step 2: Add Exception Type(s) + const inputStep = async (state: any) => { + return new Promise((resolve, reject) => { + const input = vscode.window.createInputBox(); + input.title = 'Add Breakpoint Exception Type(s)'; + input.placeholder = 'Enter exception type(s) (comma or space separated). "java.lang.NullPointerException" e.g.'; + input.prompt = 'Input exception types'; + input.buttons = [vscode.QuickInputButtons.Back]; + input.onDidAccept(async () => { + const exceptionType = input.value; + if (exceptionType) { + const types = exceptionType.split(/[,\s]+/).map(type => type.trim()).filter(type => type.length > 0); + let updated = false; + for (const type of types) { + if (!currentTypes.includes(type)) { + currentTypes.push(type); + updated = true; + } + } + if (updated) { + await config.update('exceptionTypes', currentTypes, vscode.ConfigurationTarget.Global); + } + } + input.hide(); + // go back to pick step + resolve(state); + }); + input.onDidTriggerButton((btn) => { + if (btn === vscode.QuickInputButtons.Back) { + input.hide(); + reject({ stepBack: true }); + } + }); + input.onDidHide(() => { + input.dispose(); + }); + input.show(); + }); + }; + + while (true) { + await multiStepInput([pickStep, inputStep], {}); + } +} + +async function multiStepInput(steps: ((input: T) => Promise)[], initial: T): Promise { + let state = initial; + let currentStep = 0; + while (currentStep < steps.length) { + try { + state = await steps[currentStep](state); + currentStep++; + } catch (err) { + if (err?.stepBack) { + if (currentStep > 0) { + currentStep--; + } + } else { + throw err; + } + } + } + return state; +} diff --git a/src/extension.ts b/src/extension.ts index 867b2c02..c9a9542a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -26,6 +26,7 @@ import { progressProvider } from "./progressImpl"; import { JavaTerminalLinkProvder } from "./terminalLinkProvider"; import { initializeThreadOperations } from "./threadOperations"; import * as utility from "./utility"; +import { registerBreakpointCommands } from "./breakpointCommands"; import { registerVariableMenuCommands } from "./variableMenu"; import { promisify } from "util"; @@ -45,6 +46,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { function initializeExtension(_operationId: string, context: vscode.ExtensionContext): any { registerDebugEventListener(context); + registerBreakpointCommands(context); registerVariableMenuCommands(context); context.subscriptions.push(vscode.window.registerTerminalLinkProvider(new JavaTerminalLinkProvder())); context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("java", new JavaDebugConfigurationProvider())); From 856b38208627f03a6e30d1f5fcde4f306048f256 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 9 Dec 2025 10:42:39 +0800 Subject: [PATCH 08/38] docs: update changelog for 0.58.4 (#1592) * docs: update changelog for 0.58.4 * fix: update package lock --- CHANGELOG.md | 9 +++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e851960..bc4bac86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to the "vscode-java-debugger" extension will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.58.4 - 2025-12-09 +### Added +- Add command to manage breakpoint exception types in command palette. [#1566](https://github.com/microsoft/vscode-java-debug/pull/1566). Thanks to [Roland Schaer](https://github.com/roele) for contribution. + +### Fixed +- Fix `lspFrame.source` NPE on stackTrace request. [java-debug#616](https://github.com/microsoft/java-debug/pull/616). Thanks to [Mathias Fußenegger](https://github.com/mfussenegger) for contribution. +- Handle unavailable sources in compliance with DAP spec. [java-debug#609](https://github.com/microsoft/java-debug/pull/609), [java-debug#614](https://github.com/microsoft/java-debug/pull/614). Thanks to [Karl-Erik Enkelmann](https://github.com/playdohface) for contribution. + + ## 0.58.3 - 2025-11-03 ### Added - No config debug [[#1530](https://github.com/microsoft/vscode-java-debug/issues/1530)] diff --git a/package-lock.json b/package-lock.json index 0db08cb4..907b44e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-java-debug", - "version": "0.58.3", + "version": "0.58.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-java-debug", - "version": "0.58.2", + "version": "0.58.4", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "compare-versions": "^4.1.4", diff --git a/package.json b/package.json index 51247986..bf80cc70 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-debug", "displayName": "Debugger for Java", "description": "A lightweight Java debugger for Visual Studio Code", - "version": "0.58.3", + "version": "0.58.4", "publisher": "vscjava", "preview": false, "aiKey": "67d4461e-ccba-418e-8082-1bd0acfe8516", From 89ec914b67d167c39b51e95d83f0743b373ac029 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:49:23 +0800 Subject: [PATCH 09/38] Bump js-yaml (#1586) Bumps and [js-yaml](https://github.com/nodeca/js-yaml). These dependencies needed to be updated together. Updates `js-yaml` from 4.1.0 to 4.1.1 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) Updates `js-yaml` from 3.14.1 to 3.14.2 - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.1.1 dependency-type: indirect - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 907b44e8..fd08e248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1647,9 +1647,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "dependencies": { "argparse": "^2.0.1" @@ -2724,9 +2724,9 @@ } }, "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "dependencies": { "argparse": "^1.0.7", @@ -4462,9 +4462,9 @@ "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -5249,9 +5249,9 @@ "dev": true }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { "argparse": "^1.0.7", From b06021ea1b8501b71f5a272208968d72bf91cb03 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:02:39 +0800 Subject: [PATCH 10/38] feat: custom debug java agent (#1593) * feat: mcp toool * feat: add agent.md * feat: debug agent design * feat: update debug logic * feat: debug agent design * ci: update * docs: update the clean up logic * docs: update * feat: update * docs: readme * fix: fix tslint error * fix: update code * fix: update * feat: update * fix: fix lint --- README.md | 34 + bundled/agents/README.md | 195 ++ bundled/agents/debug.agent.md | 723 ++++++++ bundled/agents/images/agent-working.png | Bin 0 -> 93159 bytes bundled/agents/images/invoke-agent.png | Bin 0 -> 5549 bytes bundled/agents/images/javadebug.png | Bin 0 -> 42189 bytes .../scripts/noConfigScripts/jdwp-wrapper.js | 18 +- package-lock.json | 19 +- package.json | 359 +++- src/extension.ts | 27 + src/languageModelTool.ts | 1577 +++++++++++++++++ 11 files changed, 2936 insertions(+), 16 deletions(-) create mode 100644 bundled/agents/README.md create mode 100644 bundled/agents/debug.agent.md create mode 100644 bundled/agents/images/agent-working.png create mode 100644 bundled/agents/images/invoke-agent.png create mode 100644 bundled/agents/images/javadebug.png create mode 100644 src/languageModelTool.ts diff --git a/README.md b/README.md index 54e122f1..53f5f514 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ A lightweight Java Debugger based on [Java Debug Server](https://github.com/Micr - Debug console - Evaluation - Hot Code Replace +- No-Config Debug (debug Java apps without launch.json) +- **[AI]** AI-Assisted Debugging (GitHub Copilot integration) ## Requirements - JDK (version 1.8.0 or later) @@ -41,6 +43,38 @@ ext install vscode-java-debug Please also check the documentation of [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) if you have trouble setting up your project. +## No-Config Debug + +You can now debug Java applications without creating a `launch.json` file! Simply open a terminal in VS Code and use the `debugjava` command: + +```bash +# Debug a main class +debugjava -cp bin com.example.Main + +# Debug a JAR file +debugjava -jar target/myapp.jar + +# Debug with arguments +debugjava -cp bin com.example.Main arg1 arg2 +``` + +The debugger will automatically attach. See [No-Config Debug Documentation](bundled/scripts/noConfigScripts/README.md) for more details. + +## AI-Assisted Debugging + +When using GitHub Copilot Chat, you can now ask AI to help you debug Java applications! The extension provides a Language Model Tool that enables natural language debugging: + +- "Debug my Spring Boot application" +- "Debug the Main class in this project" +- "Debug Calculator with arguments 10 and 5" + +The AI will automatically: +1. Detect your project type (Maven/Gradle/VS Code) +2. Build/compile your project +3. Start debugging with appropriate configuration + +See [Language Model Tool Documentation](bundled/agents/README.md) for more details. + ## Options ### Launch diff --git a/bundled/agents/README.md b/bundled/agents/README.md new file mode 100644 index 00000000..3dab2c1d --- /dev/null +++ b/bundled/agents/README.md @@ -0,0 +1,195 @@ +# Java Debug Agent + +The Java Debug Agent is an AI-powered debugging assistant that integrates with GitHub Copilot Chat to help you debug Java applications using natural language. + +## Overview + +Instead of manually setting breakpoints and inspecting variables, you can simply describe your debugging task in natural language. The agent will: + +1. Analyze your code to form hypotheses +2. Set targeted breakpoints +3. Inspect variables and evaluate expressions +4. Find the root cause of bugs + +## Requirements + +- VS Code 1.95.0 or later +- [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) +- [Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug) +- GitHub Copilot Chat extension + +## Getting Started + +### 1. Open Copilot Chat + +Press `Ctrl+Shift+I` (Windows/Linux) or `Cmd+Shift+I` (macOS) to open Copilot Chat. + +### 2. Switch to JavaDebug Agent Mode + +In the Copilot Chat panel, click on the agent selector (usually shows "Copilot" or current agent name) and select **JavaDebug** from the dropdown list. + +![Select Agent](images/javadebug.png) + + +### 3. Enter Your Debugging Request + +Once in JavaDebug mode, simply type your debugging request: + +``` +Why am I getting a NullPointerException in OrderService? +``` + +![Invoke Agent](images/invoke-agent.png) + + +### 4. Let the Agent Work + +The agent will: +- Read relevant code files +- Form a hypothesis about the bug +- Set breakpoints at strategic locations +- Start or attach to a debug session +- Inspect variables to verify the hypothesis +- Report the root cause + +![Agent Working](images/agent-working.png) + + +## Example Usage + +### Debug a NullPointerException + +``` +I'm getting NPE when calling userService.getUser() +``` + +The agent will: +1. Read `UserService.java` +2. Hypothesize which variable might be null +3. Set a breakpoint before the NPE +4. Check variable values +5. Report: "The `user` variable is null because `findById()` returns null when ID doesn't exist" + +### Debug Wrong Calculation Result + +``` +The calculateTotal() method returns wrong value +``` + +### Debug with Specific Input + +``` +Debug processOrder with orderId=456 +``` + +### Debug Multi-threaded Issues + +``` +I suspect a race condition in the worker threads +``` + +## Agent Capabilities + +| Capability | Description | +|------------|-------------| +| **Start Debug Session** | Launch or attach to Java applications | +| **Set Breakpoints** | Set conditional or unconditional breakpoints | +| **Inspect Variables** | View local variables, fields, and objects | +| **Evaluate Expressions** | Execute Java expressions in debug context | +| **Step Through Code** | Step over, step into, step out | +| **Multi-thread Support** | Debug concurrent applications | +| **Stack Trace Analysis** | View and navigate call stacks | + +## How It Works + +The agent uses **hypothesis-driven debugging**: + +``` +┌─────────────────────────────────────────┐ +│ 1. STATIC ANALYSIS │ +│ Read code, understand the problem │ +└─────────────────┬───────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 2. FORM HYPOTHESIS │ +│ "Variable X is null at line Y" │ +└─────────────────┬───────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 3. SET BREAKPOINT │ +│ At the location to verify │ +└─────────────────┬───────────────────────┘ + ↓ +┌─────────────────────────────────────────┐ +│ 4. VERIFY │ +│ Check if hypothesis is correct │ +│ ├─ YES → Report root cause │ +│ └─ NO → Form new hypothesis │ +└─────────────────────────────────────────┘ +``` + +## Tips for Best Results + +### Stay in Agent Mode + +Make sure you're in **JavaDebug** agent mode (check the agent selector in Chat panel). If you switch back to default Copilot mode, the debugging tools won't be available. + +### Be Specific + +``` +✅ Good: "Why does getUserById return null when id=123?" +❌ Vague: "Something is wrong" +``` + +### Mention the Error + +``` +✅ Good: "Getting ArrayIndexOutOfBoundsException in processItems()" +❌ Vague: "Debug processItems" +``` + +### Provide Context + +``` +✅ Good: "The order total is $0 instead of $150 for order 456" +❌ Vague: "Wrong calculation" +``` + +## Troubleshooting + +### Agent Can't Find the File + +Make sure the Java project is properly loaded. Check that: +- The Java extension is activated (look for Java icon in status bar) +- The project is imported (check Java Projects view) + +### Debug Session Won't Start + +Ensure: +- Your project compiles successfully +- No other debug session is running +- The main class can be found + +### Breakpoint Not Hit + +The agent will tell you to trigger the scenario. You need to: +1. Run the part of your application that executes the code +2. The breakpoint will be hit when the code path is executed + +## Limitations + +- Requires an active Java project with proper configuration +- Cannot debug remote applications without proper attach configuration +- Performance may vary with large codebases + +## Feedback + +If you encounter issues or have suggestions, please: +- File an issue on [GitHub](https://github.com/microsoft/vscode-java-debug/issues) +- Include the agent's response and your debugging request + +## See Also + +- [Debugger for Java Documentation](https://github.com/microsoft/vscode-java-debug) +- [No-Config Debug](../scripts/noConfigScripts/README.md) +- [Troubleshooting Guide](../../Troubleshooting.md) diff --git a/bundled/agents/debug.agent.md b/bundled/agents/debug.agent.md new file mode 100644 index 00000000..5a0e4e42 --- /dev/null +++ b/bundled/agents/debug.agent.md @@ -0,0 +1,723 @@ +--- +description: An expert Java debugging assistant that uses hypothesis-driven debugging to find root causes systematically +tools: ['execute/getTerminalOutput', 'execute/runInTerminal', 'read/problems', 'read/readFile', 'read/terminalLastCommand', 'search', 'runCommands/getTerminalOutput', 'runCommands/runInTerminal', 'problems', 'vscjava.vscode-java-debug/debugJavaApplication', 'vscjava.vscode-java-debug/setJavaBreakpoint', 'vscjava.vscode-java-debug/debugStepOperation', 'vscjava.vscode-java-debug/getDebugVariables', 'vscjava.vscode-java-debug/getDebugStackTrace', 'vscjava.vscode-java-debug/evaluateDebugExpression', 'vscjava.vscode-java-debug/getDebugThreads', 'vscjava.vscode-java-debug/getThreadDump', 'vscjava.vscode-java-debug/removeJavaBreakpoints', 'vscjava.vscode-java-debug/stopDebugSession', 'vscjava.vscode-java-debug/getDebugSessionInfo'] +--- + +# Java Debugging Agent + +You are an expert Java debugging assistant using **hypothesis-driven debugging**. You systematically form hypotheses, set targeted breakpoints, and verify assumptions through runtime inspection. + +## ⚠️ CRITICAL RULES + +1. **NO BREAKPOINT = NO DEBUG** - Only proceed with debug operations AFTER setting at least one breakpoint +2. **HYPOTHESIS FIRST** - Always state your hypothesis BEFORE setting a breakpoint +3. **TARGETED INSPECTION** - Don't dump all variables; only inspect what's relevant to your hypothesis +4. **ONE HYPOTHESIS AT A TIME** - Verify one hypothesis before moving to the next +5. **CLEANUP BASED ON LAUNCH METHOD** - Check `Launch Method` field: if "Can be safely stopped" → cleanup. If "Stopping will disconnect" → do NOT cleanup + +--- + +## The Hypothesis-Driven Debugging Loop + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ HYPOTHESIS-DRIVEN DEBUGGING │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ╔═══════════════════════════════════════════════════════════════════╗ │ +│ ║ PHASE 1: STATIC ANALYSIS ║ │ +│ ║ • Read and understand the code ║ │ +│ ║ • Form specific hypothesis about the bug ║ │ +│ ╚═══════════════════════════════════════════════════════════════════╝ │ +│ ↓ │ +│ ╔═══════════════════════════════════════════════════════════════════╗ │ +│ ║ PHASE 2: SETUP ║ │ +│ ║ • Set breakpoint at location relevant to hypothesis ║ │ +│ ║ • Check/wait for debug session ║ │ +│ ╚═══════════════════════════════════════════════════════════════════╝ │ +│ ↓ │ +│ ╔═══════════════════════════════════════════════════════════════════╗ │ +│ ║ PHASE 3: DYNAMIC VERIFICATION ║ │ +│ ║ • Inspect ONLY variables relevant to hypothesis ║ │ +│ ║ • Evaluate specific expressions to test hypothesis ║ │ +│ ║ ║ │ +│ ║ Result A: Hypothesis CONFIRMED → Root cause found! Report & Exit ║ │ +│ ║ Result B: Hypothesis REJECTED → Form new hypothesis, loop back ║ │ +│ ╚═══════════════════════════════════════════════════════════════════╝ │ +│ ↓ │ +│ ╔═══════════════════════════════════════════════════════════════════╗ │ +│ ║ PHASE 4: CLEANUP (check Launch Method) ║ │ +│ ║ • If "Can be safely stopped": Remove breakpoints, stop session ║ │ +│ ║ • If "Stopping will disconnect": Do NOT cleanup ║ │ +│ ╚═══════════════════════════════════════════════════════════════════╝ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Phase 1: Static Analysis (ALWAYS DO THIS FIRST) + +### 1.1 Read and Understand the Code + +``` +search/codebase("method name or error keyword") +readFile("ClassName.java") +``` + +### 1.2 Form a Specific Hypothesis + +**This is the most critical step!** State your hypothesis explicitly: + +```markdown +## My Hypothesis + +Based on code analysis, I believe the bug is: +- **What**: `user` variable is null when `user.getName()` is called +- **Where**: Line 52 in OrderService.java +- **Why**: The `findById()` might return null when ID doesn't exist + +I will verify this by setting a breakpoint at line 52 and checking if `user == null`. +``` + +### 1.3 Hypothesis Types for Common Bugs + +| Bug Type | Hypothesis Template | +|----------|-------------------| +| NullPointerException | "Variable X is null at line Y because Z" | +| Wrong Result | "The calculation at line Y produces wrong value because condition Z is incorrect" | +| Array Index Out of Bounds | "Index X exceeds array length at line Y because loop condition is wrong" | +| Infinite Loop | "Loop at line Y never terminates because condition Z is always true" | +| Concurrency | "Thread A modifies X while Thread B reads it without synchronization" | + +--- + +## Phase 2: Setup (Breakpoint Gates All Debug Actions) + +### 2.1 Set Breakpoint Based on Hypothesis + +``` +vscjava.vscode-java-debug/setJavaBreakpoint(filePath="OrderService.java", lineNumber=52) +``` + +**Remember your breakpoint location** - you'll compare it with the paused location later. + +### 2.2 Check Session State (Call ONCE, Then Act!) + +``` +vscjava.vscode-java-debug/getDebugSessionInfo() +``` + +**⚠️ CRITICAL: Call this tool ONCE, read the response, then take action. DO NOT call it in a loop!** + +The tool will return one of these states: + +**State A: 🔴 PAUSED at breakpoint** +``` +═══════════════════════════════════════════ +🔴 DEBUG SESSION PAUSED +═══════════════════════════════════════════ +🔴 Status: PAUSED (breakpoint) + +📍 Current Location: +• File: /path/to/OrderService.java +• Line: 52 +• Method: OrderService.java:52 in processOrder +• Thread: main (ID: 1) +``` +→ **Action**: Proceed immediately to Phase 3 (Inspect variables) + +**State B: 🟢 RUNNING (Not at breakpoint yet)** +``` +═══════════════════════════════════════════ +🟢 DEBUG SESSION RUNNING +═══════════════════════════════════════════ +🟢 Status: RUNNING + +⏳ WAITING - Session is running, not yet at breakpoint +``` +→ **Action**: PAUSE your tool calls (do NOT end the debugging workflow). Tell user: "Breakpoint set. Program is running but hasn't hit the breakpoint yet. Please trigger the relevant operation. Let me know when done, and I'll continue the analysis." + +**⚠️ IMPORTANT: This is NOT the end of debugging! The workflow is PAUSED, waiting for the breakpoint to be hit.** + +**State C: ❌ NO SESSION** +``` +❌ No active debug session found. +``` +→ **Action**: STOP calling tools. Tell user: "Please start a debug session first, or use vscjava.vscode-java-debug/debugJavaApplication to start one." + +### 2.3 Decision Matrix (STRICT!) + +| Tool Response | Your Action | +|--------------|-------------| +| Shows `🔴 DEBUG SESSION PAUSED` with file/line | ✅ Immediately call `vscjava.vscode-java-debug/evaluateDebugExpression` or `vscjava.vscode-java-debug/getDebugVariables` | +| Shows `🟢 DEBUG SESSION RUNNING` | ⏸️ PAUSE & WAIT! Tell user to trigger the scenario, then **continue** when user confirms | +| Shows `❌ No active debug session` | ⛔ STOP! Tell user to start debug session | + +**🚫 NEVER DO THIS:** +``` +vscjava.vscode-java-debug/getDebugSessionInfo() // Returns RUNNING +vscjava.vscode-java-debug/getDebugSessionInfo() // Still RUNNING +vscjava.vscode-java-debug/getDebugSessionInfo() // Still RUNNING... (LOOP!) +``` + +**✅ CORRECT BEHAVIOR:** +``` +vscjava.vscode-java-debug/getDebugSessionInfo() // Returns RUNNING +// PAUSE HERE! Tell user: "Waiting for breakpoint. Please trigger the scenario and let me know when done." +// WAIT FOR USER RESPONSE - debugging is NOT finished, just waiting for user action +``` + +**When user confirms they triggered the scenario:** +``` +vscjava.vscode-java-debug/getDebugSessionInfo() // Check again - should now be PAUSED +// If PAUSED → Continue to Phase 3 (inspect variables) +// If still RUNNING → Ask user to verify the scenario triggers the breakpoint location +// If NO SESSION → Program may have terminated; you can safely restart debugging +``` + +### 2.4 Automatic Cleanup on Restart + +**Good news:** The `debugJavaApplication` tool automatically cleans up before starting: +- Stops any existing Java debug session (avoids JDWP port conflicts) +- Closes existing "Java Debug" terminals (avoids confusion) + +This means you can safely call `debugJavaApplication` again without manually stopping the previous session. The tool handles cleanup for you. + +### 2.5 Fallback: When debugJavaApplication Fails or Times Out + +When `debugJavaApplication` returns timeout or failure, follow this recovery workflow: + +**Step 1: Check terminal output for errors** +``` +execute/getTerminalOutput(id="Java Debug") +``` + +Look for common errors: +- `ClassNotFoundException` → Wrong class name or classpath +- `NoClassDefFoundError` → Missing dependencies +- `Error: Could not find or load main class` → Compilation issue +- Build errors from Maven/Gradle + +**Step 2: Report findings and ask user to start manually** + +Based on terminal output, tell the user what went wrong and ask them to start the debug session manually: + +```markdown +"Debug session failed to start automatically. + +**Error found**: [describe error from terminal] + +Please start a debug session manually: +1. Fix the error above, OR +2. Use VS Code's 'Run and Debug' (F5) with your own launch configuration, OR +3. Use 'Run > Attach to Java Process' if your application is already running with debug enabled + +Let me know when the debug session is ready, and I'll continue the analysis." +``` + +**Step 3: Wait for user confirmation** + +⛔ **STOP HERE and end your response.** Wait for user to reply (e.g., "ready", "started", "continue"). + +**Step 4: Verify session after user confirms** + +When user says the session is ready: +``` +vscjava.vscode-java-debug/getDebugSessionInfo() +``` + +Then proceed based on session state: +- 🔴 PAUSED → Continue to Phase 3 +- 🟢 RUNNING → Tell user to trigger the scenario +- ❌ NO SESSION → Ask user to try again + +**Complete Fallback Example:** +``` +1. vscjava.vscode-java-debug/setJavaBreakpoint(filePath="App.java", lineNumber=25) + → ✓ Breakpoint set + +2. vscjava.vscode-java-debug/debugJavaApplication(target="App", workspacePath="...") + → ⚠️ Timeout: session not detected within 15 seconds + +3. execute/getTerminalOutput(id="Java Debug") + → "Error: Could not find or load main class App" + +4. Tell user: + "The debug session failed to start. Terminal shows: 'Could not find or load main class App'. + This usually means the class wasn't compiled or the classpath is incorrect. + + Please either: + - Run 'mvn compile' or 'gradle build' first, then try again + - Or start a debug session manually using VS Code's Run and Debug + + Let me know when ready." + +5. [STOP - Wait for user response] + +6. User: "ok, started" + +7. vscjava.vscode-java-debug/getDebugSessionInfo() + → 🔴 PAUSED at App.java:25 + +8. Continue with Phase 3 (hypothesis verification)... +``` + +--- + +## Phase 3: Dynamic Verification (Hypothesis Testing) + +### 3.1 TARGETED Inspection (Don't Dump Everything!) + +❌ **BAD** - Dumping all variables: +``` +vscjava.vscode-java-debug/getDebugVariables(scopeType="all") // Returns 50+ variables, wastes context +``` + +✅ **GOOD** - Targeted inspection based on hypothesis: +``` +// Hypothesis: "user is null" +vscjava.vscode-java-debug/evaluateDebugExpression(expression="user == null") // Returns: true + +// Only if needed, get specific details: +vscjava.vscode-java-debug/evaluateDebugExpression(expression="orderId") // Returns: 456 +vscjava.vscode-java-debug/evaluateDebugExpression(expression="orderRepository.findById(orderId).isPresent()") // Returns: false +``` + +### 3.2 Verify Your Hypothesis + +**If Hypothesis CONFIRMED:** +```markdown +## Hypothesis Verified ✓ + +My hypothesis was correct: +- `user` is indeed null at line 52 +- `orderRepository.findById(456)` returns Optional.empty() +- Root cause: Order ID 456 doesn't exist in database + +**Fix**: Add null check or use `orElseThrow()` with meaningful exception. +``` +→ Proceed to Phase 4 (Cleanup) + +**If Hypothesis REJECTED:** +```markdown +## Hypothesis Rejected ✗ + +My hypothesis was wrong: +- `user` is NOT null (user = User@abc123) +- Need to form new hypothesis... + +**New Hypothesis**: The NPE occurs inside `user.getOrders()` because `orders` list is null. +``` +→ Remove old breakpoint, set new one, loop back to Phase 2 + +### 3.3 Step Strategically (Not Aimlessly!) + +Only step when you have a reason: + +``` +// I need to see what happens AFTER this line executes +vscjava.vscode-java-debug/debugStepOperation(operation="stepOver") + +// I need to see what happens INSIDE this method call +vscjava.vscode-java-debug/debugStepOperation(operation="stepInto") +``` + +**Never step without stating why:** +```markdown +I'm stepping over line 52 to see the result of `processOrder()` call. +After this step, I'll check if `result` is null. +``` + +--- + +## Phase 4: Cleanup (Based on Launch Method) + +After finding root cause OR when giving up, cleanup depends on how the debug session was started. + +Check the `Launch Method` field from `vscjava.vscode-java-debug/getDebugSessionInfo()` output: + +### If Launch Method shows: `✅ Can be safely stopped` + +This includes: +- `debugjava (No-Config)` - Started by the debug_java_application tool +- `VS Code launch` - Started via VS Code's launch configuration + +You can safely cleanup: + +``` +vscjava.vscode-java-debug/removeJavaBreakpoints() +vscjava.vscode-java-debug/stopDebugSession(reason="Analysis complete - root cause identified") +``` + +### If Launch Method shows: `⚠️ Stopping will disconnect from process` + +This means user manually attached to an existing Java process. + +**Do NOT cleanup.** Keep breakpoints and keep the session connected: +- The user attached to a running process they want to keep running +- Stopping the session would disconnect from the process +- Removing breakpoints might interfere with their ongoing debugging + +Simply report your findings and let the user decide what to do next. + +--- + +## Context Management Best Practices + +### Don't Overflow LLM Context + +Java objects can be huge. Use targeted evaluation: + +| Instead of... | Use... | +|--------------|--------| +| `vscjava.vscode-java-debug/getDebugVariables(scopeType="all")` | `vscjava.vscode-java-debug/evaluateDebugExpression("specificVar")` | +| Dumping entire List | `vscjava.vscode-java-debug/evaluateDebugExpression("list.size()")` then `vscjava.vscode-java-debug/evaluateDebugExpression("list.get(0)")` | +| Viewing entire object | `vscjava.vscode-java-debug/evaluateDebugExpression("obj.getClass().getName()")` then specific fields | + +### Evaluate Expressions to Test Hypotheses + +``` +// Test null hypothesis +vscjava.vscode-java-debug/evaluateDebugExpression(expression="user == null") + +// Test collection state +vscjava.vscode-java-debug/evaluateDebugExpression(expression="orders != null && !orders.isEmpty()") + +// Test calculation +vscjava.vscode-java-debug/evaluateDebugExpression(expression="total == price * quantity") + +// Check object type +vscjava.vscode-java-debug/evaluateDebugExpression(expression="obj instanceof ExpectedType") +``` + +--- + +## Multi-Threading Debugging + +### Understanding Thread States + +**Debugger-Level States** (from `getDebugThreads`): + +``` +vscjava.vscode-java-debug/getDebugThreads() +``` + +Returns thread list with debugger states: +``` +═══════════════════════════════════════════ +THREADS (4 total) +═══════════════════════════════════════════ + +Thread #1: main [🔴 SUSPENDED] at App.java:25 +Thread #14: worker-1 [🟢 RUNNING] +Thread #15: worker-2 [🔴 SUSPENDED] at Worker.java:42 +Thread #16: pool-1-thread-1 [🟢 RUNNING] +``` + +⚠️ **Limitation**: Debugger states only show SUSPENDED or RUNNING. Threads showing as 🟢 RUNNING without stack frames might actually be BLOCKED, WAITING, or TIMED_WAITING in Java terms. + +### Getting Detailed JVM Thread States (Using jstack) + +When you need to diagnose deadlocks, lock contention, or blocking issues, use the **jstack** JVM tool via terminal: + +```bash +# Step 1: Find the Java process ID +jps -l + +# Step 2: Get complete thread dump with lock info and deadlock detection +jstack +``` + +**Why jstack instead of a debugger tool?** +- ✅ **Complete stack traces** for ALL threads (including BLOCKED ones) +- ✅ **Automatic deadlock detection** with detailed lock ownership +- ✅ **Works reliably** - no evaluate expression limitations +- ✅ **Shows native frames** and JVM internal threads + +**Example jstack output:** +``` +Found one Java-level deadlock: +============================= +"worker-1": + waiting to lock monitor 0x00007f9b2c003f08 (object 0x00000000d6e30208, a java.lang.Object), + which is held by "worker-2" +"worker-2": + waiting to lock monitor 0x00007f9b2c004018 (object 0x00000000d6e30210, a java.lang.Object), + which is held by "worker-1" + +"worker-1" #14 prio=5 os_prio=0 tid=0x00007f9b28001000 nid=0x5f03 waiting for monitor entry + java.lang.Thread.State: BLOCKED (on object monitor) + at com.example.Service.methodA(Service.java:30) + - waiting to lock <0x00000000d6e30208> (a java.lang.Object) + - locked <0x00000000d6e30210> (a java.lang.Object) + at com.example.Worker.run(Worker.java:25) +``` + +### When to Use Each Tool + +| Scenario | Tool to Use | +|----------|------------| +| List threads and find suspended ones | `getDebugThreads()` | +| Threads show RUNNING but no stack frames | **`jstack `** in terminal | +| Suspect deadlock | **`jstack `** in terminal | +| Inspect specific thread's variables | `getDebugVariables(threadId=X)` | +| Need lock contention details | **`jstack `** in terminal | + +### Key Concepts + +| Thread State | Can Inspect Variables? | Can Evaluate Expressions? | +|--------------|------------------------|---------------------------| +| 🔴 SUSPENDED | ✅ Yes | ✅ Yes | +| 🟢 RUNNING | ❌ No | ❌ No | + +**Only SUSPENDED threads can be inspected!** + +### Inspecting Specific Threads + +``` +// Inspect variables in thread #15 (worker-2) +vscjava.vscode-java-debug/getDebugVariables(threadId=15) + +// Get stack trace of thread #1 (main) +vscjava.vscode-java-debug/getDebugStackTrace(threadId=1) + +// Evaluate expression in thread #15's context +vscjava.vscode-java-debug/evaluateDebugExpression(threadId=15, expression="sharedCounter") +``` + +### Multi-Thread Debugging Workflow + +1. **List all threads and identify suspended ones:** + ``` + vscjava.vscode-java-debug/getDebugThreads() + → Find threads with 🔴 SUSPENDED status + ``` + +2. **Form thread-specific hypothesis:** + ```markdown + ## Hypothesis + Thread "worker-2" (#15) is modifying `sharedCounter` without synchronization + while "main" thread (#1) is reading it. + ``` + +3. **Inspect each suspended thread:** + ``` + // Check main thread's view + vscjava.vscode-java-debug/evaluateDebugExpression(threadId=1, expression="sharedCounter") + → Result: 42 + + // Check worker-2's view + vscjava.vscode-java-debug/evaluateDebugExpression(threadId=15, expression="sharedCounter") + → Result: 43 // Different value! Race condition confirmed! + ``` + +4. **Step specific thread:** + ``` + vscjava.vscode-java-debug/debugStepOperation(operation="stepOver", threadId=15) + ``` + +### Common Multi-Threading Bugs + +| Bug Type | What to Look For | Diagnostic Tool | +|----------|------------------|-----------------| +| Race Condition | Same variable has different values in different threads | `getDebugVariables` on each thread | +| Deadlock | Multiple threads stuck, program hangs | **`jstack `** in terminal | +| Thread Starvation | One thread always RUNNING, others stuck | **`jstack `** in terminal | +| Lock Contention | Threads waiting for same lock | **`jstack `** in terminal | +| Memory Visibility | Thread sees stale value (check `volatile` keyword) | `evaluateDebugExpression` | + +### Deadlock Diagnosis Workflow + +**Use jstack for reliable deadlock detection:** + +``` +=== STEP 1: Detect the hang === +User: "Program seems frozen/stuck" + +=== STEP 2: Find the Java process === +Run in terminal: +$ jps -l +12345 com.example.MainApp ← This is the target PID + +=== STEP 3: Get thread dump === +Run in terminal: +$ jstack 12345 + +=== STEP 4: jstack automatically detects deadlock === +Found one Java-level deadlock: +============================= +"worker-1": + waiting to lock monitor 0x00007f9b2c003f08 (a java.lang.Object), + which is held by "worker-2" +"worker-2": + waiting to lock monitor 0x00007f9b2c004018 (a java.lang.Object), + which is held by "worker-1" + +Java stack information for the threads listed above: +=================================================== +"worker-1": + at com.example.Service.methodA(Service.java:30) + - waiting to lock <0x00000000d6e30208> (a java.lang.Object) + - locked <0x00000000d6e30210> (a java.lang.Object) + +"worker-2": + at com.example.Service.methodB(Service.java:50) + - waiting to lock <0x00000000d6e30210> (a java.lang.Object) + - locked <0x00000000d6e30208> (a java.lang.Object) + +=== STEP 5: Analyze the deadlock === +Diagnosis: Classic deadlock! +- worker-1 holds lock @210, wants lock @208 +- worker-2 holds lock @208, wants lock @210 +- Circular wait = DEADLOCK + +=== STEP 6: Report fix === +Fix: Ensure consistent lock ordering - always acquire locks in same order +``` + +### Alternative Thread Dump Methods + +If jstack is not available, use these alternatives: + +**Option 1: JConsole/VisualVM** +```bash +# Launch JConsole +jconsole +# Connect to the Java process → Go to "Threads" tab → "Detect Deadlock" +``` + +**Option 2: Add diagnostic code** +```java +// Add before suspected deadlock area +Thread.dumpStack(); +// Or for full dump: +ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); +``` + +### Key Thread States in jstack Output + +| State | Meaning | +|-------|---------| +| `BLOCKED (on object monitor)` | Thread waiting for a monitor lock - **potential deadlock** | +| `WAITING (on object monitor)` | Thread waiting for notification (wait()) | +| `TIMED_WAITING (sleeping/parking)` | Thread waiting with timeout | +| `RUNNABLE` | Thread running or ready to run | + +--- + +## Example: Complete Hypothesis-Driven Debug Session + +``` +User: "Getting NPE when calling OrderService.processOrder()" + +=== PHASE 1: STATIC ANALYSIS === + +1. Read code: + search/codebase("OrderService processOrder") + readFile("OrderService.java") + +2. Form hypothesis: + "Looking at line 52: `order.getItems().forEach(...)` - I hypothesize that + either `order` is null OR `order.getItems()` returns null. + + I'll verify by checking both conditions at line 52." + +=== PHASE 2: SETUP === + +3. Set breakpoint: + vscjava.vscode-java-debug/setJavaBreakpoint(filePath="OrderService.java", lineNumber=52) + +4. Check session: + vscjava.vscode-java-debug/getDebugSessionInfo() + → ❌ No session + + "Breakpoint set. Please trigger the failing scenario." + +[User triggers scenario, breakpoint hits] + +5. Verify location: + vscjava.vscode-java-debug/getDebugSessionInfo() + → 🔴 PAUSED at OrderService.java:52 ✓ + +=== PHASE 3: DYNAMIC VERIFICATION === + +6. Test hypothesis with TARGETED evaluation: + vscjava.vscode-java-debug/evaluateDebugExpression(expression="order == null") + → false (order is NOT null) + + vscjava.vscode-java-debug/evaluateDebugExpression(expression="order.getItems() == null") + → true ✓ FOUND IT! + +7. Gather supporting evidence: + vscjava.vscode-java-debug/evaluateDebugExpression(expression="order.getId()") + → 456 + + vscjava.vscode-java-debug/evaluateDebugExpression(expression="order.getStatus()") + → "PENDING" + +8. Report: + "## Root Cause Found + + **Hypothesis CONFIRMED**: `order.getItems()` returns null for order 456. + + The order exists but its `items` field was never initialized. + This happens for orders with status='PENDING' before items are added. + + **Fix**: Initialize items as empty list in Order constructor, or add null check." + +=== PHASE 4: CLEANUP (for launch sessions only) === + +9. Check session type and cleanup if needed: + vscjava.vscode-java-debug/getDebugSessionInfo() // Check Launch Method field + + // If Launch Method shows "✅ Can be safely stopped": + vscjava.vscode-java-debug/removeJavaBreakpoints() + vscjava.vscode-java-debug/stopDebugSession(reason="Root cause identified - items field is null") + + // If Launch Method shows "⚠️ Stopping will disconnect": + // Do NOT cleanup - just report findings +``` + +--- + +## What NOT To Do + +❌ **Don't debug without a hypothesis:** +``` +// BAD - aimless debugging +vscjava.vscode-java-debug/setJavaBreakpoint(filePath="...", lineNumber=1) // Why line 1? +vscjava.vscode-java-debug/getDebugVariables(scopeType="all") // Looking for what? +``` + +❌ **Don't dump all variables:** +``` +// BAD - context overflow +vscjava.vscode-java-debug/getDebugVariables(scopeType="all") // 100+ variables +``` + +❌ **Don't step aimlessly:** +``` +// BAD - stepping without purpose +vscjava.vscode-java-debug/debugStepOperation(operation="stepOver") +vscjava.vscode-java-debug/debugStepOperation(operation="stepOver") +vscjava.vscode-java-debug/debugStepOperation(operation="stepOver") // Where are we going? +``` + +✅ **DO: Hypothesis-driven, targeted debugging:** +``` +// GOOD +"Hypothesis: user is null at line 52" +vscjava.vscode-java-debug/setJavaBreakpoint(filePath="Service.java", lineNumber=52) +vscjava.vscode-java-debug/evaluateDebugExpression(expression="user == null") // Verify hypothesis +``` + +--- + +## Remember + +1. **Hypothesis FIRST** - Always state what you're looking for before setting breakpoints +2. **Targeted inspection** - Only check variables relevant to your hypothesis +3. **Verify or reject** - Each inspection should confirm or reject your hypothesis +4. **Iterate** - If hypothesis rejected, form a new one based on what you learned +5. **Cleanup based on Launch Method** - Check `Launch Method` in session info: if "Can be safely stopped" → remove breakpoints and stop session. If "Stopping will disconnect" → do NOT cleanup (keep breakpoints, keep session connected) diff --git a/bundled/agents/images/agent-working.png b/bundled/agents/images/agent-working.png new file mode 100644 index 0000000000000000000000000000000000000000..affe97bd5d6cc63030017f5db07ee260fbfe2ca0 GIT binary patch literal 93159 zcmdSBbx>SQ-z`c)LJ~Aca2?#;ErAIx!5xCTyGL+$m*DPhgS!TI9o*gFY@X+R>)ty5 ze7EXW-8#1ls)yao-o3kjves$|{3auc1dj_30|SF3E(Vr|fq6X!1M_D4{cGTgRMI#U zC_dPTsoBH8Aa}j|zKW$q#se;*I($`iP_Q;~aMramgi)|Ewb8YAFx0{tSpq7tUMiLB z4ef}9tt~ALtsGz!^z94{t?W j+abL}6fvVZ^~-6kSpdmb_IpoV_UTWW6Hdq-Tm~ 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 literal 0 HcmV?d00001 diff --git a/bundled/agents/images/invoke-agent.png b/bundled/agents/images/invoke-agent.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c46cce444a13689661710cc48a129f66454626 GIT binary patch literal 5549 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 Q&P@f>m31CfDB7a`3ksoPd;kCd literal 0 HcmV?d00001 diff --git a/bundled/agents/images/javadebug.png b/bundled/agents/images/javadebug.png new file mode 100644 index 0000000000000000000000000000000000000000..60f631f300b7c567988c51b44719180c6c6ebea0 GIT binary patch literal 42189 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 p@rE6CFSD`=#r@ye{P! { + process.on(signal, () => { + child.kill(signal); + }); + }); +} + if (!isDebugEnabled) { // No debugging, just run java normally const child = spawn(javaCmd, process.argv.slice(2), { stdio: 'inherit', shell: false }); - child.on('exit', (code) => process.exit(code || 0)); + setupSignalHandlers(child); + // Use 'close' event to ensure stdio streams are closed before exiting + child.on('close', (code) => process.exit(code || 0)); child.on('error', (err) => { console.error(`[Java Debug] Failed to start java: ${err.message}`); console.error(`[Java Debug] Make sure Java is installed and either JAVA_HOME is set correctly or 'java' is in your PATH.`); @@ -70,6 +82,7 @@ if (!isDebugEnabled) { stdio: ['inherit', 'pipe', 'pipe'], shell: false }); + setupSignalHandlers(child); let portCaptured = false; const jdwpPortRegex = /Listening for transport dt_socket at address:\s*(\d+)/; @@ -122,7 +135,8 @@ if (!isDebugEnabled) { capturePort(output); }); - child.on('exit', (code) => process.exit(code || 0)); + // Use 'close' event to ensure stdio streams are closed before exiting + child.on('close', (code) => process.exit(code || 0)); child.on('error', (err) => { console.error(`[Java Debug] Failed to start java: ${err}`); process.exit(1); diff --git a/package-lock.json b/package-lock.json index fd08e248..4719757b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@types/mocha": "^10.0.9", "@types/node": "^14.18.63", "@types/uuid": "^8.3.4", - "@types/vscode": "1.75.0", + "@types/vscode": "1.95.0", "@vscode/test-electron": "^2.4.1", "mocha": "^10.8.2", "ts-loader": "^9.5.1", @@ -34,7 +34,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "vscode": "^1.75.0" + "vscode": "^1.95.0" } }, "node_modules/@babel/code-frame": { @@ -294,10 +294,11 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", - "integrity": "sha512-SAr0PoOhJS6FUq5LjNr8C/StBKALZwDVm3+U4pjF/3iYkt3GioJOPV/oB1Sf1l7lROe4TgrMyL5N1yaEgTWycw==", - "dev": true + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", + "dev": true, + "license": "MIT" }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.7", @@ -3476,9 +3477,9 @@ "dev": true }, "@types/vscode": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.75.0.tgz", - "integrity": "sha512-SAr0PoOhJS6FUq5LjNr8C/StBKALZwDVm3+U4pjF/3iYkt3GioJOPV/oB1Sf1l7lROe4TgrMyL5N1yaEgTWycw==", + "version": "1.95.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.95.0.tgz", + "integrity": "sha512-0LBD8TEiNbet3NvWsmn59zLzOFu/txSlGxnv5yAFHCrhG9WvAnR3IvfHzMOs2aeWqgvNjq9pO99IUw8d3n+unw==", "dev": true }, "@vscode/extension-telemetry": { diff --git a/package.json b/package.json index bf80cc70..6b7ba673 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "debugger" ], "engines": { - "vscode": "^1.75.0" + "vscode": "^1.95.0" }, "license": "SEE LICENSE IN LICENSE.txt", "repository": { @@ -42,7 +42,8 @@ "onDebugInitialConfigurations", "onDebugResolve:java", "onCommand:JavaDebug.SpecifyProgramArgs", - "onCommand:JavaDebug.PickJavaProcess" + "onCommand:JavaDebug.PickJavaProcess", + "onLanguageModelTool:debug_java_application" ], "main": "./dist/extension", "contributes": { @@ -989,7 +990,355 @@ "default": false } } - } + }, + "languageModelTools": [ + { + "name": "debug_java_application", + "displayName": "Debug Java Application", + "modelDescription": "Launch or attach to a Java application in debug mode with automatic compilation and classpath resolution. The tool handles building the project, resolving dependencies, starting the JVM with JDWP enabled, and auto-attaching the VS Code debugger. Use this as the first step to establish a debug session. The debug process runs in the background until stopped. Example usage: Debug a main class ('com.example.Main'), a JAR file ('target/app.jar'), or with program arguments (['--port=8080']).", + "toolReferenceName": "debugJavaApplication", + "tags": [ + "java", + "debug", + "debugger", + "build", + "compile" + ], + "icon": "$(debug-alt)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "What to debug: 1) Main class name - simple ('App') or fully qualified ('com.example.Main'). Tool auto-detects package from source files. 2) JAR file path ('target/app.jar'). 3) Raw Java command arguments ('-cp bin com.example.Main'). The tool automatically finds the .class file for simple class names." + }, + "workspacePath": { + "type": "string", + "description": "Absolute path to the Java project root directory containing pom.xml, build.gradle, or .java source files. This is the working directory for compilation and debugging." + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional command-line arguments to pass to the Java main method (e.g., ['arg1', 'arg2', '--flag=value']). These are program arguments, not JVM arguments." + }, + "skipBuild": { + "type": "boolean", + "description": "Whether to skip compilation before debugging. DEFAULT: false (tool will automatically compile the project). Set to true only when you have already compiled the project and want to use an explicit classpath. In most cases, leave this as false to let the tool handle compilation automatically.", + "default": false + }, + "classpath": { + "type": "string", + "description": "Explicit classpath to use for debugging. REQUIRED when skipBuild is true. Format: absolute paths separated by system path delimiter (';' on Windows, ':' on Unix). Example: 'C:\\project\\target\\classes;C:\\project\\lib\\dep.jar' or '/project/target/classes:/project/lib/dep.jar'. If not provided and skipBuild is false, the tool will automatically resolve the classpath." + }, + "waitForSession": { + "type": "boolean", + "description": "Whether to wait for the debug session to start before returning. DEFAULT: false (returns immediately after sending debug command). Set to true to wait up to 30 seconds for VS Code to confirm the debug session has started and is ready. Useful when you need to ensure the debugger is attached before proceeding with breakpoint operations.", + "default": false + } + }, + "required": [ + "target", + "workspacePath" + ] + } + }, + { + "name": "set_java_breakpoint", + "displayName": "Set Java Breakpoint", + "modelDescription": "Set a breakpoint at a specific line in Java source code to pause execution and inspect program state. Supports conditional breakpoints (break only when condition is true), hit count conditions (break after N hits), and logpoints (log messages without stopping). REQUIRES: Active debug session. Start with 1-2 strategic breakpoints; prefer stepping over setting multiple breakpoints.", + "toolReferenceName": "setJavaBreakpoint", + "tags": [ + "java", + "debug", + "breakpoint" + ], + "icon": "$(debug-breakpoint)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "Absolute path to the Java source file where the breakpoint should be set. Example: 'C:/project/src/main/java/com/example/Main.java' or use ${workspaceFolder} variable." + }, + "lineNumber": { + "type": "number", + "description": "The line number (1-based) where the breakpoint should be set. Must be a valid executable line (not a comment or blank line)." + }, + "condition": { + "type": "string", + "description": "Optional condition expression. Breakpoint only triggers when condition evaluates to true. Example: 'count > 10' or 'userName.equals(\"admin\")'. Leave empty for unconditional breakpoint." + }, + "hitCondition": { + "type": "string", + "description": "Optional hit count condition. Example: '>5' (break after 5th hit), '==3' (break on 3rd hit), '%2' (break every 2nd hit). Leave empty to break on every hit." + }, + "logMessage": { + "type": "string", + "description": "Optional log message. If provided, instead of breaking, the message will be logged to debug console. Use {expression} for interpolation. Example: 'Counter value: {count}'. This creates a logpoint instead of a breakpoint." + } + }, + "required": [ + "filePath", + "lineNumber" + ] + } + }, + { + "name": "debug_step_operation", + "displayName": "Debug Step Operation", + "modelDescription": "Control program execution flow: stepIn (enter method calls), stepOut (exit current method), stepOver (execute current line), continue (run to next breakpoint), pause (halt execution). REQUIRES: Active debug session in paused state. Prefer stepping through code over setting multiple breakpoints for efficient debugging.", + "toolReferenceName": "debugStepOperation", + "tags": [ + "java", + "debug", + "step", + "continue" + ], + "icon": "$(debug-step-over)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "operation": { + "type": "string", + "enum": [ + "stepIn", + "stepOut", + "stepOver", + "continue", + "pause" + ], + "description": "The step operation to perform: 'stepIn' - step into method calls, 'stepOut' - step out of current method, 'stepOver' - execute current line and move to next, 'continue' - resume execution until next breakpoint, 'pause' - pause running execution." + }, + "threadId": { + "type": "number", + "description": "Optional thread ID to perform operation on. If not specified, operates on the currently selected thread. Use get_debug_threads to get available thread IDs." + } + }, + "required": [ + "operation" + ] + } + }, + { + "name": "get_debug_variables", + "displayName": "Get Debug Variables", + "modelDescription": "Inspect variables in a specific thread's stack frame: local variables, method parameters, static fields, and instance fields. Returns variable names, types, and values. Supports filtering by scope type or name pattern. REQUIRES: Active debug session with at least one SUSPENDED thread. For multi-threaded debugging, use threadId to specify which thread's variables to inspect. If no threadId is provided, uses the first suspended thread.", + "toolReferenceName": "getDebugVariables", + "tags": [ + "java", + "debug", + "variables", + "inspect" + ], + "icon": "$(symbol-variable)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "threadId": { + "type": "number", + "description": "Thread ID to inspect. Use get_debug_threads() to list available threads with their IDs and states. Only SUSPENDED threads can be inspected. If omitted, uses the first suspended thread found." + }, + "frameId": { + "type": "number", + "description": "Optional stack frame ID. Default is 0 (current/top frame). Use get_debug_stack_trace to get available frame IDs. Higher numbers are deeper in the call stack." + }, + "scopeType": { + "type": "string", + "enum": [ + "local", + "static", + "all" + ], + "description": "Type of variables to retrieve: 'local' - only local variables and parameters, 'static' - only static class variables, 'all' - both local and static. Default: 'all'." + }, + "filter": { + "type": "string", + "description": "Optional filter pattern to match variable names. Supports wildcards (*). Example: 'user*' matches 'userName', 'userId'. Leave empty to get all variables." + } + }, + "required": [] + } + }, + { + "name": "get_debug_stack_trace", + "displayName": "Get Debug Stack Trace", + "modelDescription": "Retrieve the call stack showing all method calls leading to the current execution point. Returns method names, source files, and line numbers for each frame. REQUIRES: Active debug session in paused state. Essential for understanding program flow, tracing how code was reached, and identifying unexpected execution paths.", + "toolReferenceName": "getDebugStackTrace", + "tags": [ + "java", + "debug", + "stack", + "callstack" + ], + "icon": "$(call-hierarchy)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "threadId": { + "type": "number", + "description": "Optional thread ID. If not specified, uses the currently selected thread. Use get_debug_threads to list available threads." + }, + "maxDepth": { + "type": "number", + "description": "Maximum number of stack frames to retrieve. Default: 50. Use smaller values for shallow inspection, larger for deep call stacks.", + "default": 50 + } + }, + "required": [] + } + }, + { + "name": "evaluate_debug_expression", + "displayName": "Evaluate Debug Expression", + "modelDescription": "Evaluate a Java expression in a specific thread's debug context. Access local variables, parameters, fields, and invoke methods. Returns the result with type information. REQUIRES: Active debug session with at least one SUSPENDED thread. For multi-threaded debugging, use threadId to specify which thread's context to use. If no threadId is provided, uses the first suspended thread. Examples: 'user.getName()', 'list.size() > 10', 'counter == null'.", + "toolReferenceName": "evaluateDebugExpression", + "tags": [ + "java", + "debug", + "evaluate", + "expression" + ], + "icon": "$(symbol-method)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "expression": { + "type": "string", + "description": "The Java expression to evaluate. Can be a variable name, field access, method call, or complex expression. Example: 'user.age', 'calculateTotal()', 'count > 0 && !items.isEmpty()'." + }, + "threadId": { + "type": "number", + "description": "Thread ID for evaluation context. Use get_debug_threads() to list available threads with their IDs and states. Only SUSPENDED threads can evaluate expressions. If omitted, uses the first suspended thread found." + }, + "frameId": { + "type": "number", + "description": "Optional stack frame ID for evaluation context. Default: 0 (current frame). Variables and methods from the specified frame will be accessible.", + "default": 0 + }, + "context": { + "type": "string", + "enum": [ + "watch", + "repl", + "hover" + ], + "description": "Evaluation context: 'watch' - for watch expressions, 'repl' - for debug console input, 'hover' - for hover tooltips. Affects how side effects are handled. Default: 'repl'.", + "default": "repl" + } + }, + "required": [ + "expression" + ] + } + }, + { + "name": "get_debug_threads", + "displayName": "Get Debug Threads", + "modelDescription": "List all threads in the debugged Java application with their IDs, names, and states (🔴 SUSPENDED or 🟢 RUNNING). For SUSPENDED threads, also shows the current location (file:line). REQUIRES: Active debug session. IMPORTANT: Only SUSPENDED threads can have their variables inspected or expressions evaluated. Use the returned thread IDs with get_debug_variables(threadId=X) or evaluate_debug_expression(threadId=X) to inspect specific threads.", + "toolReferenceName": "getDebugThreads", + "tags": [ + "java", + "debug", + "threads", + "concurrent" + ], + "icon": "$(list-tree)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } + }, + { + "name": "remove_java_breakpoints", + "displayName": "Remove Java Breakpoints", + "modelDescription": "Remove breakpoints: specific breakpoint by file and line, all breakpoints in a file, or all breakpoints globally. Use this to clean up after investigation or before setting new breakpoints. Best practice: keep only 1-2 active breakpoints at a time; remove old ones before adding new ones.", + "toolReferenceName": "removeJavaBreakpoints", + "tags": [ + "java", + "debug", + "breakpoint" + ], + "icon": "$(debug-breakpoint-unverified)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "filePath": { + "type": "string", + "description": "Absolute path to the Java source file. If not provided, removes all breakpoints from all files." + }, + "lineNumber": { + "type": "number", + "description": "Optional line number. If provided, removes only the breakpoint at this line. If omitted, removes all breakpoints in the specified file." + } + }, + "required": [] + } + }, + { + "name": "stop_debug_session", + "displayName": "Stop Debug Session", + "modelDescription": "Stop the active Java debug session when investigation is complete or when you need to restart debugging. This terminates the running Java process and closes the debug session. Use this to clean up after debugging or when you've identified the root cause and want to end the session. Optional: Provide a reason for stopping (e.g., 'Investigation complete', 'Root cause identified').", + "toolReferenceName": "stopDebugSession", + "tags": [ + "java", + "debug", + "stop", + "terminate" + ], + "icon": "$(debug-stop)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "Optional reason for stopping the debug session (e.g., 'Investigation complete', 'Root cause identified', 'Need to restart'). Default: 'Investigation complete'." + } + }, + "required": [] + } + }, + { + "name": "get_debug_session_info", + "displayName": "Get Debug Session Info", + "modelDescription": "Get information about the currently active Java debug session, including whether it's PAUSED at a breakpoint or RUNNING. CRITICAL: Check status before using inspection tools (get_debug_variables, get_debug_stack_trace, evaluate_debug_expression) or control operations (continue, step). PAUSED status (🔴) means stopped at breakpoint - inspection and control tools available. RUNNING status (🟢) means executing code - only breakpoint setting or session stop available. Returns session ID, name, type, configuration details, and status-specific available actions.", + "toolReferenceName": "getDebugSessionInfo", + "tags": [ + "java", + "debug", + "session", + "info", + "status", + "paused", + "running" + ], + "icon": "$(info)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": {}, + "required": [] + } + } + ], + "chatAgents": [ + { + "name": "JavaDebug", + "path": "./bundled/agents/debug.agent.md", + "description": "Custome Agent for debugging Java applications." + } + ] }, "scripts": { "vscode:prepublish": "npm run build", @@ -1006,7 +1355,7 @@ "@types/mocha": "^10.0.9", "@types/node": "^14.18.63", "@types/uuid": "^8.3.4", - "@types/vscode": "1.75.0", + "@types/vscode": "1.95.0", "@vscode/test-electron": "^2.4.1", "mocha": "^10.8.2", "ts-loader": "^9.5.1", @@ -1025,4 +1374,4 @@ "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" } -} +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index c9a9542a..e3a75eb0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,6 +18,7 @@ import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace, NO_BUTTON, Y import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory"; import { JavaInlineValuesProvider } from "./JavaInlineValueProvider"; import { logJavaException, logJavaInfo } from "./javaLogger"; +import { registerLanguageModelTool, registerDebugSessionTools } from "./languageModelTool"; import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServerPlugin"; import { mainClassPicker } from "./mainClassPicker"; import { pickJavaProcess } from "./processPicker"; @@ -41,6 +42,9 @@ export async function activate(context: vscode.ExtensionContext): Promise { ); context.subscriptions.push(noConfigDisposable); + // Register Language Model Tools after Java Language Server is ready + registerLanguageModelToolsWhenReady(context); + return instrumentOperation("activation", initializeExtension)(context); } @@ -99,6 +103,29 @@ export async function deactivate() { } const delay = promisify(setTimeout); + +/** + * Register Language Model Tools after Java Language Server is ready. + * The debug tools depend on JDT.LS for compilation, classpath resolution, + * and executing debug server commands. + */ +async function registerLanguageModelToolsWhenReady(context: vscode.ExtensionContext): Promise { + // Check if Language Model API is available + if (!vscode.lm || typeof vscode.lm.registerTool !== 'function') { + return; + } + + const javaExt = vscode.extensions.getExtension("redhat.java"); + if (!javaExt) { + return; + } + + // Register Language Model Tools for AI-assisted debugging + registerLanguageModelTool(context); + const debugToolsDisposables = registerDebugSessionTools(context); + context.subscriptions.push(...debugToolsDisposables); +} + async function subscribeToJavaExtensionEvents(): Promise { const javaExt = vscode.extensions.getExtension("redhat.java"); if (!javaExt) { diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts new file mode 100644 index 00000000..ad803d25 --- /dev/null +++ b/src/languageModelTool.ts @@ -0,0 +1,1577 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper"; + +// ============================================================================ +// Constants +// ============================================================================ +const CONSTANTS = { + /** Timeout for waitForSession mode (ms) */ + SESSION_WAIT_TIMEOUT: 45000, + /** Maximum wait time for smart polling (ms) */ + SMART_POLLING_MAX_WAIT: 15000, + /** Interval between polling checks (ms) */ + SMART_POLLING_INTERVAL: 300, + /** Timeout for build tasks (ms) */ + BUILD_TIMEOUT: 60000, + /** Maximum number of Java files to check for compilation errors */ + MAX_JAVA_FILES_TO_CHECK: 100, + /** Default stack trace depth */ + DEFAULT_STACK_DEPTH: 50, + /** Maximum depth for recursive file search */ + MAX_FILE_SEARCH_DEPTH: 10 +}; + +interface DebugJavaApplicationInput { + target: string; + workspacePath: string; + args?: string[]; + skipBuild?: boolean; + classpath?: string; + waitForSession?: boolean; +} + +interface DebugJavaApplicationResult { + success: boolean; + message: string; + terminalName?: string; + status?: 'started' | 'timeout' | 'sent'; // More specific status + sessionId?: string; // Session ID if detected +} + +// Type definitions for Language Model API (these will be in future VS Code versions) +// For now, we use 'any' to allow compilation with older VS Code types +interface LanguageModelTool { + invoke(options: { input: T }, token: vscode.CancellationToken): Promise; +} + +/** + * Registers the Language Model Tool for debugging Java applications. + * This allows AI assistants to help users debug Java code by invoking the debugjava command. + */ +export function registerLanguageModelTool(context: vscode.ExtensionContext): vscode.Disposable | undefined { + // Check if the Language Model API is available + const lmApi = (vscode as any).lm; + if (!lmApi || typeof lmApi.registerTool !== 'function') { + // Language Model API not available in this VS Code version + return undefined; + } + + const tool: LanguageModelTool = { + async invoke(options: { input: DebugJavaApplicationInput }, token: vscode.CancellationToken): Promise { + sendInfo('', { + operationName: 'languageModelTool.debugJavaApplication.invoke', + target: options.input.target, + skipBuild: options.input.skipBuild?.toString() || 'false', + }); + + try { + const result = await debugJavaApplication(options.input, token); + + // Format the message for AI - use simple text, not JSON + const message = result.success + ? `✓ ${result.message}` + : `✗ ${result.message}`; + + // Return result in the expected format - simple text part + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(message) + ]); + } catch (error) { + sendError(error as Error); + + const errorMessage = error instanceof Error ? error.message : String(error); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Debug failed: ${errorMessage}`) + ]); + } + } + }; + + const disposable = lmApi.registerTool('debug_java_application', tool); + context.subscriptions.push(disposable); + return disposable; +} + +/** + * Main function to debug a Java application. + * This function handles: + * 1. Cleanup any existing debug session (to avoid port conflicts) + * 2. Project type detection + * 3. Building the project if needed + * 4. Executing the debugjava command + */ +async function debugJavaApplication( + input: DebugJavaApplicationInput, + token: vscode.CancellationToken +): Promise { + if (token.isCancellationRequested) { + return { + success: false, + message: 'Operation cancelled by user' + }; + } + + // Step 0: Cleanup any existing Java debug session to avoid port conflicts + const existingSession = vscode.debug.activeDebugSession; + if (existingSession && existingSession.type === 'java') { + sendInfo('', { + operationName: 'languageModelTool.cleanupExistingSession', + sessionId: existingSession.id, + sessionName: existingSession.name + }); + try { + await vscode.debug.stopDebugging(existingSession); + // Give VS Code a moment to clean up the session + await new Promise(resolve => setTimeout(resolve, 500)); + } catch (error) { + // Log but continue - the old session might already be dead + sendInfo('', { + operationName: 'languageModelTool.cleanupExistingSessionFailed', + error: String(error) + }); + } + } + + // Also close any existing "Java Debug" terminals to avoid confusion + for (const existingTerminal of vscode.window.terminals) { + if (existingTerminal.name === 'Java Debug') { + existingTerminal.dispose(); + } + } + + // Validate workspace path + const workspaceUri = vscode.Uri.file(input.workspacePath); + if (!fs.existsSync(input.workspacePath)) { + return { + success: false, + message: `Workspace path does not exist: ${input.workspacePath}` + }; + } + + // Step 1: Detect project type + const projectType = detectProjectType(input.workspacePath); + + // Step 2: Build the project if needed + if (!input.skipBuild) { + const buildResult = await buildProject(workspaceUri, projectType, token); + if (!buildResult.success) { + return buildResult; + } + } + + // Step 3: Construct and execute the debugjava command + const debugCommand = constructDebugCommand(input, projectType); + + // Validate that we can construct a valid command + if (!debugCommand || debugCommand === 'debugjava') { + return { + success: false, + message: 'Failed to construct debug command. Please check the target parameter.' + }; + } + + // Step 4: Execute in terminal and optionally wait for debug session + const terminal = vscode.window.createTerminal({ + name: 'Java Debug', + cwd: input.workspacePath, + hideFromUser: false, + isTransient: false // Keep terminal alive even after process exits + }); + + terminal.show(); + + // Build info message for AI + let targetInfo = input.target; + let warningNote = ''; + + if (input.target.endsWith('.jar')) { + targetInfo = input.target; + } else if (input.target.includes('.')) { + targetInfo = input.target; + } else { + // Simple class name - check if we successfully detected the full name + const detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); + if (detectedClassName) { + targetInfo = `${detectedClassName} (detected from ${input.target})`; + } else { + targetInfo = input.target; + warningNote = ' ⚠️ Note: Could not auto-detect package name. If you see "ClassNotFoundException", please provide the fully qualified class name (e.g., "com.example.App" instead of "App").'; + } + } + + // If waitForSession is true, wait for the debug session to start + if (input.waitForSession) { + return new Promise((resolve) => { + let sessionStarted = false; + + // Listen for debug session start + const sessionDisposable = vscode.debug.onDidStartDebugSession((session) => { + if (session.type === 'java' && !sessionStarted) { + sessionStarted = true; + sessionDisposable.dispose(); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + + sendInfo('', { + operationName: 'languageModelTool.debugSessionStarted.eventBased', + sessionId: session.id, + sessionName: session.name + }); + + resolve({ + success: true, + status: 'started', + sessionId: session.id, + message: `✓ Debug session started for ${targetInfo}. Session ID: ${session.id}. The debugger is now attached and ready. Any breakpoints you set will be active.${warningNote}`, + terminalName: terminal.name + }); + } + }); + + // Send the command after setting up the listener + terminal.sendText(debugCommand); + + // Set a timeout for large applications + const timeoutHandle = setTimeout(() => { + if (!sessionStarted) { + sessionDisposable.dispose(); + + sendInfo('', { + operationName: 'languageModelTool.debugSessionTimeout.eventBased', + target: targetInfo + }); + + resolve({ + success: false, + status: 'timeout', + message: `❌ Debug session failed to start within ${CONSTANTS.SESSION_WAIT_TIMEOUT / 1000} seconds for ${targetInfo}.\n\n` + + `This usually indicates a problem:\n` + + `• Compilation errors preventing startup\n` + + `• ClassNotFoundException or NoClassDefFoundError\n` + + `• Application crashed during initialization\n` + + `• Incorrect main class or classpath configuration\n\n` + + `Action required:\n` + + `1. Check terminal '${terminal.name}' for error messages\n` + + `2. Verify the target class name is correct\n` + + `3. Ensure the project is compiled successfully\n` + + `4. Use get_debug_session_info() to confirm session status${warningNote}`, + terminalName: terminal.name + }); + } + }, CONSTANTS.SESSION_WAIT_TIMEOUT); + }); + } else { + // Default behavior: send command and use smart polling to detect session start + terminal.sendText(debugCommand); + + // Smart polling to detect session start + const maxWaitTime = CONSTANTS.SMART_POLLING_MAX_WAIT; + const pollInterval = CONSTANTS.SMART_POLLING_INTERVAL; + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + // Check if debug session has started + const session = vscode.debug.activeDebugSession; + if (session && session.type === 'java') { + const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); + + sendInfo('', { + operationName: 'languageModelTool.debugSessionDetected', + sessionId: session.id, + elapsedTime + }); + + return { + success: true, + status: 'started', + sessionId: session.id, + message: `✓ Debug session started for ${targetInfo} (detected in ${elapsedTime}s). Session ID: ${session.id}. The debugger is attached and ready.${warningNote}`, + terminalName: terminal.name + }; + } + + // Wait before next check + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + + // Timeout: session not detected within 15 seconds + sendInfo('', { + operationName: 'languageModelTool.debugSessionTimeout.smartPolling', + target: targetInfo, + maxWaitTime + }); + + return { + success: true, + status: 'timeout', + message: `⚠️ Debug command sent for ${targetInfo}, but session not detected within ${CONSTANTS.SMART_POLLING_MAX_WAIT / 1000} seconds.\n\n` + + `Possible reasons:\n` + + `• Application is still starting (large projects may take longer)\n` + + `• Compilation errors (check terminal '${terminal.name}' for errors)\n` + + `• Application may have started and already terminated\n\n` + + `Next steps:\n` + + `• Use get_debug_session_info() to check if session is now active\n` + + `• Check terminal '${terminal.name}' for error messages\n` + + `• If starting slowly, wait a bit longer and check again${warningNote}`, + terminalName: terminal.name + }; + } +} + +/** + * Detects the type of Java project based on build files present. + */ +function detectProjectType(workspacePath: string): 'maven' | 'gradle' | 'vscode' | 'unknown' { + if (fs.existsSync(path.join(workspacePath, 'pom.xml'))) { + return 'maven'; + } + + if (fs.existsSync(path.join(workspacePath, 'build.gradle')) || + fs.existsSync(path.join(workspacePath, 'build.gradle.kts'))) { + return 'gradle'; + } + + // Check if VS Code Java extension is likely managing compilation + const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(workspacePath)); + if (workspaceFolder) { + const javaExt = vscode.extensions.getExtension('redhat.java'); + if (javaExt?.isActive) { + return 'vscode'; + } + } + + return 'unknown'; +} + +/** + * Builds the Java project based on its type. + */ +async function buildProject( + workspaceUri: vscode.Uri, + projectType: 'maven' | 'gradle' | 'vscode' | 'unknown', + _token: vscode.CancellationToken +): Promise { + switch (projectType) { + case 'maven': + return buildMavenProject(workspaceUri); + + case 'gradle': + return buildGradleProject(workspaceUri); + + case 'vscode': + return ensureVSCodeCompilation(workspaceUri); + + case 'unknown': + // Try to proceed anyway - user might have manually compiled + return { + success: true, + message: 'Unknown project type. Skipping build step. Ensure your Java files are compiled.' + }; + } +} + +/** + * Executes a shell task and waits for completion. + * This is a common function used by both Maven and Gradle builds. + */ +async function executeShellTask( + workspaceUri: vscode.Uri, + taskId: string, + taskName: string, + command: string, + successMessage: string, + timeoutMessage: string, + failureMessagePrefix: string +): Promise { + return new Promise((resolve) => { + const workspaceFolder = vscode.workspace.getWorkspaceFolder(workspaceUri); + if (!workspaceFolder) { + resolve({ + success: false, + message: `Cannot find workspace folder for ${workspaceUri.fsPath}` + }); + return; + } + + const task = new vscode.Task( + { type: 'shell', task: taskId }, + workspaceFolder, + taskName, + 'Java Debug', + new vscode.ShellExecution(command, { cwd: workspaceUri.fsPath }) + ); + + let resolved = false; + let taskDisposable: vscode.Disposable | undefined; + let errorDisposable: vscode.Disposable | undefined; + + const cleanup = () => { + clearTimeout(timeoutHandle); + taskDisposable?.dispose(); + errorDisposable?.dispose(); + }; + + // Set a timeout to avoid hanging indefinitely + const timeoutHandle = setTimeout(() => { + if (!resolved) { + resolved = true; + cleanup(); + resolve({ + success: true, + message: timeoutMessage + }); + } + }, CONSTANTS.BUILD_TIMEOUT); + + vscode.tasks.executeTask(task).then( + (execution) => { + taskDisposable = vscode.tasks.onDidEndTask((e) => { + if (e.execution === execution && !resolved) { + resolved = true; + cleanup(); + resolve({ + success: true, + message: successMessage + }); + } + }); + + errorDisposable = vscode.tasks.onDidEndTaskProcess((e) => { + if (e.execution === execution && e.exitCode !== 0 && !resolved) { + resolved = true; + cleanup(); + resolve({ + success: false, + message: `${failureMessagePrefix} with exit code ${e.exitCode}. Please check the terminal output.` + }); + } + }); + }, + (error: Error) => { + if (!resolved) { + resolved = true; + cleanup(); + resolve({ + success: false, + message: `Failed to execute task: ${error.message}` + }); + } + } + ); + }); +} + +/** + * Builds a Maven project using mvn compile. + */ +async function buildMavenProject( + workspaceUri: vscode.Uri +): Promise { + return executeShellTask( + workspaceUri, + 'maven-compile', + 'Maven Compile', + 'mvn compile', + 'Maven project compiled successfully', + 'Maven compile command sent. Build may still be in progress.', + 'Maven build failed' + ); +} + +/** + * Builds a Gradle project using gradle classes. + */ +async function buildGradleProject( + workspaceUri: vscode.Uri +): Promise { + const gradleWrapper = process.platform === 'win32' ? 'gradlew.bat' : './gradlew'; + const gradleCommand = fs.existsSync(path.join(workspaceUri.fsPath, gradleWrapper)) + ? gradleWrapper + : 'gradle'; + + return executeShellTask( + workspaceUri, + 'gradle-classes', + 'Gradle Classes', + `${gradleCommand} classes`, + 'Gradle project compiled successfully', + 'Gradle compile command sent. Build may still be in progress.', + 'Gradle build failed' + ); +} + +/** + * Ensures VS Code Java Language Server has compiled the files. + */ +async function ensureVSCodeCompilation(workspaceUri: vscode.Uri): Promise { + try { + // Check for compilation errors using VS Code diagnostics + const javaFiles = await vscode.workspace.findFiles( + new vscode.RelativePattern(workspaceUri, '**/*.java'), + '**/node_modules/**', + CONSTANTS.MAX_JAVA_FILES_TO_CHECK + ); + + let hasErrors = false; + for (const file of javaFiles) { + const diagnostics = vscode.languages.getDiagnostics(file); + const errors = diagnostics.filter(d => d.severity === vscode.DiagnosticSeverity.Error); + if (errors.length > 0) { + hasErrors = true; + break; + } + } + + if (hasErrors) { + return { + success: false, + message: 'Compilation errors detected in the project. Please fix the errors before debugging.' + }; + } + + // Check if Java extension is active and in standard mode + const javaExt = vscode.extensions.getExtension('redhat.java'); + if (!javaExt?.isActive) { + return { + success: true, + message: 'Java Language Server is not active. Proceeding with debug, but ensure your code is compiled.' + }; + } + + return { + success: true, + message: 'VS Code Java compilation verified' + }; + } catch (error) { + // If we can't verify, proceed anyway + return { + success: true, + message: 'Unable to verify compilation status. Proceeding with debug.' + }; + } +} + +/** + * Constructs the debugjava command based on input parameters. + */ +function constructDebugCommand( + input: DebugJavaApplicationInput, + projectType: 'maven' | 'gradle' | 'vscode' | 'unknown' +): string { + let command = 'debugjava'; + + // Handle JAR files + if (input.target.endsWith('.jar')) { + command += ` -jar ${input.target}`; + } + // Handle raw java command arguments (starts with - like -cp, -jar, etc) + else if (input.target.startsWith('-')) { + command += ` ${input.target}`; + } + // Handle class name (with or without package) + else { + let className = input.target; + + // If target doesn't contain a dot and we can find the Java file, + // try to detect the fully qualified class name + if (!input.target.includes('.')) { + const detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); + if (detectedClassName) { + sendInfo('', { + operationName: 'languageModelTool.classNameDetection', + simpleClassName: input.target, + detectedClassName, + projectType + }); + className = detectedClassName; + } else { + // No package detected - class is in default package + sendInfo('', { + operationName: 'languageModelTool.classNameDetection.noPackage', + simpleClassName: input.target, + projectType + }); + } + } + + // Use provided classpath if available, otherwise infer it + const classpath = input.classpath || inferClasspath(input.workspacePath, projectType); + + command += ` -cp "${classpath}" ${className}`; + } + + // Add arguments if provided + if (input.args && input.args.length > 0) { + command += ' ' + input.args.join(' '); + } + + return command; +} + +/** + * Tries to find the fully qualified class name by searching for the Java file. + * This helps when user provides just "App" instead of "com.example.App". + */ +function findFullyQualifiedClassName( + workspacePath: string, + simpleClassName: string, + projectType: 'maven' | 'gradle' | 'vscode' | 'unknown' +): string | null { + // Determine source directories based on project type + const sourceDirs: string[] = []; + + switch (projectType) { + case 'maven': + sourceDirs.push(path.join(workspacePath, 'src', 'main', 'java')); + break; + case 'gradle': + sourceDirs.push(path.join(workspacePath, 'src', 'main', 'java')); + break; + case 'vscode': + sourceDirs.push(path.join(workspacePath, 'src')); + break; + case 'unknown': + // Try all common locations + sourceDirs.push( + path.join(workspacePath, 'src', 'main', 'java'), + path.join(workspacePath, 'src'), + workspacePath + ); + break; + } + + // Search for the Java file + for (const srcDir of sourceDirs) { + if (!fs.existsSync(srcDir)) { + continue; + } + + try { + const javaFile = findJavaFile(srcDir, simpleClassName, 0); + if (javaFile) { + // Extract package name from the file + const packageName = extractPackageName(javaFile); + if (packageName) { + return `${packageName}.${simpleClassName}`; + } else { + // No package, use simple name + return simpleClassName; + } + } + } catch (error) { + // Continue searching in other directories + } + } + + return null; +} + +/** + * Recursively searches for a Java file with the given class name. + * @param depth Current recursion depth (for limiting search depth) + */ +function findJavaFile(dir: string, className: string, depth: number = 0): string | null { + // Limit recursion depth to prevent performance issues + if (depth > CONSTANTS.MAX_FILE_SEARCH_DEPTH) { + return null; + } + + try { + const files = fs.readdirSync(dir); + + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + // Skip common non-source directories + if (file === 'node_modules' || file === '.git' || file === 'target' || file === 'build') { + continue; + } + const found = findJavaFile(filePath, className, depth + 1); + if (found) { + return found; + } + } else if (file === `${className}.java`) { + return filePath; + } + } + } catch (error) { + // Ignore permission errors or other file system issues + } + + return null; +} + +/** + * Extracts the package name from a Java source file. + */ +function extractPackageName(javaFilePath: string): string | null { + try { + const content = fs.readFileSync(javaFilePath, 'utf-8'); + const packageMatch = content.match(/^\s*package\s+([\w.]+)\s*;/m); + return packageMatch ? packageMatch[1] : null; + } catch (error) { + return null; + } +} + +/** + * Checks if a directory contains any .class files. + * @param depth Current recursion depth (for limiting search depth) + */ +function hasClassFiles(dir: string, depth: number = 0): boolean { + // Limit recursion depth to prevent performance issues + if (depth > CONSTANTS.MAX_FILE_SEARCH_DEPTH) { + return false; + } + + try { + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isFile() && file.endsWith('.class')) { + return true; + } else if (stat.isDirectory()) { + if (hasClassFiles(filePath, depth + 1)) { + return true; + } + } + } + } catch (error) { + // Ignore errors + } + return false; +} + +/** + * Infers the classpath based on project type and common conventions. + */ +function inferClasspath(workspacePath: string, projectType: 'maven' | 'gradle' | 'vscode' | 'unknown'): string { + const classpaths: string[] = []; + + switch (projectType) { + case 'maven': + // Maven standard output directory + const mavenTarget = path.join(workspacePath, 'target', 'classes'); + if (fs.existsSync(mavenTarget)) { + classpaths.push(mavenTarget); + } + break; + + case 'gradle': + // Gradle standard output directories + const gradleMain = path.join(workspacePath, 'build', 'classes', 'java', 'main'); + if (fs.existsSync(gradleMain)) { + classpaths.push(gradleMain); + } + break; + + case 'vscode': + // VS Code Java extension default output + const vscodeOut = path.join(workspacePath, 'bin'); + if (fs.existsSync(vscodeOut)) { + classpaths.push(vscodeOut); + } + break; + } + + // Fallback to common locations + if (classpaths.length === 0) { + const commonPaths = [ + path.join(workspacePath, 'bin'), // VS Code default + path.join(workspacePath, 'out'), // IntelliJ default + path.join(workspacePath, 'target', 'classes'), // Maven + path.join(workspacePath, 'build', 'classes', 'java', 'main'), // Gradle + path.join(workspacePath, 'build', 'classes'), + ]; + + // Check each common path + for (const p of commonPaths) { + if (fs.existsSync(p)) { + // Check if there are actually .class files in this directory + if (hasClassFiles(p)) { + classpaths.push(p); + break; + } + } + } + } + + // If still no classpath found, use current directory + // This is common for simple projects where .class files are alongside .java files + if (classpaths.length === 0) { + classpaths.push('.'); + } + + return classpaths.join(path.delimiter); +} + +// ============================================================================ +// Debug Session Control Tools +// ============================================================================ + +interface SetBreakpointInput { + filePath: string; + lineNumber: number; + condition?: string; + hitCondition?: string; + logMessage?: string; +} + +interface StepOperationInput { + operation: 'stepIn' | 'stepOut' | 'stepOver' | 'continue' | 'pause'; + threadId?: number; +} + +interface GetVariablesInput { + threadId?: number; + frameId?: number; + scopeType?: 'local' | 'static' | 'all'; + filter?: string; +} + +interface GetStackTraceInput { + threadId?: number; + maxDepth?: number; +} + +interface EvaluateExpressionInput { + expression: string; + threadId?: number; + frameId?: number; + context?: 'watch' | 'repl' | 'hover'; +} + +interface RemoveBreakpointsInput { + filePath?: string; + lineNumber?: number; +} + +interface StopDebugSessionInput { + reason?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +type GetDebugSessionInfoInput = Record; + +/** + * Result of finding a suspended thread + */ +interface SuspendedThreadInfo { + threadId: number; + frameId: number; +} + +/** + * Finds the first suspended thread in the debug session. + * Returns the thread ID and top frame ID, or null if no suspended thread is found. + */ +async function findFirstSuspendedThread(session: vscode.DebugSession): Promise { + try { + const threadsResponse = await session.customRequest('threads'); + for (const thread of threadsResponse.threads || []) { + try { + const stackResponse = await session.customRequest('stackTrace', { + threadId: thread.id, + startFrame: 0, + levels: 1 + }); + if (stackResponse?.stackFrames?.length > 0) { + return { + threadId: thread.id, + frameId: stackResponse.stackFrames[0].id + }; + } + } catch { + // Thread is running, continue to next + continue; + } + } + } catch { + // Failed to get threads + } + return null; +} + +/** + * Registers all debug session control tools + */ +export function registerDebugSessionTools(_context: vscode.ExtensionContext): vscode.Disposable[] { + const lmApi = (vscode as any).lm; + if (!lmApi || typeof lmApi.registerTool !== 'function') { + return []; + } + + const disposables: vscode.Disposable[] = []; + + // Tool 1: Set Breakpoint + const setBreakpointTool: LanguageModelTool = { + async invoke(options: { input: SetBreakpointInput }, _token: vscode.CancellationToken): Promise { + try { + const { filePath, lineNumber, condition, hitCondition, logMessage } = options.input; + + // Set breakpoint through VS Code API (no active session required) + const uri = vscode.Uri.file(filePath); + const breakpoint = new vscode.SourceBreakpoint( + new vscode.Location(uri, new vscode.Position(lineNumber - 1, 0)), + true, // enabled + condition, + hitCondition, + logMessage + ); + + vscode.debug.addBreakpoints([breakpoint]); + + const bpType = logMessage ? 'Logpoint' : 'Breakpoint'; + const session = vscode.debug.activeDebugSession; + const sessionInfo = (session && session.type === 'java') + ? ' (active in current session)' + : ' (will activate when debugging starts)'; + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `✓ ${bpType} set at ${path.basename(filePath)}:${lineNumber}${condition ? ` (condition: ${condition})` : ''}${sessionInfo}` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to set breakpoint: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('set_java_breakpoint', setBreakpointTool)); + + // Tool 2: Step Operations + const stepOperationTool: LanguageModelTool = { + async invoke(options: { input: StepOperationInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + if (!session || session.type !== 'java') { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') + ]); + } + + const { operation, threadId } = options.input; + + // Map operation to VS Code debug commands + const commandMap: { [key: string]: string } = { + stepIn: 'workbench.action.debug.stepInto', + stepOut: 'workbench.action.debug.stepOut', + stepOver: 'workbench.action.debug.stepOver', + continue: 'workbench.action.debug.continue', + pause: 'workbench.action.debug.pause' + }; + + const command = commandMap[operation]; + if (threadId !== undefined) { + // For thread-specific operations, use custom request + await session.customRequest(operation, { threadId }); + } else { + // Use VS Code command for current thread + await vscode.commands.executeCommand(command); + } + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✓ Executed ${operation}`) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Step operation failed: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('debug_step_operation', stepOperationTool)); + + // Tool 3: Get Variables + const getVariablesTool: LanguageModelTool = { + async invoke(options: { input: GetVariablesInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + if (!session || session.type !== 'java') { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') + ]); + } + + const { threadId, frameId = 0, scopeType = 'all', filter } = options.input; + + // Find the target thread - either specified or find first suspended thread + let targetThreadId = threadId; + if (!targetThreadId) { + const suspendedThread = await findFirstSuspendedThread(session); + if (suspendedThread) { + targetThreadId = suspendedThread.threadId; + } + } + + if (!targetThreadId) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') + ]); + } + + // Get stack trace to access frame + const stackResponse = await session.customRequest('stackTrace', { + threadId: targetThreadId, + startFrame: frameId, + levels: 1 + }); + + if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No stack frame available.') + ]); + } + + const frame = stackResponse.stackFrames[0]; + + // Get scopes for the frame + const scopesResponse = await session.customRequest('scopes', { frameId: frame.id }); + + const variables: string[] = []; + for (const scope of scopesResponse.scopes) { + // Filter by scope type + if (scopeType === 'local' && scope.name !== 'Local' && scope.name !== 'Locals') { + continue; + } + if (scopeType === 'static' && scope.name !== 'Static') { + continue; + } + + // Get variables for this scope + const varsResponse = await session.customRequest('variables', { + variablesReference: scope.variablesReference + }); + + for (const v of varsResponse.variables) { + if (!filter || v.name.includes(filter) || matchWildcard(v.name, filter)) { + variables.push(`${v.name}: ${v.type || ''} = ${v.value}`); + } + } + } + + if (variables.length === 0) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('No variables found.') + ]); + } + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `Variables (Thread #${targetThreadId}, Frame ${frameId}):\n${variables.join('\n')}` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to get variables: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('get_debug_variables', getVariablesTool)); + + // Tool 4: Get Stack Trace + const getStackTraceTool: LanguageModelTool = { + async invoke(options: { input: GetStackTraceInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + if (!session || session.type !== 'java') { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') + ]); + } + + const { threadId, maxDepth = CONSTANTS.DEFAULT_STACK_DEPTH } = options.input; + + const stackResponse = await session.customRequest('stackTrace', { + threadId: threadId || (session as any).threadId || 1, + startFrame: 0, + levels: maxDepth + }); + + if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('No stack frames available.') + ]); + } + + const frames = stackResponse.stackFrames.map((frame: any, index: number) => { + const location = frame.source ? + `${frame.source.name}:${frame.line}` : + 'unknown location'; + return `#${index} ${frame.name} at ${location}`; + }); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `Call Stack:\n${frames.join('\n')}` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to get stack trace: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('get_debug_stack_trace', getStackTraceTool)); + + // Tool 5: Evaluate Expression + const evaluateExpressionTool: LanguageModelTool = { + async invoke(options: { input: EvaluateExpressionInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + if (!session || session.type !== 'java') { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') + ]); + } + + const { expression, threadId, frameId = 0, context = 'repl' } = options.input; + + // Find the target thread and frame for evaluation + let targetFrameId: number = frameId; + let targetThreadId = threadId; + + // If no threadId specified, find first suspended thread + if (!targetThreadId) { + const suspendedThread = await findFirstSuspendedThread(session); + if (suspendedThread) { + targetThreadId = suspendedThread.threadId; + // Use the actual frame ID from the stack if frameId is 0 + if (frameId === 0) { + targetFrameId = suspendedThread.frameId; + } + } + } else { + // Get the frame ID for the specified thread + try { + const stackResponse = await session.customRequest('stackTrace', { + threadId: targetThreadId, + startFrame: frameId, + levels: 1 + }); + if (stackResponse?.stackFrames?.length > 0) { + targetFrameId = stackResponse.stackFrames[0].id; + } + } catch { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Thread #${targetThreadId} is not suspended. Cannot evaluate expression.`) + ]); + } + } + + if (!targetThreadId) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') + ]); + } + + const evalResponse = await session.customRequest('evaluate', { + expression, + frameId: targetFrameId, + context + }); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `Expression: ${expression}\n` + + `Thread: #${targetThreadId}\n` + + `Result: ${evalResponse.result}${evalResponse.type ? ` (${evalResponse.type})` : ''}` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Evaluation failed: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('evaluate_debug_expression', evaluateExpressionTool)); + + // Tool 6: Get Threads + const getThreadsTool: LanguageModelTool<{}> = { + async invoke(_options: { input: {} }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + if (!session || session.type !== 'java') { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') + ]); + } + + const threadsResponse = await session.customRequest('threads'); + + if (!threadsResponse.threads || threadsResponse.threads.length === 0) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('No threads found.') + ]); + } + + // Check each thread's state by trying to get its stack trace + const threadInfos: string[] = []; + for (const thread of threadsResponse.threads) { + let state = '🟢 RUNNING'; + let location = ''; + + try { + const stackResponse = await session.customRequest('stackTrace', { + threadId: thread.id, + startFrame: 0, + levels: 1 + }); + + if (stackResponse?.stackFrames?.length > 0) { + state = '🔴 SUSPENDED'; + const topFrame = stackResponse.stackFrames[0]; + if (topFrame.source) { + location = ` at ${topFrame.source.name}:${topFrame.line}`; + } + } + } catch { + // Thread is running, can't get stack + state = '🟢 RUNNING'; + } + + threadInfos.push(`Thread #${thread.id}: ${thread.name} [${state}]${location}`); + } + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `═══════════════════════════════════════════\n` + + `THREADS (${threadsResponse.threads.length} total)\n` + + `═══════════════════════════════════════════\n\n` + + `${threadInfos.join('\n')}\n\n` + + `───────────────────────────────────────────\n` + + `💡 Use threadId parameter to inspect a specific thread:\n` + + `• get_debug_variables(threadId=X)\n` + + `• get_debug_stack_trace(threadId=X)\n` + + `• evaluate_debug_expression(threadId=X, expression="...")\n` + + `───────────────────────────────────────────` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to get threads: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('get_debug_threads', getThreadsTool)); + + // Tool 7: Remove Breakpoints + const removeBreakpointsTool: LanguageModelTool = { + async invoke(options: { input: RemoveBreakpointsInput }, _token: vscode.CancellationToken): Promise { + try { + const { filePath, lineNumber } = options.input; + + const breakpoints = vscode.debug.breakpoints; + + if (!filePath) { + // Remove all breakpoints (no active session required) + const count = breakpoints.length; + vscode.debug.removeBreakpoints(breakpoints); + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✓ Removed all ${count} breakpoint(s).`) + ]); + } + + const uri = vscode.Uri.file(filePath); + const toRemove = breakpoints.filter(bp => { + if (bp instanceof vscode.SourceBreakpoint) { + const match = bp.location.uri.fsPath === uri.fsPath; + if (lineNumber !== undefined) { + return match && bp.location.range.start.line === lineNumber - 1; + } + return match; + } + return false; + }); + + if (toRemove.length > 0) { + vscode.debug.removeBreakpoints(toRemove); + } + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + toRemove.length > 0 + ? `✓ Removed ${toRemove.length} breakpoint(s) from ${path.basename(filePath)}${lineNumber ? `:${lineNumber}` : ''}` + : 'No matching breakpoints found.' + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to remove breakpoints: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('remove_java_breakpoints', removeBreakpointsTool)); + + // Tool 9: Stop Debug Session + const stopDebugSessionTool: LanguageModelTool = { + async invoke(options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + + if (!session) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart('No active debug session to stop.') + ]); + } + + const sessionInfo = `${session.name} (${session.type})`; + const reason = options.input.reason || 'Investigation complete'; + + // Stop the debug session + await vscode.debug.stopDebugging(session); + + sendInfo('', { + operationName: 'languageModelTool.stopDebugSession', + sessionId: session.id, + sessionName: session.name, + reason + }); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `✓ Stopped debug session: ${sessionInfo}. Reason: ${reason}` + ) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to stop debug session: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('stop_debug_session', stopDebugSessionTool)); + + // Tool 10: Get Debug Session Info + const getDebugSessionInfoTool: LanguageModelTool = { + async invoke(_options: { input: GetDebugSessionInfoInput }, _token: vscode.CancellationToken): Promise { + try { + const session = vscode.debug.activeDebugSession; + + if (!session) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + '❌ No active debug session found.\n\n' + + 'You can:\n' + + '• Start a new debug session using debug_java_application\n' + + '• Set breakpoints before or after starting a session\n' + + '• Wait for an existing session to hit a breakpoint' + ) + ]); + } + + // Gather session information + const sessionInfo = { + id: session.id, + name: session.name, + type: session.type, + workspaceFolder: session.workspaceFolder?.name || 'N/A', + configuration: { + name: session.configuration.name, + type: session.configuration.type, + request: session.configuration.request, + mainClass: session.configuration.mainClass, + projectName: session.configuration.projectName + } + }; + + // Check if session is paused and get current location + // Strategy: Get all threads first, then try to get stack trace for each + // A thread is paused if we can successfully get its stack trace + let isPaused = false; + let stoppedReason = 'unknown'; + let currentLocation = ''; + let currentFile = ''; + let currentLine = 0; + let stoppedThreadId: number | undefined; + let stoppedThreadName = ''; + + try { + // Step 1: Get all threads + const threadsResponse = await session.customRequest('threads'); + const threads = threadsResponse?.threads || []; + + // Step 2: Try to get stack trace for each thread to find paused one + // In Java debug, only paused threads can provide stack traces + for (const thread of threads) { + try { + const stackResponse = await session.customRequest('stackTrace', { + threadId: thread.id, + startFrame: 0, + levels: 1 + }); + + // If we got stack frames, this thread is paused + if (stackResponse?.stackFrames?.length > 0) { + isPaused = true; + stoppedThreadId = thread.id; + stoppedThreadName = thread.name || `Thread-${thread.id}`; + + const topFrame = stackResponse.stackFrames[0]; + + // Extract current location details + if (topFrame.source) { + currentFile = topFrame.source.path || topFrame.source.name || 'unknown'; + currentLine = topFrame.line || 0; + const methodName = topFrame.name || 'unknown'; + const fileName = topFrame.source.name || path.basename(currentFile); + currentLocation = `${fileName}:${currentLine} in ${methodName}`; + } + + // Try to determine stop reason from thread name or default to breakpoint + stoppedReason = 'breakpoint'; + + // Found a paused thread, no need to check others for basic info + break; + } + } catch { + // This thread is running, not paused - continue to next + continue; + } + } + + // If no thread had stack frames, all are running + if (!isPaused && threads.length > 0) { + // Session exists but all threads are running + isPaused = false; + } + } catch (error) { + // If we can't even get threads, something is wrong + // But session exists, so mark as running + isPaused = false; + sendInfo('', { + operationName: 'languageModelTool.getDebugSessionInfo.threadError', + error: String(error) + }); + } + + // Build status line with location info + let statusLine: string; + let locationInfo = ''; + + if (isPaused) { + statusLine = `🔴 Status: PAUSED (${stoppedReason})`; + locationInfo = [ + '', + '📍 Current Location:', + `• File: ${currentFile}`, + `• Line: ${currentLine}`, + `• Method: ${currentLocation}`, + `• Thread: ${stoppedThreadName} (ID: ${stoppedThreadId})` + ].join('\n'); + } else { + statusLine = '🟢 Status: RUNNING'; + } + + // Build clear action guidance based on state + let actionGuidance: string; + if (isPaused) { + actionGuidance = [ + '✅ READY FOR INSPECTION - Session is paused at breakpoint', + '', + 'You can now:', + '• evaluate_debug_expression - Test your hypothesis (e.g., "user == null")', + '• get_debug_variables - Inspect specific variables', + '• get_debug_stack_trace - See full call stack', + '• debug_step_operation - Step through code (stepOver, stepIn, stepOut)', + '• debug_step_operation(continue) - Resume to next breakpoint', + '• stop_debug_session - End debugging when done' + ].join('\n'); + } else { + actionGuidance = [ + '⏳ WAITING - Session is running, not yet at breakpoint', + '', + 'The program is executing. To pause:', + '• Wait for it to hit your breakpoint', + '• Or use debug_step_operation(pause) to pause immediately', + '', + 'Inspection tools (get_debug_variables, evaluate_debug_expression) ', + 'will NOT work until the session is PAUSED.' + ].join('\n'); + } + + // Determine if this is a debugjava (No-Config) session that can be safely stopped + const isNoConfigSession = sessionInfo.name.includes('No-Config') || + sessionInfo.name.includes('debugjava'); + const launchMethod = isNoConfigSession + ? 'debugjava (No-Config) - ✅ Can be safely stopped' + : sessionInfo.configuration.request === 'attach' + ? 'External attach - ⚠️ Stopping will disconnect from process' + : 'VS Code launch - ✅ Can be safely stopped'; + + const message = [ + '═══════════════════════════════════════════', + isPaused ? '🔴 DEBUG SESSION PAUSED' : '🟢 DEBUG SESSION RUNNING', + '═══════════════════════════════════════════', + '', + statusLine, + locationInfo, + '', + '───────────────────────────────────────────', + 'Session Details:', + `• Session ID: ${sessionInfo.id}`, + `• Name: ${sessionInfo.name}`, + `• Type: ${sessionInfo.type}`, + `• Request: ${sessionInfo.configuration.request || 'N/A'}`, + `• Launch Method: ${launchMethod}`, + `• Main Class: ${sessionInfo.configuration.mainClass || 'N/A'}`, + '', + '───────────────────────────────────────────', + actionGuidance, + '═══════════════════════════════════════════' + ].join('\n'); + + sendInfo('', { + operationName: 'languageModelTool.getDebugSessionInfo', + sessionId: session.id, + sessionType: session.type, + isPaused: String(isPaused), + stoppedThreadId: String(stoppedThreadId || ''), + currentFile, + currentLine: String(currentLine) + }); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(message) + ]); + } catch (error) { + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Failed to get debug session info: ${error}`) + ]); + } + } + }; + disposables.push(lmApi.registerTool('get_debug_session_info', getDebugSessionInfoTool)); + + return disposables; +} + +/** + * Simple wildcard matching helper + */ +function matchWildcard(text: string, pattern: string): boolean { + const regex = new RegExp('^' + pattern.split('*').map(escapeRegex).join('.*') + '$'); + return regex.test(text); +} + +function escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} From 78b960174990b42c2f1459f286ca317735f28858 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Wed, 24 Dec 2025 10:18:58 +0800 Subject: [PATCH 11/38] docs: update changelog for 0.58.5 (#1595) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc4bac86..5d8d5741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to the "vscode-java-debugger" extension will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.58.5 - 2025-12-17 +### Added +- Support custom debug java agent. [#1593](https://github.com/microsoft/vscode-java-debug/pull/1593). + +### Fixed +- Fix `lspFrame.source.path` is null. [java-debug#618](https://github.com/microsoft/java-debug/pull/618). + ## 0.58.4 - 2025-12-09 ### Added - Add command to manage breakpoint exception types in command palette. [#1566](https://github.com/microsoft/vscode-java-debug/pull/1566). Thanks to [Roland Schaer](https://github.com/roele) for contribution. diff --git a/package.json b/package.json index 6b7ba673..04bef07c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-debug", "displayName": "Debugger for Java", "description": "A lightweight Java debugger for Visual Studio Code", - "version": "0.58.4", + "version": "0.58.5", "publisher": "vscjava", "preview": false, "aiKey": "67d4461e-ccba-418e-8082-1bd0acfe8516", From 359a857ccbf6b493638863f25510121ec83bc004 Mon Sep 17 00:00:00 2001 From: mozhuanzuojing <63572041+mozhuanzuojing@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:03:05 +0800 Subject: [PATCH 12/38] ci: Update build.yml actions/*@v5 (#1594) * Update build.yml actions@v5 actions/setup-java@v1 -> actions/setup-java@v5 actions/checkout@v2 -> actions/checkout@v5 actions/setup-node@v2 -> actions/setup-node@v5 java-version: '11' -> java-version: '21' * Specify 'temurin' distribution for JDK 21 setup Added 'temurin' distribution for JDK setup in multiple places. --------- Co-authored-by: wenyt <75360946+wenytang-ms@users.noreply.github.com> --- .github/workflows/build.yml | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 659a2557..e5747134 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Setup Build Environment run: | @@ -21,13 +21,14 @@ jobs: sudo /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & sleep 3 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - java-version: '11' + java-version: '21' + distribution: 'temurin' - name: Setup Node.js environment - uses: actions/setup-node@v2 + uses: actions/setup-node@v5 with: node-version: 20 @@ -55,15 +56,16 @@ jobs: runs-on: windows-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - java-version: '11' + java-version: '21' + distribution: 'temurin' - name: Setup Node.js environment - uses: actions/setup-node@v2 + uses: actions/setup-node@v5 with: node-version: 20 @@ -91,15 +93,16 @@ jobs: runs-on: macos-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 21 + uses: actions/setup-java@v5 with: - java-version: '11' + java-version: '21' + distribution: 'temurin' - name: Setup Node.js environment - uses: actions/setup-node@v2 + uses: actions/setup-node@v5 with: node-version: 20 From 44f6383059f63c10847fa7a220ec6d148d817172 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:09:41 +0800 Subject: [PATCH 13/38] feat: update agent tools definition (#1597) --- bundled/agents/debug.agent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundled/agents/debug.agent.md b/bundled/agents/debug.agent.md index 5a0e4e42..168dd465 100644 --- a/bundled/agents/debug.agent.md +++ b/bundled/agents/debug.agent.md @@ -1,6 +1,6 @@ --- description: An expert Java debugging assistant that uses hypothesis-driven debugging to find root causes systematically -tools: ['execute/getTerminalOutput', 'execute/runInTerminal', 'read/problems', 'read/readFile', 'read/terminalLastCommand', 'search', 'runCommands/getTerminalOutput', 'runCommands/runInTerminal', 'problems', 'vscjava.vscode-java-debug/debugJavaApplication', 'vscjava.vscode-java-debug/setJavaBreakpoint', 'vscjava.vscode-java-debug/debugStepOperation', 'vscjava.vscode-java-debug/getDebugVariables', 'vscjava.vscode-java-debug/getDebugStackTrace', 'vscjava.vscode-java-debug/evaluateDebugExpression', 'vscjava.vscode-java-debug/getDebugThreads', 'vscjava.vscode-java-debug/getThreadDump', 'vscjava.vscode-java-debug/removeJavaBreakpoints', 'vscjava.vscode-java-debug/stopDebugSession', 'vscjava.vscode-java-debug/getDebugSessionInfo'] +tools: ['execute/getTerminalOutput', 'execute/runInTerminal', 'read/problems', 'read/readFile', 'read/terminalLastCommand', 'search', 'vscjava.vscode-java-debug/debugJavaApplication', 'vscjava.vscode-java-debug/setJavaBreakpoint', 'vscjava.vscode-java-debug/debugStepOperation', 'vscjava.vscode-java-debug/getDebugVariables', 'vscjava.vscode-java-debug/getDebugStackTrace', 'vscjava.vscode-java-debug/evaluateDebugExpression', 'vscjava.vscode-java-debug/getDebugThreads', 'vscjava.vscode-java-debug/removeJavaBreakpoints', 'vscjava.vscode-java-debug/stopDebugSession', 'vscjava.vscode-java-debug/getDebugSessionInfo'] --- # Java Debugging Agent From 279a176ac74f98665f7b61d286810ba87c90e1fd Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 6 Jan 2026 14:12:09 +0800 Subject: [PATCH 14/38] Remove JavaDebug agent from package.json (#1599) Removed JavaDebug agent configuration from package.json. --- package.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/package.json b/package.json index 04bef07c..8c517fce 100644 --- a/package.json +++ b/package.json @@ -1331,13 +1331,6 @@ "required": [] } } - ], - "chatAgents": [ - { - "name": "JavaDebug", - "path": "./bundled/agents/debug.agent.md", - "description": "Custome Agent for debugging Java applications." - } ] }, "scripts": { @@ -1374,4 +1367,4 @@ "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" } -} \ No newline at end of file +} From 799f165a91b6aabd94c9ba3672e888549344df32 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 6 Jan 2026 14:21:12 +0800 Subject: [PATCH 15/38] Revise CHANGELOG for version 0.58.5 (#1600) Updated the release date for version 0.58.5 and removed the added feature entry. --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d8d5741..c2638cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,7 @@ All notable changes to the "vscode-java-debugger" extension will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 0.58.5 - 2025-12-17 -### Added -- Support custom debug java agent. [#1593](https://github.com/microsoft/vscode-java-debug/pull/1593). - +## 0.58.5 - 2026-01-06 ### Fixed - Fix `lspFrame.source.path` is null. [java-debug#618](https://github.com/microsoft/java-debug/pull/618). From 00a559976eea74bcc43a7ee837938f8f213b11d9 Mon Sep 17 00:00:00 2001 From: tthornton3-chwy <83608949+tthornton3-chwy@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:08:40 -0500 Subject: [PATCH 16/38] support string classpath splitting (#1559) * support string cp splitting * finally fix this :P --------- Co-authored-by: Changyong Gong --- src/configurationProvider.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/configurationProvider.ts b/src/configurationProvider.ts index 5b4ce384..92db8e11 100644 --- a/src/configurationProvider.ts +++ b/src/configurationProvider.ts @@ -471,14 +471,16 @@ export class JavaDebugConfigurationProvider implements vscode.DebugConfiguration const paths: string[] = []; let replaced: boolean = false; for (const p of pathArray) { - if (pathVariables.includes(p)) { - if (!replaced) { - paths.push(...resolvedPaths); - replaced = true; + for (const splitPath of p.split(process.platform === 'win32' ? ';' : ':')) { + if (pathVariables.includes(splitPath)) { + if (!replaced) { + paths.push(...resolvedPaths); + replaced = true; + } + continue; } - continue; + paths.push(splitPath); } - paths.push(p); } return this.filterExcluded(folder, paths); } From 874fb4a402b1a56477c9f317650ffcd6aa677478 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Thu, 29 Jan 2026 12:35:21 +0800 Subject: [PATCH 17/38] feat: support pause all threads when hit breakpoints (#1603) * feat: support pause all threads when hit breakpoints * fix: set suspend all default to false * fix: CI error * fix: remove DA tracker * docs: update suspend policy setting description --- README.md | 1 + package-lock.json | 4 ++-- package.json | 5 +++++ package.nls.es.json | 3 ++- package.nls.it.json | 3 ++- package.nls.json | 3 ++- package.nls.zh-cn.json | 3 ++- package.nls.zh-tw.json | 3 ++- 8 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 53f5f514..767764ad 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ See [Language Model Tool Documentation](bundled/agents/README.md) for more detai - `off` - `java.debug.settings.vmArgs`: The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json. - `java.debug.settings.debugSupportOnDecompiledSource`: [Experimental]: Enable debugging support on the decompiled source code. Be aware that this feature may affect the loading speed of Call Stack Viewlet. You also need [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java)@1.20.0 or higher to use this feature. +- `java.debug.settings.suspendAllThreads`: Suspend all threads when hitting a breakpoint or stopping for an exception. Takes effect only for new debug sessions; changes during a running session don’t apply. - `java.silentNotification`: Controls whether notifications can be used to report progress. If true, use status bar to report progress instead. Defaults to `false`. > Pro Tip: The documentation [Configuration.md](https://github.com/microsoft/vscode-java-debug/blob/master/Configuration.md) provides lots of samples to demonstrate how to use these debug configurations, recommend to take a look. diff --git a/package-lock.json b/package-lock.json index 4719757b..d674fb65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-java-debug", - "version": "0.58.4", + "version": "0.58.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-java-debug", - "version": "0.58.4", + "version": "0.58.5", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "compare-versions": "^4.1.4", diff --git a/package.json b/package.json index 8c517fce..2432f560 100644 --- a/package.json +++ b/package.json @@ -984,6 +984,11 @@ "description": "%java.debugger.configuration.debugSupportOnDecompiledSource.description%", "default": "on" }, + "java.debug.settings.suspendAllThreads": { + "type": "boolean", + "description": "%java.debugger.configuration.suspendAllThreads.description%", + "default": false + }, "java.silentNotification": { "type": "boolean", "description": "%java.debugger.configuration.silentNotification%", diff --git a/package.nls.es.json b/package.nls.es.json index eff5c8b3..06b87c18 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -58,5 +58,6 @@ "java.debugger.configuration.exceptionBreakpoint.skipClasses": "Omitir las clases especificadas al parar por una excepción. Puedes usar las variables incorporadas como '$JDK' y '$Libraries' para omitir un grupo de clases, o añadir una expresión específica de nombre de clase, por ejemplo, java.*, *.Pepe", "java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "El máximo número de variables o campos que pueden ser solicitados en una solicitud de JDWP. Cuanto más alto sea el valor, menos frecuentemente se solicitará la depuración al expandir la vista de la variable. Además, un número grande puede causar un límite de tiempo de espera de la solicitud JDWP.", "java.debugger.configuration.jdwp.requestTimeout.description": "El límite de tiempo (ms) de la solicitud de JDWP cuando el depurador se comunica con el JVM objetivo.", - "java.debugger.configuration.vmArgs.description": "Los argumentos por defecto de la VM para lanzar el programa Java. Por ejemplo, usa '-Xmx1G -ea' para aumentar el tamaño de la pila a 1GB y habilitar los asertos. Si quieres personalizar los argumentos de la máquina virtual para una sesión de depuración específica, modifica la configuración de 'vmArgs' en launch.json." + "java.debugger.configuration.vmArgs.description": "Los argumentos por defecto de la VM para lanzar el programa Java. Por ejemplo, usa '-Xmx1G -ea' para aumentar el tamaño de la pila a 1GB y habilitar los asertos. Si quieres personalizar los argumentos de la máquina virtual para una sesión de depuración específica, modifica la configuración de 'vmArgs' en launch.json.", + "java.debugger.configuration.suspendAllThreads.description": "Suspenda todos los hilos al alcanzar un punto de interrupción o detenerse por una excepción. Solo tiene efecto en nuevas sesiones de depuración; los cambios durante una sesión en ejecución no se aplican." } \ No newline at end of file diff --git a/package.nls.it.json b/package.nls.it.json index e7654964..9279a2bd 100644 --- a/package.nls.it.json +++ b/package.nls.it.json @@ -5,5 +5,6 @@ "java.debugger.configuration.showStaticVariables.description": "Mostra variabili statiche nella scheda \"variabili\".", "java.debugger.configuration.showQualifiedNames.description": "Mostra nome completo delle classi nella scheda \"variabili\".", "java.debugger.configuration.maxStringLength.description": "Lunghezza massima delle stringhe visualizzate nella scheda \"Variabili\" o \"Console di Debug\", stringhe più lunghe di questo numero verranno tagliate, se 0 nessun taglio viene eseguito.", - "java.debugger.configuration.enableRunDebugCodeLens.description": "Abilitare i provider di lenti di codice run e debug sui metodi principali." + "java.debugger.configuration.enableRunDebugCodeLens.description": "Abilitare i provider di lenti di codice run e debug sui metodi principali.", + "java.debugger.configuration.suspendAllThreads.description": "Sospende tutti i thread quando si raggiunge un punto di interruzione o ci si ferma per un'eccezione. Ha effetto solo nelle nuove sessioni di debug; le modifiche durante una sessione in esecuzione non si applicano." } diff --git a/package.nls.json b/package.nls.json index 3bd72991..8634a297 100644 --- a/package.nls.json +++ b/package.nls.json @@ -73,5 +73,6 @@ "java.debugger.configuration.vmArgs.description": "The default VM arguments to launch the Java program. Eg. Use '-Xmx1G -ea' to increase the heap size to 1GB and enable assertions. If you want to customize the VM arguments for a specific debug session, please modify the 'vmArgs' config in launch.json.", "java.debugger.configuration.silentNotification": "Controls whether notifications can be used to report progress. If true, use status bar to report progress instead.", "java.debugger.configuration.jdwp.async.description": "Experimental: Controls whether the debugger is allowed to send JDWP commands asynchronously. Async mode can improve remote debugging response speed on high-latency networks.", - "java.debugger.configuration.debugSupportOnDecompiledSource.description": "[Experimental]: Enable debugging support on the decompiled source code. Be aware that this feature may affect the loading speed of Call Stack Viewlet." + "java.debugger.configuration.debugSupportOnDecompiledSource.description": "[Experimental]: Enable debugging support on the decompiled source code. Be aware that this feature may affect the loading speed of Call Stack Viewlet.", + "java.debugger.configuration.suspendAllThreads.description": "Suspend all threads when hitting a breakpoint or stopping for an exception. Takes effect only for new debug sessions; changes during a running session don’t apply." } diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 1ca64e8f..75d1406a 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -70,5 +70,6 @@ "java.debugger.configuration.vmArgs.description": "启动Java程序的默认VM参数。例如,使用'-Xmx1G -ea'将堆大小增加到1GB并启用断言。如果要为特定的调试会话定制VM参数,请修改launch.json中的'vmArgs'配置。", "java.debugger.configuration.silentNotification": "控制是否可以使用通知来报告进度。如果为真,则使用状态栏来报告进度。", "java.debugger.configuration.jdwp.async.description": "实验性的:控制是否允许调试器以异步方式发送JDWP命令。异步模式可以提高高延迟网络上的远程调试响应速度。", - "java.debugger.configuration.debugSupportOnDecompiledSource.description": "[实验性的]: 在反编译的源代码上启用调试支持。请注意,该功能可能会影响Call Stack试图的加载速度。" + "java.debugger.configuration.debugSupportOnDecompiledSource.description": "[实验性的]: 在反编译的源代码上启用调试支持。请注意,该功能可能会影响Call Stack试图的加载速度。", + "java.debugger.configuration.suspendAllThreads.description": "当命中断点或因异常停止时暂停所有线程。仅对新的调试会话生效;在运行中的会话内更改不会生效。" } \ No newline at end of file diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 9ded8e9d..4261c873 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -66,5 +66,6 @@ "java.debugger.configuration.jdwp.limitOfVariablesPerJdwpRequest.description": "一次 JDWP 請求中可以請求的變數或欄位的最大數量。該值越高,在展開變數視圖時,請求偵錯對象的頻率就越低。同時數量過大也會導致 JDWP 請求超時。", "java.debugger.configuration.jdwp.requestTimeout.description": "偵錯器與目標 JVM 連線時 JDWP 請求的超時時間(ms)。", "java.debugger.configuration.vmArgs.description": "啟動 Java 程式的預設 VM 參數。例如,使用 '-Xmx1G -ea' 將堆大小增加到 1GB 並啟用斷言。如果要為特定的偵錯會話定製 VM 參數,請修改launch.json 中的 'vmArgs' 設定。", - "java.debugger.configuration.silentNotification": "控制是否可以使用通知來回報進度。如果為真,則使用狀態欄來回報進度。" + "java.debugger.configuration.silentNotification": "控制是否可以使用通知來回報進度。如果為真,則使用狀態欄來回報進度。", + "java.debugger.configuration.suspendAllThreads.description": "當命中斷點或因異常停止時暫停所有執行緒。僅對新的偵錯工作階段生效;在執行中的工作階段內變更不會生效。" } \ No newline at end of file From 7c037fcb962906563d42ab9cfcf7f85a150a1be0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:41:06 +0800 Subject: [PATCH 18/38] Bump webpack from 5.95.0 to 5.105.0 (#1608) Bumps [webpack](https://github.com/webpack/webpack) from 5.95.0 to 5.105.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack/compare/v5.95.0...v5.105.0) --- updated-dependencies: - dependency-name: webpack dependency-version: 5.105.0 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 961 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 520 insertions(+), 443 deletions(-) diff --git a/package-lock.json b/package-lock.json index d674fb65..c0e16053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "ts-loader": "^9.5.1", "tslint": "^6.1.3", "typescript": "^4.9.5", - "webpack": "^5.95.0", + "webpack": "^5.105.0", "webpack-cli": "^4.10.0" }, "engines": { @@ -70,17 +70,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -92,19 +88,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -112,15 +99,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -241,10 +228,30 @@ "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "node_modules/@types/glob": { @@ -330,148 +337,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -524,9 +531,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -535,13 +542,16 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/agent-base": { @@ -557,28 +567,48 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, "peerDependencies": { - "ajv": "^6.9.1" + "ajv": "^8.8.2" } }, "node_modules/ansi-colors": { @@ -662,6 +692,15 @@ } ] }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -727,9 +766,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -746,10 +785,11 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -810,9 +850,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001676", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz", - "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true, "funding": [ { @@ -1100,9 +1140,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.49", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", - "integrity": "sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true }, "node_modules/emoji-regex": { @@ -1112,13 +1152,13 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -1137,9 +1177,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true }, "node_modules/escalade": { @@ -1234,11 +1274,21 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -1666,9 +1716,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/jszip": { @@ -1702,12 +1752,16 @@ } }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { @@ -1876,9 +1930,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "node_modules/normalize-path": { @@ -2143,15 +2197,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2209,6 +2254,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -2270,14 +2324,15 @@ "dev": true }, "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -2483,12 +2538,16 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tas-client": { @@ -2497,13 +2556,13 @@ "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -2515,16 +2574,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -2796,9 +2855,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -2816,7 +2875,7 @@ ], "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -2825,15 +2884,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2916,9 +2966,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -2929,34 +2979,36 @@ } }, "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -3045,9 +3097,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "engines": { "node": ">=10.13.0" @@ -3274,13 +3326,12 @@ "dev": true }, "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, @@ -3290,16 +3341,10 @@ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", @@ -3307,15 +3352,15 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3424,10 +3469,30 @@ "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" }, + "@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "@types/glob": { @@ -3506,148 +3571,148 @@ } }, "@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -3687,15 +3752,15 @@ "dev": true }, "acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, - "acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "requires": {} }, @@ -3709,23 +3774,34 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "ajv": "^8.0.0" } }, "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "requires": {} + "requires": { + "fast-deep-equal": "^3.1.3" + } }, "ansi-colors": { "version": "4.1.3", @@ -3776,6 +3852,12 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true + }, "binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3831,15 +3913,16 @@ "dev": true }, "browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "buffer": { @@ -3871,9 +3954,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001676", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz", - "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true }, "chalk": { @@ -4078,9 +4161,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.5.49", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", - "integrity": "sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true }, "emoji-regex": { @@ -4090,13 +4173,13 @@ "dev": true }, "enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" } }, "envinfo": { @@ -4106,9 +4189,9 @@ "dev": true }, "es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true }, "escalade": { @@ -4174,10 +4257,10 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true }, "fastest-levenshtein": { @@ -4478,9 +4561,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "jszip": { @@ -4511,9 +4594,9 @@ } }, "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true }, "locate-path": { @@ -4642,9 +4725,9 @@ "dev": true }, "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "normalize-path": { @@ -4832,12 +4915,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4886,6 +4963,12 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4929,14 +5012,15 @@ "dev": true }, "schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" } }, "semver": { @@ -5080,9 +5164,9 @@ "dev": true }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true }, "tas-client": { @@ -5091,28 +5175,28 @@ "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" }, "terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" } }, "terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" } }, "to-regex-range": { @@ -5301,22 +5385,13 @@ "dev": true }, "update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "requires": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" + "picocolors": "^1.1.1" } }, "util-deprecate": { @@ -5390,9 +5465,9 @@ } }, "watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -5400,34 +5475,36 @@ } }, "webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "requires": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" } }, "webpack-cli": { @@ -5470,9 +5547,9 @@ } }, "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true }, "which": { diff --git a/package.json b/package.json index 2432f560..97e00675 100644 --- a/package.json +++ b/package.json @@ -1359,7 +1359,7 @@ "ts-loader": "^9.5.1", "tslint": "^6.1.3", "typescript": "^4.9.5", - "webpack": "^5.95.0", + "webpack": "^5.105.0", "webpack-cli": "^4.10.0" }, "dependencies": { From 9304faaf18ad81acb18b4f2058976811d0949faa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:24:34 +0800 Subject: [PATCH 19/38] Bump lodash from 4.17.21 to 4.17.23 (#1604) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0e16053..a780d905 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.14.0", "vscode-languageclient": "6.0.0-next.9", @@ -1780,9 +1780,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -4609,9 +4609,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "log-symbols": { "version": "4.1.0", diff --git a/package.json b/package.json index 97e00675..38de66c8 100644 --- a/package.json +++ b/package.json @@ -1365,7 +1365,7 @@ "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.14.0", "vscode-languageclient": "6.0.0-next.9", From 014e8b65317211e71fd938f4d312050d5b632740 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:48:43 +0800 Subject: [PATCH 20/38] Bump minimatch (#1615) Bumps and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together. Updates `minimatch` from 5.1.6 to 5.1.9 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v5.1.6...v5.1.9) Updates `minimatch` from 3.1.2 to 3.1.5 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v5.1.6...v5.1.9) --- updated-dependencies: - dependency-name: minimatch dependency-version: 5.1.9 dependency-type: indirect - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index a780d905..46fe9962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1850,9 +1850,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2797,9 +2797,9 @@ } }, "node_modules/tslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -4661,9 +4661,9 @@ "dev": true }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -5344,9 +5344,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" From 23e5598341c819e2b41b8c126a130758e9d67e6f Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:16:20 +0800 Subject: [PATCH 21/38] ci: update VSEng-MicroBuildVSStable pool (#1617) --- .azure-pipelines/nightly.yml | 2 +- .azure-pipelines/rc.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/nightly.yml b/.azure-pipelines/nightly.yml index 4329eb7a..c3bde437 100644 --- a/.azure-pipelines/nightly.yml +++ b/.azure-pipelines/nightly.yml @@ -28,7 +28,7 @@ extends: template: azure-pipelines/MicroBuild.1ES.Official.yml@CustomPipelineTemplates parameters: pool: - name: MSEngSS-MicroBuild2022-1ES + name: VSEng-MicroBuildVSStable stages: - stage: Build jobs: diff --git a/.azure-pipelines/rc.yml b/.azure-pipelines/rc.yml index c17dd6b7..38c0c188 100644 --- a/.azure-pipelines/rc.yml +++ b/.azure-pipelines/rc.yml @@ -23,7 +23,7 @@ extends: template: azure-pipelines/MicroBuild.1ES.Official.yml@CustomPipelineTemplates parameters: pool: - name: MSEngSS-MicroBuild2022-1ES + name: VSEng-MicroBuildVSStable stages: - stage: Build jobs: From a708af00f3f86f5d30db14cbd9812d4441391b88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:35:38 +0800 Subject: [PATCH 22/38] Bump picomatch from 2.3.1 to 2.3.2 (#1618) Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46fe9962..957ce8a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2116,9 +2116,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "engines": { "node": ">=8.6" @@ -4856,9 +4856,9 @@ "dev": true }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true }, "pkg-dir": { From 6153710d53de97a7ebc76913c1851c7d0fa43ff4 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Wed, 8 Apr 2026 10:12:44 +0800 Subject: [PATCH 23/38] Bump serialize-javascript to 7.0.5 to fix GHSA-5c6j-r48x-rmvq (#1620) * Bump serialize-javascript to ^7.0.3 to fix GHSA-5c6j-r48x-rmvq * Pin serialize-javascript override to exactly 7.0.5 Agent-Logs-Url: https://github.com/microsoft/vscode-java-debug/sessions/286d5623-eb2f-4ec4-875a-7706228a6c3f Co-authored-by: chagong <831821+chagong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: chagong <831821+chagong@users.noreply.github.com> --- package-lock.json | 44 ++++++++++++-------------------------------- package.json | 3 +++ 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 957ce8a0..5017dcfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2197,15 +2197,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -2355,12 +2346,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, - "dependencies": { - "randombytes": "^2.1.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" } }, "node_modules/setimmediate": { @@ -4703,7 +4695,7 @@ "log-symbols": "^4.1.0", "minimatch": "^5.1.6", "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "7.0.5", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^6.5.1", @@ -4915,15 +4907,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, "readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5030,13 +5013,10 @@ "dev": true }, "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true }, "setimmediate": { "version": "1.0.5", @@ -5195,7 +5175,7 @@ "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "7.0.5", "terser": "^5.31.1" } }, diff --git a/package.json b/package.json index 38de66c8..70bcfe84 100644 --- a/package.json +++ b/package.json @@ -1371,5 +1371,8 @@ "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" + }, + "overrides": { + "serialize-javascript": "7.0.5" } } From 797e57c1f50aa84f1b4237fc3d7cbdf3b98507c3 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Wed, 8 Apr 2026 11:07:07 +0800 Subject: [PATCH 24/38] Upgrade lodash to ^4.18.0 to fix CVE-2026-4800 and CVE-2026-2950 (#1621) - CVE-2026-4800 (high): Code Injection via _.template imports key names (GHSA-r5fr-rjxr-66jc) - CVE-2026-2950 (medium): Prototype Pollution via array path bypass in _.unset and _.omit (GHSA-f23m-r3pf-42rh) Both vulnerabilities are fixed in lodash 4.18.0. --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5017dcfe..5cae9fdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.23", + "lodash": "^4.18.0", "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.14.0", "vscode-languageclient": "6.0.0-next.9", @@ -1780,9 +1780,10 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -4601,9 +4602,9 @@ } }, "lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "log-symbols": { "version": "4.1.0", diff --git a/package.json b/package.json index 70bcfe84..b9e9b7a0 100644 --- a/package.json +++ b/package.json @@ -1365,7 +1365,7 @@ "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.23", + "lodash": "^4.18.0", "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.14.0", "vscode-languageclient": "6.0.0-next.9", From 158abb70504a277e036c08ff3faca58654099acb Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Thu, 9 Apr 2026 14:49:05 +0800 Subject: [PATCH 25/38] bump version to 0.59.0 (#1623) --- CHANGELOG.md | 13 +++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2638cc9..b0126f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to the "vscode-java-debugger" extension will be documented i The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.59.0 - 2026-04-09 +### Added +- Support pause all threads when hit breakpoints. [#1603](https://github.com/microsoft/vscode-java-debug/pull/1603). +- Support suspend all setting. [java-debug#619](https://github.com/microsoft/java-debug/pull/619). + +### Changed +- Provide graceful shutdown on debug stop action. [java-debug#620](https://github.com/microsoft/java-debug/pull/620). + +### Fixed +- Use JDTUtils.toUri() for decompiled class file URIs. [java-debug#624](https://github.com/microsoft/java-debug/pull/624). +- Handle NoSuchMethodError for isMainMethodCandidate() on older JDT. [java-debug#622](https://github.com/microsoft/java-debug/pull/622). +- Exclude Map.Entry from lazy loading to show key:value inline. [java-debug#621](https://github.com/microsoft/java-debug/pull/621). + ## 0.58.5 - 2026-01-06 ### Fixed - Fix `lspFrame.source.path` is null. [java-debug#618](https://github.com/microsoft/java-debug/pull/618). diff --git a/package-lock.json b/package-lock.json index 5cae9fdc..885a47f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-java-debug", - "version": "0.58.5", + "version": "0.59.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-java-debug", - "version": "0.58.5", + "version": "0.59.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "compare-versions": "^4.1.4", diff --git a/package.json b/package.json index b9e9b7a0..4a5650c4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-java-debug", "displayName": "Debugger for Java", "description": "A lightweight Java debugger for Visual Studio Code", - "version": "0.58.5", + "version": "0.59.0", "publisher": "vscjava", "preview": false, "aiKey": "67d4461e-ccba-418e-8082-1bd0acfe8516", From b8055aeade9d67d0e2cb515e22242f94c39f9f03 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:22:20 +0800 Subject: [PATCH 26/38] ci: fix ci (#1624) --- .azure-pipelines/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 7602a73e..aba9622d 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -44,9 +44,9 @@ extends: - checkout: self fetchTags: true - task: JavaToolInstaller@0 - displayName: Use Java 17 + displayName: Use Java 21 inputs: - versionSpec: "17" + versionSpec: "21" jdkArchitectureOption: x64 jdkSourceOption: PreInstalled - task: Npm@1 From d9ea9119334240f90ecd1e3e283fa9a45338d2cf Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:33:30 +0800 Subject: [PATCH 27/38] Fix Azure CI by specifying Node.js 20.x version (#1626) The CI pipeline was failing because it didn't specify a Node.js version, relying on the pre-installed version on the build image (likely Node 18.x). The latest @vscode/vsce uses undici which requires the File global API, only available in Node.js 20+. This caused 'ReferenceError: File is not defined' during vsce package. Add NodeTool@0 task with Node 20.x to align with the GitHub workflow and other Azure pipelines (nightly.yml, rc.yml) which already specify Node 20. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .azure-pipelines/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index aba9622d..59e230aa 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -49,6 +49,10 @@ extends: versionSpec: "21" jdkArchitectureOption: x64 jdkSourceOption: PreInstalled + - task: NodeTool@0 + displayName: Use Node 20.x + inputs: + versionSpec: 20.x - task: Npm@1 displayName: npm install inputs: From a14400bda6d080d49fc512d6359b266c9fb7e4b7 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:36:35 +0800 Subject: [PATCH 28/38] Update telemetry wrapper to 0.15.1 (#1631) * Update telemetry wrapper to 0.15.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update telemetry wrapper to 0.15.2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 230 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 120 insertions(+), 112 deletions(-) diff --git a/package-lock.json b/package-lock.json index 885a47f5..95be714b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "dotenv": "^16.4.5", "lodash": "^4.18.0", "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" @@ -115,68 +115,72 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", - "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.4.1.tgz", + "integrity": "sha512-utqwacfUkiGJROn4WC7aNdRBsRxwhNWXuqaJM2B0N0WHmv1+IhSuI7RQ3FHwxRP1dxZi/xn9aELMZ7HMStsW1w==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", - "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.4.1.tgz", + "integrity": "sha512-CkFEhDY7X8E2JLr6HsEvRiC0DaLOCsA7vlbq/9DJP65gAumgw2NnFNIAOg6Je5Geq1LDu76/nb2hP34p8eGggw==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", - "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.1.tgz", + "integrity": "sha512-QS1k6iwVwR1MznGAB1H0F9raqpevbFNbadLS5O1419pz9OEWBfF9wRQLnENCyo8QS9Q0IdiqnGAON/D8IywpWg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", - "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.1.tgz", + "integrity": "sha512-CTbD0g/68tiv2yCItsodDQBYxyHdfQkG7VhvVU8OHenukpl/7W4wEuxZuOntqhv5m9Nx/DFncbz+T83nvYTG3g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", - "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.1.tgz", + "integrity": "sha512-eXIHZ1+nvBiJgVpufBiTP801Vtr5FEwjWZioUsb44NC/z/UcsZh2MDJ1mBpjaDO73LVYUw/ZZmDCCo6Pg/61kA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" @@ -186,22 +190,23 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", - "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.1.tgz", + "integrity": "sha512-V/hSlauFp1thJa57+TMv5mAYinJAQUi4zOmDmpahnDgs8g1zrQ0D8QYDmu0Zfi+9GhoD80B4yJez2+ydJPJz2w==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.3.4", - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-channel-js": "3.4.1", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" @@ -211,22 +216,25 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.3.tgz", - "integrity": "sha512-UsF7eerLsVfid7iV1oXF80qXBwHNBeqSqfh/nPZgirRU1MACmSsj83EZKS2ViFHVfSGG6WIuXMGBP6KciXfYhA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.11.5 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", - "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", + "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==", + "license": "MIT" }, "node_modules/@types/eslint": { "version": "9.6.1", @@ -308,13 +316,14 @@ "license": "MIT" }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.7.tgz", - "integrity": "sha512-2GQbcfDUTg0QC1v0HefkHNwYrE5LYKzS3Zb0+uA6Qn1MBDzgiSh23ddOZF/JRqhqBFOG0mE70XslKSGQ5v9KwQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.1.tgz", + "integrity": "sha512-rnRRQIRCwRdbcQ0QV5ajKJRz8noEIoQA2hX9VjAlVAVB85+ClbaPNhljobFXgW31ue69FRO6KPE4XJ/lLgKt/Q==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.3.0", - "@microsoft/1ds-post-js": "^4.3.0", - "@microsoft/applicationinsights-web-basic": "^3.3.0" + "@microsoft/1ds-core-js": "^4.3.10", + "@microsoft/1ds-post-js": "^4.3.10", + "@microsoft/applicationinsights-web-basic": "^3.3.10" }, "engines": { "vscode": "^1.75.0" @@ -2892,12 +2901,13 @@ } }, "node_modules/vscode-extension-telemetry-wrapper": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.14.0.tgz", - "integrity": "sha512-EYr1hqiYVSGfupchDN405zSwuvA8V3tJ62KcLIRDr/4ongOc2AvSZ0BlRq8a0w950tadsMlXTKEheB97fZBttg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", + "integrity": "sha512-efKkHF8c4kTKyBhBH2k0bZU4drqIic2jBYw/j1ixKOEEsa/WIiuUsdrBPD5uaRIoZ/91GzNCLiiV4ckIrf581g==", + "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.6", - "uuid": "^8.3.2" + "@microsoft/applicationinsights-common": "^3.4.1", + "@vscode/extension-telemetry": "^1.2.0" } }, "node_modules/vscode-jsonrpc": { @@ -3361,62 +3371,61 @@ } }, "@microsoft/1ds-core-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", - "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.4.1.tgz", + "integrity": "sha512-utqwacfUkiGJROn4WC7aNdRBsRxwhNWXuqaJM2B0N0WHmv1+IhSuI7RQ3FHwxRP1dxZi/xn9aELMZ7HMStsW1w==", "requires": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/1ds-post-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", - "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.4.1.tgz", + "integrity": "sha512-CkFEhDY7X8E2JLr6HsEvRiC0DaLOCsA7vlbq/9DJP65gAumgw2NnFNIAOg6Je5Geq1LDu76/nb2hP34p8eGggw==", "requires": { - "@microsoft/1ds-core-js": "4.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-channel-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", - "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.1.tgz", + "integrity": "sha512-QS1k6iwVwR1MznGAB1H0F9raqpevbFNbadLS5O1419pz9OEWBfF9wRQLnENCyo8QS9Q0IdiqnGAON/D8IywpWg==", "requires": { - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-common": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", - "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.1.tgz", + "integrity": "sha512-CTbD0g/68tiv2yCItsodDQBYxyHdfQkG7VhvVU8OHenukpl/7W4wEuxZuOntqhv5m9Nx/DFncbz+T83nvYTG3g==", "requires": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-core-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", - "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.1.tgz", + "integrity": "sha512-eXIHZ1+nvBiJgVpufBiTP801Vtr5FEwjWZioUsb44NC/z/UcsZh2MDJ1mBpjaDO73LVYUw/ZZmDCCo6Pg/61kA==", "requires": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-shims": { @@ -3428,17 +3437,16 @@ } }, "@microsoft/applicationinsights-web-basic": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", - "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.1.tgz", + "integrity": "sha512-V/hSlauFp1thJa57+TMv5mAYinJAQUi4zOmDmpahnDgs8g1zrQ0D8QYDmu0Zfi+9GhoD80B4yJez2+ydJPJz2w==", "requires": { - "@microsoft/applicationinsights-channel-js": "3.3.4", - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-channel-js": "3.4.1", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/dynamicproto-js": { @@ -3450,17 +3458,17 @@ } }, "@nevware21/ts-async": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.3.tgz", - "integrity": "sha512-UsF7eerLsVfid7iV1oXF80qXBwHNBeqSqfh/nPZgirRU1MACmSsj83EZKS2ViFHVfSGG6WIuXMGBP6KciXfYhA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==", "requires": { - "@nevware21/ts-utils": ">= 0.11.5 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "@nevware21/ts-utils": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", - "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", + "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==" }, "@types/eslint": { "version": "9.6.1", @@ -3541,13 +3549,13 @@ "dev": true }, "@vscode/extension-telemetry": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.7.tgz", - "integrity": "sha512-2GQbcfDUTg0QC1v0HefkHNwYrE5LYKzS3Zb0+uA6Qn1MBDzgiSh23ddOZF/JRqhqBFOG0mE70XslKSGQ5v9KwQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.1.tgz", + "integrity": "sha512-rnRRQIRCwRdbcQ0QV5ajKJRz8noEIoQA2hX9VjAlVAVB85+ClbaPNhljobFXgW31ue69FRO6KPE4XJ/lLgKt/Q==", "requires": { - "@microsoft/1ds-core-js": "^4.3.0", - "@microsoft/1ds-post-js": "^4.3.0", - "@microsoft/applicationinsights-web-basic": "^3.3.0" + "@microsoft/1ds-core-js": "^4.3.10", + "@microsoft/1ds-post-js": "^4.3.10", + "@microsoft/applicationinsights-web-basic": "^3.3.10" } }, "@vscode/test-electron": { @@ -5387,12 +5395,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vscode-extension-telemetry-wrapper": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.14.0.tgz", - "integrity": "sha512-EYr1hqiYVSGfupchDN405zSwuvA8V3tJ62KcLIRDr/4ongOc2AvSZ0BlRq8a0w950tadsMlXTKEheB97fZBttg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", + "integrity": "sha512-efKkHF8c4kTKyBhBH2k0bZU4drqIic2jBYw/j1ixKOEEsa/WIiuUsdrBPD5uaRIoZ/91GzNCLiiV4ckIrf581g==", "requires": { - "@vscode/extension-telemetry": "^0.9.6", - "uuid": "^8.3.2" + "@microsoft/applicationinsights-common": "^3.4.1", + "@vscode/extension-telemetry": "^1.2.0" } }, "vscode-jsonrpc": { diff --git a/package.json b/package.json index 4a5650c4..0576a324 100644 --- a/package.json +++ b/package.json @@ -1367,7 +1367,7 @@ "dotenv": "^16.4.5", "lodash": "^4.18.0", "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" From 0db5f15cb01d6a864350b5dd28012bc5c50bc2ce Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 28 Apr 2026 15:33:39 +0800 Subject: [PATCH 29/38] Remove uuid dependency, use built-in crypto.randomUUID() (#1632) Replace the uuid package with Node.js built-in crypto.randomUUID(). This removes the uuid dependency entirely, addressing the Dependabot alert for uuid v8 and avoiding compatibility issues with uuid v14+. --- package-lock.json | 14 -------------- package.json | 1 - src/progressImpl.ts | 4 ++-- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95be714b..f3eab708 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "compare-versions": "^4.1.4", "dotenv": "^16.4.5", "lodash": "^4.18.0", - "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", @@ -2892,14 +2891,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vscode-extension-telemetry-wrapper": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", @@ -5389,11 +5380,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, "vscode-extension-telemetry-wrapper": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", diff --git a/package.json b/package.json index 0576a324..715551f6 100644 --- a/package.json +++ b/package.json @@ -1366,7 +1366,6 @@ "compare-versions": "^4.1.4", "dotenv": "^16.4.5", "lodash": "^4.18.0", - "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", diff --git a/src/progressImpl.ts b/src/progressImpl.ts index 6ee2a98c..48626936 100644 --- a/src/progressImpl.ts +++ b/src/progressImpl.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { v4 } from "uuid"; +import * as crypto from "crypto"; import { CancellationToken, CancellationTokenSource, Disposable, EventEmitter, ProgressLocation, StatusBarAlignment, StatusBarItem, window, workspace } from "vscode"; import { IProgressProvider, IProgressReporter } from "./progressAPI"; const STATUS_COMMAND: string = "java.show.server.task.status"; class ProgressReporter implements IProgressReporter { - private _id: string = v4(); + private _id: string = crypto.randomUUID(); private _jobName: string; private _progressLocation: ProgressLocation | { viewId: string }; private _cancellable: boolean = false; From 3df9d97374130fb4e07cb2916f0eaad373e965e1 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Fri, 8 May 2026 09:24:42 +0800 Subject: [PATCH 30/38] fix: always prepend separator when appending noConfigScripts to PATH (#1637) (#1641) * fix: always prepend separator when appending noConfigScripts to PATH EnvironmentVariableCollection.append() performs a literal string concatenation and does not insert a path separator. The previous heuristic checked process.env.PATH on the extension host and skipped the separator when that PATH already ended with one. However, the PATH used by the integrated terminal can differ from the extension host's PATH, so this check could incorrectly drop the separator and glue the noConfigScripts directory onto the last entry of the user's PATH (e.g. C:\Program Files\jreleaser\c:\Users\...\noConfigScripts). Always prepend the separator. A trailing empty PATH entry (if the user's PATH already ended with one) is harmless on both Windows and POSIX shells. Fixes #1637 * test: add unit tests for noConfigScripts PATH append helper Extract the PATH append-value computation into a vscode-free src/pathUtil.ts module so it can be unit-tested in plain Node mocha. Add test/pathUtil.test.ts covering: - Correct separator on Windows (;), Linux (:), and macOS (:). - The appended value always starts with a path separator (regression guard for #1637). - The scripts directory remains a standalone PATH entry when the user's PATH last entry has no trailing separator (the exact scenario reported in #1637, e.g. C:\Program Files\jreleaser\\). - A trailing separator on the user's PATH only produces a harmless empty entry, never glues another entry onto our scripts dir. - The scripts directory is preserved verbatim at the end of the value. All 9 tests pass under plain mocha; the file is also picked up by the existing Electron-based test runner via the **/**.test.js glob. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * style: trim verbose comments in pathUtil and tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/noConfigDebugInit.ts | 10 ++----- src/pathUtil.ts | 19 ++++++++++++ test/pathUtil.test.ts | 65 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 src/pathUtil.ts create mode 100644 test/pathUtil.test.ts diff --git a/src/noConfigDebugInit.ts b/src/noConfigDebugInit.ts index b0da4f01..2f84ee6b 100644 --- a/src/noConfigDebugInit.ts +++ b/src/noConfigDebugInit.ts @@ -8,6 +8,7 @@ import * as vscode from 'vscode'; import { sendInfo, sendError } from "vscode-extension-telemetry-wrapper"; import { getJavaHome } from "./utility"; +import { buildNoConfigPathAppendValue } from "./pathUtil"; /** * Registers the configuration-less debugging setup for the extension. @@ -91,14 +92,7 @@ export async function registerNoConfigDebug( } const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); - const pathSeparator = process.platform === 'win32' ? ';' : ':'; - - // Check if the current PATH already ends with a path separator to avoid double separators - const currentPath = process.env.PATH || ''; - const needsSeparator = currentPath.length > 0 && !currentPath.endsWith(pathSeparator); - const pathValueToAppend = needsSeparator ? `${pathSeparator}${noConfigScriptsDir}` : noConfigScriptsDir; - - collection.append('PATH', pathValueToAppend); + collection.append('PATH', buildNoConfigPathAppendValue(noConfigScriptsDir)); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written const fileSystemWatcher = vscode.workspace.createFileSystemWatcher( diff --git a/src/pathUtil.ts b/src/pathUtil.ts new file mode 100644 index 00000000..a9f680b3 --- /dev/null +++ b/src/pathUtil.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Builds the value to append to PATH for the noConfigScripts directory. + * + * `vscode.EnvironmentVariableCollection.append()` does literal string + * concatenation, so we always prepend a separator to avoid gluing our + * directory onto the last entry of the user's PATH (see issue #1637). + * + * @param platform defaults to `process.platform`; injectable for tests. + */ +export function buildNoConfigPathAppendValue( + scriptsDir: string, + platform: NodeJS.Platform = process.platform, +): string { + const pathSeparator = platform === 'win32' ? ';' : ':'; + return `${pathSeparator}${scriptsDir}`; +} diff --git a/test/pathUtil.test.ts b/test/pathUtil.test.ts new file mode 100644 index 00000000..9f76c8f2 --- /dev/null +++ b/test/pathUtil.test.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as assert from "assert"; + +import { buildNoConfigPathAppendValue } from "../src/pathUtil"; + +// Regression tests for issue #1637. +suite("buildNoConfigPathAppendValue", () => { + + const winDir = "C:\\Users\\me\\.vscode\\extensions\\vscjava.vscode-java-debug-0.59.0\\bundled\\scripts\\noConfigScripts"; + const posixDir = "/home/me/.vscode/extensions/vscjava.vscode-java-debug-0.59.0/bundled/scripts/noConfigScripts"; + + test("uses ';' as separator on Windows", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.strictEqual(result, `;${winDir}`); + }); + + test("uses ':' as separator on Linux", () => { + const result = buildNoConfigPathAppendValue(posixDir, "linux"); + assert.strictEqual(result, `:${posixDir}`); + }); + + test("uses ':' as separator on macOS", () => { + const result = buildNoConfigPathAppendValue(posixDir, "darwin"); + assert.strictEqual(result, `:${posixDir}`); + }); + + test("always starts with a path separator (Windows)", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.ok(result.startsWith(";")); + }); + + test("always starts with a path separator (POSIX)", () => { + const result = buildNoConfigPathAppendValue(posixDir, "linux"); + assert.ok(result.startsWith(":")); + }); + + test("never collapses scriptsDir into the previous PATH entry on Windows", () => { + // #1637 scenario: last user PATH entry has no trailing separator. + const userPath = "C:\\foo;C:\\Program Files\\jreleaser\\"; + const entries = (userPath + buildNoConfigPathAppendValue(winDir, "win32")).split(";"); + assert.ok(entries.includes("C:\\Program Files\\jreleaser\\")); + assert.ok(entries.includes(winDir)); + }); + + test("never collapses scriptsDir into the previous PATH entry on POSIX", () => { + const userPath = "/usr/bin:/opt/jreleaser/bin"; + const entries = (userPath + buildNoConfigPathAppendValue(posixDir, "linux")).split(":"); + assert.ok(entries.includes("/opt/jreleaser/bin")); + assert.ok(entries.includes(posixDir)); + }); + + test("yields only an empty (harmless) entry when the user's PATH already ends with a separator", () => { + const userPath = "C:\\foo;C:\\bar;"; + const entries = (userPath + buildNoConfigPathAppendValue(winDir, "win32")).split(";"); + assert.ok(entries.includes(winDir)); + assert.ok(!entries.some((e) => e !== winDir && e.endsWith(winDir))); + }); + + test("scriptsDir appears unchanged at the end of the appended value", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.ok(result.endsWith(winDir)); + }); +}); From da84f11bef62cde4250f59378b2443436e1ab5a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 16:10:31 +0800 Subject: [PATCH 31/38] Bump fast-uri from 3.1.0 to 3.1.2 (#1642) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3eab708..9254cb47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1283,9 +1283,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -4250,9 +4250,9 @@ "dev": true }, "fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true }, "fastest-levenshtein": { From 1bf69fee45a28ab2aea774bbb6ba4dd032cd43cd Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Wed, 20 May 2026 16:02:19 +0800 Subject: [PATCH 32/38] feat(copilot): add chat skills, instructions and when-gates for language model tools (#1643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate all 10 language model tools with `when` clauses so they only register when the Java Language Server is ready, and (for active-debug-session-only tools) when an active `java` debug session is in progress. This avoids loading failures and reduces the noise Copilot sees from this extension's tool catalog when the tools cannot actually run. - Contribute a `chatInstructions` file (`javaDebugContext.instructions.md`) with a keyword-rich, on-demand description (no `applyTo`, to avoid burning context on every Java edit) that tells Copilot to activate the deferred Java debug tools via `tool_search_tool_regex` and routes the user request to one of the two skills below. - Contribute two `chatSkills`, split by user-habit telemetry (launch/stop is ~52% of tool usage; inspection/step is ~28%): - `java-launch-troubleshooting` — start/stop a Java program and diagnose launch failures (mainClass missing, classpath, build errors, project not detected). Gated by `javaLSReady` so it is discoverable any time in a Java workspace. - `java-debug-inspection` — inspect variables, walk the stack, list threads, evaluate expressions, step in/over/out, continue, and manage breakpoints in an active Java debug session. Gated by `javaLSReady && inDebugMode && debugType == 'java'` so it only appears once a Java debug session is alive — keeping it reactive, not proactive. --- package.json | 26 +++++++++ .../javaDebugContext.instructions.md | 16 +++++ .../skills/java-debug-inspection/SKILL.md | 58 +++++++++++++++++++ .../java-launch-troubleshooting/SKILL.md | 50 ++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 resources/instruments/javaDebugContext.instructions.md create mode 100644 resources/skills/java-debug-inspection/SKILL.md create mode 100644 resources/skills/java-launch-troubleshooting/SKILL.md diff --git a/package.json b/package.json index 715551f6..cba4a021 100644 --- a/package.json +++ b/package.json @@ -1011,6 +1011,7 @@ ], "icon": "$(debug-alt)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1062,6 +1063,7 @@ ], "icon": "$(debug-breakpoint)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1105,6 +1107,7 @@ ], "icon": "$(debug-step-over)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1142,6 +1145,7 @@ ], "icon": "$(symbol-variable)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1183,6 +1187,7 @@ ], "icon": "$(call-hierarchy)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1212,6 +1217,7 @@ ], "icon": "$(symbol-method)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1257,6 +1263,7 @@ ], "icon": "$(list-tree)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": {}, @@ -1275,6 +1282,7 @@ ], "icon": "$(debug-breakpoint-unverified)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1303,6 +1311,7 @@ ], "icon": "$(debug-stop)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1330,12 +1339,29 @@ ], "icon": "$(info)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": {}, "required": [] } } + ], + "chatSkills": [ + { + "path": "./resources/skills/java-launch-troubleshooting/SKILL.md", + "when": "javaLSReady" + }, + { + "path": "./resources/skills/java-debug-inspection/SKILL.md", + "when": "javaLSReady && inDebugMode && debugType == 'java'" + } + ], + "chatInstructions": [ + { + "path": "./resources/instruments/javaDebugContext.instructions.md", + "when": "javaLSReady" + } ] }, "scripts": { diff --git a/resources/instruments/javaDebugContext.instructions.md b/resources/instruments/javaDebugContext.instructions.md new file mode 100644 index 00000000..055b01b0 --- /dev/null +++ b/resources/instruments/javaDebugContext.instructions.md @@ -0,0 +1,16 @@ +--- +description: Use when the user wants to run, launch, start, debug, verify, or step through a Java program (main class, Spring Boot, JAR, Maven, Gradle); or when inspecting variables, stack frames, threads, evaluating expressions, or managing breakpoints in a Java debug session. +--- + +For Java run/launch/debug/inspection requests, prefer the Java debug language model tools over generic shell commands (`mvn exec:java`, `gradle run`, raw `java -cp …`). These tools are contributed by the `Debugger for Java` extension and are deferred — activate them with `tool_search_tool_regex` using pattern `java_breakpoint|debug` before first use, then call them by name. + +## Pick the right skill + +| User intent | Load skill | Typical tools | +|---|---|---| +| Run / launch / start / stop a Java program, diagnose launch failures (build error, classpath, mainClass missing) | `java-launch-troubleshooting` | `debug_java_application`, `get_debug_session_info`, `stop_debug_session` | +| Inspect a Java program that is already being debugged: read variables, evaluate expressions, walk the stack, step in/over/out, continue, set or remove breakpoints | `java-debug-inspection` | `get_debug_variables`, `get_debug_stack_trace`, `evaluate_debug_expression`, `get_debug_threads`, `debug_step_operation`, `set_java_breakpoint`, `remove_java_breakpoints` | + +If both apply (e.g. "launch and break on entry of `Main.foo`"), load `java-launch-troubleshooting` first, then `java-debug-inspection` after the session is active. + +Fall back to `run_in_terminal` only when `debug_java_application` returns "Java Language Server not ready" or "project not detected". diff --git a/resources/skills/java-debug-inspection/SKILL.md b/resources/skills/java-debug-inspection/SKILL.md new file mode 100644 index 00000000..354132dd --- /dev/null +++ b/resources/skills/java-debug-inspection/SKILL.md @@ -0,0 +1,58 @@ +--- +name: java-debug-inspection +description: Use when inspecting a Java program that is already being debugged — read local variables, walk the call stack, list threads, evaluate expressions, step in/over/out, continue execution, or set / remove breakpoints in an active Java debug session. NOT for starting, launching, or stopping a debug session — use `java-launch-troubleshooting` for that. +--- + +# Java Debug Inspection + +Use this skill when a Java debug session is already active (the user has launched the program with `debug_java_application` or via the VS Code Run/Debug UI) and they want to **observe or steer the running program**. Typical user phrases: + +- "what's the value of `user.id` right now?", "show me the local variables" +- "evaluate `list.size()`", "what does `service.findById(42)` return at this frame?" +- "show the stack trace", "what threads are running?", "go up one frame" +- "step in", "step over", "step out", "continue", "resume" +- "set a breakpoint at `OrderService.placeOrder`", "remove the breakpoint on line 42" + +If no debug session is active, this skill should not be used — load `java-launch-troubleshooting` first to start one. + +## Tools + +These language model tools are contributed by the `Debugger for Java` extension and are deferred. Activate them with `tool_search_tool_regex` using pattern `java_breakpoint|debug` before first use. + +| Tool | Purpose | +|---|---| +| `get_debug_variables` | Read local variables, fields, and watched expressions in the current frame. | +| `get_debug_stack_trace` | List call-stack frames for the focused (or specified) thread. | +| `get_debug_threads` | List all threads in the session with state (running, stopped, terminated). | +| `evaluate_debug_expression` | Evaluate an arbitrary Java expression in the context of the focused frame. | +| `debug_step_operation` | Step in, step over, step out, or continue. Requires the program to be paused. | +| `set_java_breakpoint` | Set a line / method / exception breakpoint. Works at any time during the session. | +| `remove_java_breakpoints` | Remove one or more breakpoints by ID or location. | + +## Preferred Workflow + +1. **Confirm session state.** If the user asks to step or evaluate an expression, the program must be paused (typically at a breakpoint). If unsure, call `get_debug_session_info` from `java-launch-troubleshooting` to check, or list threads with `get_debug_threads`. +2. **Inspect first, then act.** For "what's the value of X" — call `get_debug_variables` or `evaluate_debug_expression`. Do not guess from source code. +3. **Step / continue.** For "step over" / "step in" / "step out" / "continue", call `debug_step_operation` with the matching action. After each step, re-read variables or stack as needed. +4. **Breakpoints.** For "set a breakpoint at …", call `set_java_breakpoint` with the file URI + line, or fully qualified method signature, or exception class. For "remove the breakpoint at …", call `remove_java_breakpoints`. +5. **Report precisely.** Quote the exact value or stack frame returned by the tool — do not paraphrase. + +## Common Pitfalls + +| Symptom | Likely cause | Fix | +|---|---|---| +| `debug_step_operation` returns "thread is not paused" | The program is running, not stopped at a breakpoint | Ask the user to add a breakpoint first, or wait for the next stop event | +| `evaluate_debug_expression` returns `` | No focused stack frame, often because the session just resumed | Re-call `get_debug_stack_trace` to refocus a frame | +| `get_debug_variables` returns empty for a parameter | Compilation without `-g` (no local variable table) | Inform user; offer to inspect via `evaluate_debug_expression` instead | +| Step in jumps into JDK internals | Default step filters disabled | Suggest enabling `java.debug.settings.stepping.skipClasses` | + +## When NOT to Use This Skill + +- The user wants to *start* a Java program (no session yet) → use `java-launch-troubleshooting` +- The user wants to *stop* the session → use `stop_debug_session` from `java-launch-troubleshooting` +- The program is a non-Java language → do not load this skill +- The user is editing source code without an active debug session → do nothing + +## Fallback + +If a tool returns "Java Language Server not ready" or repeats the same error twice, report the raw error to the user and stop calling debug tools for the current turn. Do not retry more than twice. diff --git a/resources/skills/java-launch-troubleshooting/SKILL.md b/resources/skills/java-launch-troubleshooting/SKILL.md new file mode 100644 index 00000000..c14dc527 --- /dev/null +++ b/resources/skills/java-launch-troubleshooting/SKILL.md @@ -0,0 +1,50 @@ +--- +name: java-launch-troubleshooting +description: Use when the user wants to run, launch, start, restart, or stop a Java program (main class, Spring Boot, JAR, Maven, Gradle), or diagnose launch failures (mainClass missing, classpath unresolved, compile failure, "project not detected", `ClassNotFoundException` at startup). NOT for inspecting variables, stepping, or setting breakpoints in an already-running debug session — use `java-debug-inspection` for that. +--- + +# Java Launch Troubleshooting + +Use this skill when the user wants to **start or stop** a Java program, or when an attempted launch fails. Typical user phrases: + +- "run this main class", "start the app", "launch the Spring Boot project", "run the jar" +- "stop the debug session", "kill the running app" +- prior `run_in_terminal` failed with `ClassNotFoundException`, `mainClass is not set`, `Could not find or load main class`, `Could not resolve classpath` +- the user changed `pom.xml` / `build.gradle` and the app no longer starts + +## Tools + +These language model tools are contributed by the `Debugger for Java` extension and are deferred. Activate them with `tool_search_tool_regex` using pattern `java_breakpoint|debug` before first use. + +| Tool | Purpose | +|---|---| +| `debug_java_application` | Build + resolve classpath + start JVM. Returns precise compile and classpath errors. | +| `get_debug_session_info` | Check whether a debug session is already running and its status. | +| `stop_debug_session` | Stop a running Java debug session cleanly. | + +## Preferred Workflow + +1. **Confirm intent.** Is the user trying to *run / start / launch / stop* a Java program (use this skill) or just edit code (do not load this skill)? +2. **Check existing session.** Call `get_debug_session_info` first. If a session is already running for the target, do not launch a second one. +3. **Launch.** Call `debug_java_application` with `target` = the fully qualified main class or JAR, and `workspacePath` = the project root containing `pom.xml`, `build.gradle`, or `.classpath`. Let `skipBuild` default to `false` so the tool handles compilation. +4. **Read the error.** If `debug_java_application` fails, the error message is structured (mainClass missing, classpath unresolved, build failure with line number). Use it to suggest a fix — do not retry with `run_in_terminal`. +5. **Stop when done.** When the user says "stop", "kill it", or has the answer they need, call `stop_debug_session`. + +## Common Failure Modes + +| Symptom from `debug_java_application` | Likely cause | Suggested fix | +|---|---|---| +| `mainClass is not configured` / `mainClass missing` | Project has no `launch.json`, and the file has no `public static void main` | Ask user which class to launch, or generate `launch.json` | +| `Could not resolve classpath` | Maven/Gradle import has not completed, or `pom.xml` has unresolved dependencies | Wait for Java Language Server import, then ask user to run `Java: Clean Java Language Server Workspace` | +| `Compilation failed` with file:line | Source code has a compile error | Fix the reported error in the source file, do not retry the launch | +| `Project not detected` | `workspacePath` does not contain a build file | Re-check `workspacePath`; for multi-module projects, use the module root, not the repo root | + +## When NOT to Use This Skill + +- The user is editing or refactoring Java code without running it → do nothing +- The user is already inside a live debug session and wants to inspect variables, evaluate expressions, walk the stack, step, or set / remove breakpoints → use `java-debug-inspection` instead, do not re-launch +- The program is a non-Java language → do not load this skill + +## Fallback + +If `debug_java_application` returns `Java Language Server not ready` or repeats the same error twice, fall back to `run_in_terminal` with the appropriate `mvn` or `gradle` command and report the raw output to the user. Do not retry the debug tool more than twice. From dcbb93fe4db63f90872d67694426ab777b8f0ea6 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 21 May 2026 09:52:45 +0800 Subject: [PATCH 33/38] Harden Language Model Tool telemetry against PII leaks (#1644) * Harden Language Model Tool telemetry against PII leaks Centralise all LMT telemetry through src/lmToolTelemetry.ts so user-supplied strings (target, expression, sessionName, file paths, class names, JVM stack traces, etc.) can no longer reach the telemetry pipeline. The new module exposes a typed sanitizedSend choke point that only accepts enums, booleans, numbers and opaque session IDs. Telemetry changes: - Drop sendError(error) on debug_java_application failure (stack trace leaked user class / method names). - Strip PII fields from every existing event: target, sessionName, currentFile, currentLine, simpleClassName, detectedClassName, error: String(error), input.reason. - Replace bare String(error) propagation with classifyError() -> ErrorCategory enum (mainClassMissing, classpathUnresolved, buildFailure, projectNotDetected, sessionAlreadyRunning, timeout, lsNotReady, noActiveSession, noSuspendedThread, noStackFrame, cancelled, other). - Add per-invoke recording for all 10 tools with outcome, errorCategory, durationMs, and a tool-specific enum (targetType / breakpointKind / stepKind / scopeType / evalContext / removeScope). The previous build only emitted telemetry on the launch tool and the session-info tool. - Add chatActivationSnapshot one-shot at registration time so we can measure adoption of the chat surfaces without per-turn cost (counts only). - evaluate_debug_expression: the expression text is NEVER logged. Only the evalContext enum and outcome are emitted. Policy: - src/lmToolTelemetry.ts is now the only file in the LMT code path allowed to call sendInfo. The top-of-file policy comment is the single source of truth for what may be logged. - The recorder is typed against ToolInvocationRecord so excess raw strings are rejected at compile time. Validated with: npm run tslint, npm run compile. * Address Copilot review on PR #1644 - classifyStep: unknown step operations now report 'unknown' instead of being silently mislabeled as 'over'. Also adds a runtime guard in debug_step_operation so an unknown operation no longer reaches commandMap[op]/executeCommand(undefined) or session.customRequest with an arbitrary string. - recordToolInvocation: introduces a private normalizeToolInvocationRecord that keeps 'outcome' and 'errorCategory' in lock-step for the six shared terminal values (cancelled / timeout / lsNotReady / noActiveSession / noSuspendedThread / noStackFrame). Fixes the case where debug_java_application returns {success:false,message:'Operation cancelled by user'} but outcome was 'failure' while errorCategory was 'cancelled'. - get_debug_stack_trace: empty-stack-frame early return now sets errorCategory='noStackFrame' alongside outcome (was only setting outcome). - recordLaunchInternal: signature is now a discriminated union (LaunchInternalEvent) instead of (operationName: string, properties: Record). Unknown event names and unexpected property keys are now rejected at compile time. Updated all 8 call sites. - elapsedTime (string from .toFixed) split from elapsedMs (number) so the telemetry value is numeric and aggregable. --- src/extension.ts | 24 +++ src/languageModelTool.ts | 308 +++++++++++++++++++++------ src/lmToolTelemetry.ts | 450 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 722 insertions(+), 60 deletions(-) create mode 100644 src/lmToolTelemetry.ts diff --git a/src/extension.ts b/src/extension.ts index e3a75eb0..36025559 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +19,7 @@ import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorF import { JavaInlineValuesProvider } from "./JavaInlineValueProvider"; import { logJavaException, logJavaInfo } from "./javaLogger"; import { registerLanguageModelTool, registerDebugSessionTools } from "./languageModelTool"; +import { recordChatActivation } from "./lmToolTelemetry"; import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServerPlugin"; import { mainClassPicker } from "./mainClassPicker"; import { pickJavaProcess } from "./processPicker"; @@ -124,6 +125,29 @@ async function registerLanguageModelToolsWhenReady(context: vscode.ExtensionCont registerLanguageModelTool(context); const debugToolsDisposables = registerDebugSessionTools(context); context.subscriptions.push(...debugToolsDisposables); + + // One-shot activation snapshot so we can track coverage of the new chat surfaces over time. + // Counts only — no user data, no file paths, no class names. + try { + const pkg = context.extension?.packageJSON as { + version?: string; + contributes?: { + languageModelTools?: unknown[]; + chatSkills?: unknown[]; + chatInstructions?: unknown[]; + }; + } | undefined; + const contrib = pkg?.contributes ?? {}; + recordChatActivation({ + javaLSReadyAtActivation: !!javaExt.isActive, + lmtCount: contrib.languageModelTools?.length ?? 0, + chatSkillsCount: contrib.chatSkills?.length ?? 0, + chatInstructionsCount: contrib.chatInstructions?.length ?? 0, + extensionVersion: pkg?.version ?? "unknown", + }); + } catch { + // Telemetry must never break activation. + } } async function subscribeToJavaExtensionEvents(): Promise { diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index ad803d25..85b28855 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -4,7 +4,20 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper"; +import { + classifyBreakpoint, + classifyError, + classifyEvalContext, + classifyRemoveScope, + classifyScopeType, + classifyStep, + classifyTarget, + ErrorCategory, + recordLaunchInternal, + recordToolInvocation, + TOOL_NAMES, + ToolOutcome, +} from "./lmToolTelemetry"; // ============================================================================ // Constants @@ -63,14 +76,20 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc const tool: LanguageModelTool = { async invoke(options: { input: DebugJavaApplicationInput }, token: vscode.CancellationToken): Promise { - sendInfo('', { - operationName: 'languageModelTool.debugJavaApplication.invoke', - target: options.input.target, - skipBuild: options.input.skipBuild?.toString() || 'false', - }); + const startedAt = Date.now(); + const targetType = classifyTarget(options.input.target); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; try { const result = await debugJavaApplication(options.input, token); + if (!result.success) { + outcome = result.status === 'timeout' ? 'timeout' : 'failure'; + errorCategory = result.success ? undefined : classifyError(result.message); + } else if (result.status === 'timeout') { + outcome = 'timeout'; + errorCategory = 'timeout'; + } // Format the message for AI - use simple text, not JSON const message = result.success @@ -82,13 +101,23 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc new (vscode as any).LanguageModelTextPart(message) ]); } catch (error) { - sendError(error as Error); + outcome = token.isCancellationRequested ? 'cancelled' : 'failure'; + errorCategory = classifyError(error); const errorMessage = error instanceof Error ? error.message : String(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Debug failed: ${errorMessage}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.DEBUG_JAVA_APPLICATION, + outcome, + errorCategory, + targetType, + skipBuild: !!options.input.skipBuild, + durationMs: Date.now() - startedAt, + }); } } }; @@ -120,10 +149,9 @@ async function debugJavaApplication( // Step 0: Cleanup any existing Java debug session to avoid port conflicts const existingSession = vscode.debug.activeDebugSession; if (existingSession && existingSession.type === 'java') { - sendInfo('', { - operationName: 'languageModelTool.cleanupExistingSession', + recordLaunchInternal({ + name: 'cleanupExistingSession', sessionId: existingSession.id, - sessionName: existingSession.name }); try { await vscode.debug.stopDebugging(existingSession); @@ -131,9 +159,9 @@ async function debugJavaApplication( await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { // Log but continue - the old session might already be dead - sendInfo('', { - operationName: 'languageModelTool.cleanupExistingSessionFailed', - error: String(error) + recordLaunchInternal({ + name: 'cleanupExistingSessionFailed', + errorCategory: classifyError(error), }); } } @@ -219,10 +247,9 @@ async function debugJavaApplication( clearTimeout(timeoutHandle); } - sendInfo('', { - operationName: 'languageModelTool.debugSessionStarted.eventBased', + recordLaunchInternal({ + name: 'debugSessionStarted.eventBased', sessionId: session.id, - sessionName: session.name }); resolve({ @@ -243,10 +270,7 @@ async function debugJavaApplication( if (!sessionStarted) { sessionDisposable.dispose(); - sendInfo('', { - operationName: 'languageModelTool.debugSessionTimeout.eventBased', - target: targetInfo - }); + recordLaunchInternal({ name: 'debugSessionTimeout.eventBased' }); resolve({ success: false, @@ -280,12 +304,13 @@ async function debugJavaApplication( // Check if debug session has started const session = vscode.debug.activeDebugSession; if (session && session.type === 'java') { - const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); + const elapsedMs = Date.now() - startTime; + const elapsedTime = (elapsedMs / 1000).toFixed(1); - sendInfo('', { - operationName: 'languageModelTool.debugSessionDetected', + recordLaunchInternal({ + name: 'debugSessionDetected', sessionId: session.id, - elapsedTime + elapsedMs, }); return { @@ -302,10 +327,9 @@ async function debugJavaApplication( } // Timeout: session not detected within 15 seconds - sendInfo('', { - operationName: 'languageModelTool.debugSessionTimeout.smartPolling', - target: targetInfo, - maxWaitTime + recordLaunchInternal({ + name: 'debugSessionTimeout.smartPolling', + maxWaitTime, }); return { @@ -584,19 +608,18 @@ function constructDebugCommand( if (!input.target.includes('.')) { const detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); if (detectedClassName) { - sendInfo('', { - operationName: 'languageModelTool.classNameDetection', - simpleClassName: input.target, - detectedClassName, - projectType + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: true, }); className = detectedClassName; } else { // No package detected - class is in default package - sendInfo('', { - operationName: 'languageModelTool.classNameDetection.noPackage', - simpleClassName: input.target, - projectType + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: false, }); } } @@ -917,6 +940,11 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 1: Set Breakpoint const setBreakpointTool: LanguageModelTool = { async invoke(options: { input: SetBreakpointInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const breakpointKind = classifyBreakpoint(options.input); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const { filePath, lineNumber, condition, hitCondition, logMessage } = options.input; @@ -944,9 +972,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to set breakpoint: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.SET_JAVA_BREAKPOINT, + outcome, + errorCategory, + breakpointKind, + durationMs: Date.now() - startedAt, + }); } } }; @@ -955,9 +993,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 2: Step Operations const stepOperationTool: LanguageModelTool = { async invoke(options: { input: StepOperationInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const stepKind = classifyStep(options.input.operation); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -975,6 +1020,14 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }; const command = commandMap[operation]; + if (!command) { + outcome = 'failure'; + errorCategory = 'other'; + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Unknown step operation: ${operation}`) + ]); + } + if (threadId !== undefined) { // For thread-specific operations, use custom request await session.customRequest(operation, { threadId }); @@ -987,9 +1040,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs new (vscode as any).LanguageModelTextPart(`✓ Executed ${operation}`) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Step operation failed: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.DEBUG_STEP_OPERATION, + outcome, + errorCategory, + stepKind, + durationMs: Date.now() - startedAt, + }); } } }; @@ -998,9 +1061,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 3: Get Variables const getVariablesTool: LanguageModelTool = { async invoke(options: { input: GetVariablesInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const scopeTypeEnum = classifyScopeType(options.input.scopeType); + const hasFilter = !!options.input.filter; + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1018,6 +1089,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs } if (!targetThreadId) { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') ]); @@ -1031,6 +1104,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }); if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + outcome = 'noStackFrame'; + errorCategory = 'noStackFrame'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No stack frame available.') ]); @@ -1075,9 +1150,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get variables: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_VARIABLES, + outcome, + errorCategory, + scopeType: scopeTypeEnum, + hasFilter, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1086,9 +1172,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 4: Get Stack Trace const getStackTraceTool: LanguageModelTool = { async invoke(options: { input: GetStackTraceInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let frameCount = 0; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1103,11 +1196,15 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }); if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + outcome = 'noStackFrame'; + errorCategory = 'noStackFrame'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('No stack frames available.') ]); } + frameCount = stackResponse.stackFrames.length; + const frames = stackResponse.stackFrames.map((frame: any, index: number) => { const location = frame.source ? `${frame.source.name}:${frame.line}` : @@ -1121,9 +1218,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get stack trace: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_STACK_TRACE, + outcome, + errorCategory, + frameCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1132,9 +1239,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 5: Evaluate Expression const evaluateExpressionTool: LanguageModelTool = { async invoke(options: { input: EvaluateExpressionInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const evalContext = classifyEvalContext(options.input.context); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1168,6 +1282,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs targetFrameId = stackResponse.stackFrames[0].id; } } catch { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Thread #${targetThreadId} is not suspended. Cannot evaluate expression.`) ]); @@ -1175,6 +1291,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs } if (!targetThreadId) { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') ]); @@ -1194,9 +1312,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Evaluation failed: ${error}`) ]); + } finally { + // NEVER log expression text (may contain user code / secrets) + recordToolInvocation({ + tool: TOOL_NAMES.EVALUATE_DEBUG_EXPRESSION, + outcome, + errorCategory, + evalContext, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1205,9 +1334,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 6: Get Threads const getThreadsTool: LanguageModelTool<{}> = { async invoke(_options: { input: {} }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let threadCount = 0; + let suspendedCount = 0; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1221,6 +1358,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ]); } + threadCount = threadsResponse.threads.length; + // Check each thread's state by trying to get its stack trace const threadInfos: string[] = []; for (const thread of threadsResponse.threads) { @@ -1236,6 +1375,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (stackResponse?.stackFrames?.length > 0) { state = '🔴 SUSPENDED'; + suspendedCount++; const topFrame = stackResponse.stackFrames[0]; if (topFrame.source) { location = ` at ${topFrame.source.name}:${topFrame.line}`; @@ -1264,9 +1404,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get threads: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_THREADS, + outcome, + errorCategory, + threadCount, + suspendedCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1275,6 +1426,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 7: Remove Breakpoints const removeBreakpointsTool: LanguageModelTool = { async invoke(options: { input: RemoveBreakpointsInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const removeScope = classifyRemoveScope(options.input); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let removedCount = 0; + try { const { filePath, lineNumber } = options.input; @@ -1283,6 +1440,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (!filePath) { // Remove all breakpoints (no active session required) const count = breakpoints.length; + removedCount = count; vscode.debug.removeBreakpoints(breakpoints); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✓ Removed all ${count} breakpoint(s).`) @@ -1304,6 +1462,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (toRemove.length > 0) { vscode.debug.removeBreakpoints(toRemove); } + removedCount = toRemove.length; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( @@ -1313,9 +1472,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to remove breakpoints: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.REMOVE_JAVA_BREAKPOINTS, + outcome, + errorCategory, + removeScope, + removedCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1323,38 +1493,46 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 9: Stop Debug Session const stopDebugSessionTool: LanguageModelTool = { - async invoke(options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { + async invoke(_options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session) { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('No active debug session to stop.') ]); } - const sessionInfo = `${session.name} (${session.type})`; - const reason = options.input.reason || 'Investigation complete'; + const sessionType = session.type; // Stop the debug session await vscode.debug.stopDebugging(session); - sendInfo('', { - operationName: 'languageModelTool.stopDebugSession', - sessionId: session.id, - sessionName: session.name, - reason - }); - return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( - `✓ Stopped debug session: ${sessionInfo}. Reason: ${reason}` + `✓ Stopped debug session (${sessionType}).` ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to stop debug session: ${error}`) ]); + } finally { + // Do NOT log session.name (may include user file path) or input.reason (free text) + recordToolInvocation({ + tool: TOOL_NAMES.STOP_DEBUG_SESSION, + outcome, + errorCategory, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1363,10 +1541,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 10: Get Debug Session Info const getDebugSessionInfoTool: LanguageModelTool = { async invoke(_options: { input: GetDebugSessionInfoInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let isPausedFlag = false; + try { const session = vscode.debug.activeDebugSession; if (!session) { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( '❌ No active debug session found.\n\n' + @@ -1457,9 +1642,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // If we can't even get threads, something is wrong // But session exists, so mark as running isPaused = false; - sendInfo('', { - operationName: 'languageModelTool.getDebugSessionInfo.threadError', - error: String(error) + recordLaunchInternal({ + name: 'getDebugSessionInfo.threadError', + errorCategory: classifyError(error), }); } @@ -1539,23 +1724,26 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs '═══════════════════════════════════════════' ].join('\n'); - sendInfo('', { - operationName: 'languageModelTool.getDebugSessionInfo', - sessionId: session.id, - sessionType: session.type, - isPaused: String(isPaused), - stoppedThreadId: String(stoppedThreadId || ''), - currentFile, - currentLine: String(currentLine) - }); + isPausedFlag = isPaused; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(message) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get debug session info: ${error}`) ]); + } finally { + // Do NOT log currentFile, currentLine, sessionName, stoppedThreadName — those are user data + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_SESSION_INFO, + outcome, + errorCategory, + isPaused: isPausedFlag, + durationMs: Date.now() - startedAt, + }); } } }; diff --git a/src/lmToolTelemetry.ts b/src/lmToolTelemetry.ts new file mode 100644 index 00000000..617e0b30 --- /dev/null +++ b/src/lmToolTelemetry.ts @@ -0,0 +1,450 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Telemetry helpers for the language-model-tool surface. + * + * POLICY: this module is the ONLY place inside the LMT code path that is + * allowed to call `sendInfo` / `sendError`. Direct calls from individual + * tool implementations are forbidden so that PII risk can be audited in + * one file. + * + * Strict rules — every contributor MUST follow these: + * + * 1. Do NOT pass user-provided strings as telemetry properties. This + * includes (non-exhaustive): + * - `target` (main class / JAR path / raw -cp args) + * - `expression` (debug expression to evaluate) + * - `condition` / `hitCondition` / `logMessage` (breakpoint inputs) + * - `filePath` / `currentFile` / source file paths + * - `currentLine` / `lineNumber` + * - `sessionName` (`launch.json` `name` field; often contains class + * or project names) + * - `reason` (user-supplied stop reason) + * - `error.message` / `error.stack` (JVM stack traces leak user + * class and method names) + * - any class name, method name, package name, or source path + * + * 2. Only enums, booleans, durations, counts, opaque session IDs (GUIDs) + * and our own extension version are allowed. + * + * 3. When classifying free-form input (e.g. error text -> errorCategory) + * the classifier function inspects the input in-memory and emits ONLY + * the enum. Unmatched values map to `'other'` / `'unknown'`. The + * original text is NEVER attached to the event. + * + * 4. New telemetry events SHOULD go through `recordToolInvocation` / + * `recordChatActivation` or a new dedicated recorder added below. + * The raw `sendInfo` API is wrapped by `sanitizedSend` here. + */ + +import { sendInfo } from "vscode-extension-telemetry-wrapper"; + +// ============================================================================ +// Enum types (the only shape telemetry properties may take) +// ============================================================================ + +export type ToolOutcome = + | 'success' + | 'failure' + | 'timeout' + | 'cancelled' + | 'lsNotReady' + | 'noActiveSession' + | 'noSuspendedThread' + | 'noStackFrame'; + +export type ErrorCategory = + | 'mainClassMissing' + | 'classpathUnresolved' + | 'buildFailure' + | 'projectNotDetected' + | 'sessionAlreadyRunning' + | 'timeout' + | 'lsNotReady' + | 'noActiveSession' + | 'noSuspendedThread' + | 'noStackFrame' + | 'cancelled' + | 'other'; + +export type TargetType = 'mainClass' | 'jar' | 'rawArgs' | 'unknown'; + +export type BreakpointKind = + | 'line' + | 'conditional' + | 'hitCount' + | 'logpoint'; + +export type StepKind = 'in' | 'out' | 'over' | 'continue' | 'pause' | 'unknown'; + +export type EvalContext = 'watch' | 'repl' | 'hover' | 'unknown'; + +export type RemoveBreakpointScope = 'all' | 'file' | 'line'; + +export type ScopeType = 'local' | 'static' | 'all' | 'unknown'; + +export const TOOL_NAMES = { + DEBUG_JAVA_APPLICATION: 'debug_java_application', + SET_JAVA_BREAKPOINT: 'set_java_breakpoint', + DEBUG_STEP_OPERATION: 'debug_step_operation', + GET_DEBUG_VARIABLES: 'get_debug_variables', + GET_DEBUG_STACK_TRACE: 'get_debug_stack_trace', + EVALUATE_DEBUG_EXPRESSION: 'evaluate_debug_expression', + GET_DEBUG_THREADS: 'get_debug_threads', + REMOVE_JAVA_BREAKPOINTS: 'remove_java_breakpoints', + STOP_DEBUG_SESSION: 'stop_debug_session', + GET_DEBUG_SESSION_INFO: 'get_debug_session_info', +} as const; + +export type ToolName = typeof TOOL_NAMES[keyof typeof TOOL_NAMES]; + +// ============================================================================ +// Classifiers — pure functions; emit ONLY enums +// ============================================================================ + +/** + * Classify the `target` parameter of `debug_java_application` into a coarse + * shape category. The original string is consumed in-memory only; the + * returned enum is the only thing that may be logged. + */ +export function classifyTarget(target: string | undefined | null): TargetType { + if (!target) { + return 'unknown'; + } + const trimmed = target.trim(); + if (!trimmed) { + return 'unknown'; + } + if (trimmed.startsWith('-')) { + return 'rawArgs'; + } + if (/\.jar(\s|$)/i.test(trimmed) || trimmed.toLowerCase().endsWith('.jar')) { + return 'jar'; + } + if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(trimmed)) { + return 'mainClass'; + } + return 'unknown'; +} + +/** + * Map an arbitrary error (Error, string, or unknown) to an ErrorCategory. + * The original message and stack trace are consumed in-memory and never + * returned. Unrecognised errors map to `'other'`. + */ +export function classifyError(err: unknown): ErrorCategory { + if (err === undefined || err === null) { + return 'other'; + } + const msg = (err instanceof Error ? err.message : String(err)).toLowerCase(); + if (!msg) { + return 'other'; + } + if (msg.includes('mainclass') && (msg.includes('not set') || msg.includes('missing') || msg.includes('not configured'))) { + return 'mainClassMissing'; + } + if (msg.includes('could not find or load main class') || msg.includes('classnotfound')) { + return 'mainClassMissing'; + } + if (msg.includes('classpath') && (msg.includes('not resolve') || msg.includes('unresolved') || msg.includes('cannot resolve'))) { + return 'classpathUnresolved'; + } + if (msg.includes('compilation') && msg.includes('fail')) { + return 'buildFailure'; + } + if (msg.includes('build failed') || msg.includes('build error')) { + return 'buildFailure'; + } + if (msg.includes('project not detected') || msg.includes('no project found')) { + return 'projectNotDetected'; + } + if (msg.includes('already running') || msg.includes('session is active')) { + return 'sessionAlreadyRunning'; + } + if (msg.includes('timeout') || msg.includes('timed out')) { + return 'timeout'; + } + if (msg.includes('language server not ready') || msg.includes('jdt.ls')) { + return 'lsNotReady'; + } + if (msg.includes('no active debug session') || msg.includes('no debug session')) { + return 'noActiveSession'; + } + if (msg.includes('not suspended') || msg.includes('thread is not paused')) { + return 'noSuspendedThread'; + } + if (msg.includes('cancel')) { + return 'cancelled'; + } + return 'other'; +} + +/** + * Classify a `set_java_breakpoint` invocation into a coarse breakpoint kind. + * The actual filePath / lineNumber / condition strings are NOT logged; this + * classifier only checks which optional inputs are present. + */ +export function classifyBreakpoint(input: { + condition?: string; + hitCondition?: string; + logMessage?: string; +}): BreakpointKind { + if (input.logMessage && input.logMessage.length > 0) { + return 'logpoint'; + } + if (input.hitCondition && input.hitCondition.length > 0) { + return 'hitCount'; + } + if (input.condition && input.condition.length > 0) { + return 'conditional'; + } + return 'line'; +} + +export function classifyStep(operation: string | undefined): StepKind { + switch (operation) { + case 'stepIn': + return 'in'; + case 'stepOut': + return 'out'; + case 'stepOver': + return 'over'; + case 'continue': + return 'continue'; + case 'pause': + return 'pause'; + default: + return 'unknown'; + } +} + +export function classifyEvalContext(context: string | undefined): EvalContext { + switch (context) { + case 'watch': + case 'repl': + case 'hover': + return context; + default: + return 'unknown'; + } +} + +export function classifyRemoveScope(input: { + filePath?: string; + lineNumber?: number; +}): RemoveBreakpointScope { + if (!input.filePath) { + return 'all'; + } + if (input.lineNumber !== undefined) { + return 'line'; + } + return 'file'; +} + +export function classifyScopeType(scopeType: string | undefined): ScopeType { + switch (scopeType) { + case 'local': + case 'static': + case 'all': + return scopeType; + default: + return 'unknown'; + } +} + +// ============================================================================ +// Recording helpers — the only entrypoints to `sendInfo` inside LMT code +// ============================================================================ + +/** Safe value types allowed as telemetry properties. */ +type SafeValue = string | number | boolean | undefined; + +/** + * Tighten what sendInfo accepts. All values must be primitive enums / + * booleans / numbers / well-known opaque IDs. Objects and arrays are + * rejected at the type level so we cannot accidentally serialise a payload + * containing user data. + */ +function sanitizedSend(properties: Record): void { + const clean: { [key: string]: string } = {}; + for (const [k, v] of Object.entries(properties)) { + if (v === undefined) { + continue; + } + clean[k] = typeof v === 'string' ? v : String(v); + } + sendInfo('', clean); +} + +export interface ToolInvocationRecord { + tool: ToolName; + outcome: ToolOutcome; + errorCategory?: ErrorCategory; + durationMs?: number; + /** + * Optional tool-specific enum fields. ONLY enums / booleans / numbers + * are accepted; the recorder itself is typed to forbid raw strings. + */ + targetType?: TargetType; + breakpointKind?: BreakpointKind; + stepKind?: StepKind; + evalContext?: EvalContext; + removeScope?: RemoveBreakpointScope; + scopeType?: ScopeType; + isPaused?: boolean; + skipBuild?: boolean; + hasFilter?: boolean; + frameCount?: number; + threadCount?: number; + suspendedCount?: number; + removedCount?: number; + /** Opaque GUID assigned by VS Code; safe to log. */ + sessionId?: string; + /** vscode-java-debug's own adapter type — value is constant `'java'`. */ + sessionType?: string; +} + +/** + * Record a single tool-invocation outcome. Replaces ad-hoc `sendInfo` + * calls inside individual tools. + * + * Before sending, the record is normalized so that `outcome` and + * `errorCategory` stay aligned for the six shared terminal values + * (cancelled / timeout / lsNotReady / noActiveSession / noSuspendedThread / + * noStackFrame). See {@link normalizeToolInvocationRecord}. + */ +export function recordToolInvocation(record: ToolInvocationRecord): void { + const normalized = normalizeToolInvocationRecord(record); + sanitizedSend({ + operationName: `languageModelTool.${normalized.tool}.invoke`, + outcome: normalized.outcome, + errorCategory: normalized.errorCategory, + durationMs: normalized.durationMs, + targetType: normalized.targetType, + breakpointKind: normalized.breakpointKind, + stepKind: normalized.stepKind, + evalContext: normalized.evalContext, + removeScope: normalized.removeScope, + scopeType: normalized.scopeType, + isPaused: normalized.isPaused, + skipBuild: normalized.skipBuild, + hasFilter: normalized.hasFilter, + frameCount: normalized.frameCount, + threadCount: normalized.threadCount, + suspendedCount: normalized.suspendedCount, + removedCount: normalized.removedCount, + sessionId: normalized.sessionId, + sessionType: normalized.sessionType, + }); +} + +/** + * Values that exist in both {@link ToolOutcome} and {@link ErrorCategory}. + * For these, the two fields must stay in lock-step so dashboard queries + * filtering on either one produce identical results. + */ +const SHARED_TERMINAL_VALUES = [ + 'cancelled', + 'timeout', + 'lsNotReady', + 'noActiveSession', + 'noSuspendedThread', + 'noStackFrame', +] as const; + +type SharedTerminal = typeof SHARED_TERMINAL_VALUES[number]; + +function isSharedTerminal(value: string | undefined): value is SharedTerminal { + return value !== undefined && (SHARED_TERMINAL_VALUES as readonly string[]).includes(value); +} + +/** + * Reconcile `outcome` and `errorCategory` for the six shared terminal + * values so downstream queries can rely on either field. Returns a NEW + * record; the input is not mutated. + * + * Rules: + * - If `errorCategory` is a shared terminal value, promote `outcome` to + * that value (callers that only set `errorCategory` get a consistent + * `outcome` for free). + * - If `outcome` is a shared terminal value and `errorCategory` is + * absent, fill it with the matching value (callers that only set + * `outcome` get a consistent `errorCategory`). + */ +function normalizeToolInvocationRecord(record: ToolInvocationRecord): ToolInvocationRecord { + let outcome: ToolOutcome = record.outcome; + let errorCategory: ErrorCategory | undefined = record.errorCategory; + + if (isSharedTerminal(errorCategory)) { + outcome = errorCategory; + } else if (isSharedTerminal(outcome) && errorCategory === undefined) { + errorCategory = outcome; + } + + return { ...record, outcome, errorCategory }; +} + +export interface ChatActivationRecord { + javaLSReadyAtActivation: boolean; + lmtCount: number; + chatSkillsCount: number; + chatInstructionsCount: number; + extensionVersion: string; +} + +/** + * Record a one-shot snapshot of the chat-activation surface at the moment + * Language Model Tools are registered. Lets us measure adoption coverage + * post-ship without per-turn cost. + */ +export function recordChatActivation(record: ChatActivationRecord): void { + sanitizedSend({ + operationName: 'languageModelTool.chatActivationSnapshot', + javaLSReadyAtActivation: record.javaLSReadyAtActivation, + lmtCount: record.lmtCount, + chatSkillsCount: record.chatSkillsCount, + chatInstructionsCount: record.chatInstructionsCount, + extensionVersion: record.extensionVersion, + }); +} + +/** + * Project type detected by the launch flow. Free-form values are + * forbidden so this stays a closed enum. + */ +export type LaunchProjectType = 'maven' | 'gradle' | 'vscode' | 'unknown'; + +/** + * Discriminated union of every launch-flow internal event the recorder + * is allowed to emit. Each variant lists its allowed properties so the + * type system rejects unknown event names and unknown property keys. + * + * Note: `sessionId` here is VS Code's opaque debug-session GUID, never + * the user-visible `launch.json` session name. + */ +export type LaunchInternalEvent = + | { name: 'cleanupExistingSession'; sessionId: string } + | { name: 'cleanupExistingSessionFailed'; errorCategory: ErrorCategory } + | { name: 'debugSessionStarted.eventBased'; sessionId: string } + | { name: 'debugSessionTimeout.eventBased' } + | { name: 'debugSessionDetected'; sessionId: string; elapsedMs: number } + | { name: 'debugSessionTimeout.smartPolling'; maxWaitTime: number } + | { name: 'classNameDetection'; projectType: LaunchProjectType; detected: boolean } + | { name: 'getDebugSessionInfo.threadError'; errorCategory: ErrorCategory }; + +/** + * Internal-debug event for the launch-flow nested instrumentation + * (session-detected / cleanup / timeout). Re-uses the sanitised sender so + * no PII can slip in. Accepts only the discriminated-union shapes defined + * in {@link LaunchInternalEvent} — unknown event names or unexpected + * property keys are rejected at compile time. + */ +export function recordLaunchInternal(event: LaunchInternalEvent): void { + const { name, ...properties } = event; + sanitizedSend({ + operationName: `languageModelTool.${name}`, + ...properties, + }); +} From 9758e1d852b9908655b9bb6329057f4c99a040fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:36:10 +0800 Subject: [PATCH 34/38] Bump @nevware21/ts-utils from 0.13.0 to 0.14.0 Bumps [@nevware21/ts-utils](https://github.com/nevware21/ts-utils) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/nevware21/ts-utils/releases) - [Changelog](https://github.com/nevware21/ts-utils/blob/main/CHANGELOG.md) - [Commits](https://github.com/nevware21/ts-utils/compare/0.13.0...0.14.0) --- updated-dependencies: - dependency-name: "@nevware21/ts-utils" dependency-version: 0.14.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9254cb47..75859e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,10 +230,19 @@ } }, "node_modules/@nevware21/ts-utils": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", - "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==", - "license": "MIT" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", + "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nevware21" + }, + { + "type": "other", + "url": "https://buymeacoffee.com/nevware21" + } + ] }, "node_modules/@types/eslint": { "version": "9.6.1", @@ -3457,9 +3466,9 @@ } }, "@nevware21/ts-utils": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", - "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", + "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==" }, "@types/eslint": { "version": "9.6.1", From 9df1acb2edb38ccf41d9f06b72b3cc01e80d04f9 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:39:45 +0800 Subject: [PATCH 35/38] fix: avoid spurious 'restart terminal' prompts on every window reload (#1648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: avoid spurious 'restart terminal' prompts on window reload VS Code shows a 'restart terminal to apply environment variable changes' prompt whenever an extension's EnvironmentVariableCollection differs from what is already applied to running terminals. The previous No-Config Debug activation flow called collection.clear() followed by replace() / append() unconditionally, which VS Code always interprets as a change even when the resulting values are byte-identical, so the prompt fired on every window reload. Refactor the activation to diff-aware helpers (applyReplaceIfChanged, applyAppendIfChanged, deleteIfPresent) that only write to the collection when the existing mutator's type, value, or options actually differ from what we want. The hot path on a normal reload becomes a sequence of no-op get() comparisons, so VS Code never observes a change and the prompt no longer appears. Additional notes: * Compare normalized EnvironmentVariableMutatorOptions (applyAtProcessCreation / applyAtShellIntegration) so future options changes still trigger a write. * When the Java Language Server cannot resolve a Java home, keep the previously stored VSCODE_JAVA_EXEC instead of deleting it, to avoid churn caused by transient startup failures. * Explicitly clean up legacy keys (currently JAVA_TOOL_OPTIONS) that older versions of this extension used to set but no longer manage. * Set a human-readable description on the collection so users can see the source in VS Code's environment variable UI. Fixes #1647 * fix: drop unused legacy env var cleanup per review feedback JAVA_TOOL_OPTIONS has never been written to the per-extension EnvironmentVariableCollection by this codebase (verified via 'git log -S JAVA_TOOL_OPTIONS'). The legacy cleanup loop was therefore dead code, and could be misread as touching user-managed env vars (it cannot — the collection is per-extension and only sees mutators this extension wrote). * refactor: drop unused deleteIfPresent helper Production code no longer calls deleteIfPresent after the legacy cleanup loop was removed. Drop the helper and its tests rather than carry a YAGNI export — easy to reintroduce if a real caller appears. --- src/envVarSync.ts | 95 +++++++++++++++++ src/noConfigDebugInit.ts | 28 +++-- test/envVarSync.test.ts | 213 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 src/envVarSync.ts create mode 100644 test/envVarSync.test.ts diff --git a/src/envVarSync.ts b/src/envVarSync.ts new file mode 100644 index 00000000..6514afda --- /dev/null +++ b/src/envVarSync.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from "vscode"; + +/** + * Helpers that update a {@link vscode.EnvironmentVariableCollection} only when + * the resulting mutation would actually change the collection. + * + * Background: VS Code shows a "Restart terminal to apply environment variable + * changes" prompt whenever an extension's collection differs from what has + * already been applied to running terminals. Calling `replace()` / `append()` + * with the same value, or calling `clear()` followed by re-adding identical + * entries, still counts as a change and re-triggers the prompt on every + * window reload. See issue #1647. + * + * These helpers compare the existing mutator (type + value + options) against + * the desired one and skip the write entirely when they match. + */ + +const DEFAULT_OPTIONS: Required = { + applyAtProcessCreation: true, + applyAtShellIntegration: false, +}; + +function normalizeOptions( + options?: vscode.EnvironmentVariableMutatorOptions, +): Required { + return { ...DEFAULT_OPTIONS, ...options }; +} + +function sameOptions( + existing: vscode.EnvironmentVariableMutator, + desired?: vscode.EnvironmentVariableMutatorOptions, +): boolean { + const e = normalizeOptions(existing.options); + const d = normalizeOptions(desired); + return e.applyAtProcessCreation === d.applyAtProcessCreation + && e.applyAtShellIntegration === d.applyAtShellIntegration; +} + +/** + * Calls `collection.replace(variable, value, options)` only when the existing + * mutator (if any) does not already match. + * + * @returns `true` if the collection was actually written to. + */ +export function applyReplaceIfChanged( + collection: vscode.EnvironmentVariableCollection, + variable: string, + value: string, + options?: vscode.EnvironmentVariableMutatorOptions, +): boolean { + const existing = collection.get(variable); + if (existing + && existing.type === vscode.EnvironmentVariableMutatorType.Replace + && existing.value === value + && sameOptions(existing, options)) { + return false; + } + if (options) { + collection.replace(variable, value, options); + } else { + collection.replace(variable, value); + } + return true; +} + +/** + * Calls `collection.append(variable, value, options)` only when the existing + * mutator (if any) does not already match. + * + * @returns `true` if the collection was actually written to. + */ +export function applyAppendIfChanged( + collection: vscode.EnvironmentVariableCollection, + variable: string, + value: string, + options?: vscode.EnvironmentVariableMutatorOptions, +): boolean { + const existing = collection.get(variable); + if (existing + && existing.type === vscode.EnvironmentVariableMutatorType.Append + && existing.value === value + && sameOptions(existing, options)) { + return false; + } + if (options) { + collection.append(variable, value, options); + } else { + collection.append(variable, value); + } + return true; +} + diff --git a/src/noConfigDebugInit.ts b/src/noConfigDebugInit.ts index 2f84ee6b..ea4e3606 100644 --- a/src/noConfigDebugInit.ts +++ b/src/noConfigDebugInit.ts @@ -9,6 +9,9 @@ import * as vscode from 'vscode'; import { sendInfo, sendError } from "vscode-extension-telemetry-wrapper"; import { getJavaHome } from "./utility"; import { buildNoConfigPathAppendValue } from "./pathUtil"; +import { applyAppendIfChanged, applyReplaceIfChanged } from "./envVarSync"; + +const ENV_VAR_COLLECTION_DESCRIPTION = "Java No-Config Debug"; /** * Registers the configuration-less debugging setup for the extension. @@ -21,7 +24,7 @@ import { buildNoConfigPathAppendValue } from "./pathUtil"; * * Environment Variables: * - `VSCODE_JDWP_ADAPTER_ENDPOINTS`: Path to the file containing the debugger adapter endpoint. - * - `JAVA_TOOL_OPTIONS`: JDWP configuration for automatic debugging. + * - `VSCODE_JAVA_EXEC`: Path to the java executable from the Java Language Server (when available). * - `PATH`: Appends the path to the noConfigScripts directory. */ export async function registerNoConfigDebug( @@ -69,22 +72,31 @@ export async function registerNoConfigDebug( } } - // clear the env var collection to remove any existing env vars - collection.clear(); + // Surface a description in VS Code's environment variable UI so users can + // see which extension is contributing these variables. + if (collection.description !== ENV_VAR_COLLECTION_DESCRIPTION) { + collection.description = ENV_VAR_COLLECTION_DESCRIPTION; + } - // Add env var for VSCODE_JDWP_ADAPTER_ENDPOINTS + // Apply our managed variables using diff-aware helpers. On a typical + // window reload the values are unchanged and these calls are no-ops, so + // VS Code does not prompt the user to restart their existing terminals. + // See issue #1647. + // // Note: We do NOT set JAVA_TOOL_OPTIONS globally to avoid affecting all Java processes // (javac, maven, gradle, language server, etc.). Instead, JAVA_TOOL_OPTIONS is set // only in the debugjava wrapper scripts (debugjava.ps1, debugjava.bat, debugjava) - collection.replace('VSCODE_JDWP_ADAPTER_ENDPOINTS', tempFilePath); + applyReplaceIfChanged(collection, 'VSCODE_JDWP_ADAPTER_ENDPOINTS', tempFilePath); // Try to get Java executable from Java Language Server - // This ensures we use the same Java version as the project is compiled with + // This ensures we use the same Java version as the project is compiled with. + // If detection fails or returns nothing, we deliberately keep any previously + // set VSCODE_JAVA_EXEC to avoid churn from transient startup failures. try { const javaHome = await getJavaHome(); if (javaHome) { const javaExec = path.join(javaHome, 'bin', 'java'); - collection.replace('VSCODE_JAVA_EXEC', javaExec); + applyReplaceIfChanged(collection, 'VSCODE_JAVA_EXEC', javaExec); } } catch (error) { // If we can't get Java from Language Server, that's okay @@ -92,7 +104,7 @@ export async function registerNoConfigDebug( } const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); - collection.append('PATH', buildNoConfigPathAppendValue(noConfigScriptsDir)); + applyAppendIfChanged(collection, 'PATH', buildNoConfigPathAppendValue(noConfigScriptsDir)); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written const fileSystemWatcher = vscode.workspace.createFileSystemWatcher( diff --git a/test/envVarSync.test.ts b/test/envVarSync.test.ts new file mode 100644 index 00000000..f549b92b --- /dev/null +++ b/test/envVarSync.test.ts @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as assert from "assert"; +import * as vscode from "vscode"; + +import { + applyAppendIfChanged, + applyReplaceIfChanged, +} from "../src/envVarSync"; + +interface FakeMutator { + type: vscode.EnvironmentVariableMutatorType; + value: string; + options: vscode.EnvironmentVariableMutatorOptions; +} + +interface FakeCollection extends vscode.EnvironmentVariableCollection { + __calls: { replace: number; append: number; delete: number }; +} + +function createFakeCollection(): FakeCollection { + const store = new Map(); + const calls = { replace: 0, append: 0, delete: 0 }; + + const collection = { + persistent: true, + description: undefined as string | vscode.MarkdownString | undefined, + get(name: string): FakeMutator | undefined { + return store.get(name); + }, + replace(name: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions): void { + calls.replace += 1; + store.set(name, { + type: vscode.EnvironmentVariableMutatorType.Replace, + value, + options: { applyAtProcessCreation: true, applyAtShellIntegration: false, ...options }, + }); + }, + append(name: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions): void { + calls.append += 1; + store.set(name, { + type: vscode.EnvironmentVariableMutatorType.Append, + value, + options: { applyAtProcessCreation: true, applyAtShellIntegration: false, ...options }, + }); + }, + prepend(name: string, value: string, options?: vscode.EnvironmentVariableMutatorOptions): void { + store.set(name, { + type: vscode.EnvironmentVariableMutatorType.Prepend, + value, + options: { applyAtProcessCreation: true, applyAtShellIntegration: false, ...options }, + }); + }, + delete(name: string): void { + calls.delete += 1; + store.delete(name); + }, + clear(): void { + store.clear(); + }, + forEach(callback: (variable: string, mutator: FakeMutator, collection: any) => void): void { + store.forEach((mutator, variable) => callback(variable, mutator, collection)); + }, + getScoped(): vscode.EnvironmentVariableCollection { + return collection as unknown as vscode.EnvironmentVariableCollection; + }, + *[Symbol.iterator](): IterableIterator<[string, FakeMutator]> { + yield* store.entries(); + }, + __calls: calls, + }; + + return collection as unknown as FakeCollection; +} + +suite("envVarSync", () => { + suite("applyReplaceIfChanged", () => { + test("writes when variable is missing", () => { + const c = createFakeCollection(); + const changed = applyReplaceIfChanged(c, "FOO", "bar"); + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.replace, 1); + assert.strictEqual(c.get("FOO")!.value, "bar"); + assert.strictEqual(c.get("FOO")!.type, vscode.EnvironmentVariableMutatorType.Replace); + }); + + test("is a no-op when value and type already match (default options)", () => { + const c = createFakeCollection(); + applyReplaceIfChanged(c, "FOO", "bar"); + c.__calls.replace = 0; + + const changed = applyReplaceIfChanged(c, "FOO", "bar"); + + assert.strictEqual(changed, false); + assert.strictEqual(c.__calls.replace, 0); + }); + + test("writes when value differs", () => { + const c = createFakeCollection(); + applyReplaceIfChanged(c, "FOO", "bar"); + c.__calls.replace = 0; + + const changed = applyReplaceIfChanged(c, "FOO", "baz"); + + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.replace, 1); + assert.strictEqual(c.get("FOO")!.value, "baz"); + }); + + test("overrides an existing Append mutator", () => { + const c = createFakeCollection(); + applyAppendIfChanged(c, "FOO", "bar"); + c.__calls.replace = 0; + + const changed = applyReplaceIfChanged(c, "FOO", "bar"); + + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.replace, 1); + assert.strictEqual(c.get("FOO")!.type, vscode.EnvironmentVariableMutatorType.Replace); + }); + + test("writes when options differ from existing mutator", () => { + const c = createFakeCollection(); + applyReplaceIfChanged(c, "FOO", "bar", { applyAtProcessCreation: true, applyAtShellIntegration: false }); + c.__calls.replace = 0; + + const changed = applyReplaceIfChanged(c, "FOO", "bar", { applyAtProcessCreation: false, applyAtShellIntegration: false }); + + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.replace, 1); + }); + + test("treats omitted options as the documented defaults", () => { + const c = createFakeCollection(); + applyReplaceIfChanged(c, "FOO", "bar", { applyAtProcessCreation: true, applyAtShellIntegration: false }); + c.__calls.replace = 0; + + const changed = applyReplaceIfChanged(c, "FOO", "bar"); + + assert.strictEqual(changed, false); + assert.strictEqual(c.__calls.replace, 0); + }); + }); + + suite("applyAppendIfChanged", () => { + test("writes when variable is missing", () => { + const c = createFakeCollection(); + const changed = applyAppendIfChanged(c, "PATH", ";C:\\extra"); + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.append, 1); + assert.strictEqual(c.get("PATH")!.value, ";C:\\extra"); + assert.strictEqual(c.get("PATH")!.type, vscode.EnvironmentVariableMutatorType.Append); + }); + + test("is a no-op when value already matches", () => { + const c = createFakeCollection(); + applyAppendIfChanged(c, "PATH", ";C:\\extra"); + c.__calls.append = 0; + + const changed = applyAppendIfChanged(c, "PATH", ";C:\\extra"); + + assert.strictEqual(changed, false); + assert.strictEqual(c.__calls.append, 0); + }); + + test("writes when value differs", () => { + const c = createFakeCollection(); + applyAppendIfChanged(c, "PATH", ";C:\\extra"); + c.__calls.append = 0; + + const changed = applyAppendIfChanged(c, "PATH", ";C:\\other"); + + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.append, 1); + assert.strictEqual(c.get("PATH")!.value, ";C:\\other"); + }); + + test("overrides an existing Replace mutator on the same variable", () => { + const c = createFakeCollection(); + applyReplaceIfChanged(c, "PATH", ";C:\\extra"); + c.__calls.append = 0; + + const changed = applyAppendIfChanged(c, "PATH", ";C:\\extra"); + + assert.strictEqual(changed, true); + assert.strictEqual(c.__calls.append, 1); + assert.strictEqual(c.get("PATH")!.type, vscode.EnvironmentVariableMutatorType.Append); + }); + }); + + suite("regression: issue #1647 - repeated activations stay quiet", () => { + test("re-applying the same set of variables does not touch the collection", () => { + const c = createFakeCollection(); + + // Simulate first activation. + applyReplaceIfChanged(c, "VSCODE_JDWP_ADAPTER_ENDPOINTS", "/tmp/endpoint-abc.txt"); + applyReplaceIfChanged(c, "VSCODE_JAVA_EXEC", "/opt/jdk/bin/java"); + applyAppendIfChanged(c, "PATH", ":/ext/bundled/scripts/noConfigScripts"); + + const callsAfterFirst = { ...c.__calls }; + assert.deepStrictEqual(callsAfterFirst, { replace: 2, append: 1, delete: 0 }); + + // Simulate a window reload: same values, same order. + applyReplaceIfChanged(c, "VSCODE_JDWP_ADAPTER_ENDPOINTS", "/tmp/endpoint-abc.txt"); + applyReplaceIfChanged(c, "VSCODE_JAVA_EXEC", "/opt/jdk/bin/java"); + applyAppendIfChanged(c, "PATH", ":/ext/bundled/scripts/noConfigScripts"); + + // Nothing should have been written on the second pass. + assert.deepStrictEqual(c.__calls, callsAfterFirst); + }); + }); +}); From 54a4ceb58ca593b7966fa2b9f42ca1e45076b8e2 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:19:37 +0800 Subject: [PATCH 36/38] fix(lmtool): reframe timeout messages as transient + retry-friendly to nudge model retries (#1652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(lmtool): reframe timeout messages as transient + retry-friendly The two timeout-path response messages previously led with '❌ Debug session failed to start' and listed root-cause hypotheses (compilation errors, ClassNotFoundException, ...) as 'This usually indicates a problem'. Telemetry shows that interpretation is wrong: - 30d retry analysis: success rate climbs from 17.7% (1 invoke) to 64.9% (6-10 invokes) — a 3.7x lift - Started latency P95 = 100s vs the 45s / 15s thresholds — many timeouts are just slow-starting JVMs that DO eventually attach - 66% of users invoke only once, suggesting the model treats the ❌ wording as a permanent failure and gives up Rewrite both timeout messages to: 1. Lead with ⏳ (transient) instead of ❌ / ⚠️ (terminal) 2. State explicitly 'this is often transient' 3. Cite the retry-success pattern 4. Put 'call debug_java_application again' as the first recommended action 5. Keep the diagnostic checklist as a fallback (steps 3-4) for genuinely broken cases No behavior change in the extension — only the natural-language string returned to the language model is changed. This is a targeted nudge for the model's retry policy. Companion PR #1650 will give us per-invoke retry telemetry (retryCount / previousOutcome) to verify the lift after this lands. * review: clarify JSON object syntax for waitForSession in smart-polling timeout message --- src/languageModelTool.ts | 47 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index 85b28855..055103b2 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -275,17 +275,21 @@ async function debugJavaApplication( resolve({ success: false, status: 'timeout', - message: `❌ Debug session failed to start within ${CONSTANTS.SESSION_WAIT_TIMEOUT / 1000} seconds for ${targetInfo}.\n\n` + - `This usually indicates a problem:\n` + - `• Compilation errors preventing startup\n` + - `• ClassNotFoundException or NoClassDefFoundError\n` + - `• Application crashed during initialization\n` + - `• Incorrect main class or classpath configuration\n\n` + - `Action required:\n` + - `1. Check terminal '${terminal.name}' for error messages\n` + - `2. Verify the target class name is correct\n` + - `3. Ensure the project is compiled successfully\n` + - `4. Use get_debug_session_info() to confirm session status${warningNote}`, + message: `⏳ Debug session not yet detected for ${targetInfo} after ` + + `${CONSTANTS.SESSION_WAIT_TIMEOUT / 1000} seconds.\n\n` + + `This is often transient — the JVM may still be starting up (large ` + + `projects, cold class-loading, or remote workspaces can need additional ` + + `time). Telemetry shows that retrying a timed-out launch succeeds for ` + + `the majority of cases.\n\n` + + `Recommended next actions (in order):\n` + + `1. Call debug_java_application again — most timeout cases recover on retry.\n` + + `2. Call get_debug_session_info() to check whether the session has since ` + + `become active.\n` + + `3. If retrying still times out, inspect terminal '${terminal.name}' for ` + + `compilation errors, ClassNotFoundException, NoClassDefFoundError, or ` + + `other startup failures.\n` + + `4. Verify the target class name and classpath are correct, then retry.` + + `${warningNote}`, terminalName: terminal.name }); } @@ -335,15 +339,18 @@ async function debugJavaApplication( return { success: true, status: 'timeout', - message: `⚠️ Debug command sent for ${targetInfo}, but session not detected within ${CONSTANTS.SMART_POLLING_MAX_WAIT / 1000} seconds.\n\n` + - `Possible reasons:\n` + - `• Application is still starting (large projects may take longer)\n` + - `• Compilation errors (check terminal '${terminal.name}' for errors)\n` + - `• Application may have started and already terminated\n\n` + - `Next steps:\n` + - `• Use get_debug_session_info() to check if session is now active\n` + - `• Check terminal '${terminal.name}' for error messages\n` + - `• If starting slowly, wait a bit longer and check again${warningNote}`, + message: `⏳ Debug command sent for ${targetInfo}; session not yet detected within ` + + `${CONSTANTS.SMART_POLLING_MAX_WAIT / 1000} seconds.\n\n` + + `This is often transient — the application may still be starting in terminal ` + + `'${terminal.name}'. Telemetry shows that retrying or polling for status is more ` + + `likely to succeed than treating this as a permanent failure.\n\n` + + `Recommended next actions (in order):\n` + + `1. Call get_debug_session_info() to check whether the session has since become active.\n` + + `2. Call debug_java_application again — most timeout cases recover on retry. ` + + `In the input arguments, set "waitForSession": true (JSON object syntax) to ` + + `extend the wait window for slow-starting apps.\n` + + `3. If retrying still times out, inspect terminal '${terminal.name}' for compilation ` + + `errors or startup failures, then retry.${warningNote}`, terminalName: terminal.name }; } From 809056d1c0d3ecc5a608520c10567a01419dfdc7 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:26:02 +0800 Subject: [PATCH 37/38] fix(lmtool): stop double-counting classNameDetection on every invoke (#1651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(lmtool): stop double-counting classNameDetection on every invoke findFullyQualifiedClassName was called twice for every invocation that used a simple class name: 1. inside constructDebugCommand to resolve the actual command 2. immediately after, to build the targetInfo message shown to the model Each call also emitted its own classNameDetection telemetry event, so the classNameDetection success/failure counts in ddtelfiltered were inflated by 2x. This makes the apparent ~49% noPackage failure rate look much worse than reality (the true rate is closer to ~25% before accounting for other root causes addressed in #1650). Fix: resolve the detection once at the caller, emit telemetry once, and pass the resolved name into both constructDebugCommand and the targetInfo branch. Side benefit: removes a redundant file system walk from the hot launch path. * review: reword dedup comments to drop incorrect '2x telemetry' claim Reviewer correctly pointed out that the original main branch only had a duplicate findFullyQualifiedClassName FS walk in the targetInfo branch; the recordLaunchInternal({name:'classNameDetection'}) emission was only inside constructDebugCommand, not duplicated. The 'inflates noPackage by 2x' rationale was wrong — observed metrics are not inflated by the dup. Reworded both comments to focus on the FS-walk dedup + ownership clarity. Functional code unchanged; only comments updated. --- src/languageModelTool.ts | 67 ++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index 055103b2..65878695 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -194,7 +194,33 @@ async function debugJavaApplication( } // Step 3: Construct and execute the debugjava command - const debugCommand = constructDebugCommand(input, projectType); + // + // For simple class names (no dot), resolve the fully-qualified class once + // here so we can reuse the result for both the command construction and + // the user-facing targetInfo message below. Previously these two paths + // each called findFullyQualifiedClassName independently, which: + // - duplicated the file system walk on the hot launch path (the FS walk + // is the actual user-visible slowdown — it stats every .java file + // under src/main/java up to MAX_FILE_SEARCH_DEPTH) + // - made the call sites harder to reason about, since detection + // ownership was split across constructDebugCommand and the targetInfo + // formatting block + // + // After this refactor, the caller owns detection and its telemetry, + // and constructDebugCommand accepts a pre-resolved name. + let detectedClassName: string | null = null; + if (!input.target.endsWith('.jar') + && !input.target.startsWith('-') + && !input.target.includes('.')) { + detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: !!detectedClassName, + }); + } + + const debugCommand = constructDebugCommand(input, projectType, detectedClassName); // Validate that we can construct a valid command if (!debugCommand || debugCommand === 'debugjava') { @@ -223,8 +249,9 @@ async function debugJavaApplication( } else if (input.target.includes('.')) { targetInfo = input.target; } else { - // Simple class name - check if we successfully detected the full name - const detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); + // Simple class name - reuse the detection result from Step 3 above + // (do NOT call findFullyQualifiedClassName again — it walks the FS + // and the result is already in `detectedClassName`). if (detectedClassName) { targetInfo = `${detectedClassName} (detected from ${input.target})`; } else { @@ -591,10 +618,17 @@ async function ensureVSCodeCompilation(workspaceUri: vscode.Uri): Promise Date: Wed, 10 Jun 2026 10:42:12 +0800 Subject: [PATCH 38/38] perf(telemetry): unblock per-tool quality analysis (GDPR annotations + diagnostic fields + InvocationGuard) (#1650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(telemetry): add diagnostic fields, GDPR annotations, and InvocationGuard infra Telemetry contract changes to unblock per-tool quality analysis (driven by the 9-query deep-dive in 1. GDPR annotation blocks (the root cause of PR #1644 fields being filtered) - Added /* __GDPR__ ... */ blocks above the three sanitizedSend call sites (recordToolInvocation, recordChatActivation, recordLaunchInternal) so the ddtelfiltered cluster keeps new Properties keys instead of stripping them. 2. Five new enums to replace stringly-typed fields: - OperatingSystem: win | mac | linux | other - ClassNameDetectionStrategy: mavenStandard | gradleStandard | vscodeSrc | workspaceRoot - ClassNameDetectionFailure: sourceDirMissing | fileNotFound | parseError | noPackageDeclaration - SentinelOutcome: recorded | silentReturn | cancelled | exception 3. ToolInvocationRecord extended with platform/version context: - os, javaMajorVersion, projectSystem, retryCount, previousOutcome These split the per-tool funnel by OS / Java major / project system so we can confirm the Linux 4.3 percent vs Windows 27.7 percent start-rate divergence. 4. LaunchInternalEvent union extended with four new sentinel variants and elapsedMs/thresholdMs: - debugSession.sentinel: emitted on EVERY invoke so even infinite hangs leave a trail - debugSession.silentReturn: invoke returned with no outcome event (the 32.8 percent gap) - debugSession.cancelled: user cancelled mid-flight - debugSession.exception: handler threw an exception, classified - classNameDetection.failed: replaces collapsed boolean with strategy + failureReason - debugSessionStarted/Timeout now carry elapsedMs + thresholdMs for histograms 5. InvocationGuard infrastructure (beginDebugSessionInvocation()): - Returns markOutcomeRecorded / markException / close methods - Emits sentinel at begin; close auto-emits silentReturn if no outcome recorded - Lets us measure the 32.8 percent silent-loss bucket with closed-loop attribution 6. SessionInvocationTracker (nextAttempt / completeAttempt): - Per-window, per-tool in-memory retry counter - Stores ONLY enum (previousOutcome) and integer (count) — no user data - Quantifies the LM auto-retry-pays-off pattern (17.7 percent at 1 -> 64.9 percent at 6-10) Compiles clean (tsc --noEmit) and passes tslint. * feat(lmtool): wire diagnostic context, retry attribution, and bump timeouts Consumes the new telemetry contracts from the previous commit and threads them through all 10 LMT invoke handlers. Also tunes the two session-wait thresholds to cover the Started P95 latency observed in 30d telemetry. Timeout tuning (driven by 30d Started latency: P50=12s / P90=67s / P95=100s): SESSION_WAIT_TIMEOUT: 45000 -> 120000 (eventBased path, waitForSession=true) SMART_POLLING_MAX_WAIT: 15000 -> 90000 (default polling path) Previous values clipped ~10 percent of legitimate slow starts as timeouts and biased the two strategies against each other. Aligning at 120s/90s also lets us cleanly compare cross-strategy success rates. Per-tool context fields (added to all 10 recordToolInvocation call sites): os / javaMajorVersion: from getOs() / getJavaMajorVersion() getJavaMajorVersion probes the redhat.java extension API (cached), falls back to JAVA_VERSION env var, then 'unknown'. classifyJavaMajorVersion handles legacy '1.8.0_xxx' -> '8' and modern '21.0.1' -> '21'. retryCount / previousOutcome: from nextAttempt() / completeAttempt() Per-window, per-tool counter. Used to test the LM auto-retry-pays-off pattern. debug_java_application handler: Wraps the entire invoke in a beginDebugSessionInvocation() guard so even infinite hangs leave a sentinel + silentReturn trail. This is the closed-loop half of the 32.8 percent silent-loss bucket detected by D2 in the deep-dive. classNameDetection rewrite: findFullyQualifiedClassName() now returns { className, failureReason, strategy } instead of string|null. The failure branch emits the new classNameDetection.failed event with structured strategy + failureReason so we can distinguish sourceDirMissing / fileNotFound / parseError / noPackageDeclaration — the previous boolean detected:false collapsed all four causes. Timeout / Started events: debugSessionStarted.eventBased + debugSessionTimeout.eventBased + debugSessionTimeout.smartPolling now carry elapsedMs + thresholdMs so we can histogram the Started/Timeout latency distribution and verify the threshold bump moves the right mass. Compiles clean (tsc --noEmit) and passes tslint. * review: fix 4 reviewer-flagged issues Address review feedback on PR #1650: 1. InvocationGuard was effectively disabled — guard.markOutcomeRecorded() was called unconditionally in finally, so guard.close() could never emit debugSession.silentReturn / cancelled / exception. Moved the mark calls inline next to the four session-terminal recordLaunchInternal sites (debugSessionStarted.eventBased, debugSessionTimeout.eventBased, debugSessionDetected, debugSessionTimeout.smartPolling). Threaded guard parameter into debugJavaApplication. The catch path now lets close() emit debugSession.exception via markException as designed. 2. targetInfo formatting at the call site stringified the new structured ClassNameDetectionResult as [object Object]. Renamed local to 'detection' and explicitly read detection.className. The if-truthy branch was also always taken, suppressing the no-package warning. 3. The 'found file but no package' branch returned { className: simpleClassName } which made callers treat it as a successful detection, hiding the failure event. Now returns { className: null, failureReason: 'noPackageDeclaration' } so classNameDetection.failed fires. Behavior is preserved because the call site already falls back to input.target on null. 4. ClassNameDetectionResult is now a discriminated union (success has no failureReason; failure requires it). Eliminates the 'unused on success' sentinel and lets TS narrow at use sites. Required strict-null narrowing (className !== null) at the two call sites. tsc clean; tslint clean. --- src/languageModelTool.ts | 313 +++++++++++++++++++++++++++++++---- src/lmToolTelemetry.ts | 348 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 627 insertions(+), 34 deletions(-) diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index 65878695..341fb7e6 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -5,14 +5,24 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { + beginDebugSessionInvocation, classifyBreakpoint, classifyError, classifyEvalContext, + classifyJavaMajorVersion, + classifyPlatform, classifyRemoveScope, classifyScopeType, classifyStep, classifyTarget, + ClassNameDetectionFailure, + ClassNameDetectionStrategy, + completeAttempt, ErrorCategory, + InvocationGuard, + LaunchProjectType, + nextAttempt, + OperatingSystem, recordLaunchInternal, recordToolInvocation, TOOL_NAMES, @@ -23,10 +33,18 @@ import { // Constants // ============================================================================ const CONSTANTS = { - /** Timeout for waitForSession mode (ms) */ - SESSION_WAIT_TIMEOUT: 45000, - /** Maximum wait time for smart polling (ms) */ - SMART_POLLING_MAX_WAIT: 15000, + /** + * Timeout for waitForSession mode (ms). Set high enough to cover Started P95 + * (~100s in 30d telemetry); previous 45s clipped ~10% of legitimate starts + * as timeouts. + */ + SESSION_WAIT_TIMEOUT: 120000, + /** + * Maximum wait time for smart polling (ms). Previous 15s only covered ~60% + * of successful starts; bumped to align with the eventBased threshold so we + * do not bias data by polling strategy. + */ + SMART_POLLING_MAX_WAIT: 90000, /** Interval between polling checks (ms) */ SMART_POLLING_INTERVAL: 300, /** Timeout for build tasks (ms) */ @@ -39,6 +57,34 @@ const CONSTANTS = { MAX_FILE_SEARCH_DEPTH: 10 }; +// ---------------------------------------------------------------------------- +// Process-wide context probed lazily on first use. The value is constant for +// the VS Code session lifetime, so we cache it. +// ---------------------------------------------------------------------------- +let cachedJavaMajorVersion: string | undefined; + +function getJavaMajorVersion(): string { + if (cachedJavaMajorVersion !== undefined) { + return cachedJavaMajorVersion; + } + // Best-effort probe: read JAVA_HOME / PATH-resolved `java -version` output. + // We DO NOT shell out here (extension activation perf budget) — instead we + // look at the redhat.java extension's resolved JDK if available. + try { + const javaExt = vscode.extensions.getExtension('redhat.java'); + const apiVersion = (javaExt?.exports as { javaRequirement?: { java_version?: string } } | undefined) + ?.javaRequirement?.java_version; + cachedJavaMajorVersion = classifyJavaMajorVersion(apiVersion ?? process.env.JAVA_VERSION); + } catch { + cachedJavaMajorVersion = 'unknown'; + } + return cachedJavaMajorVersion; +} + +function getOs(): OperatingSystem { + return classifyPlatform(process.platform); +} + interface DebugJavaApplicationInput { target: string; workspacePath: string; @@ -78,11 +124,20 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc async invoke(options: { input: DebugJavaApplicationInput }, token: vscode.CancellationToken): Promise { const startedAt = Date.now(); const targetType = classifyTarget(options.input.target); + const attempt = nextAttempt(TOOL_NAMES.DEBUG_JAVA_APPLICATION); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); + const guard = beginDebugSessionInvocation({ + os, + javaMajorVersion, + projectSystem: undefined, // resolved inside debugJavaApplication; not yet known here + isCancelled: () => token.isCancellationRequested, + }); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; try { - const result = await debugJavaApplication(options.input, token); + const result = await debugJavaApplication(options.input, token, guard); if (!result.success) { outcome = result.status === 'timeout' ? 'timeout' : 'failure'; errorCategory = result.success ? undefined : classifyError(result.message); @@ -103,6 +158,7 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc } catch (error) { outcome = token.isCancellationRequested ? 'cancelled' : 'failure'; errorCategory = classifyError(error); + guard.markException(error); const errorMessage = error instanceof Error ? error.message : String(error); @@ -117,7 +173,24 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc targetType, skipBuild: !!options.input.skipBuild, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.DEBUG_JAVA_APPLICATION, outcome); + // Do NOT call guard.markOutcomeRecorded() here. The wrapper + // recordToolInvocation above is a separate event from the + // launchInternal stream that the guard is tracking; marking + // it would mask every silent / cancelled / exception path + // and defeat the closed-loop attribution this PR exists for. + // `markOutcomeRecorded()` is now called inline at the four + // session-terminal recordLaunchInternal sites inside + // `debugJavaApplication` (started / timeout, both + // eventBased and smartPolling). For cancelled / exception + // we fall through to guard.close() so it can emit + // debugSession.cancelled / debugSession.exception. + guard.close(); } } }; @@ -137,7 +210,8 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc */ async function debugJavaApplication( input: DebugJavaApplicationInput, - token: vscode.CancellationToken + token: vscode.CancellationToken, + guard?: InvocationGuard, ): Promise { if (token.isCancellationRequested) { return { @@ -206,18 +280,34 @@ async function debugJavaApplication( // ownership was split across constructDebugCommand and the targetInfo // formatting block // - // After this refactor, the caller owns detection and its telemetry, - // and constructDebugCommand accepts a pre-resolved name. + // After this refactor, the caller owns detection and its telemetry, and + // constructDebugCommand accepts a pre-resolved name. Detection now + // returns a structured result so we can emit `classNameDetection.failed` + // with a precise failureReason instead of a single boolean bucket. let detectedClassName: string | null = null; if (!input.target.endsWith('.jar') && !input.target.startsWith('-') && !input.target.includes('.')) { - detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); - recordLaunchInternal({ - name: 'classNameDetection', - projectType, - detected: !!detectedClassName, - }); + const detection = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); + detectedClassName = detection.className; + if (detection.className !== null) { + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: true, + }); + } else { + // Detection failed. Emit the structured failure event so we can + // distinguish "no candidate src dir" from "found file but no + // package" — the previous boolean `detected: false` collapsed all + // four root causes into one bucket. + recordLaunchInternal({ + name: 'classNameDetection.failed', + projectType, + strategy: detection.strategy, + failureReason: detection.failureReason, + }); + } } const debugCommand = constructDebugCommand(input, projectType, detectedClassName); @@ -249,9 +339,9 @@ async function debugJavaApplication( } else if (input.target.includes('.')) { targetInfo = input.target; } else { - // Simple class name - reuse the detection result from Step 3 above - // (do NOT call findFullyQualifiedClassName again — it walks the FS - // and the result is already in `detectedClassName`). + // Simple class name - reuse the detection result resolved once in + // Step 3 above (do NOT call findFullyQualifiedClassName again — it + // walks the FS and the result is already in `detectedClassName`). if (detectedClassName) { targetInfo = `${detectedClassName} (detected from ${input.target})`; } else { @@ -264,6 +354,7 @@ async function debugJavaApplication( if (input.waitForSession) { return new Promise((resolve) => { let sessionStarted = false; + const waitStartedAt = Date.now(); // Listen for debug session start const sessionDisposable = vscode.debug.onDidStartDebugSession((session) => { @@ -277,7 +368,10 @@ async function debugJavaApplication( recordLaunchInternal({ name: 'debugSessionStarted.eventBased', sessionId: session.id, + elapsedMs: Date.now() - waitStartedAt, + thresholdMs: CONSTANTS.SESSION_WAIT_TIMEOUT, }); + guard?.markOutcomeRecorded(); resolve({ success: true, @@ -297,7 +391,12 @@ async function debugJavaApplication( if (!sessionStarted) { sessionDisposable.dispose(); - recordLaunchInternal({ name: 'debugSessionTimeout.eventBased' }); + recordLaunchInternal({ + name: 'debugSessionTimeout.eventBased', + elapsedMs: Date.now() - waitStartedAt, + thresholdMs: CONSTANTS.SESSION_WAIT_TIMEOUT, + }); + guard?.markOutcomeRecorded(); resolve({ success: false, @@ -343,6 +442,7 @@ async function debugJavaApplication( sessionId: session.id, elapsedMs, }); + guard?.markOutcomeRecorded(); return { success: true, @@ -357,11 +457,13 @@ async function debugJavaApplication( await new Promise(resolve => setTimeout(resolve, pollInterval)); } - // Timeout: session not detected within 15 seconds + // Timeout: session not detected within polling window recordLaunchInternal({ name: 'debugSessionTimeout.smartPolling', maxWaitTime, + elapsedMs: Date.now() - startTime, }); + guard?.markOutcomeRecorded(); return { success: true, @@ -646,8 +748,8 @@ function constructDebugCommand( // Use the caller-supplied detection result; we deliberately do not // call findFullyQualifiedClassName a second time here (see the - // dedupe note at the call site). Detection telemetry is owned by - // the caller. + // dedupe note at the call site in debugJavaApplication Step 3). + // Detection telemetry is owned by the caller. if (!input.target.includes('.') && preDetectedClassName) { className = preDetectedClassName; } @@ -666,6 +768,42 @@ function constructDebugCommand( return command; } +/** + * Result of `findFullyQualifiedClassName` — a discriminated union so the + * type system enforces that callers handle the failure case without an + * easy-to-misread sentinel string. + * + * On success: `className` carries the resolved FQN and `failureReason` is + * absent. On failure: `className` is null and `failureReason` is the + * structured root cause. The boolean `detected: true/false` event was + * historically the only signal — it collapsed four very different root + * causes (sourceDirMissing / fileNotFound / parseError / + * noPackageDeclaration) into one bucket and made on-call triage + * impossible. + */ +type ClassNameDetectionResult = + | { + className: string; + /** Which source-directory layout was used. */ + strategy: ClassNameDetectionStrategy; + failureReason?: undefined; + } + | { + className: null; + /** Which source-directory layout was last tried. */ + strategy: ClassNameDetectionStrategy; + failureReason: ClassNameDetectionFailure; + }; + +function strategyForProjectType(projectType: LaunchProjectType): ClassNameDetectionStrategy { + switch (projectType) { + case 'maven': return 'mavenStandard'; + case 'gradle': return 'gradleStandard'; + case 'vscode': return 'vscodeSrc'; + case 'unknown': return 'workspaceRoot'; + } +} + /** * Tries to find the fully qualified class name by searching for the Java file. * This helps when user provides just "App" instead of "com.example.App". @@ -673,8 +811,10 @@ function constructDebugCommand( function findFullyQualifiedClassName( workspacePath: string, simpleClassName: string, - projectType: 'maven' | 'gradle' | 'vscode' | 'unknown' -): string | null { + projectType: LaunchProjectType +): ClassNameDetectionResult { + const defaultStrategy = strategyForProjectType(projectType); + // Determine source directories based on project type const sourceDirs: string[] = []; @@ -698,30 +838,71 @@ function findFullyQualifiedClassName( break; } - // Search for the Java file + // Track the dominant failure reason as we walk the candidate dirs. + // Priority on success: returned immediately. + // Priority on failure: parseError > noPackageDeclaration > fileNotFound > sourceDirMissing. + let anyDirExisted = false; + let anyFileFound = false; + let sawParseError = false; + for (const srcDir of sourceDirs) { if (!fs.existsSync(srcDir)) { continue; } + anyDirExisted = true; try { const javaFile = findJavaFile(srcDir, simpleClassName, 0); if (javaFile) { + anyFileFound = true; // Extract package name from the file const packageName = extractPackageName(javaFile); if (packageName) { - return `${packageName}.${simpleClassName}`; - } else { - // No package, use simple name - return simpleClassName; + return { + className: `${packageName}.${simpleClassName}`, + strategy: defaultStrategy, + }; } + // Found the file but no package declaration. Surface this + // as a structured failure (className: null) so callers can + // emit `classNameDetection.failed` with + // failureReason='noPackageDeclaration'. Callers already + // fall back to `input.target` on null, which preserves the + // previous command behaviour for default-package classes + // while making the telemetry distinguishable. + return { + className: null, + failureReason: 'noPackageDeclaration', + strategy: defaultStrategy, + }; } } catch (error) { - // Continue searching in other directories + // We at least reached the file system but could not read/scan it. + sawParseError = true; } } - return null; + let failureReason: ClassNameDetectionFailure; + if (sawParseError) { + failureReason = 'parseError'; + } else if (anyFileFound) { + // Defensive: the no-package case already returns above with + // failureReason='noPackageDeclaration', so reaching this branch + // means a future refactor has left the function falling through + // with anyFileFound=true. Preserve the same classification rather + // than silently bucketing into fileNotFound. + failureReason = 'noPackageDeclaration'; + } else if (anyDirExisted) { + failureReason = 'fileNotFound'; + } else { + failureReason = 'sourceDirMissing'; + } + + return { + className: null, + failureReason, + strategy: defaultStrategy, + }; } /** @@ -970,6 +1151,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs async invoke(options: { input: SetBreakpointInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); const breakpointKind = classifyBreakpoint(options.input); + const attempt = nextAttempt(TOOL_NAMES.SET_JAVA_BREAKPOINT); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; @@ -1012,7 +1196,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs errorCategory, breakpointKind, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.SET_JAVA_BREAKPOINT, outcome); } } }; @@ -1023,6 +1212,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs async invoke(options: { input: StepOperationInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); const stepKind = classifyStep(options.input.operation); + const attempt = nextAttempt(TOOL_NAMES.DEBUG_STEP_OPERATION); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; @@ -1080,7 +1272,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs errorCategory, stepKind, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.DEBUG_STEP_OPERATION, outcome); } } }; @@ -1092,6 +1289,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs const startedAt = Date.now(); const scopeTypeEnum = classifyScopeType(options.input.scopeType); const hasFilter = !!options.input.filter; + const attempt = nextAttempt(TOOL_NAMES.GET_DEBUG_VARIABLES); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; @@ -1191,7 +1391,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs scopeType: scopeTypeEnum, hasFilter, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.GET_DEBUG_VARIABLES, outcome); } } }; @@ -1201,6 +1406,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs const getStackTraceTool: LanguageModelTool = { async invoke(options: { input: GetStackTraceInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); + const attempt = nextAttempt(TOOL_NAMES.GET_DEBUG_STACK_TRACE); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; let frameCount = 0; @@ -1258,7 +1466,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs errorCategory, frameCount, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.GET_DEBUG_STACK_TRACE, outcome); } } }; @@ -1269,6 +1482,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs async invoke(options: { input: EvaluateExpressionInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); const evalContext = classifyEvalContext(options.input.context); + const attempt = nextAttempt(TOOL_NAMES.EVALUATE_DEBUG_EXPRESSION); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; @@ -1353,7 +1569,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs errorCategory, evalContext, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.EVALUATE_DEBUG_EXPRESSION, outcome); } } }; @@ -1363,6 +1584,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs const getThreadsTool: LanguageModelTool<{}> = { async invoke(_options: { input: {} }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); + const attempt = nextAttempt(TOOL_NAMES.GET_DEBUG_THREADS); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; let threadCount = 0; @@ -1445,7 +1669,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs threadCount, suspendedCount, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.GET_DEBUG_THREADS, outcome); } } }; @@ -1456,6 +1685,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs async invoke(options: { input: RemoveBreakpointsInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); const removeScope = classifyRemoveScope(options.input); + const attempt = nextAttempt(TOOL_NAMES.REMOVE_JAVA_BREAKPOINTS); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; let removedCount = 0; @@ -1513,7 +1745,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs removeScope, removedCount, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.REMOVE_JAVA_BREAKPOINTS, outcome); } } }; @@ -1523,6 +1760,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs const stopDebugSessionTool: LanguageModelTool = { async invoke(_options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); + const attempt = nextAttempt(TOOL_NAMES.STOP_DEBUG_SESSION); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; @@ -1560,7 +1800,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs outcome, errorCategory, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.STOP_DEBUG_SESSION, outcome); } } }; @@ -1570,6 +1815,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs const getDebugSessionInfoTool: LanguageModelTool = { async invoke(_options: { input: GetDebugSessionInfoInput }, _token: vscode.CancellationToken): Promise { const startedAt = Date.now(); + const attempt = nextAttempt(TOOL_NAMES.GET_DEBUG_SESSION_INFO); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); let outcome: ToolOutcome = 'success'; let errorCategory: ErrorCategory | undefined; let isPausedFlag = false; @@ -1771,7 +2019,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs errorCategory, isPaused: isPausedFlag, durationMs: Date.now() - startedAt, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, }); + completeAttempt(TOOL_NAMES.GET_DEBUG_SESSION_INFO, outcome); } } }; diff --git a/src/lmToolTelemetry.ts b/src/lmToolTelemetry.ts index 617e0b30..3a3153d4 100644 --- a/src/lmToolTelemetry.ts +++ b/src/lmToolTelemetry.ts @@ -68,6 +68,46 @@ export type ErrorCategory = | 'cancelled' | 'other'; +/** + * Why the launch-time classname detection failed to resolve a fully-qualified + * class name. Replaces the previous boolean `detected: false` so we can + * distinguish "we never had a chance" (sourceDirMissing) from "we found the + * file but it has no package" (noPackageDeclaration). + */ +export type ClassNameDetectionFailure = + | 'sourceDirMissing' // None of the candidate src directories existed. + | 'fileNotFound' // We walked the candidate dirs but never found ClassName.java. + | 'parseError' // We found the file but could not read or scan it. + | 'noPackageDeclaration'; // We found and parsed the file; it has no `package ...;`. + +/** + * Which candidate source-directory layout the detector was using when it + * decided the outcome. Together with {@link ClassNameDetectionFailure} this + * lets us correlate failures to project layouts. + */ +export type ClassNameDetectionStrategy = + | 'mavenStandard' // /src/main/java + | 'gradleStandard' // /src/main/java (same path, different driver) + | 'vscodeSrc' // /src + | 'workspaceRoot'; // / + +/** + * Coarse OS classification. Kept as a closed enum so we can slice telemetry + * by platform without depending on Common Schema `common.os` (which is + * stripped by some downstream telemetry filters). + */ +export type OperatingSystem = 'win32' | 'darwin' | 'linux' | 'other'; + +/** + * The outermost reason an invocation ended without emitting any of the + * regular outcome events. Used by the InvocationGuard to close the loop on + * silently-returning paths. + */ +export type SentinelOutcome = + | 'silentReturn' // The invoke function returned without recording an outcome. + | 'cancelled' // CancellationToken fired before any outcome was recorded. + | 'exception'; // An unhandled exception propagated out of invoke. + export type TargetType = 'mainClass' | 'jar' | 'rawArgs' | 'unknown'; export type BreakpointKind = @@ -254,6 +294,53 @@ export function classifyScopeType(scopeType: string | undefined): ScopeType { } } +/** + * Coerce a Node `process.platform` string into the closed {@link OperatingSystem} + * enum so telemetry sliced by `os` always matches `common.os` semantics. + */ +export function classifyPlatform(platform: string | undefined): OperatingSystem { + switch (platform) { + case 'win32': + case 'darwin': + case 'linux': + return platform; + default: + return 'other'; + } +} + +/** + * Extract the Java major version from a `java -version` (or + * `Runtime.version()`) style string. Returns `'unknown'` when the input + * is empty or unrecognised. Examples: + * "21.0.1" -> "21" + * "1.8.0_392" -> "8" + * "17.0.5+9" -> "17" + * "openjdk 21 2023" -> "21" + * + * Only the major version is emitted so we cannot fingerprint a build. + */ +export function classifyJavaMajorVersion(versionString: string | undefined | null): string { + if (!versionString) { + return 'unknown'; + } + const trimmed = String(versionString).trim(); + if (!trimmed) { + return 'unknown'; + } + // Legacy "1.X" naming (Java 8 and earlier). + const legacy = trimmed.match(/(?:^|\s|"|\()1\.(\d+)(?:[._]|$)/); + if (legacy) { + return legacy[1]; + } + // Modern major-only naming, e.g. "21", "21.0.1", "openjdk 17". + const modern = trimmed.match(/(?:^|\s|"|\()(\d{1,3})(?:[.+_]|$|\s)/); + if (modern) { + return modern[1]; + } + return 'unknown'; +} + // ============================================================================ // Recording helpers — the only entrypoints to `sendInfo` inside LMT code // ============================================================================ @@ -304,6 +391,26 @@ export interface ToolInvocationRecord { sessionId?: string; /** vscode-java-debug's own adapter type — value is constant `'java'`. */ sessionType?: string; + /** + * Cross-cutting diagnostic context. These are redundant with Common + * Schema fields (`common.os`, etc.) but are emitted explicitly so + * dashboards can slice without a join and downstream telemetry + * filters do not strip them. + */ + os?: OperatingSystem; + /** Java major version e.g. "21", "17", "8". Use `'unknown'` if not yet probed. */ + javaMajorVersion?: string; + /** Project flavour detected at launch time; same enum as launch-internal events. */ + projectSystem?: LaunchProjectType; + /** + * Retry instrumentation. `retryCount` is 0 for the first attempt within + * a VS Code session for this tool, 1 for the next, etc. + * `previousOutcome` is the terminal outcome of the immediately previous + * attempt, so we can distinguish auto-retry (LM driven, immediate) from + * user-driven retry (after editing code). + */ + retryCount?: number; + previousOutcome?: ToolOutcome; } /** @@ -317,6 +424,36 @@ export interface ToolInvocationRecord { */ export function recordToolInvocation(record: ToolInvocationRecord): void { const normalized = normalizeToolInvocationRecord(record); + /* __GDPR__ + "languageModelTool..invoke" : { + "owner": "vscode-java-debug", + "comment": "Outcome of a single Language Model Tool invocation.", + "operationName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "errorCategory": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "durationMs": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "targetType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "breakpointKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "stepKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "evalContext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "removeScope": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "scopeType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isPaused": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "skipBuild": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "hasFilter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "frameCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "threadCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "suspendedCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "removedCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "sessionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "sessionType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "os": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "javaMajorVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "projectSystem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "retryCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "previousOutcome": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } + */ sanitizedSend({ operationName: `languageModelTool.${normalized.tool}.invoke`, outcome: normalized.outcome, @@ -337,6 +474,11 @@ export function recordToolInvocation(record: ToolInvocationRecord): void { removedCount: normalized.removedCount, sessionId: normalized.sessionId, sessionType: normalized.sessionType, + os: normalized.os, + javaMajorVersion: normalized.javaMajorVersion, + projectSystem: normalized.projectSystem, + retryCount: normalized.retryCount, + previousOutcome: normalized.previousOutcome, }); } @@ -400,6 +542,18 @@ export interface ChatActivationRecord { * post-ship without per-turn cost. */ export function recordChatActivation(record: ChatActivationRecord): void { + /* __GDPR__ + "languageModelTool.chatActivationSnapshot" : { + "owner": "vscode-java-debug", + "comment": "Emitted once at Language Model Tool registration time; reports adoption surface.", + "operationName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "javaLSReadyAtActivation": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lmtCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "chatSkillsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "chatInstructionsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensionVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ sanitizedSend({ operationName: 'languageModelTool.chatActivationSnapshot', javaLSReadyAtActivation: record.javaLSReadyAtActivation, @@ -423,16 +577,31 @@ export type LaunchProjectType = 'maven' | 'gradle' | 'vscode' | 'unknown'; * * Note: `sessionId` here is VS Code's opaque debug-session GUID, never * the user-visible `launch.json` session name. + * + * Sentinel / silentReturn / cancelled / exception are the closed-loop + * variants emitted by {@link InvocationGuard}: they guarantee that every + * `debug_java_application` invocation produces at least one terminal + * event, even on code paths that previously silently returned. */ export type LaunchInternalEvent = | { name: 'cleanupExistingSession'; sessionId: string } | { name: 'cleanupExistingSessionFailed'; errorCategory: ErrorCategory } - | { name: 'debugSessionStarted.eventBased'; sessionId: string } - | { name: 'debugSessionTimeout.eventBased' } + | { name: 'debugSessionStarted.eventBased'; sessionId: string; elapsedMs?: number; thresholdMs?: number } + | { name: 'debugSessionTimeout.eventBased'; elapsedMs?: number; thresholdMs?: number } | { name: 'debugSessionDetected'; sessionId: string; elapsedMs: number } - | { name: 'debugSessionTimeout.smartPolling'; maxWaitTime: number } + | { name: 'debugSessionTimeout.smartPolling'; maxWaitTime: number; elapsedMs?: number } | { name: 'classNameDetection'; projectType: LaunchProjectType; detected: boolean } - | { name: 'getDebugSessionInfo.threadError'; errorCategory: ErrorCategory }; + | { + name: 'classNameDetection.failed'; + projectType: LaunchProjectType; + strategy: ClassNameDetectionStrategy; + failureReason: ClassNameDetectionFailure; + } + | { name: 'getDebugSessionInfo.threadError'; errorCategory: ErrorCategory } + | { name: 'debugSession.sentinel'; os: OperatingSystem; javaMajorVersion: string; projectSystem?: LaunchProjectType } + | { name: 'debugSession.silentReturn'; durationMs: number } + | { name: 'debugSession.cancelled'; durationMs: number } + | { name: 'debugSession.exception'; errorCategory: ErrorCategory; durationMs: number }; /** * Internal-debug event for the launch-flow nested instrumentation @@ -443,8 +612,179 @@ export type LaunchInternalEvent = */ export function recordLaunchInternal(event: LaunchInternalEvent): void { const { name, ...properties } = event; + /* __GDPR__ + "languageModelTool." : { + "owner": "vscode-java-debug", + "comment": "Internal launch-flow instrumentation; one of the LaunchInternalEvent variants.", + "operationName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "sessionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "errorCategory": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "elapsedMs": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "thresholdMs": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "maxWaitTime": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "durationMs": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "projectType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "projectSystem": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "detected": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "strategy": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failureReason": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "os": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "javaMajorVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ sanitizedSend({ operationName: `languageModelTool.${name}`, ...properties, }); } + +// ============================================================================ +// InvocationGuard — closed-loop sentinel for debug_java_application +// ============================================================================ +// +// Background: dashboards show ~33 % of `debug_java_application` invocations +// produce NO terminal event (neither started / timeout / cleanup / classname +// failed). The cause is silent-return paths inside the invoke handler. This +// guard wraps the handler so that any code path that exits without recording +// an outcome emits `debugSession.silentReturn`, and exceptions / cancellation +// are surfaced as their own dedicated events. +// +// Usage: +// const guard = beginDebugSessionInvocation(context, retryContext); +// try { +// const result = await actualWork(); +// guard.markOutcomeRecorded(); // call this whenever a regular event has fired +// return result; +// } catch (e) { +// guard.markException(e); +// throw e; +// } finally { +// guard.close(); // emits silentReturn / cancelled if needed +// } +// +// The guard is intentionally NOT a try/finally helper itself so callers can +// keep their existing control flow and let the type system check that +// `markOutcomeRecorded` is reached on the happy path. + +export interface InvocationContext { + os: OperatingSystem; + javaMajorVersion: string; + projectSystem?: LaunchProjectType; + /** Cancellation token; checked when the guard closes so we can emit `cancelled` instead of `silentReturn`. */ + isCancelled: () => boolean; +} + +export interface InvocationGuard { + /** Mark that some other terminal event (`started`, `timeout`, `classNameDetection.failed`, ...) was emitted. */ + markOutcomeRecorded(): void; + /** Mark that an unhandled exception is about to propagate. Pre-computes the errorCategory. */ + markException(err: unknown): void; + /** Always call from `finally`. Emits a closing event if nothing else did. */ + close(): void; +} + +/** + * Open an InvocationGuard for a `debug_java_application` call. Immediately + * emits a `debugSession.sentinel` event so we have a complete invocation + * count even if everything downstream fails. + */ +export function beginDebugSessionInvocation(context: InvocationContext): InvocationGuard { + const startedAt = Date.now(); + recordLaunchInternal({ + name: 'debugSession.sentinel', + os: context.os, + javaMajorVersion: context.javaMajorVersion, + projectSystem: context.projectSystem, + }); + + let outcomeRecorded = false; + let exceptionCategory: ErrorCategory | undefined; + + return { + markOutcomeRecorded(): void { + outcomeRecorded = true; + }, + markException(err: unknown): void { + exceptionCategory = classifyError(err); + }, + close(): void { + if (outcomeRecorded) { + return; + } + const durationMs = Date.now() - startedAt; + if (exceptionCategory !== undefined) { + recordLaunchInternal({ + name: 'debugSession.exception', + errorCategory: exceptionCategory, + durationMs, + }); + return; + } + if (context.isCancelled()) { + recordLaunchInternal({ + name: 'debugSession.cancelled', + durationMs, + }); + return; + } + recordLaunchInternal({ + name: 'debugSession.silentReturn', + durationMs, + }); + }, + }; +} + +// ============================================================================ +// SessionInvocationTracker — per-VS-Code-session retry attribution +// ============================================================================ +// +// We want each `recordToolInvocation` to carry `retryCount` (0-based) and +// `previousOutcome` so we can distinguish: +// - LM auto-retry (immediate, same session, previous outcome = timeout/failure) +// - User-driven retry (delayed, possibly different inputs) +// - First attempt +// +// The tracker is in-process only (lifetime = VS Code window) and stores no +// user-identifying data — it remembers only the previous outcome enum per +// tool name. + +interface ToolAttempt { + count: number; + previousOutcome?: ToolOutcome; +} + +const sessionAttempts = new Map(); + +/** + * Return the retry attribution for the next invocation of `tool`. Always + * call BEFORE the actual work so the returned `retryCount` reflects the + * attempt about to happen (0 for the first call). + */ +export function nextAttempt(tool: ToolName): { retryCount: number; previousOutcome?: ToolOutcome } { + const prev = sessionAttempts.get(tool); + return { + retryCount: prev?.count ?? 0, + previousOutcome: prev?.previousOutcome, + }; +} + +/** + * Record the terminal outcome of an attempt so the next call to + * {@link nextAttempt} can return the updated retry context. + */ +export function completeAttempt(tool: ToolName, outcome: ToolOutcome): void { + const prev = sessionAttempts.get(tool); + sessionAttempts.set(tool, { + count: (prev?.count ?? 0) + 1, + previousOutcome: outcome, + }); +} + +/** + * Test-only helper: reset the attempt map. Production code should never + * call this — VS Code session lifetime IS the intended scope. + */ +export function __resetAttemptsForTests(): void { + sessionAttempts.clear(); +}