diff --git a/.azure-pipelines/ci.yml b/.azure-pipelines/ci.yml index 7602a73e..59e230aa 100644 --- a/.azure-pipelines/ci.yml +++ b/.azure-pipelines/ci.yml @@ -44,11 +44,15 @@ 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: NodeTool@0 + displayName: Use Node 20.x + inputs: + versionSpec: 20.x - task: Npm@1 displayName: npm install inputs: 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: 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 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/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 diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml new file mode 100644 index 00000000..0e0cb45e --- /dev/null +++ b/.github/workflows/triage-agent.yml @@ -0,0 +1,126 @@ +name: AI Triage + +on: + issues: + types: [opened] + workflow_dispatch: + inputs: + issue_number: + 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 + +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 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 810a2c50..b0126f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,36 @@ 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). + +## 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)] + ## 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/README.md b/README.md index 54e122f1..767764ad 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 @@ -159,6 +193,7 @@ Please also check the documentation of [Language Support for Java by Red Hat](ht - `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/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..168dd465 --- /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', '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 + +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 00000000..affe97bd Binary files /dev/null and b/bundled/agents/images/agent-working.png differ diff --git a/bundled/agents/images/invoke-agent.png b/bundled/agents/images/invoke-agent.png new file mode 100644 index 00000000..e9c46cce Binary files /dev/null and b/bundled/agents/images/invoke-agent.png differ diff --git a/bundled/agents/images/javadebug.png b/bundled/agents/images/javadebug.png new file mode 100644 index 00000000..60f631f3 Binary files /dev/null and b/bundled/agents/images/javadebug.png differ 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..881a9211 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava @@ -0,0 +1,12 @@ +#!/bin/bash +# Java No-Config Debug Wrapper Script for Unix/Linux/macOS +# This script intercepts java commands and automatically enables JDWP debugging + +# Get the directory of this script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# 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 new file mode 100644 index 00000000..3606188c --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.bat @@ -0,0 +1,19 @@ +@echo off +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% + +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" %* + +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 new file mode 100644 index 00000000..47d7cf75 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.fish @@ -0,0 +1,12 @@ +#!/usr/bin/env fish +# Java No-Config Debug Wrapper Script for Fish Shell +# This script intercepts java commands and automatically enables JDWP debugging + +# Get the directory of this script +set script_dir (dirname (status -f)) + +# 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 new file mode 100644 index 00000000..0404a860 --- /dev/null +++ b/bundled/scripts/noConfigScripts/debugjava.ps1 @@ -0,0 +1,32 @@ +# Java No-Config Debug Wrapper Script for PowerShell +# This script intercepts java commands and automatically enables JDWP debugging + +# Get the directory of this script +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# 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 + } +} diff --git a/bundled/scripts/noConfigScripts/jdwp-wrapper.js b/bundled/scripts/noConfigScripts/jdwp-wrapper.js new file mode 100644 index 00000000..cae86170 --- /dev/null +++ b/bundled/scripts/noConfigScripts/jdwp-wrapper.js @@ -0,0 +1,144 @@ +#!/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(); + +// Helper function to setup signal handlers for graceful termination +function setupSignalHandlers(child) { + const signals = ['SIGINT', 'SIGTERM']; + signals.forEach(signal => { + 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 + }); + 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.`); + process.exit(1); + }); +} else { + // Debugging enabled, capture JDWP port + const child = spawn(javaCmd, process.argv.slice(2), { + stdio: ['inherit', 'pipe', 'pipe'], + shell: false + }); + setupSignalHandlers(child); + + 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); + }); + + // 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 290a7dec..75859e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "vscode-java-debug", - "version": "0.58.2", + "version": "0.59.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-java-debug", - "version": "0.58.2", + "version": "0.59.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.21", - "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "lodash": "^4.18.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" @@ -24,17 +23,17 @@ "@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", "tslint": "^6.1.3", "typescript": "^4.9.5", - "webpack": "^5.95.0", + "webpack": "^5.105.0", "webpack-cli": "^4.10.0" }, "engines": { - "vscode": "^1.75.0" + "vscode": "^1.95.0" } }, "node_modules/@babel/code-frame": { @@ -70,17 +69,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 +87,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 +98,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", @@ -128,68 +114,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" @@ -199,22 +189,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" @@ -224,27 +215,59 @@ "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.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", + "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": { @@ -294,19 +317,21 @@ "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", - "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" @@ -329,148 +354,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" } }, @@ -523,9 +548,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" @@ -534,13 +559,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": { @@ -556,28 +584,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": { @@ -661,6 +709,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", @@ -726,9 +783,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": [ { @@ -745,10 +802,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" @@ -809,9 +867,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": [ { @@ -1099,9 +1157,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": { @@ -1111,13 +1169,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" @@ -1136,9 +1194,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": { @@ -1233,11 +1291,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.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "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", @@ -1647,9 +1715,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" @@ -1665,9 +1733,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": { @@ -1701,12 +1769,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": { @@ -1725,9 +1797,10 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "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", @@ -1795,9 +1868,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" @@ -1875,9 +1948,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": { @@ -2061,9 +2134,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" @@ -2142,24 +2215,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", - "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", @@ -2208,6 +2263,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", @@ -2269,14 +2333,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" @@ -2299,12 +2364,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": { @@ -2482,12 +2548,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": { @@ -2496,13 +2566,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" }, @@ -2514,16 +2584,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" @@ -2724,9 +2794,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", @@ -2737,9 +2807,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" @@ -2795,9 +2865,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": [ { @@ -2815,7 +2885,7 @@ ], "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -2824,36 +2894,20 @@ "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", "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.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": { @@ -2915,9 +2969,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", @@ -2928,34 +2982,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" @@ -3044,9 +3100,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" @@ -3273,13 +3329,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" } }, @@ -3289,16 +3344,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", @@ -3306,15 +3355,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", @@ -3322,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": { @@ -3389,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": { @@ -3411,22 +3458,42 @@ } }, "@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.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", + "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": { @@ -3476,19 +3543,19 @@ "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": { - "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": { @@ -3505,148 +3572,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" } }, @@ -3686,15 +3753,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": {} }, @@ -3708,23 +3775,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", @@ -3775,6 +3853,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", @@ -3830,15 +3914,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": { @@ -3870,9 +3955,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": { @@ -4077,9 +4162,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": { @@ -4089,13 +4174,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": { @@ -4105,9 +4190,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": { @@ -4173,10 +4258,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.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": { @@ -4462,9 +4547,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" @@ -4477,9 +4562,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": { @@ -4510,9 +4595,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": { @@ -4525,9 +4610,9 @@ } }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "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", @@ -4577,9 +4662,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" @@ -4619,7 +4704,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", @@ -4641,9 +4726,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": { @@ -4772,9 +4857,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": { @@ -4831,21 +4916,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", - "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", @@ -4885,6 +4955,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", @@ -4928,14 +5004,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": { @@ -4945,13 +5022,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", @@ -5079,9 +5153,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": { @@ -5090,28 +5164,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": "7.0.5", + "terser": "^5.31.1" } }, "to-regex-range": { @@ -5249,9 +5323,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", @@ -5259,9 +5333,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" @@ -5300,22 +5374,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": { @@ -5324,18 +5389,13 @@ "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.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": { @@ -5389,9 +5449,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", @@ -5399,34 +5459,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": { @@ -5469,9 +5531,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 d5b58868..cba4a021 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.59.0", "publisher": "vscjava", "preview": false, "aiKey": "67d4461e-ccba-418e-8082-1bd0acfe8516", @@ -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": { @@ -99,6 +100,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" @@ -979,13 +984,385 @@ "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%", "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, + "when": "javaLSReady", + "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, + "when": "javaLSReady", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "when": "javaLSReady", + "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, + "when": "javaLSReady && inDebugMode && debugType == 'java'", + "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, + "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": { "vscode:prepublish": "npm run build", @@ -1002,23 +1379,25 @@ "@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", "tslint": "^6.1.3", "typescript": "^4.9.5", - "webpack": "^5.95.0", + "webpack": "^5.105.0", "webpack-cli": "^4.10.0" }, "dependencies": { "compare-versions": "^4.1.4", "dotenv": "^16.4.5", - "lodash": "^4.17.21", - "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "lodash": "^4.18.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" + }, + "overrides": { + "serialize-javascript": "7.0.5" } } 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 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. 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/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); } 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/extension.ts b/src/extension.ts index 6d8c2623..36025559 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,10 +13,13 @@ 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"; 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"; @@ -25,17 +28,30 @@ 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"; 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); + + // Register Language Model Tools after Java Language Server is ready + registerLanguageModelToolsWhenReady(context); + return instrumentOperation("activation", initializeExtension)(context); } 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())); @@ -88,6 +104,52 @@ 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); + + // 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 { 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..341fb7e6 --- /dev/null +++ b/src/languageModelTool.ts @@ -0,0 +1,2046 @@ +// 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 { + beginDebugSessionInvocation, + classifyBreakpoint, + classifyError, + classifyEvalContext, + classifyJavaMajorVersion, + classifyPlatform, + classifyRemoveScope, + classifyScopeType, + classifyStep, + classifyTarget, + ClassNameDetectionFailure, + ClassNameDetectionStrategy, + completeAttempt, + ErrorCategory, + InvocationGuard, + LaunchProjectType, + nextAttempt, + OperatingSystem, + recordLaunchInternal, + recordToolInvocation, + TOOL_NAMES, + ToolOutcome, +} from "./lmToolTelemetry"; + +// ============================================================================ +// Constants +// ============================================================================ +const CONSTANTS = { + /** + * 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) */ + 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 +}; + +// ---------------------------------------------------------------------------- +// 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; + 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 { + 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, guard); + 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 + ? `✓ ${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) { + outcome = token.isCancellationRequested ? 'cancelled' : 'failure'; + errorCategory = classifyError(error); + guard.markException(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, + 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(); + } + } + }; + + 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, + guard?: InvocationGuard, +): 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') { + recordLaunchInternal({ + name: 'cleanupExistingSession', + sessionId: existingSession.id, + }); + 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 + recordLaunchInternal({ + name: 'cleanupExistingSessionFailed', + errorCategory: classifyError(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 + // + // 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. 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('.')) { + 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); + + // 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 - 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 { + 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; + const waitStartedAt = Date.now(); + + // Listen for debug session start + const sessionDisposable = vscode.debug.onDidStartDebugSession((session) => { + if (session.type === 'java' && !sessionStarted) { + sessionStarted = true; + sessionDisposable.dispose(); + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + + recordLaunchInternal({ + name: 'debugSessionStarted.eventBased', + sessionId: session.id, + elapsedMs: Date.now() - waitStartedAt, + thresholdMs: CONSTANTS.SESSION_WAIT_TIMEOUT, + }); + guard?.markOutcomeRecorded(); + + 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(); + + recordLaunchInternal({ + name: 'debugSessionTimeout.eventBased', + elapsedMs: Date.now() - waitStartedAt, + thresholdMs: CONSTANTS.SESSION_WAIT_TIMEOUT, + }); + guard?.markOutcomeRecorded(); + + resolve({ + success: false, + status: 'timeout', + 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 + }); + } + }, 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 elapsedMs = Date.now() - startTime; + const elapsedTime = (elapsedMs / 1000).toFixed(1); + + recordLaunchInternal({ + name: 'debugSessionDetected', + sessionId: session.id, + elapsedMs, + }); + guard?.markOutcomeRecorded(); + + 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 polling window + recordLaunchInternal({ + name: 'debugSessionTimeout.smartPolling', + maxWaitTime, + elapsedMs: Date.now() - startTime, + }); + guard?.markOutcomeRecorded(); + + return { + success: true, + status: 'timeout', + 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 + }; + } +} + +/** + * 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. + * + * @param preDetectedClassName Fully-qualified class name pre-resolved by the + * caller (and already reported via the `classNameDetection` telemetry event). + * When non-null and the target is a simple class name, this is used instead + * of re-running findFullyQualifiedClassName here. The caller is expected to + * handle telemetry emission so we do not double-count detection outcomes. + */ +function constructDebugCommand( + input: DebugJavaApplicationInput, + projectType: 'maven' | 'gradle' | 'vscode' | 'unknown', + preDetectedClassName: string | null = null +): 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; + + // Use the caller-supplied detection result; we deliberately do not + // call findFullyQualifiedClassName a second time here (see the + // dedupe note at the call site in debugJavaApplication Step 3). + // Detection telemetry is owned by the caller. + if (!input.target.includes('.') && preDetectedClassName) { + className = preDetectedClassName; + } + + // 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; +} + +/** + * 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". + */ +function findFullyQualifiedClassName( + workspacePath: string, + simpleClassName: string, + projectType: LaunchProjectType +): ClassNameDetectionResult { + const defaultStrategy = strategyForProjectType(projectType); + + // 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; + } + + // 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 { + 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) { + // We at least reached the file system but could not read/scan it. + sawParseError = true; + } + } + + 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, + }; +} + +/** + * 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 { + 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; + + 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.SET_JAVA_BREAKPOINT, outcome); + } + } + }; + disposables.push(lmApi.registerTool('set_java_breakpoint', setBreakpointTool)); + + // 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); + const attempt = nextAttempt(TOOL_NAMES.DEBUG_STEP_OPERATION); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); + 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.') + ]); + } + + 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 (!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 }); + } 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.DEBUG_STEP_OPERATION, outcome); + } + } + }; + disposables.push(lmApi.registerTool('debug_step_operation', stepOperationTool)); + + // 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; + const attempt = nextAttempt(TOOL_NAMES.GET_DEBUG_VARIABLES); + const os = getOs(); + const javaMajorVersion = getJavaMajorVersion(); + 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.') + ]); + } + + 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) { + 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.') + ]); + } + + // 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) { + outcome = 'noStackFrame'; + errorCategory = 'noStackFrame'; + 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.GET_DEBUG_VARIABLES, outcome); + } + } + }; + 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 { + 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; + + 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.') + ]); + } + + 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) { + 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}` : + '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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.GET_DEBUG_STACK_TRACE, outcome); + } + } + }; + 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 { + 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; + + 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.') + ]); + } + + 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 { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Thread #${targetThreadId} is not suspended. Cannot evaluate expression.`) + ]); + } + } + + 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.') + ]); + } + + 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.EVALUATE_DEBUG_EXPRESSION, outcome); + } + } + }; + disposables.push(lmApi.registerTool('evaluate_debug_expression', evaluateExpressionTool)); + + // Tool 6: Get Threads + 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; + 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.') + ]); + } + + 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.') + ]); + } + + 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) { + 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'; + suspendedCount++; + 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.GET_DEBUG_THREADS, outcome); + } + } + }; + disposables.push(lmApi.registerTool('get_debug_threads', getThreadsTool)); + + // 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); + 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; + + try { + const { filePath, lineNumber } = options.input; + + const breakpoints = vscode.debug.breakpoints; + + 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).`) + ]); + } + + 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); + } + removedCount = toRemove.length; + + 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) { + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.REMOVE_JAVA_BREAKPOINTS, outcome); + } + } + }; + 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 { + 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; + + 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 sessionType = session.type; + + // Stop the debug session + await vscode.debug.stopDebugging(session); + + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart( + `✓ 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.STOP_DEBUG_SESSION, outcome); + } + } + }; + 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 { + 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; + + 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' + + '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; + recordLaunchInternal({ + name: 'getDebugSessionInfo.threadError', + errorCategory: classifyError(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'); + + 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, + os, + javaMajorVersion, + retryCount: attempt.retryCount, + previousOutcome: attempt.previousOutcome, + }); + completeAttempt(TOOL_NAMES.GET_DEBUG_SESSION_INFO, outcome); + } + } + }; + 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, '\\$&'); +} diff --git a/src/lmToolTelemetry.ts b/src/lmToolTelemetry.ts new file mode 100644 index 00000000..3a3153d4 --- /dev/null +++ b/src/lmToolTelemetry.ts @@ -0,0 +1,790 @@ +// 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'; + +/** + * 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 = + | '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'; + } +} + +/** + * 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 +// ============================================================================ + +/** 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; + /** + * 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; +} + +/** + * 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); + /* __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, + 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, + os: normalized.os, + javaMajorVersion: normalized.javaMajorVersion, + projectSystem: normalized.projectSystem, + retryCount: normalized.retryCount, + previousOutcome: normalized.previousOutcome, + }); +} + +/** + * 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 { + /* __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, + 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. + * + * 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; elapsedMs?: number; thresholdMs?: number } + | { name: 'debugSessionTimeout.eventBased'; elapsedMs?: number; thresholdMs?: number } + | { name: 'debugSessionDetected'; sessionId: string; elapsedMs: number } + | { name: 'debugSessionTimeout.smartPolling'; maxWaitTime: number; elapsedMs?: number } + | { name: 'classNameDetection'; projectType: LaunchProjectType; detected: boolean } + | { + 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 + * (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; + /* __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(); +} diff --git a/src/noConfigDebugInit.ts b/src/noConfigDebugInit.ts new file mode 100644 index 00000000..ea4e3606 --- /dev/null +++ b/src/noConfigDebugInit.ts @@ -0,0 +1,253 @@ +// 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"; +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. + * + * 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. + * - `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( + 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); + }); + } + } + + // 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; + } + + // 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) + 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. + // 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'); + applyReplaceIfChanged(collection, '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'); + applyAppendIfChanged(collection, 'PATH', buildNoConfigPathAppendValue(noConfigScriptsDir)); + + // 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(); + }), + ); +} 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/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; 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); + }); + }); +}); 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)); + }); +});