From a14400bda6d080d49fc512d6359b266c9fb7e4b7 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:36:35 +0800 Subject: [PATCH 1/7] Update telemetry wrapper to 0.15.1 (#1631) * Update telemetry wrapper to 0.15.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update telemetry wrapper to 0.15.2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 230 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 120 insertions(+), 112 deletions(-) diff --git a/package-lock.json b/package-lock.json index 885a47f..95be714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "dotenv": "^16.4.5", "lodash": "^4.18.0", "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" @@ -115,68 +115,72 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", - "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.4.1.tgz", + "integrity": "sha512-utqwacfUkiGJROn4WC7aNdRBsRxwhNWXuqaJM2B0N0WHmv1+IhSuI7RQ3FHwxRP1dxZi/xn9aELMZ7HMStsW1w==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", - "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.4.1.tgz", + "integrity": "sha512-CkFEhDY7X8E2JLr6HsEvRiC0DaLOCsA7vlbq/9DJP65gAumgw2NnFNIAOg6Je5Geq1LDu76/nb2hP34p8eGggw==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", - "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.1.tgz", + "integrity": "sha512-QS1k6iwVwR1MznGAB1H0F9raqpevbFNbadLS5O1419pz9OEWBfF9wRQLnENCyo8QS9Q0IdiqnGAON/D8IywpWg==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", - "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.1.tgz", + "integrity": "sha512-CTbD0g/68tiv2yCItsodDQBYxyHdfQkG7VhvVU8OHenukpl/7W4wEuxZuOntqhv5m9Nx/DFncbz+T83nvYTG3g==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", - "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.1.tgz", + "integrity": "sha512-eXIHZ1+nvBiJgVpufBiTP801Vtr5FEwjWZioUsb44NC/z/UcsZh2MDJ1mBpjaDO73LVYUw/ZZmDCCo6Pg/61kA==", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" @@ -186,22 +190,23 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", - "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.1.tgz", + "integrity": "sha512-V/hSlauFp1thJa57+TMv5mAYinJAQUi4zOmDmpahnDgs8g1zrQ0D8QYDmu0Zfi+9GhoD80B4yJez2+ydJPJz2w==", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.3.4", - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-channel-js": "3.4.1", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" }, "peerDependencies": { "tslib": ">= 1.0.0" @@ -211,22 +216,25 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.3.tgz", "integrity": "sha512-JTWTU80rMy3mdxOjjpaiDQsTLZ6YSGGqsjURsY6AUQtIj0udlF/jYmhdLZu8693ZIC0T1IwYnFa0+QeiMnziBA==", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.10.4 < 2.x" } }, "node_modules/@nevware21/ts-async": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.3.tgz", - "integrity": "sha512-UsF7eerLsVfid7iV1oXF80qXBwHNBeqSqfh/nPZgirRU1MACmSsj83EZKS2ViFHVfSGG6WIuXMGBP6KciXfYhA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.11.5 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", - "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", + "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==", + "license": "MIT" }, "node_modules/@types/eslint": { "version": "9.6.1", @@ -308,13 +316,14 @@ "license": "MIT" }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.7.tgz", - "integrity": "sha512-2GQbcfDUTg0QC1v0HefkHNwYrE5LYKzS3Zb0+uA6Qn1MBDzgiSh23ddOZF/JRqhqBFOG0mE70XslKSGQ5v9KwQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.1.tgz", + "integrity": "sha512-rnRRQIRCwRdbcQ0QV5ajKJRz8noEIoQA2hX9VjAlVAVB85+ClbaPNhljobFXgW31ue69FRO6KPE4XJ/lLgKt/Q==", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.3.0", - "@microsoft/1ds-post-js": "^4.3.0", - "@microsoft/applicationinsights-web-basic": "^3.3.0" + "@microsoft/1ds-core-js": "^4.3.10", + "@microsoft/1ds-post-js": "^4.3.10", + "@microsoft/applicationinsights-web-basic": "^3.3.10" }, "engines": { "vscode": "^1.75.0" @@ -2892,12 +2901,13 @@ } }, "node_modules/vscode-extension-telemetry-wrapper": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.14.0.tgz", - "integrity": "sha512-EYr1hqiYVSGfupchDN405zSwuvA8V3tJ62KcLIRDr/4ongOc2AvSZ0BlRq8a0w950tadsMlXTKEheB97fZBttg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", + "integrity": "sha512-efKkHF8c4kTKyBhBH2k0bZU4drqIic2jBYw/j1ixKOEEsa/WIiuUsdrBPD5uaRIoZ/91GzNCLiiV4ckIrf581g==", + "license": "MIT", "dependencies": { - "@vscode/extension-telemetry": "^0.9.6", - "uuid": "^8.3.2" + "@microsoft/applicationinsights-common": "^3.4.1", + "@vscode/extension-telemetry": "^1.2.0" } }, "node_modules/vscode-jsonrpc": { @@ -3361,62 +3371,61 @@ } }, "@microsoft/1ds-core-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", - "integrity": "sha512-3gbDUQgAO8EoyQTNcAEkxpuPnioC0May13P1l1l0NKZ128L9Ts/sj8QsfwCRTjHz0HThlA+4FptcAJXNYUy3rg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.4.1.tgz", + "integrity": "sha512-utqwacfUkiGJROn4WC7aNdRBsRxwhNWXuqaJM2B0N0WHmv1+IhSuI7RQ3FHwxRP1dxZi/xn9aELMZ7HMStsW1w==", "requires": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/1ds-post-js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.3.4.tgz", - "integrity": "sha512-nlKjWricDj0Tn68Dt0P8lX9a+X7LYrqJ6/iSfQwMfDhRIGLqW+wxx8gxS+iGWC/oc8zMQAeiZaemUpCwQcwpRQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-4.4.1.tgz", + "integrity": "sha512-CkFEhDY7X8E2JLr6HsEvRiC0DaLOCsA7vlbq/9DJP65gAumgw2NnFNIAOg6Je5Geq1LDu76/nb2hP34p8eGggw==", "requires": { - "@microsoft/1ds-core-js": "4.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-channel-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.3.4.tgz", - "integrity": "sha512-Z4nrxYwGKP9iyrYtm7iPQXVOFy4FsEsX0nDKkAi96Qpgw+vEh6NH4ORxMMuES0EollBQ3faJyvYCwckuCVIj0g==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.1.tgz", + "integrity": "sha512-QS1k6iwVwR1MznGAB1H0F9raqpevbFNbadLS5O1419pz9OEWBfF9wRQLnENCyo8QS9Q0IdiqnGAON/D8IywpWg==", "requires": { - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-common": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.3.4.tgz", - "integrity": "sha512-4ms16MlIvcP4WiUPqopifNxcWCcrXQJ2ADAK/75uok2mNQe6ZNRsqb/P+pvhUxc8A5HRlvoXPP1ptDSN5Girgw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.1.tgz", + "integrity": "sha512-CTbD0g/68tiv2yCItsodDQBYxyHdfQkG7VhvVU8OHenukpl/7W4wEuxZuOntqhv5m9Nx/DFncbz+T83nvYTG3g==", "requires": { - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-core-js": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.3.4.tgz", - "integrity": "sha512-MummANF0mgKIkdvVvfmHQTBliK114IZLRhTL0X0Ep+zjDwWMHqYZgew0nlFKAl6ggu42abPZFK5afpE7qjtYJA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.1.tgz", + "integrity": "sha512-eXIHZ1+nvBiJgVpufBiTP801Vtr5FEwjWZioUsb44NC/z/UcsZh2MDJ1mBpjaDO73LVYUw/ZZmDCCo6Pg/61kA==", "requires": { "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/applicationinsights-shims": { @@ -3428,17 +3437,16 @@ } }, "@microsoft/applicationinsights-web-basic": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.3.4.tgz", - "integrity": "sha512-OpEPXr8vU/t/M8T9jvWJzJx/pCyygIiR1nGM/2PTde0wn7anl71Gxl5fWol7K/WwFEORNjkL3CEyWOyDc+28AA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.1.tgz", + "integrity": "sha512-V/hSlauFp1thJa57+TMv5mAYinJAQUi4zOmDmpahnDgs8g1zrQ0D8QYDmu0Zfi+9GhoD80B4yJez2+ydJPJz2w==", "requires": { - "@microsoft/applicationinsights-channel-js": "3.3.4", - "@microsoft/applicationinsights-common": "3.3.4", - "@microsoft/applicationinsights-core-js": "3.3.4", + "@microsoft/applicationinsights-channel-js": "3.4.1", + "@microsoft/applicationinsights-core-js": "3.4.1", "@microsoft/applicationinsights-shims": "3.0.1", "@microsoft/dynamicproto-js": "^2.0.3", - "@nevware21/ts-async": ">= 0.5.2 < 2.x", - "@nevware21/ts-utils": ">= 0.11.3 < 2.x" + "@nevware21/ts-async": ">= 0.5.5 < 2.x", + "@nevware21/ts-utils": ">= 0.12.6 < 2.x" } }, "@microsoft/dynamicproto-js": { @@ -3450,17 +3458,17 @@ } }, "@nevware21/ts-async": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.3.tgz", - "integrity": "sha512-UsF7eerLsVfid7iV1oXF80qXBwHNBeqSqfh/nPZgirRU1MACmSsj83EZKS2ViFHVfSGG6WIuXMGBP6KciXfYhA==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==", "requires": { - "@nevware21/ts-utils": ">= 0.11.5 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "@nevware21/ts-utils": { - "version": "0.11.5", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.11.5.tgz", - "integrity": "sha512-7nIzWKR50mf3htOg53kwPLqD5iJaRfVyBvb1NJhlIncyP1WzK8vAQbU9rqIsRtv7td1CnqspdP6IWNEjOjaeug==" + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", + "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==" }, "@types/eslint": { "version": "9.6.1", @@ -3541,13 +3549,13 @@ "dev": true }, "@vscode/extension-telemetry": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.7.tgz", - "integrity": "sha512-2GQbcfDUTg0QC1v0HefkHNwYrE5LYKzS3Zb0+uA6Qn1MBDzgiSh23ddOZF/JRqhqBFOG0mE70XslKSGQ5v9KwQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-1.5.1.tgz", + "integrity": "sha512-rnRRQIRCwRdbcQ0QV5ajKJRz8noEIoQA2hX9VjAlVAVB85+ClbaPNhljobFXgW31ue69FRO6KPE4XJ/lLgKt/Q==", "requires": { - "@microsoft/1ds-core-js": "^4.3.0", - "@microsoft/1ds-post-js": "^4.3.0", - "@microsoft/applicationinsights-web-basic": "^3.3.0" + "@microsoft/1ds-core-js": "^4.3.10", + "@microsoft/1ds-post-js": "^4.3.10", + "@microsoft/applicationinsights-web-basic": "^3.3.10" } }, "@vscode/test-electron": { @@ -5387,12 +5395,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vscode-extension-telemetry-wrapper": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.14.0.tgz", - "integrity": "sha512-EYr1hqiYVSGfupchDN405zSwuvA8V3tJ62KcLIRDr/4ongOc2AvSZ0BlRq8a0w950tadsMlXTKEheB97fZBttg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", + "integrity": "sha512-efKkHF8c4kTKyBhBH2k0bZU4drqIic2jBYw/j1ixKOEEsa/WIiuUsdrBPD5uaRIoZ/91GzNCLiiV4ckIrf581g==", "requires": { - "@vscode/extension-telemetry": "^0.9.6", - "uuid": "^8.3.2" + "@microsoft/applicationinsights-common": "^3.4.1", + "@vscode/extension-telemetry": "^1.2.0" } }, "vscode-jsonrpc": { diff --git a/package.json b/package.json index 4a5650c..0576a32 100644 --- a/package.json +++ b/package.json @@ -1367,7 +1367,7 @@ "dotenv": "^16.4.5", "lodash": "^4.18.0", "uuid": "^8.3.2", - "vscode-extension-telemetry-wrapper": "^0.14.0", + "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", "vscode-tas-client": "^0.1.84" From 0db5f15cb01d6a864350b5dd28012bc5c50bc2ce Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 28 Apr 2026 15:33:39 +0800 Subject: [PATCH 2/7] Remove uuid dependency, use built-in crypto.randomUUID() (#1632) Replace the uuid package with Node.js built-in crypto.randomUUID(). This removes the uuid dependency entirely, addressing the Dependabot alert for uuid v8 and avoiding compatibility issues with uuid v14+. --- package-lock.json | 14 -------------- package.json | 1 - src/progressImpl.ts | 4 ++-- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95be714..f3eab70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "compare-versions": "^4.1.4", "dotenv": "^16.4.5", "lodash": "^4.18.0", - "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", @@ -2892,14 +2891,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vscode-extension-telemetry-wrapper": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", @@ -5389,11 +5380,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, "vscode-extension-telemetry-wrapper": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/vscode-extension-telemetry-wrapper/-/vscode-extension-telemetry-wrapper-0.15.2.tgz", diff --git a/package.json b/package.json index 0576a32..715551f 100644 --- a/package.json +++ b/package.json @@ -1366,7 +1366,6 @@ "compare-versions": "^4.1.4", "dotenv": "^16.4.5", "lodash": "^4.18.0", - "uuid": "^8.3.2", "vscode-extension-telemetry-wrapper": "^0.15.2", "vscode-languageclient": "6.0.0-next.9", "vscode-languageserver-types": "3.16.0", diff --git a/src/progressImpl.ts b/src/progressImpl.ts index 6ee2a98..4862693 100644 --- a/src/progressImpl.ts +++ b/src/progressImpl.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { v4 } from "uuid"; +import * as crypto from "crypto"; import { CancellationToken, CancellationTokenSource, Disposable, EventEmitter, ProgressLocation, StatusBarAlignment, StatusBarItem, window, workspace } from "vscode"; import { IProgressProvider, IProgressReporter } from "./progressAPI"; const STATUS_COMMAND: string = "java.show.server.task.status"; class ProgressReporter implements IProgressReporter { - private _id: string = v4(); + private _id: string = crypto.randomUUID(); private _jobName: string; private _progressLocation: ProgressLocation | { viewId: string }; private _cancellable: boolean = false; From 3df9d97374130fb4e07cb2916f0eaad373e965e1 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Fri, 8 May 2026 09:24:42 +0800 Subject: [PATCH 3/7] fix: always prepend separator when appending noConfigScripts to PATH (#1637) (#1641) * fix: always prepend separator when appending noConfigScripts to PATH EnvironmentVariableCollection.append() performs a literal string concatenation and does not insert a path separator. The previous heuristic checked process.env.PATH on the extension host and skipped the separator when that PATH already ended with one. However, the PATH used by the integrated terminal can differ from the extension host's PATH, so this check could incorrectly drop the separator and glue the noConfigScripts directory onto the last entry of the user's PATH (e.g. C:\Program Files\jreleaser\c:\Users\...\noConfigScripts). Always prepend the separator. A trailing empty PATH entry (if the user's PATH already ended with one) is harmless on both Windows and POSIX shells. Fixes #1637 * test: add unit tests for noConfigScripts PATH append helper Extract the PATH append-value computation into a vscode-free src/pathUtil.ts module so it can be unit-tested in plain Node mocha. Add test/pathUtil.test.ts covering: - Correct separator on Windows (;), Linux (:), and macOS (:). - The appended value always starts with a path separator (regression guard for #1637). - The scripts directory remains a standalone PATH entry when the user's PATH last entry has no trailing separator (the exact scenario reported in #1637, e.g. C:\Program Files\jreleaser\\). - A trailing separator on the user's PATH only produces a harmless empty entry, never glues another entry onto our scripts dir. - The scripts directory is preserved verbatim at the end of the value. All 9 tests pass under plain mocha; the file is also picked up by the existing Electron-based test runner via the **/**.test.js glob. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * style: trim verbose comments in pathUtil and tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/noConfigDebugInit.ts | 10 ++----- src/pathUtil.ts | 19 ++++++++++++ test/pathUtil.test.ts | 65 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 src/pathUtil.ts create mode 100644 test/pathUtil.test.ts diff --git a/src/noConfigDebugInit.ts b/src/noConfigDebugInit.ts index b0da4f0..2f84ee6 100644 --- a/src/noConfigDebugInit.ts +++ b/src/noConfigDebugInit.ts @@ -8,6 +8,7 @@ import * as vscode from 'vscode'; import { sendInfo, sendError } from "vscode-extension-telemetry-wrapper"; import { getJavaHome } from "./utility"; +import { buildNoConfigPathAppendValue } from "./pathUtil"; /** * Registers the configuration-less debugging setup for the extension. @@ -91,14 +92,7 @@ export async function registerNoConfigDebug( } const noConfigScriptsDir = path.join(extPath, 'bundled', 'scripts', 'noConfigScripts'); - const pathSeparator = process.platform === 'win32' ? ';' : ':'; - - // Check if the current PATH already ends with a path separator to avoid double separators - const currentPath = process.env.PATH || ''; - const needsSeparator = currentPath.length > 0 && !currentPath.endsWith(pathSeparator); - const pathValueToAppend = needsSeparator ? `${pathSeparator}${noConfigScriptsDir}` : noConfigScriptsDir; - - collection.append('PATH', pathValueToAppend); + collection.append('PATH', buildNoConfigPathAppendValue(noConfigScriptsDir)); // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written const fileSystemWatcher = vscode.workspace.createFileSystemWatcher( diff --git a/src/pathUtil.ts b/src/pathUtil.ts new file mode 100644 index 0000000..a9f680b --- /dev/null +++ b/src/pathUtil.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Builds the value to append to PATH for the noConfigScripts directory. + * + * `vscode.EnvironmentVariableCollection.append()` does literal string + * concatenation, so we always prepend a separator to avoid gluing our + * directory onto the last entry of the user's PATH (see issue #1637). + * + * @param platform defaults to `process.platform`; injectable for tests. + */ +export function buildNoConfigPathAppendValue( + scriptsDir: string, + platform: NodeJS.Platform = process.platform, +): string { + const pathSeparator = platform === 'win32' ? ';' : ':'; + return `${pathSeparator}${scriptsDir}`; +} diff --git a/test/pathUtil.test.ts b/test/pathUtil.test.ts new file mode 100644 index 0000000..9f76c8f --- /dev/null +++ b/test/pathUtil.test.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as assert from "assert"; + +import { buildNoConfigPathAppendValue } from "../src/pathUtil"; + +// Regression tests for issue #1637. +suite("buildNoConfigPathAppendValue", () => { + + const winDir = "C:\\Users\\me\\.vscode\\extensions\\vscjava.vscode-java-debug-0.59.0\\bundled\\scripts\\noConfigScripts"; + const posixDir = "/home/me/.vscode/extensions/vscjava.vscode-java-debug-0.59.0/bundled/scripts/noConfigScripts"; + + test("uses ';' as separator on Windows", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.strictEqual(result, `;${winDir}`); + }); + + test("uses ':' as separator on Linux", () => { + const result = buildNoConfigPathAppendValue(posixDir, "linux"); + assert.strictEqual(result, `:${posixDir}`); + }); + + test("uses ':' as separator on macOS", () => { + const result = buildNoConfigPathAppendValue(posixDir, "darwin"); + assert.strictEqual(result, `:${posixDir}`); + }); + + test("always starts with a path separator (Windows)", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.ok(result.startsWith(";")); + }); + + test("always starts with a path separator (POSIX)", () => { + const result = buildNoConfigPathAppendValue(posixDir, "linux"); + assert.ok(result.startsWith(":")); + }); + + test("never collapses scriptsDir into the previous PATH entry on Windows", () => { + // #1637 scenario: last user PATH entry has no trailing separator. + const userPath = "C:\\foo;C:\\Program Files\\jreleaser\\"; + const entries = (userPath + buildNoConfigPathAppendValue(winDir, "win32")).split(";"); + assert.ok(entries.includes("C:\\Program Files\\jreleaser\\")); + assert.ok(entries.includes(winDir)); + }); + + test("never collapses scriptsDir into the previous PATH entry on POSIX", () => { + const userPath = "/usr/bin:/opt/jreleaser/bin"; + const entries = (userPath + buildNoConfigPathAppendValue(posixDir, "linux")).split(":"); + assert.ok(entries.includes("/opt/jreleaser/bin")); + assert.ok(entries.includes(posixDir)); + }); + + test("yields only an empty (harmless) entry when the user's PATH already ends with a separator", () => { + const userPath = "C:\\foo;C:\\bar;"; + const entries = (userPath + buildNoConfigPathAppendValue(winDir, "win32")).split(";"); + assert.ok(entries.includes(winDir)); + assert.ok(!entries.some((e) => e !== winDir && e.endsWith(winDir))); + }); + + test("scriptsDir appears unchanged at the end of the appended value", () => { + const result = buildNoConfigPathAppendValue(winDir, "win32"); + assert.ok(result.endsWith(winDir)); + }); +}); From da84f11bef62cde4250f59378b2443436e1ab5a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 16:10:31 +0800 Subject: [PATCH 4/7] Bump fast-uri from 3.1.0 to 3.1.2 (#1642) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3eab70..9254cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1283,9 +1283,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -4250,9 +4250,9 @@ "dev": true }, "fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true }, "fastest-levenshtein": { From 1bf69fee45a28ab2aea774bbb6ba4dd032cd43cd Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Wed, 20 May 2026 16:02:19 +0800 Subject: [PATCH 5/7] feat(copilot): add chat skills, instructions and when-gates for language model tools (#1643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate all 10 language model tools with `when` clauses so they only register when the Java Language Server is ready, and (for active-debug-session-only tools) when an active `java` debug session is in progress. This avoids loading failures and reduces the noise Copilot sees from this extension's tool catalog when the tools cannot actually run. - Contribute a `chatInstructions` file (`javaDebugContext.instructions.md`) with a keyword-rich, on-demand description (no `applyTo`, to avoid burning context on every Java edit) that tells Copilot to activate the deferred Java debug tools via `tool_search_tool_regex` and routes the user request to one of the two skills below. - Contribute two `chatSkills`, split by user-habit telemetry (launch/stop is ~52% of tool usage; inspection/step is ~28%): - `java-launch-troubleshooting` — start/stop a Java program and diagnose launch failures (mainClass missing, classpath, build errors, project not detected). Gated by `javaLSReady` so it is discoverable any time in a Java workspace. - `java-debug-inspection` — inspect variables, walk the stack, list threads, evaluate expressions, step in/over/out, continue, and manage breakpoints in an active Java debug session. Gated by `javaLSReady && inDebugMode && debugType == 'java'` so it only appears once a Java debug session is alive — keeping it reactive, not proactive. --- package.json | 26 +++++++++ .../javaDebugContext.instructions.md | 16 +++++ .../skills/java-debug-inspection/SKILL.md | 58 +++++++++++++++++++ .../java-launch-troubleshooting/SKILL.md | 50 ++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 resources/instruments/javaDebugContext.instructions.md create mode 100644 resources/skills/java-debug-inspection/SKILL.md create mode 100644 resources/skills/java-launch-troubleshooting/SKILL.md diff --git a/package.json b/package.json index 715551f..cba4a02 100644 --- a/package.json +++ b/package.json @@ -1011,6 +1011,7 @@ ], "icon": "$(debug-alt)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1062,6 +1063,7 @@ ], "icon": "$(debug-breakpoint)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1105,6 +1107,7 @@ ], "icon": "$(debug-step-over)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1142,6 +1145,7 @@ ], "icon": "$(symbol-variable)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1183,6 +1187,7 @@ ], "icon": "$(call-hierarchy)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1212,6 +1217,7 @@ ], "icon": "$(symbol-method)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1257,6 +1263,7 @@ ], "icon": "$(list-tree)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": {}, @@ -1275,6 +1282,7 @@ ], "icon": "$(debug-breakpoint-unverified)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": { @@ -1303,6 +1311,7 @@ ], "icon": "$(debug-stop)", "canBeReferencedInPrompt": true, + "when": "javaLSReady && inDebugMode && debugType == 'java'", "inputSchema": { "type": "object", "properties": { @@ -1330,12 +1339,29 @@ ], "icon": "$(info)", "canBeReferencedInPrompt": true, + "when": "javaLSReady", "inputSchema": { "type": "object", "properties": {}, "required": [] } } + ], + "chatSkills": [ + { + "path": "./resources/skills/java-launch-troubleshooting/SKILL.md", + "when": "javaLSReady" + }, + { + "path": "./resources/skills/java-debug-inspection/SKILL.md", + "when": "javaLSReady && inDebugMode && debugType == 'java'" + } + ], + "chatInstructions": [ + { + "path": "./resources/instruments/javaDebugContext.instructions.md", + "when": "javaLSReady" + } ] }, "scripts": { diff --git a/resources/instruments/javaDebugContext.instructions.md b/resources/instruments/javaDebugContext.instructions.md new file mode 100644 index 0000000..055b01b --- /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 0000000..354132d --- /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 0000000..c14dc52 --- /dev/null +++ b/resources/skills/java-launch-troubleshooting/SKILL.md @@ -0,0 +1,50 @@ +--- +name: java-launch-troubleshooting +description: Use when the user wants to run, launch, start, restart, or stop a Java program (main class, Spring Boot, JAR, Maven, Gradle), or diagnose launch failures (mainClass missing, classpath unresolved, compile failure, "project not detected", `ClassNotFoundException` at startup). NOT for inspecting variables, stepping, or setting breakpoints in an already-running debug session — use `java-debug-inspection` for that. +--- + +# Java Launch Troubleshooting + +Use this skill when the user wants to **start or stop** a Java program, or when an attempted launch fails. Typical user phrases: + +- "run this main class", "start the app", "launch the Spring Boot project", "run the jar" +- "stop the debug session", "kill the running app" +- prior `run_in_terminal` failed with `ClassNotFoundException`, `mainClass is not set`, `Could not find or load main class`, `Could not resolve classpath` +- the user changed `pom.xml` / `build.gradle` and the app no longer starts + +## Tools + +These language model tools are contributed by the `Debugger for Java` extension and are deferred. Activate them with `tool_search_tool_regex` using pattern `java_breakpoint|debug` before first use. + +| Tool | Purpose | +|---|---| +| `debug_java_application` | Build + resolve classpath + start JVM. Returns precise compile and classpath errors. | +| `get_debug_session_info` | Check whether a debug session is already running and its status. | +| `stop_debug_session` | Stop a running Java debug session cleanly. | + +## Preferred Workflow + +1. **Confirm intent.** Is the user trying to *run / start / launch / stop* a Java program (use this skill) or just edit code (do not load this skill)? +2. **Check existing session.** Call `get_debug_session_info` first. If a session is already running for the target, do not launch a second one. +3. **Launch.** Call `debug_java_application` with `target` = the fully qualified main class or JAR, and `workspacePath` = the project root containing `pom.xml`, `build.gradle`, or `.classpath`. Let `skipBuild` default to `false` so the tool handles compilation. +4. **Read the error.** If `debug_java_application` fails, the error message is structured (mainClass missing, classpath unresolved, build failure with line number). Use it to suggest a fix — do not retry with `run_in_terminal`. +5. **Stop when done.** When the user says "stop", "kill it", or has the answer they need, call `stop_debug_session`. + +## Common Failure Modes + +| Symptom from `debug_java_application` | Likely cause | Suggested fix | +|---|---|---| +| `mainClass is not configured` / `mainClass missing` | Project has no `launch.json`, and the file has no `public static void main` | Ask user which class to launch, or generate `launch.json` | +| `Could not resolve classpath` | Maven/Gradle import has not completed, or `pom.xml` has unresolved dependencies | Wait for Java Language Server import, then ask user to run `Java: Clean Java Language Server Workspace` | +| `Compilation failed` with file:line | Source code has a compile error | Fix the reported error in the source file, do not retry the launch | +| `Project not detected` | `workspacePath` does not contain a build file | Re-check `workspacePath`; for multi-module projects, use the module root, not the repo root | + +## When NOT to Use This Skill + +- The user is editing or refactoring Java code without running it → do nothing +- The user is already inside a live debug session and wants to inspect variables, evaluate expressions, walk the stack, step, or set / remove breakpoints → use `java-debug-inspection` instead, do not re-launch +- The program is a non-Java language → do not load this skill + +## Fallback + +If `debug_java_application` returns `Java Language Server not ready` or repeats the same error twice, fall back to `run_in_terminal` with the appropriate `mvn` or `gradle` command and report the raw output to the user. Do not retry the debug tool more than twice. From dcbb93fe4db63f90872d67694426ab777b8f0ea6 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Thu, 21 May 2026 09:52:45 +0800 Subject: [PATCH 6/7] Harden Language Model Tool telemetry against PII leaks (#1644) * Harden Language Model Tool telemetry against PII leaks Centralise all LMT telemetry through src/lmToolTelemetry.ts so user-supplied strings (target, expression, sessionName, file paths, class names, JVM stack traces, etc.) can no longer reach the telemetry pipeline. The new module exposes a typed sanitizedSend choke point that only accepts enums, booleans, numbers and opaque session IDs. Telemetry changes: - Drop sendError(error) on debug_java_application failure (stack trace leaked user class / method names). - Strip PII fields from every existing event: target, sessionName, currentFile, currentLine, simpleClassName, detectedClassName, error: String(error), input.reason. - Replace bare String(error) propagation with classifyError() -> ErrorCategory enum (mainClassMissing, classpathUnresolved, buildFailure, projectNotDetected, sessionAlreadyRunning, timeout, lsNotReady, noActiveSession, noSuspendedThread, noStackFrame, cancelled, other). - Add per-invoke recording for all 10 tools with outcome, errorCategory, durationMs, and a tool-specific enum (targetType / breakpointKind / stepKind / scopeType / evalContext / removeScope). The previous build only emitted telemetry on the launch tool and the session-info tool. - Add chatActivationSnapshot one-shot at registration time so we can measure adoption of the chat surfaces without per-turn cost (counts only). - evaluate_debug_expression: the expression text is NEVER logged. Only the evalContext enum and outcome are emitted. Policy: - src/lmToolTelemetry.ts is now the only file in the LMT code path allowed to call sendInfo. The top-of-file policy comment is the single source of truth for what may be logged. - The recorder is typed against ToolInvocationRecord so excess raw strings are rejected at compile time. Validated with: npm run tslint, npm run compile. * Address Copilot review on PR #1644 - classifyStep: unknown step operations now report 'unknown' instead of being silently mislabeled as 'over'. Also adds a runtime guard in debug_step_operation so an unknown operation no longer reaches commandMap[op]/executeCommand(undefined) or session.customRequest with an arbitrary string. - recordToolInvocation: introduces a private normalizeToolInvocationRecord that keeps 'outcome' and 'errorCategory' in lock-step for the six shared terminal values (cancelled / timeout / lsNotReady / noActiveSession / noSuspendedThread / noStackFrame). Fixes the case where debug_java_application returns {success:false,message:'Operation cancelled by user'} but outcome was 'failure' while errorCategory was 'cancelled'. - get_debug_stack_trace: empty-stack-frame early return now sets errorCategory='noStackFrame' alongside outcome (was only setting outcome). - recordLaunchInternal: signature is now a discriminated union (LaunchInternalEvent) instead of (operationName: string, properties: Record). Unknown event names and unexpected property keys are now rejected at compile time. Updated all 8 call sites. - elapsedTime (string from .toFixed) split from elapsedMs (number) so the telemetry value is numeric and aggregable. --- src/extension.ts | 24 +++ src/languageModelTool.ts | 308 +++++++++++++++++++++------ src/lmToolTelemetry.ts | 450 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 722 insertions(+), 60 deletions(-) create mode 100644 src/lmToolTelemetry.ts diff --git a/src/extension.ts b/src/extension.ts index e3a75eb..3602555 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,6 +19,7 @@ import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorF import { JavaInlineValuesProvider } from "./JavaInlineValueProvider"; import { logJavaException, logJavaInfo } from "./javaLogger"; import { registerLanguageModelTool, registerDebugSessionTools } from "./languageModelTool"; +import { recordChatActivation } from "./lmToolTelemetry"; import { IMainClassOption, IMainMethod, resolveMainMethod } from "./languageServerPlugin"; import { mainClassPicker } from "./mainClassPicker"; import { pickJavaProcess } from "./processPicker"; @@ -124,6 +125,29 @@ async function registerLanguageModelToolsWhenReady(context: vscode.ExtensionCont registerLanguageModelTool(context); const debugToolsDisposables = registerDebugSessionTools(context); context.subscriptions.push(...debugToolsDisposables); + + // One-shot activation snapshot so we can track coverage of the new chat surfaces over time. + // Counts only — no user data, no file paths, no class names. + try { + const pkg = context.extension?.packageJSON as { + version?: string; + contributes?: { + languageModelTools?: unknown[]; + chatSkills?: unknown[]; + chatInstructions?: unknown[]; + }; + } | undefined; + const contrib = pkg?.contributes ?? {}; + recordChatActivation({ + javaLSReadyAtActivation: !!javaExt.isActive, + lmtCount: contrib.languageModelTools?.length ?? 0, + chatSkillsCount: contrib.chatSkills?.length ?? 0, + chatInstructionsCount: contrib.chatInstructions?.length ?? 0, + extensionVersion: pkg?.version ?? "unknown", + }); + } catch { + // Telemetry must never break activation. + } } async function subscribeToJavaExtensionEvents(): Promise { diff --git a/src/languageModelTool.ts b/src/languageModelTool.ts index ad803d2..85b2885 100644 --- a/src/languageModelTool.ts +++ b/src/languageModelTool.ts @@ -4,7 +4,20 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper"; +import { + classifyBreakpoint, + classifyError, + classifyEvalContext, + classifyRemoveScope, + classifyScopeType, + classifyStep, + classifyTarget, + ErrorCategory, + recordLaunchInternal, + recordToolInvocation, + TOOL_NAMES, + ToolOutcome, +} from "./lmToolTelemetry"; // ============================================================================ // Constants @@ -63,14 +76,20 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc const tool: LanguageModelTool = { async invoke(options: { input: DebugJavaApplicationInput }, token: vscode.CancellationToken): Promise { - sendInfo('', { - operationName: 'languageModelTool.debugJavaApplication.invoke', - target: options.input.target, - skipBuild: options.input.skipBuild?.toString() || 'false', - }); + const startedAt = Date.now(); + const targetType = classifyTarget(options.input.target); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; try { const result = await debugJavaApplication(options.input, token); + if (!result.success) { + outcome = result.status === 'timeout' ? 'timeout' : 'failure'; + errorCategory = result.success ? undefined : classifyError(result.message); + } else if (result.status === 'timeout') { + outcome = 'timeout'; + errorCategory = 'timeout'; + } // Format the message for AI - use simple text, not JSON const message = result.success @@ -82,13 +101,23 @@ export function registerLanguageModelTool(context: vscode.ExtensionContext): vsc new (vscode as any).LanguageModelTextPart(message) ]); } catch (error) { - sendError(error as Error); + outcome = token.isCancellationRequested ? 'cancelled' : 'failure'; + errorCategory = classifyError(error); const errorMessage = error instanceof Error ? error.message : String(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Debug failed: ${errorMessage}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.DEBUG_JAVA_APPLICATION, + outcome, + errorCategory, + targetType, + skipBuild: !!options.input.skipBuild, + durationMs: Date.now() - startedAt, + }); } } }; @@ -120,10 +149,9 @@ async function debugJavaApplication( // Step 0: Cleanup any existing Java debug session to avoid port conflicts const existingSession = vscode.debug.activeDebugSession; if (existingSession && existingSession.type === 'java') { - sendInfo('', { - operationName: 'languageModelTool.cleanupExistingSession', + recordLaunchInternal({ + name: 'cleanupExistingSession', sessionId: existingSession.id, - sessionName: existingSession.name }); try { await vscode.debug.stopDebugging(existingSession); @@ -131,9 +159,9 @@ async function debugJavaApplication( await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { // Log but continue - the old session might already be dead - sendInfo('', { - operationName: 'languageModelTool.cleanupExistingSessionFailed', - error: String(error) + recordLaunchInternal({ + name: 'cleanupExistingSessionFailed', + errorCategory: classifyError(error), }); } } @@ -219,10 +247,9 @@ async function debugJavaApplication( clearTimeout(timeoutHandle); } - sendInfo('', { - operationName: 'languageModelTool.debugSessionStarted.eventBased', + recordLaunchInternal({ + name: 'debugSessionStarted.eventBased', sessionId: session.id, - sessionName: session.name }); resolve({ @@ -243,10 +270,7 @@ async function debugJavaApplication( if (!sessionStarted) { sessionDisposable.dispose(); - sendInfo('', { - operationName: 'languageModelTool.debugSessionTimeout.eventBased', - target: targetInfo - }); + recordLaunchInternal({ name: 'debugSessionTimeout.eventBased' }); resolve({ success: false, @@ -280,12 +304,13 @@ async function debugJavaApplication( // Check if debug session has started const session = vscode.debug.activeDebugSession; if (session && session.type === 'java') { - const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1); + const elapsedMs = Date.now() - startTime; + const elapsedTime = (elapsedMs / 1000).toFixed(1); - sendInfo('', { - operationName: 'languageModelTool.debugSessionDetected', + recordLaunchInternal({ + name: 'debugSessionDetected', sessionId: session.id, - elapsedTime + elapsedMs, }); return { @@ -302,10 +327,9 @@ async function debugJavaApplication( } // Timeout: session not detected within 15 seconds - sendInfo('', { - operationName: 'languageModelTool.debugSessionTimeout.smartPolling', - target: targetInfo, - maxWaitTime + recordLaunchInternal({ + name: 'debugSessionTimeout.smartPolling', + maxWaitTime, }); return { @@ -584,19 +608,18 @@ function constructDebugCommand( if (!input.target.includes('.')) { const detectedClassName = findFullyQualifiedClassName(input.workspacePath, input.target, projectType); if (detectedClassName) { - sendInfo('', { - operationName: 'languageModelTool.classNameDetection', - simpleClassName: input.target, - detectedClassName, - projectType + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: true, }); className = detectedClassName; } else { // No package detected - class is in default package - sendInfo('', { - operationName: 'languageModelTool.classNameDetection.noPackage', - simpleClassName: input.target, - projectType + recordLaunchInternal({ + name: 'classNameDetection', + projectType, + detected: false, }); } } @@ -917,6 +940,11 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 1: Set Breakpoint const setBreakpointTool: LanguageModelTool = { async invoke(options: { input: SetBreakpointInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const breakpointKind = classifyBreakpoint(options.input); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const { filePath, lineNumber, condition, hitCondition, logMessage } = options.input; @@ -944,9 +972,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to set breakpoint: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.SET_JAVA_BREAKPOINT, + outcome, + errorCategory, + breakpointKind, + durationMs: Date.now() - startedAt, + }); } } }; @@ -955,9 +993,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 2: Step Operations const stepOperationTool: LanguageModelTool = { async invoke(options: { input: StepOperationInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const stepKind = classifyStep(options.input.operation); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -975,6 +1020,14 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }; const command = commandMap[operation]; + if (!command) { + outcome = 'failure'; + errorCategory = 'other'; + return new (vscode as any).LanguageModelToolResult([ + new (vscode as any).LanguageModelTextPart(`✗ Unknown step operation: ${operation}`) + ]); + } + if (threadId !== undefined) { // For thread-specific operations, use custom request await session.customRequest(operation, { threadId }); @@ -987,9 +1040,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs new (vscode as any).LanguageModelTextPart(`✓ Executed ${operation}`) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Step operation failed: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.DEBUG_STEP_OPERATION, + outcome, + errorCategory, + stepKind, + durationMs: Date.now() - startedAt, + }); } } }; @@ -998,9 +1061,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 3: Get Variables const getVariablesTool: LanguageModelTool = { async invoke(options: { input: GetVariablesInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const scopeTypeEnum = classifyScopeType(options.input.scopeType); + const hasFilter = !!options.input.filter; + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1018,6 +1089,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs } if (!targetThreadId) { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') ]); @@ -1031,6 +1104,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }); if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + outcome = 'noStackFrame'; + errorCategory = 'noStackFrame'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No stack frame available.') ]); @@ -1075,9 +1150,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get variables: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_VARIABLES, + outcome, + errorCategory, + scopeType: scopeTypeEnum, + hasFilter, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1086,9 +1172,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 4: Get Stack Trace const getStackTraceTool: LanguageModelTool = { async invoke(options: { input: GetStackTraceInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let frameCount = 0; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1103,11 +1196,15 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs }); if (!stackResponse.stackFrames || stackResponse.stackFrames.length === 0) { + outcome = 'noStackFrame'; + errorCategory = 'noStackFrame'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('No stack frames available.') ]); } + frameCount = stackResponse.stackFrames.length; + const frames = stackResponse.stackFrames.map((frame: any, index: number) => { const location = frame.source ? `${frame.source.name}:${frame.line}` : @@ -1121,9 +1218,19 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get stack trace: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_STACK_TRACE, + outcome, + errorCategory, + frameCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1132,9 +1239,16 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 5: Evaluate Expression const evaluateExpressionTool: LanguageModelTool = { async invoke(options: { input: EvaluateExpressionInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const evalContext = classifyEvalContext(options.input.context); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1168,6 +1282,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs targetFrameId = stackResponse.stackFrames[0].id; } } catch { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Thread #${targetThreadId} is not suspended. Cannot evaluate expression.`) ]); @@ -1175,6 +1291,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs } if (!targetThreadId) { + outcome = 'noSuspendedThread'; + errorCategory = 'noSuspendedThread'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No suspended thread found. Use get_debug_threads() to see thread states.') ]); @@ -1194,9 +1312,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Evaluation failed: ${error}`) ]); + } finally { + // NEVER log expression text (may contain user code / secrets) + recordToolInvocation({ + tool: TOOL_NAMES.EVALUATE_DEBUG_EXPRESSION, + outcome, + errorCategory, + evalContext, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1205,9 +1334,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 6: Get Threads const getThreadsTool: LanguageModelTool<{}> = { async invoke(_options: { input: {} }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let threadCount = 0; + let suspendedCount = 0; + try { const session = vscode.debug.activeDebugSession; if (!session || session.type !== 'java') { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('✗ No active Java debug session.') ]); @@ -1221,6 +1358,8 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ]); } + threadCount = threadsResponse.threads.length; + // Check each thread's state by trying to get its stack trace const threadInfos: string[] = []; for (const thread of threadsResponse.threads) { @@ -1236,6 +1375,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (stackResponse?.stackFrames?.length > 0) { state = '🔴 SUSPENDED'; + suspendedCount++; const topFrame = stackResponse.stackFrames[0]; if (topFrame.source) { location = ` at ${topFrame.source.name}:${topFrame.line}`; @@ -1264,9 +1404,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get threads: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_THREADS, + outcome, + errorCategory, + threadCount, + suspendedCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1275,6 +1426,12 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 7: Remove Breakpoints const removeBreakpointsTool: LanguageModelTool = { async invoke(options: { input: RemoveBreakpointsInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + const removeScope = classifyRemoveScope(options.input); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let removedCount = 0; + try { const { filePath, lineNumber } = options.input; @@ -1283,6 +1440,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (!filePath) { // Remove all breakpoints (no active session required) const count = breakpoints.length; + removedCount = count; vscode.debug.removeBreakpoints(breakpoints); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✓ Removed all ${count} breakpoint(s).`) @@ -1304,6 +1462,7 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs if (toRemove.length > 0) { vscode.debug.removeBreakpoints(toRemove); } + removedCount = toRemove.length; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( @@ -1313,9 +1472,20 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to remove breakpoints: ${error}`) ]); + } finally { + recordToolInvocation({ + tool: TOOL_NAMES.REMOVE_JAVA_BREAKPOINTS, + outcome, + errorCategory, + removeScope, + removedCount, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1323,38 +1493,46 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 9: Stop Debug Session const stopDebugSessionTool: LanguageModelTool = { - async invoke(options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { + async invoke(_options: { input: StopDebugSessionInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + try { const session = vscode.debug.activeDebugSession; if (!session) { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart('No active debug session to stop.') ]); } - const sessionInfo = `${session.name} (${session.type})`; - const reason = options.input.reason || 'Investigation complete'; + const sessionType = session.type; // Stop the debug session await vscode.debug.stopDebugging(session); - sendInfo('', { - operationName: 'languageModelTool.stopDebugSession', - sessionId: session.id, - sessionName: session.name, - reason - }); - return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( - `✓ Stopped debug session: ${sessionInfo}. Reason: ${reason}` + `✓ Stopped debug session (${sessionType}).` ) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to stop debug session: ${error}`) ]); + } finally { + // Do NOT log session.name (may include user file path) or input.reason (free text) + recordToolInvocation({ + tool: TOOL_NAMES.STOP_DEBUG_SESSION, + outcome, + errorCategory, + durationMs: Date.now() - startedAt, + }); } } }; @@ -1363,10 +1541,17 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // Tool 10: Get Debug Session Info const getDebugSessionInfoTool: LanguageModelTool = { async invoke(_options: { input: GetDebugSessionInfoInput }, _token: vscode.CancellationToken): Promise { + const startedAt = Date.now(); + let outcome: ToolOutcome = 'success'; + let errorCategory: ErrorCategory | undefined; + let isPausedFlag = false; + try { const session = vscode.debug.activeDebugSession; if (!session) { + outcome = 'noActiveSession'; + errorCategory = 'noActiveSession'; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart( '❌ No active debug session found.\n\n' + @@ -1457,9 +1642,9 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs // If we can't even get threads, something is wrong // But session exists, so mark as running isPaused = false; - sendInfo('', { - operationName: 'languageModelTool.getDebugSessionInfo.threadError', - error: String(error) + recordLaunchInternal({ + name: 'getDebugSessionInfo.threadError', + errorCategory: classifyError(error), }); } @@ -1539,23 +1724,26 @@ export function registerDebugSessionTools(_context: vscode.ExtensionContext): vs '═══════════════════════════════════════════' ].join('\n'); - sendInfo('', { - operationName: 'languageModelTool.getDebugSessionInfo', - sessionId: session.id, - sessionType: session.type, - isPaused: String(isPaused), - stoppedThreadId: String(stoppedThreadId || ''), - currentFile, - currentLine: String(currentLine) - }); + isPausedFlag = isPaused; return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(message) ]); } catch (error) { + outcome = 'failure'; + errorCategory = classifyError(error); return new (vscode as any).LanguageModelToolResult([ new (vscode as any).LanguageModelTextPart(`✗ Failed to get debug session info: ${error}`) ]); + } finally { + // Do NOT log currentFile, currentLine, sessionName, stoppedThreadName — those are user data + recordToolInvocation({ + tool: TOOL_NAMES.GET_DEBUG_SESSION_INFO, + outcome, + errorCategory, + isPaused: isPausedFlag, + durationMs: Date.now() - startedAt, + }); } } }; diff --git a/src/lmToolTelemetry.ts b/src/lmToolTelemetry.ts new file mode 100644 index 0000000..617e0b3 --- /dev/null +++ b/src/lmToolTelemetry.ts @@ -0,0 +1,450 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Telemetry helpers for the language-model-tool surface. + * + * POLICY: this module is the ONLY place inside the LMT code path that is + * allowed to call `sendInfo` / `sendError`. Direct calls from individual + * tool implementations are forbidden so that PII risk can be audited in + * one file. + * + * Strict rules — every contributor MUST follow these: + * + * 1. Do NOT pass user-provided strings as telemetry properties. This + * includes (non-exhaustive): + * - `target` (main class / JAR path / raw -cp args) + * - `expression` (debug expression to evaluate) + * - `condition` / `hitCondition` / `logMessage` (breakpoint inputs) + * - `filePath` / `currentFile` / source file paths + * - `currentLine` / `lineNumber` + * - `sessionName` (`launch.json` `name` field; often contains class + * or project names) + * - `reason` (user-supplied stop reason) + * - `error.message` / `error.stack` (JVM stack traces leak user + * class and method names) + * - any class name, method name, package name, or source path + * + * 2. Only enums, booleans, durations, counts, opaque session IDs (GUIDs) + * and our own extension version are allowed. + * + * 3. When classifying free-form input (e.g. error text -> errorCategory) + * the classifier function inspects the input in-memory and emits ONLY + * the enum. Unmatched values map to `'other'` / `'unknown'`. The + * original text is NEVER attached to the event. + * + * 4. New telemetry events SHOULD go through `recordToolInvocation` / + * `recordChatActivation` or a new dedicated recorder added below. + * The raw `sendInfo` API is wrapped by `sanitizedSend` here. + */ + +import { sendInfo } from "vscode-extension-telemetry-wrapper"; + +// ============================================================================ +// Enum types (the only shape telemetry properties may take) +// ============================================================================ + +export type ToolOutcome = + | 'success' + | 'failure' + | 'timeout' + | 'cancelled' + | 'lsNotReady' + | 'noActiveSession' + | 'noSuspendedThread' + | 'noStackFrame'; + +export type ErrorCategory = + | 'mainClassMissing' + | 'classpathUnresolved' + | 'buildFailure' + | 'projectNotDetected' + | 'sessionAlreadyRunning' + | 'timeout' + | 'lsNotReady' + | 'noActiveSession' + | 'noSuspendedThread' + | 'noStackFrame' + | 'cancelled' + | 'other'; + +export type TargetType = 'mainClass' | 'jar' | 'rawArgs' | 'unknown'; + +export type BreakpointKind = + | 'line' + | 'conditional' + | 'hitCount' + | 'logpoint'; + +export type StepKind = 'in' | 'out' | 'over' | 'continue' | 'pause' | 'unknown'; + +export type EvalContext = 'watch' | 'repl' | 'hover' | 'unknown'; + +export type RemoveBreakpointScope = 'all' | 'file' | 'line'; + +export type ScopeType = 'local' | 'static' | 'all' | 'unknown'; + +export const TOOL_NAMES = { + DEBUG_JAVA_APPLICATION: 'debug_java_application', + SET_JAVA_BREAKPOINT: 'set_java_breakpoint', + DEBUG_STEP_OPERATION: 'debug_step_operation', + GET_DEBUG_VARIABLES: 'get_debug_variables', + GET_DEBUG_STACK_TRACE: 'get_debug_stack_trace', + EVALUATE_DEBUG_EXPRESSION: 'evaluate_debug_expression', + GET_DEBUG_THREADS: 'get_debug_threads', + REMOVE_JAVA_BREAKPOINTS: 'remove_java_breakpoints', + STOP_DEBUG_SESSION: 'stop_debug_session', + GET_DEBUG_SESSION_INFO: 'get_debug_session_info', +} as const; + +export type ToolName = typeof TOOL_NAMES[keyof typeof TOOL_NAMES]; + +// ============================================================================ +// Classifiers — pure functions; emit ONLY enums +// ============================================================================ + +/** + * Classify the `target` parameter of `debug_java_application` into a coarse + * shape category. The original string is consumed in-memory only; the + * returned enum is the only thing that may be logged. + */ +export function classifyTarget(target: string | undefined | null): TargetType { + if (!target) { + return 'unknown'; + } + const trimmed = target.trim(); + if (!trimmed) { + return 'unknown'; + } + if (trimmed.startsWith('-')) { + return 'rawArgs'; + } + if (/\.jar(\s|$)/i.test(trimmed) || trimmed.toLowerCase().endsWith('.jar')) { + return 'jar'; + } + if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(trimmed)) { + return 'mainClass'; + } + return 'unknown'; +} + +/** + * Map an arbitrary error (Error, string, or unknown) to an ErrorCategory. + * The original message and stack trace are consumed in-memory and never + * returned. Unrecognised errors map to `'other'`. + */ +export function classifyError(err: unknown): ErrorCategory { + if (err === undefined || err === null) { + return 'other'; + } + const msg = (err instanceof Error ? err.message : String(err)).toLowerCase(); + if (!msg) { + return 'other'; + } + if (msg.includes('mainclass') && (msg.includes('not set') || msg.includes('missing') || msg.includes('not configured'))) { + return 'mainClassMissing'; + } + if (msg.includes('could not find or load main class') || msg.includes('classnotfound')) { + return 'mainClassMissing'; + } + if (msg.includes('classpath') && (msg.includes('not resolve') || msg.includes('unresolved') || msg.includes('cannot resolve'))) { + return 'classpathUnresolved'; + } + if (msg.includes('compilation') && msg.includes('fail')) { + return 'buildFailure'; + } + if (msg.includes('build failed') || msg.includes('build error')) { + return 'buildFailure'; + } + if (msg.includes('project not detected') || msg.includes('no project found')) { + return 'projectNotDetected'; + } + if (msg.includes('already running') || msg.includes('session is active')) { + return 'sessionAlreadyRunning'; + } + if (msg.includes('timeout') || msg.includes('timed out')) { + return 'timeout'; + } + if (msg.includes('language server not ready') || msg.includes('jdt.ls')) { + return 'lsNotReady'; + } + if (msg.includes('no active debug session') || msg.includes('no debug session')) { + return 'noActiveSession'; + } + if (msg.includes('not suspended') || msg.includes('thread is not paused')) { + return 'noSuspendedThread'; + } + if (msg.includes('cancel')) { + return 'cancelled'; + } + return 'other'; +} + +/** + * Classify a `set_java_breakpoint` invocation into a coarse breakpoint kind. + * The actual filePath / lineNumber / condition strings are NOT logged; this + * classifier only checks which optional inputs are present. + */ +export function classifyBreakpoint(input: { + condition?: string; + hitCondition?: string; + logMessage?: string; +}): BreakpointKind { + if (input.logMessage && input.logMessage.length > 0) { + return 'logpoint'; + } + if (input.hitCondition && input.hitCondition.length > 0) { + return 'hitCount'; + } + if (input.condition && input.condition.length > 0) { + return 'conditional'; + } + return 'line'; +} + +export function classifyStep(operation: string | undefined): StepKind { + switch (operation) { + case 'stepIn': + return 'in'; + case 'stepOut': + return 'out'; + case 'stepOver': + return 'over'; + case 'continue': + return 'continue'; + case 'pause': + return 'pause'; + default: + return 'unknown'; + } +} + +export function classifyEvalContext(context: string | undefined): EvalContext { + switch (context) { + case 'watch': + case 'repl': + case 'hover': + return context; + default: + return 'unknown'; + } +} + +export function classifyRemoveScope(input: { + filePath?: string; + lineNumber?: number; +}): RemoveBreakpointScope { + if (!input.filePath) { + return 'all'; + } + if (input.lineNumber !== undefined) { + return 'line'; + } + return 'file'; +} + +export function classifyScopeType(scopeType: string | undefined): ScopeType { + switch (scopeType) { + case 'local': + case 'static': + case 'all': + return scopeType; + default: + return 'unknown'; + } +} + +// ============================================================================ +// Recording helpers — the only entrypoints to `sendInfo` inside LMT code +// ============================================================================ + +/** Safe value types allowed as telemetry properties. */ +type SafeValue = string | number | boolean | undefined; + +/** + * Tighten what sendInfo accepts. All values must be primitive enums / + * booleans / numbers / well-known opaque IDs. Objects and arrays are + * rejected at the type level so we cannot accidentally serialise a payload + * containing user data. + */ +function sanitizedSend(properties: Record): void { + const clean: { [key: string]: string } = {}; + for (const [k, v] of Object.entries(properties)) { + if (v === undefined) { + continue; + } + clean[k] = typeof v === 'string' ? v : String(v); + } + sendInfo('', clean); +} + +export interface ToolInvocationRecord { + tool: ToolName; + outcome: ToolOutcome; + errorCategory?: ErrorCategory; + durationMs?: number; + /** + * Optional tool-specific enum fields. ONLY enums / booleans / numbers + * are accepted; the recorder itself is typed to forbid raw strings. + */ + targetType?: TargetType; + breakpointKind?: BreakpointKind; + stepKind?: StepKind; + evalContext?: EvalContext; + removeScope?: RemoveBreakpointScope; + scopeType?: ScopeType; + isPaused?: boolean; + skipBuild?: boolean; + hasFilter?: boolean; + frameCount?: number; + threadCount?: number; + suspendedCount?: number; + removedCount?: number; + /** Opaque GUID assigned by VS Code; safe to log. */ + sessionId?: string; + /** vscode-java-debug's own adapter type — value is constant `'java'`. */ + sessionType?: string; +} + +/** + * Record a single tool-invocation outcome. Replaces ad-hoc `sendInfo` + * calls inside individual tools. + * + * Before sending, the record is normalized so that `outcome` and + * `errorCategory` stay aligned for the six shared terminal values + * (cancelled / timeout / lsNotReady / noActiveSession / noSuspendedThread / + * noStackFrame). See {@link normalizeToolInvocationRecord}. + */ +export function recordToolInvocation(record: ToolInvocationRecord): void { + const normalized = normalizeToolInvocationRecord(record); + sanitizedSend({ + operationName: `languageModelTool.${normalized.tool}.invoke`, + outcome: normalized.outcome, + errorCategory: normalized.errorCategory, + durationMs: normalized.durationMs, + targetType: normalized.targetType, + breakpointKind: normalized.breakpointKind, + stepKind: normalized.stepKind, + evalContext: normalized.evalContext, + removeScope: normalized.removeScope, + scopeType: normalized.scopeType, + isPaused: normalized.isPaused, + skipBuild: normalized.skipBuild, + hasFilter: normalized.hasFilter, + frameCount: normalized.frameCount, + threadCount: normalized.threadCount, + suspendedCount: normalized.suspendedCount, + removedCount: normalized.removedCount, + sessionId: normalized.sessionId, + sessionType: normalized.sessionType, + }); +} + +/** + * Values that exist in both {@link ToolOutcome} and {@link ErrorCategory}. + * For these, the two fields must stay in lock-step so dashboard queries + * filtering on either one produce identical results. + */ +const SHARED_TERMINAL_VALUES = [ + 'cancelled', + 'timeout', + 'lsNotReady', + 'noActiveSession', + 'noSuspendedThread', + 'noStackFrame', +] as const; + +type SharedTerminal = typeof SHARED_TERMINAL_VALUES[number]; + +function isSharedTerminal(value: string | undefined): value is SharedTerminal { + return value !== undefined && (SHARED_TERMINAL_VALUES as readonly string[]).includes(value); +} + +/** + * Reconcile `outcome` and `errorCategory` for the six shared terminal + * values so downstream queries can rely on either field. Returns a NEW + * record; the input is not mutated. + * + * Rules: + * - If `errorCategory` is a shared terminal value, promote `outcome` to + * that value (callers that only set `errorCategory` get a consistent + * `outcome` for free). + * - If `outcome` is a shared terminal value and `errorCategory` is + * absent, fill it with the matching value (callers that only set + * `outcome` get a consistent `errorCategory`). + */ +function normalizeToolInvocationRecord(record: ToolInvocationRecord): ToolInvocationRecord { + let outcome: ToolOutcome = record.outcome; + let errorCategory: ErrorCategory | undefined = record.errorCategory; + + if (isSharedTerminal(errorCategory)) { + outcome = errorCategory; + } else if (isSharedTerminal(outcome) && errorCategory === undefined) { + errorCategory = outcome; + } + + return { ...record, outcome, errorCategory }; +} + +export interface ChatActivationRecord { + javaLSReadyAtActivation: boolean; + lmtCount: number; + chatSkillsCount: number; + chatInstructionsCount: number; + extensionVersion: string; +} + +/** + * Record a one-shot snapshot of the chat-activation surface at the moment + * Language Model Tools are registered. Lets us measure adoption coverage + * post-ship without per-turn cost. + */ +export function recordChatActivation(record: ChatActivationRecord): void { + sanitizedSend({ + operationName: 'languageModelTool.chatActivationSnapshot', + javaLSReadyAtActivation: record.javaLSReadyAtActivation, + lmtCount: record.lmtCount, + chatSkillsCount: record.chatSkillsCount, + chatInstructionsCount: record.chatInstructionsCount, + extensionVersion: record.extensionVersion, + }); +} + +/** + * Project type detected by the launch flow. Free-form values are + * forbidden so this stays a closed enum. + */ +export type LaunchProjectType = 'maven' | 'gradle' | 'vscode' | 'unknown'; + +/** + * Discriminated union of every launch-flow internal event the recorder + * is allowed to emit. Each variant lists its allowed properties so the + * type system rejects unknown event names and unknown property keys. + * + * Note: `sessionId` here is VS Code's opaque debug-session GUID, never + * the user-visible `launch.json` session name. + */ +export type LaunchInternalEvent = + | { name: 'cleanupExistingSession'; sessionId: string } + | { name: 'cleanupExistingSessionFailed'; errorCategory: ErrorCategory } + | { name: 'debugSessionStarted.eventBased'; sessionId: string } + | { name: 'debugSessionTimeout.eventBased' } + | { name: 'debugSessionDetected'; sessionId: string; elapsedMs: number } + | { name: 'debugSessionTimeout.smartPolling'; maxWaitTime: number } + | { name: 'classNameDetection'; projectType: LaunchProjectType; detected: boolean } + | { name: 'getDebugSessionInfo.threadError'; errorCategory: ErrorCategory }; + +/** + * Internal-debug event for the launch-flow nested instrumentation + * (session-detected / cleanup / timeout). Re-uses the sanitised sender so + * no PII can slip in. Accepts only the discriminated-union shapes defined + * in {@link LaunchInternalEvent} — unknown event names or unexpected + * property keys are rejected at compile time. + */ +export function recordLaunchInternal(event: LaunchInternalEvent): void { + const { name, ...properties } = event; + sanitizedSend({ + operationName: `languageModelTool.${name}`, + ...properties, + }); +} From 9758e1d852b9908655b9bb6329057f4c99a040fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:36:10 +0800 Subject: [PATCH 7/7] Bump @nevware21/ts-utils from 0.13.0 to 0.14.0 Bumps [@nevware21/ts-utils](https://github.com/nevware21/ts-utils) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/nevware21/ts-utils/releases) - [Changelog](https://github.com/nevware21/ts-utils/blob/main/CHANGELOG.md) - [Commits](https://github.com/nevware21/ts-utils/compare/0.13.0...0.14.0) --- updated-dependencies: - dependency-name: "@nevware21/ts-utils" dependency-version: 0.14.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9254cb4..75859e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,10 +230,19 @@ } }, "node_modules/@nevware21/ts-utils": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", - "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==", - "license": "MIT" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", + "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nevware21" + }, + { + "type": "other", + "url": "https://buymeacoffee.com/nevware21" + } + ] }, "node_modules/@types/eslint": { "version": "9.6.1", @@ -3457,9 +3466,9 @@ } }, "@nevware21/ts-utils": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.13.0.tgz", - "integrity": "sha512-F3mD+DsUn9OiZmZc5tg0oKqrJCtiCstwx+wE+DNzFYh2cCRUuzTYdK9zGGP/au2BWvbOQ6Tqlbjr2+dT1P3AlQ==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", + "integrity": "sha512-WoeqTIXQ8WPhl+lD2NbMHoAQ4sJl0n7EoRoDmVJui//Usg512enl9q1fdbVobuZt3omnxnmVsDrNIvPBvFgddQ==" }, "@types/eslint": { "version": "9.6.1",