diff --git a/.npmrc b/.npmrc index 89d0f0c309ff..7a097f359bcd 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" -target="43.7.5" -ms_build_id="15523606" +target="43.7.7" +ms_build_id="15553055" runtime="electron" ignore-scripts=false build_from_source="true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 2ec89bcc11d1..ca7700e86c69 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -7ca9d9e9654f66e63d3117b868f5407605af53ca6115bcf9f0490a68568afc14 *chromedriver-v43.7.5-darwin-arm64.zip -0133b3e40b192e9ff3fa1e80773a25e591b8638a95aa50e33b773a6222c12493 *chromedriver-v43.7.5-darwin-x64.zip -f85f88fe6cf1be1e55312c1302d1f3d7d0f319dfe4e7e610f9fbf3f0b682018f *chromedriver-v43.7.5-linux-arm64.zip -97f4939ba6d58954673cbda3c14ff5c0b00e5a4b7e70a1b166d0313da91a8a5f *chromedriver-v43.7.5-linux-armv7l.zip -854d5396d3abfb91284a9e66e6cb962a7e274aad28cd86ff6d5c22492bfc06d7 *chromedriver-v43.7.5-linux-x64.zip -fd1cc7a02fc96fabb01482a87807d9e6dd7fd70df5b97be5e5704422d751d063 *chromedriver-v43.7.5-mas-arm64.zip -713387926080fab188c99674548653a144f4e3a0890924f60b81e99b2fff33b7 *chromedriver-v43.7.5-mas-x64.zip -423f1210316104b5daaf5ffee8b7e33c9f537e52b38b8f75d3bc1b9021ea47bd *chromedriver-v43.7.5-win32-arm64.zip -b868501754d37212d561bc5298caf99d38e9c6d9eef8902263a6f6d3eab5e569 *chromedriver-v43.7.5-win32-ia32.zip -9f5d50e29f3cfb13b8afd5cc711ae3219cdd76d52d2dcc86991221d2b7342a39 *chromedriver-v43.7.5-win32-x64.zip -c94a7f38ff9c34b6ef2d4861ac74b6015ce93b92814721ae1e0e7fc7ef75bc6c *electron-api.json -9236a0cc6e74086761f31edf26e053751d9f69e0ca69fec8a2cba3e7034c74fc *electron-v43.7.5-darwin-arm64-dsym-snapshot.zip -6bf01e64c0aa457c7725ddaa8abaec50a7e06a3d2f325795934e3a6370f83db7 *electron-v43.7.5-darwin-arm64-dsym.tar.xz -9486b06f8aa6b20b082e0fa8741141a463de96b25a2b362a81f192d1a823c996 *electron-v43.7.5-darwin-arm64-symbols.zip -4ddb8717f3c9ade6c5b8a86f0f2b9648067108de93f9a1e7fd6a9ffcf06692ac *electron-v43.7.5-darwin-arm64.zip -9236a0cc6e74086761f31edf26e053751d9f69e0ca69fec8a2cba3e7034c74fc *electron-v43.7.5-darwin-x64-dsym-snapshot.zip -25c9e7e63ce1b904a2bc7a49f4d1845b4ab4fb461f84a4520d1ec78c72c3d37a *electron-v43.7.5-darwin-x64-dsym.tar.xz -9cd6fc5cf09edfed1ba29cf6b72ce929dda71aecc584f9823a93196bcda25ed9 *electron-v43.7.5-darwin-x64-symbols.zip -fec7945208ade18b15c9f386c29bdf973f3cb7f779df9bc5e1a04f9de8a146ce *electron-v43.7.5-darwin-x64.zip -ec18f1ed85958b6bb0e7285a15fba7da3f546c5919e477a108150f86936e3988 *electron-v43.7.5-linux-arm64-debug.zip -5893561c6452225dd6dc8eb8bde572e6d675b5cdcdbba6e0d3c9ad11a95df564 *electron-v43.7.5-linux-arm64-symbols.zip -0e8017c56a243deed2510467e5de4be17a2c0634eb017429851ad026551e28ec *electron-v43.7.5-linux-arm64.zip -5224ccd7679443680c5a51f9ccaf2ede12b940a39191261dd9011eec6d0231d3 *electron-v43.7.5-linux-armv7l-debug.zip -0f8d9b40fde9a6dbc211f99a3caae6f0367afb636e1770f985eaba5993598cf3 *electron-v43.7.5-linux-armv7l-symbols.zip -1079cbaf38af752402c29e1a888a9f044403baf784693de3147c7c29097a4157 *electron-v43.7.5-linux-armv7l.zip -6ed81092b68add4ec6fb375615f14bdb4a6f5c598242f7800431c7b4d11f7645 *electron-v43.7.5-linux-x64-debug.zip -829e3e9c97c08cc61126c43885798325ce1feffcfdb0c882c5489db46a9fb079 *electron-v43.7.5-linux-x64-symbols.zip -dce41d6d9a36a2854c5fa1a97f90bc7a8ac8285cf2a76fe3f997c7a02432b364 *electron-v43.7.5-linux-x64.zip -9236a0cc6e74086761f31edf26e053751d9f69e0ca69fec8a2cba3e7034c74fc *electron-v43.7.5-mas-arm64-dsym-snapshot.zip -abf2db07595e11531aa2cb4c572b6d5fbe81705878b382d962d00b2262f810c7 *electron-v43.7.5-mas-arm64-dsym.tar.xz -8e2cde372c0a5f5245c334343741a94ad840e86a698f1ec943f259e39ba3f552 *electron-v43.7.5-mas-arm64-symbols.zip -bebd05a60252b9a4f6c83202e3a5b435ef8a489ff6de110fa76aa52a7903dd3c *electron-v43.7.5-mas-arm64.zip -9236a0cc6e74086761f31edf26e053751d9f69e0ca69fec8a2cba3e7034c74fc *electron-v43.7.5-mas-x64-dsym-snapshot.zip -1bbd11786d0ef8b3ba36c49e6831f4948f6fddc6a1bba1696aa2dce6724c6d8d *electron-v43.7.5-mas-x64-dsym.tar.xz -7c6c3971bcc89d4caa4a6f16a7244aac7f3ae300e86fe65820347f5e46987f9f *electron-v43.7.5-mas-x64-symbols.zip -5502585a5c698e92a5472b8fbeeaf6aa757f70d5f1a42f5592dff889c6b8ee37 *electron-v43.7.5-mas-x64.zip -012133e592664f7b1fd74a42b2f4b9e0e14ec5510ec65a079cd80d8add2eda5e *electron-v43.7.5-win32-arm64-pdb.zip -245111a9bbf1f1acf99a4c0212a7cf1226eea5cb4bcf20326802e9b5b50e2e4c *electron-v43.7.5-win32-arm64-symbols.zip -90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.5-win32-arm64-toolchain-profile.zip -996f250e86611b2e5245b8ac09a0375f24d6e343292799a6c9bf7334391fd9c9 *electron-v43.7.5-win32-arm64.zip -cfd279ac0110c4b128627eaffed83e0d3dc3b70d6d8d6a54ca5254bef5a79d15 *electron-v43.7.5-win32-ia32-pdb.zip -1b52b17efc00fab5317d191e0b871fd8e324983dcddfcc6829c867e1baf5dc9f *electron-v43.7.5-win32-ia32-symbols.zip -90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.5-win32-ia32-toolchain-profile.zip -86897584b0b36a7a55280a33bcd2040115d3651b7ea753f23ab5097286947482 *electron-v43.7.5-win32-ia32.zip -c7531d071950a50b3d8b55ec403bdcbfdd9a8ba62f91a18a88de7a638084525a *electron-v43.7.5-win32-x64-pdb.zip -1891dfd0d60dc76bbf83601a354c866642ce1fdf5b9a2063666990fa99424799 *electron-v43.7.5-win32-x64-symbols.zip -90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.5-win32-x64-toolchain-profile.zip -7acfa0646793f912ff983c8db8c3a145dc18ee40fe3d11a01840fd59cb76e5a2 *electron-v43.7.5-win32-x64.zip -81e05df0600cb229dbdb080e5200ddf3532d0c4b5a668e2cba5bc5683f805098 *electron.d.ts -c3f967e6e0fcada1d2dd24b08f190b02aaf243a014531901e82b70417193c48b *ffmpeg-v43.7.5-darwin-arm64.zip -a6860ea4f0ec3954ae2686ae2ff2fd72cb4793f7229fe0d0c9c8efd0487e7297 *ffmpeg-v43.7.5-darwin-x64.zip -ed1d9f6af263524c00ff955f50bf289e19c881625384ff421c773ef2aeb8a82d *ffmpeg-v43.7.5-linux-arm64.zip -001847bd19549781850fd6f69941a54f92356ed26bca02892e9642637215668d *ffmpeg-v43.7.5-linux-armv7l.zip -6638429b92e6d213b78114923585526aff88dbe25f91ace7f2bd5228fac08de5 *ffmpeg-v43.7.5-linux-x64.zip -5290184f135527f4b58636e14869688a9fad6249cfe9f3608c9046e06efc6660 *ffmpeg-v43.7.5-mas-arm64.zip -785ededd318692376a61ed854a95fe9103ad2f7616f64b5e3b96ce34eaecdad1 *ffmpeg-v43.7.5-mas-x64.zip -1bbd64356da38ad2c8b9dd254046b0491e07d72e27952ee7f49331191a26bdb1 *ffmpeg-v43.7.5-win32-arm64.zip -7e9044d58cf7695e5549f73a4d0a54a7b2e24af930ff69195765c890346361f2 *ffmpeg-v43.7.5-win32-ia32.zip -b1b2597a18e68c70d0f65c46cdd0f0e10cb847e227e3e34cdbe6a6eee0e663c0 *ffmpeg-v43.7.5-win32-x64.zip -47b695db7f9dedebbdeec47070622da4312574f38e8ca8d77ef94c827a522a2f *hunspell_dictionaries.zip -198993602d37429e707e494f781eea876de3e9fd85d016c01b1f7432a747b418 *libcxx-objects-v43.7.5-linux-arm64.zip -80b6639adb398b42622ec0644a3a9c9e3f696309825537a817d42cf92c0a4f2e *libcxx-objects-v43.7.5-linux-armv7l.zip -dcb37cf5b0b1941e0304379dfea3587a3ba39167d8edca9e2ab749f54349586d *libcxx-objects-v43.7.5-linux-x64.zip -88eeecbe3a86626bc8b9677d764e831b13589449126b8b5aa84cb291e38f220e *libcxx_headers.zip -88a69e434f5a001028891091b32535b7b4e95c7764e4a7597112467042615d9b *libcxxabi_headers.zip -cdf1bb3bfa3c2a95ad463f9abd0cb3c94f39b9e5eb317eced9715d6496829c34 *mksnapshot-v43.7.5-darwin-arm64.zip -08e2460962d8729e8fa36bf30758d943ef4059f99e2d41dfd329193b51f94960 *mksnapshot-v43.7.5-darwin-x64.zip -d4544b339553ba5722aeaa53c22345183ae6d041e6330de0b0f3ef21d9af2046 *mksnapshot-v43.7.5-linux-arm64-x64.zip -ab060e6272e2e536732921eccef686560268b02edf832d63c3cada562cb423e7 *mksnapshot-v43.7.5-linux-armv7l-x64.zip -1a271d78fbb4922c02043538d53a4f87fe4e1c8bd58e7a78a29aeffb2cb742f2 *mksnapshot-v43.7.5-linux-x64.zip -42e489bd90f30581941143a2285861c69316bd3a603320bbe32ef217b8650adc *mksnapshot-v43.7.5-mas-arm64.zip -c2082d5d7f90e866760d2a6424bab260b3c757cc00884e1f00d87cad6d53a453 *mksnapshot-v43.7.5-mas-x64.zip -5665055067d7f53d459f0c63884648eb7dcfbf64b417b8c0b0738b7133b97812 *mksnapshot-v43.7.5-win32-arm64-x64.zip -7c882bd39f45cc9e1112f7b949b443305e959fad57292a9f9b97c3e45d502ce8 *mksnapshot-v43.7.5-win32-ia32.zip -bff2442315ad1482db15f6230f7f98308a0b758ab46b0b6f53ff0ace715e96ce *mksnapshot-v43.7.5-win32-x64.zip +b9b7d9ff132e9655a574f03edfb01add8d43a8b0d3a79320c226f6aa99c922ea *chromedriver-v43.7.7-darwin-arm64.zip +f3b26877642739e6c668e6368daada33bb1dfcf4d5fec34143195e184e984569 *chromedriver-v43.7.7-darwin-x64.zip +091707f83e4191f36848152d664c80c255572dfe36a042250ca94037c06959d2 *chromedriver-v43.7.7-linux-arm64.zip +9c3dc509c12709522a22e3c4eb3270907050391fe05d89f68ab5ea689f9aa5b2 *chromedriver-v43.7.7-linux-armv7l.zip +7ea6157ec70b065ef6a7f77fc1822dc6441f5702b0f99660e26cfca1897ecfa4 *chromedriver-v43.7.7-linux-x64.zip +cad78ce6d3399821fde0cc5701109334409aace9b2c82ab8f4c0d3fffa152906 *chromedriver-v43.7.7-mas-arm64.zip +42221fd4d828376802e644178717f1b0626bd1188d45199e958e17ab520ff9ee *chromedriver-v43.7.7-mas-x64.zip +d2c67dad42cfdcf17fce6eac8bba81502c4b2d9cd90cc16f189b1da71dde8ca3 *chromedriver-v43.7.7-win32-arm64.zip +79aedd7837ac32ec19f98677901def62d5cdbbe63d90fe5f25ca131f8609a843 *chromedriver-v43.7.7-win32-ia32.zip +8b81ca5a4add14593548f5b4042a028e4c50758d61c5a244741ca6d07be3c95d *chromedriver-v43.7.7-win32-x64.zip +aabd65bdb7a4fbd80c114af429392aa5a126382883c7c2e8f3ee11acf2e4ce08 *electron-api.json +649e6c16d4650ec4f3037daa285b9f8e72b91742b43810bba80ca5e8bfb8cfe7 *electron-v43.7.7-darwin-arm64-dsym-snapshot.zip +941f6adb5eb6dbce25db81158a8f19fd5b4b24bcc97ea5986bb901f822b93a54 *electron-v43.7.7-darwin-arm64-dsym.tar.xz +34f7acfef1ff41f81b63895f010b866c1f39ac22e0d700445eb6e9ee595276c4 *electron-v43.7.7-darwin-arm64-symbols.zip +9327d8ba5bc9e279d1a2f7da90235301c65a2e80eb4ad3bc5610d28d483340f9 *electron-v43.7.7-darwin-arm64.zip +649e6c16d4650ec4f3037daa285b9f8e72b91742b43810bba80ca5e8bfb8cfe7 *electron-v43.7.7-darwin-x64-dsym-snapshot.zip +2a0fc941390038cad45ef580cf65ee333f61f6e4504b9433447ed8fa3585e234 *electron-v43.7.7-darwin-x64-dsym.tar.xz +6f003f93738843419063adba7dbac56ac1dcfc79dcbeee39d700c335d34d831a *electron-v43.7.7-darwin-x64-symbols.zip +cbed66567d55db4a2bffad0bb6ee9795ca0037ad241039f7473fe75680d10905 *electron-v43.7.7-darwin-x64.zip +ba871fb65de9fbfd71f9aaafc1ac278434f497ee921e9f27fee9e0a5000e9eef *electron-v43.7.7-linux-arm64-debug.zip +d13b1f995845a5a138c35100944dc1264b98be3dd5540253640a66fc388d233d *electron-v43.7.7-linux-arm64-symbols.zip +16071038a9677d0f00b11d3d0f0b7b4ea3250987727aa73aa563b87789f2d5ab *electron-v43.7.7-linux-arm64.zip +bb2295977fc6d33adce9f62def5dfaa19de0e1bd29e5bbd366e0cc35d1a57a09 *electron-v43.7.7-linux-armv7l-debug.zip +07c0739157f9674827c7d55976016972cff70e0876d68287bdf24fca54d778bd *electron-v43.7.7-linux-armv7l-symbols.zip +088c2817c1b5928c5bea706cc60aa05ea328e6d1d8bd549fda75388a3552482a *electron-v43.7.7-linux-armv7l.zip +0f20d65a6f671b1966de12d3f71c2d96a1a65e7ba8c1d534b60bc08c7f82b950 *electron-v43.7.7-linux-x64-debug.zip +a5fb797b973514622f37154d5088132705ce86641f05e6b1e858269a86961868 *electron-v43.7.7-linux-x64-symbols.zip +4d0a48398c444258dbcf2f5f83b49ca5bc53583130f354e0c299dad5b22b5271 *electron-v43.7.7-linux-x64.zip +649e6c16d4650ec4f3037daa285b9f8e72b91742b43810bba80ca5e8bfb8cfe7 *electron-v43.7.7-mas-arm64-dsym-snapshot.zip +dc850f562fbf669db1d9940d458c7ba9790392cff18d95ebaec1fe94a8d583ff *electron-v43.7.7-mas-arm64-dsym.tar.xz +61cf1a0dc10498aff1d1eefa9a5b96350cba32734bdc9f7f40724f9aa17d03d9 *electron-v43.7.7-mas-arm64-symbols.zip +987eebfdc29638b9066e5484b0dcb59e7096627edebad722c7649f8472f2c8a0 *electron-v43.7.7-mas-arm64.zip +649e6c16d4650ec4f3037daa285b9f8e72b91742b43810bba80ca5e8bfb8cfe7 *electron-v43.7.7-mas-x64-dsym-snapshot.zip +8caa427ef3e1dd3f1832a3b782ace268999afcf011540fe05a62c957917487ea *electron-v43.7.7-mas-x64-dsym.tar.xz +91f67bf1b2deedf84355d98095a6060de69fb64c01caf223a35e0edef38aee55 *electron-v43.7.7-mas-x64-symbols.zip +f5eaad26bda0bc133f8d0af0f3a77d2e16adf2572306bec611424c3bab876acf *electron-v43.7.7-mas-x64.zip +b7aa250996ba4aadcbb115e951c6a46f19f246ee6178cffc6b115088189fc1d5 *electron-v43.7.7-win32-arm64-pdb.zip +e63955e73f754a249db46a2c312fd904b6405018650d22b8a16037a9c10270e0 *electron-v43.7.7-win32-arm64-symbols.zip +90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.7-win32-arm64-toolchain-profile.zip +d3751b5046350eee0cfd88e7709697c0ef05db93bb3435195c2d0cec50f0ad1f *electron-v43.7.7-win32-arm64.zip +2e8d41b27d3c2394bfeb4ec00904d7049c8a7ccaa105e678179a983cdfb319db *electron-v43.7.7-win32-ia32-pdb.zip +67ea48dcfdcca2f7b854fbe3689d56fe047ca9a2aee0808422ef313e0bc4b52a *electron-v43.7.7-win32-ia32-symbols.zip +90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.7-win32-ia32-toolchain-profile.zip +a170eeedf4a216b3b0672b151294e7aef0b4fdc15ae5c207ea3f9f37c4a14c42 *electron-v43.7.7-win32-ia32.zip +3b65b14be1594bba502283ae02e9af54b55b1894f3f6be961c608c9e80a74179 *electron-v43.7.7-win32-x64-pdb.zip +37d27ae6956d20fde7fa1218453ad9514c106f17cf30634c452302a7453f189f *electron-v43.7.7-win32-x64-symbols.zip +90386280bc7e4ac5d451e43e26a7c76ed1c8bcdc0206ec50762c7e4f09c59c28 *electron-v43.7.7-win32-x64-toolchain-profile.zip +97dcb75065444ef031b9b6ea814ccd2109b97934fffb0c503a555d4737ca79cc *electron-v43.7.7-win32-x64.zip +d78a11ef458b68672c35957f0252396a1fa19e73a8ca3b9178e2e75cd090453d *electron.d.ts +9cd0907e7c7c652cc6f519c8e7d75cee26bed06394bb1d51fabdb9d3ada33c67 *ffmpeg-v43.7.7-darwin-arm64.zip +fc2ecfa9bb7ab0131e073f138923ee58f01b88941e366a0be1bad821ed5386bd *ffmpeg-v43.7.7-darwin-x64.zip +ed1d9f6af263524c00ff955f50bf289e19c881625384ff421c773ef2aeb8a82d *ffmpeg-v43.7.7-linux-arm64.zip +001847bd19549781850fd6f69941a54f92356ed26bca02892e9642637215668d *ffmpeg-v43.7.7-linux-armv7l.zip +6638429b92e6d213b78114923585526aff88dbe25f91ace7f2bd5228fac08de5 *ffmpeg-v43.7.7-linux-x64.zip +cbbd298f4548e53200d6b0321bea708963e94acc699059721642183301752109 *ffmpeg-v43.7.7-mas-arm64.zip +7943078d8e3ee924f1cad1354a29aa3aceaf0b7b7414df3a71924ea9c8906223 *ffmpeg-v43.7.7-mas-x64.zip +c8ec52d6bbda2789c678211a76ffc6eaf1dc5faebe1269fc4d6aafa7fbc5510f *ffmpeg-v43.7.7-win32-arm64.zip +37f7bf4b42615e880f971d8714f2b1f4629c06bd8bbd73c8993a8713f8d3628f *ffmpeg-v43.7.7-win32-ia32.zip +df14da964d36f36c50030004bb5e93a3b5fac9e4ddd6dd4f0e4ecdd49456ee4f *ffmpeg-v43.7.7-win32-x64.zip +1987f0c54ac21514090440f4dc981245cae34b71e95ddbeffd5bcb5da9a4982e *hunspell_dictionaries.zip +cd2a8fc7973a60a60f1612d87528e07eb047fad61106f240f4e39bffe9b66969 *libcxx-objects-v43.7.7-linux-arm64.zip +76dc80ad01bf2f0da658d5e58e256bcbcfc5b5e03d5ce25c3a43b9073d0e2a4f *libcxx-objects-v43.7.7-linux-armv7l.zip +5df32a0f3b4e11575d619dfc30bd614238a616f87303142b37bcf62b6206047f *libcxx-objects-v43.7.7-linux-x64.zip +f318b7480e1e9b6149cfa83dedee6027a381f4dc7a4b99af89dddbab4f6d0261 *libcxx_headers.zip +600988a1000006cb10d8b88727c6877dfe3e0cbcb49baee317551d8131593c0c *libcxxabi_headers.zip +080d43c0521b53dc9fffe91b17f9592518230aa0c819bac3e7a6aff484126895 *mksnapshot-v43.7.7-darwin-arm64.zip +3a4cf3ca11eb4f98e66e9ef111f7be82d5d517851ef0882d71bf25ef3f5e4adb *mksnapshot-v43.7.7-darwin-x64.zip +052aa030567fe51301f4ba7c52098583868296f0ea91fa1a78158f763bec1332 *mksnapshot-v43.7.7-linux-arm64-x64.zip +de81d082e286d93e6c2baa6a2ce4b5ccf36dbf5fab512c1a8274e4ad1d6ba660 *mksnapshot-v43.7.7-linux-armv7l-x64.zip +1a222e37532fb300237eedfc6eae2a836f9b8ccf988af64507c88178a9ad3a41 *mksnapshot-v43.7.7-linux-x64.zip +4aaa40fcf9c283f9c3106523fcaff9301dac65b7d36434aa1970def061c82a20 *mksnapshot-v43.7.7-mas-arm64.zip +2a46e79869eaaee734f7f70ef5650bdba3b72cce26a201c8f1a87e15364efd16 *mksnapshot-v43.7.7-mas-x64.zip +8de088472502ed3a26b5a56b016849add2cc694eb2acc76e599d582c675ca8e1 *mksnapshot-v43.7.7-win32-arm64-x64.zip +e73d7a9ada185a6182bdf6872f6c5a7ae22f18a1f7efdb9e4fcb7b21b21b0c53 *mksnapshot-v43.7.7-win32-ia32.zip +4878c2cf511a54a3d38cab480b0f3a2699f84e0d0bdf80b7f300cfbe867ca8b5 *mksnapshot-v43.7.7-win32-x64.zip diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc index be0ac4bbe573..9573b7bd9bc8 100644 --- a/build/lib/policies/policyData.jsonc +++ b/build/lib/policies/policyData.jsonc @@ -56,42 +56,6 @@ "status": "enforced" } }, - { - "key": "chat.agent.allowedNetworkDomains", - "name": "ChatAgentAllowedNetworkDomains", - "category": "InteractiveSession", - "minimumVersion": "1.116", - "localization": { - "description": { - "key": "chat.agent.allowedNetworkDomains", - "value": "Allowed domains for network access by agent tools (fetch tool, integrated browser). Applies when `#chat.agent.networkFilter#` or `#chat.agent.sandbox.enabled#` is enabled. When `#chat.agent.sandbox.network.allowNetwork#` is enabled, all domains are allowed. Supports wildcards like `*.example.com`. When both allowed and denied lists are empty, all domains are blocked. Denied domains (see `#chat.agent.deniedNetworkDomains#`) take precedence.\n\nChanges may not take full effect until VS Code is restarted." - } - }, - "type": "array", - "default": [], - "included": true, - "agentHost": { - "status": "partial" - } - }, - { - "key": "chat.agent.deniedNetworkDomains", - "name": "ChatAgentDeniedNetworkDomains", - "category": "InteractiveSession", - "minimumVersion": "1.116", - "localization": { - "description": { - "key": "chat.agent.deniedNetworkDomains", - "value": "Denied domains for network access by agent tools (fetch tool, integrated browser). Applies when `#chat.agent.networkFilter#` or `#chat.agent.sandbox.enabled#` is enabled. This does not apply when `#chat.agent.sandbox.network.allowNetwork#` is enabled. Takes precedence over `#chat.agent.allowedNetworkDomains#`. Supports wildcards like `*.example.com`.\n\nChanges may not take full effect until VS Code is restarted." - } - }, - "type": "array", - "default": [], - "included": true, - "agentHost": { - "status": "partial" - } - }, { "key": "chat.agent.enabled", "name": "ChatAgentMode", @@ -118,7 +82,7 @@ "localization": { "description": { "key": "chat.agent.networkFilter", - "value": "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to `#chat.agent.allowedNetworkDomains#` and `#chat.agent.deniedNetworkDomains#`. Domain filtering is also applied to those tools when `#chat.agent.sandbox.enabled#` is enabled.\n\nChanges may not take full effect until VS Code is restarted." + "value": "When enabled, network access by agent tools (fetch tool, integrated browser) is restricted according to `#chat.agent.sandbox.network.allowedDomains#` and `#chat.agent.sandbox.network.deniedDomains#`. Domain filtering is also applied to those tools when `#chat.agent.sandbox.enabled#` is enabled.\n\nChanges may not take full effect until VS Code is restarted." } }, "type": "boolean", @@ -178,6 +142,24 @@ "status": "notApplicable" } }, + { + "key": "chat.agent.sandbox.network.allowedDomains", + "name": "ChatAgentAllowedNetworkDomains", + "category": "InteractiveSession", + "minimumVersion": "1.116", + "localization": { + "description": { + "key": "chat.agent.allowedNetworkDomains", + "value": "Allowed domains for network access by agent tools (fetch tool, integrated browser) when `#chat.agent.networkFilter#` is enabled. In Copilot Agent Host sessions with sandboxing (`#chat.agent.sandbox.enabled#`) enabled, also restricts the integrated browser when outbound network access (`#chat.agent.sandbox.network.allowNetwork#`) is allowed; an empty allow list adds no sandbox restriction. This list does not enable outbound access. Supports wildcards like `*.example.com`. Outside sandboxing, when both allowed and denied lists are empty, all domains are blocked. Denied domains (see `#chat.agent.sandbox.network.deniedDomains#`) take precedence.\n\nChanges may not take full effect until VS Code is restarted." + } + }, + "type": "array", + "default": [], + "included": true, + "agentHost": { + "status": "partial" + } + }, { "key": "chat.agent.sandbox.network.allowNetwork", "name": "ChatAgentSandboxAllowNetwork", @@ -196,6 +178,24 @@ "status": "notApplicable" } }, + { + "key": "chat.agent.sandbox.network.deniedDomains", + "name": "ChatAgentDeniedNetworkDomains", + "category": "InteractiveSession", + "minimumVersion": "1.116", + "localization": { + "description": { + "key": "chat.agent.deniedNetworkDomains", + "value": "Denied domains for network access by agent tools (fetch tool, integrated browser) when `#chat.agent.networkFilter#` is enabled. Also applies to the integrated browser in sandboxed Copilot Agent Host sessions (`#chat.agent.sandbox.enabled#`); allowing outbound network access (`#chat.agent.sandbox.network.allowNetwork#`) does not bypass this list. Takes precedence over `#chat.agent.sandbox.network.allowedDomains#`. Supports wildcards like `*.example.com`.\n\nChanges may not take full effect until VS Code is restarted." + } + }, + "type": "array", + "default": [], + "included": true, + "agentHost": { + "status": "partial" + } + }, { "key": "chat.agentHost.claudeAgent.enabled", "name": "Claude3PIntegration", diff --git a/cgmanifest.json b/cgmanifest.json index a0e0e7e6ae0e..4e86b48bb615 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -529,13 +529,13 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "5338ed09bfdcf1bbd2e6bab722c2620efe8c4790", - "tag": "43.7.5" + "commitHash": "ddb762f50fbd70384d0385302697e60ada549024", + "tag": "43.7.7" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "43.7.5" + "version": "43.7.7" }, { "component": { diff --git a/cli/src/commands/agent_stop.rs b/cli/src/commands/agent_stop.rs index 4d617c42d70b..c5a033c7ecbb 100644 --- a/cli/src/commands/agent_stop.rs +++ b/cli/src/commands/agent_stop.rs @@ -80,7 +80,12 @@ pub async fn agent_stop(ctx: CommandContext, args: AgentStopArgs) -> Result Migrations > Migrate with Agent +user-invocable: true +disable-model-invocation: true +--- + +# Migrate Agent Customizations + +Guide the user through migrating the inventory included in the invoking prompt. The source locations and valid target folders in that prompt were evaluated for the selected harness. Treat them as authoritative: do not substitute hardcoded destination paths or migrate to a folder that is not listed. + +## Required Migration Context + +Before doing anything else, verify that the invoking prompt includes all of the following VS Code-generated values: + +- Selected harness +- Migration telemetry flow +- Recovery bundle folder and filesystem path +- Customizations that need migration +- Valid target folders reported by the selected harness + +If any value is missing, do not inspect, copy, move, convert, or delete customizations. Explain that this workflow must be started from **Agent Customizations > Migrations** by selecting **Migrate with Agent**, then stop. Do not ask the user to manually reconstruct or paste the missing context. + +Read [migration techniques](./references/migration-techniques.md) before proposing changes. + +## Safety Contract + +1. Never modify a customization before creating a recovery bundle. +2. Use the recovery bundle folder supplied in the invoking prompt. VS Code created and is watching this folder; do not move it or substitute another location. +3. In the recovery bundle, create: + - `migration-log.md`, containing the selected harness, scope, timestamps, source and destination paths, commands or tools used, validation results, and every user decision. + - `backups/`, containing a byte-for-byte copy of every file before its first modification or deletion. + - `restore.md`, containing exact steps for restoring the backups and reverting newly created files. + - `migration-results.json`, containing only the privacy-safe aggregate schema described in Finish. VS Code watches this file for the final migration outcome. +4. Append to the log after every attempted operation, including failures and rollbacks. +5. Approval to migrate a group authorizes destination writes only; it does not authorize deleting source files, removing source MCP entries, or clearing location settings. Preserve sources by default. After the destination is validated, ask separately for explicit approval for each kind of cleanup. If runtime validation is not possible, explain what was and was not verified, keep the source, and ask whether to defer cleanup. +6. Stop on an unexpected source change, destination conflict, invalid configuration, failed backup, or failed validation. Explain the problem instead of guessing. +7. Never include customization names, paths, contents, MCP configuration, or other user data in telemetry. +8. Classify each inventory item exactly once using its supplied category and scope. Do not offer the same item again in another group; if groups overlap, explain the overlap and use the item's inventory category to decide where it is handled. + +## Workflow + +### 1. Choose Scope + +Ask whether to migrate: + +- user customizations, +- workspace customizations, or +- both. + +Do not act on an unselected scope. + +If workspace customizations are selected, ask whether the user wants a pull request. If they do, keep workspace changes focused, include the validation evidence and migration rationale in the pull request, and do not include a recovery bundle stored outside the repository. + +### 2. Review the Plan + +Classify the supplied inventory by its reported category and scope, then handle applicable items in this order: + +1. prompt files that should become skills; +2. VS Code profile user-data agents, instructions, and skills; +3. MCP servers; +4. workspace or user customizations at locations defined by settings `chat.agentSkillsLocations`, `chat.instructionsFilesLocations`, and `chat.agentFilesLocations`. + +The groups are mutually exclusive for an inventory item. In particular, a file reported under `configuredLocations` belongs in group 4, not group 2, even if its type is agent, instruction, or skill. If a previously skipped item would otherwise reappear in a later group, do not ask about it again unless the user requests reconsideration. + +Before each group: + +- explain why the migration is useful; +- list the source and harness-reported destination locations; +- describe changes that cannot be preserved; +- state explicitly that approval covers destination writes only and that source cleanup will require a separate approval; +- ask for confirmation to proceed with that group. + +Work one group at a time. Do not request approval for all writes at once. + +### 3. Migrate Prompt Files to Skills + +Explain the compatibility reason for converting each prompt to a skill. Convert each selected prompt into a skill directory with a `SKILL.md`, preserving supported name, description, argument guidance, invocation semantics, and body content. Add `disable-model-invocation: true` when needed to preserve an explicit-invocation-only prompt. Record unsupported frontmatter in the log for review; do not silently drop behavior. + +Validate that each skill has valid frontmatter, a meaningful description, and a folder name that matches the skill name. Distinguish structural validation from runtime validation: test discovery and representative invocation in the selected destination harness when possible. A file inspection or a test against a different harness is not proof that the destination harness loads the skill. If runtime validation requires a new session or is otherwise unavailable, say so, leave the source in place, and defer cleanup unless the user explicitly chooses otherwise after hearing the limitation. + +### 4. Migrate User Data + +Explain that Agent Host doesn't read the VS Code profile user data folder. It only reads the harness's user folders. Copy selected agents, instructions, and skills to a compatible listed destination without silently changing their contents. +Keep user-scope items in user scope. Explain that approval to copy does not authorize deleting the VS Code source or changing sync/location settings; ask for those separately after validation. + +### 5. Migrate MCP Servers + +Explain that moving MCP configuration lets the selected Agent Host load the server directly. Review every server separately, including destination conflicts and properties the destination format cannot preserve. Call out that a disabled user server might become enabled after migration. + +Write and statically validate the destination before considering source cleanup. Leave unselected, unsupported, and conflicting servers unchanged. Test that migrated servers are discovered and can start in the selected destination harness; spawning a command directly can verify process behavior, but does not by itself prove that the harness discovers or loads the configuration. If destination-harness testing is unavailable, report that limitation and keep source entries. + +Approval for this group authorizes destination writes only. After successful destination validation, ask separately whether to remove each migrated or already-equivalent source entry. Do not remove a source entry merely because its destination entry was written or because the user approved the group. + +### 6. Customizations at Custom Locations + +Files in custom locations defined by `chat.agentSkillsLocations`, `chat.instructionsFilesLocations`, and `chat.agentFilesLocations` can continue to work when the selected harness is connected to VS Code. Moving them to a harness-reported standard location may improve portability or consistency, but is not automatically required for compatibility. Explain this trade-off before asking whether to migrate. + +Handle only items reported as `configuredLocations` and not already handled in another group. Do not re-offer files the user explicitly skipped. Treat moving a file, deleting its old copy, and clearing or changing the location setting as separate actions; obtain explicit approval for each cleanup action after validating the destination. + +### 7. Finish + +Before the final summary, write `migration-results.json` in the recovery bundle: + +```json +{ + "version": 1, + "migrationFlowId": "", + "cancelled": false, + "results": [ + { + "category": "promptFiles", + "scope": "workspace", + "customizationType": "skill", + "outcome": "migrated", + "count": 1 + } + ] +} +``` + +Aggregate results by: + +- category: `promptFiles`, `userData`, `configuredLocations`, or `mcpServers`; +- scope: `user` or `workspace`; +- customization type: `agent`, `instructions`, `skill`, or `mcpServer`; +- outcome: `migrated`, `skipped`, or `failed`. + +The total reported for each category, scope, and customization type must not exceed the inventory supplied by VS Code. The result file must never include names, paths, contents, configuration values, error messages, or other user data. + +Write the validated JSON to the exact recovery bundle supplied by VS Code. Do not write a result file when no migration telemetry flow was supplied. + +If the user stops the workflow, set `cancelled` to `true`, record completed work with its actual outcome, mark the remaining inventory as `skipped`, and still validate and write the result file before the final cancellation summary. + +Summarize: + +- migrated, skipped, and failed items; +- created, modified, deleted, and retained files; +- validation performed; +- cleanup still pending; +- recovery bundle and restoration instructions; +- pull request URL when one was requested. + +Keep the recovery bundle until the user confirms the migrated customizations work. + +For a requested pull request, verify the final diff contains only approved workspace changes, exclude the recovery bundle, and include the migration rationale and actual validation performed. Do not claim runtime validation when only static checks or direct process tests were possible. diff --git a/extensions/copilot/assets/prompts/skills/migrate-customizations/references/migration-techniques.md b/extensions/copilot/assets/prompts/skills/migrate-customizations/references/migration-techniques.md new file mode 100644 index 000000000000..be4ded8047d3 --- /dev/null +++ b/extensions/copilot/assets/prompts/skills/migrate-customizations/references/migration-techniques.md @@ -0,0 +1,82 @@ +# Agent Customization Migration Techniques + +Use these techniques together with the source inventory and target folders supplied by VS Code. Harness-reported destinations take precedence over examples in general documentation. + +## General Technique + +- Back up every source before writing. +- Group approval authorizes destination writes only. Preserve each source by default; ask for explicit cleanup approval separately, after destination validation. +- Keep user and workspace scope unchanged unless the user explicitly chooses otherwise. +- Classify each inventory item once by its supplied category and scope. Do not offer a skipped item again in another group unless the user asks to reconsider it. +- In a multi-root workspace, keep each migrated customization in the corresponding workspace root. +- Verify discovery and representative behavior in the destination environment. Remote and Dev Container user folders belong to that environment. +- Migrated and original copies do not synchronize. Remove the original only after validation. +- Separate structural checks (file shape, frontmatter, or valid JSON) from runtime checks in the selected harness. A direct server-process test is useful evidence about that process, but does not prove the harness discovers or loads its configuration. If destination-harness testing is unavailable, report the limitation and keep the source unless the user explicitly chooses otherwise after being informed. + +## Prompt Files to Skills + +Agent Host harnesses do not load `*.prompt.md` files. Convert a prompt to `/SKILL.md`. + +- Preserve supported `name`, `description`, and argument guidance. +- Preserve invocation semantics. Add `disable-model-invocation: true` when needed to retain an explicit-invocation-only prompt. +- Choose a valid, unique skill folder name. +- Record unsupported prompt frontmatter rather than silently discarding it. +- Keep the prompt body focused as a reusable workflow. +- Validate the skill's frontmatter and invoke it with a representative request. +- Do not treat structural validation as proof of runtime discovery. Test the skill in the selected destination harness when possible; if testing requires a new session, defer it and keep the prompt until the user has been told runtime validation is pending. + +## User Data Locations + +VS Code profile user data are Local-only. Move agents, instructions, and skills to a destination reported by the selected harness. + +- Preserve file type, name, and content unless a compatibility issue requires an explained change. +- Keep workspace files in their originating repository root. +- Explain that harness user folders might not participate in VS Code Settings Sync. +- Treat copying a file, deleting the source, and clearing or changing a location setting as separate decisions. Group migration approval authorizes the copy only. +- `chat.promptFilesLocations` entries are prompt-to-skill migrations, not ordinary location moves. + +## MCP Servers + +Migrate each MCP server independently. + +- Review source and destination JSON before writing. +- Detect duplicate server names and non-equivalent destination entries. +- Explain how every source property and variable will migrate by using the table below. +- A disabled user server might become enabled at the destination; review enablement before starting a session. +- Write and statically validate the destination entry before runtime testing or considering source cleanup. +- Leave unsupported, invalid, conflicting, and unselected servers unchanged. +- Do not remove a source entry solely because an equivalent destination entry already exists. After successful validation, ask separately before removing each migrated or redundant source entry. + +| Source configuration | Migration result | What to do | +|----------------------|------------------|------------| +| Standard `command`, `args`, `env`, `url`, and `headers` values | Migrate automatically and add `tools: ["*"]` | Inspect the server's tools. Replace `*` with the specific tools the agent should invoke when access to every server tool is not appropriate. | +| `${workspaceFolder}`, `${workspaceRoot}`, `${workspaceFolderBasename}`, `${workspaceRootFolderName}`, `${cwd}`, or `${pathSeparator}` | Resolve the variable and write its current value | Review the resulting value before sharing the destination file or using it on another machine. | +| `gallery`, `version`, `dev`, or `sandboxEnabled` | Migrate while removing these properties | Explain the removal and decide how to handle updates, development behavior, or sandboxing after migration. | +| `${input:...}`, `${config:...}`, `${command:...}`, or other interactive VS Code variables | Do not migrate automatically | Reconfigure the value for the destination harness. Never copy a resolved secret into the MCP file. | +| `${env:NAME}` | Do not migrate automatically | Use `$NAME`, `${NAME}`, or `${NAME:-default}`, and define the variable in the Agent Host environment. | +| `cwd` | Do not migrate automatically | Add `cwd` manually only when the server requires it, and verify the path on the Agent Host machine. | +| `envFile` | Do not migrate automatically | Export the required variables in the Agent Host environment and reference them from `env`. Never copy secret values into the MCP file. | +| SSE transport | Do not migrate automatically | Use `type: "sse"` only when the server does not support Streamable HTTP. SSE is deprecated. | +| A VS Code `oauth` object | Do not migrate automatically | Remove the nested object to use OAuth discovery, or translate supported client settings to the flat destination OAuth fields. Authenticate when prompted. | +| Environment variables with `null` values | Do not migrate automatically | Remove the entry or provide a supported value. | +| Additional VS Code-specific properties | Do not migrate automatically | Remove or replace the unsupported property before retrying. | +| A different server with the same name in the destination or another workspace root | Stop without changing the source | Rename or remove the conflicting server, and then retry. | + +Input variables require manual configuration. VS Code can prompt for values such as API keys through `${input:api-key}`, but the destination MCP format does not use that input flow. Ask the user how the value should be supplied securely in the Agent Host environment. + +## Custom Locations + +Agents, skill, and instruction files located in custom locations defined by settings `chat.agentSkillsLocations`, `chat.instructionsFilesLocations`, and `chat.agentFilesLocations` can continue to work when the selected harness is connected to VS Code. Moving them to a harness-reported standard location may improve portability or consistency, but is not automatically required for compatibility. Explain the trade-off rather than presenting the move as mandatory. + +Handle only items reported under `configuredLocations` and not already handled under another inventory category. Do not re-ask about files the user explicitly skipped. Treat copying/moving a file, deleting its old copy, and clearing or changing the location setting as separate actions requiring separate approval. + +## Verification + +After each migration group: + +1. Confirm the item appears in the Agent Customizations editor for the selected harness. +2. Start a session with that harness and run a representative task. +3. Confirm MCP servers start and expose the expected tools. +4. Review version-control changes for workspace migrations. +5. Report structural validation separately from runtime validation. If the selected harness cannot be exercised in this session, mark runtime validation as pending and keep source files and entries. +6. Only after successful destination validation, ask for explicit approval before removing old files, MCP entries, or settings. Approval to migrate is not approval to clean up. diff --git a/extensions/copilot/package-lock.json b/extensions/copilot/package-lock.json index 19df88d7dfee..99a95abe5c57 100644 --- a/extensions/copilot/package-lock.json +++ b/extensions/copilot/package-lock.json @@ -1,12 +1,12 @@ { "name": "copilot-chat", - "version": "0.69.0", + "version": "0.70.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "copilot-chat", - "version": "0.69.0", + "version": "0.70.0", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { @@ -143,7 +143,7 @@ "engines": { "node": ">=22.14.0", "npm": ">=9.0.0", - "vscode": "^1.141.0" + "vscode": "^1.142.0" } }, "node_modules/@anthropic-ai/sdk": { @@ -266,9 +266,9 @@ } }, "node_modules/@azure/core-client/node_modules/@typespec/ts-http-runtime": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.2.tgz", - "integrity": "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", + "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "dev": true, "license": "MIT", "dependencies": { @@ -277,7 +277,7 @@ "tslib": "^2.6.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@azure/core-http-compat": { @@ -387,9 +387,9 @@ } }, "node_modules/@azure/core-util/node_modules/@typespec/ts-http-runtime": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.2.tgz", - "integrity": "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", + "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.0", @@ -397,7 +397,7 @@ "tslib": "^2.6.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@azure/identity": { @@ -520,9 +520,9 @@ } }, "node_modules/@azure/logger/node_modules/@typespec/ts-http-runtime": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.2.tgz", - "integrity": "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.9.tgz", + "integrity": "sha512-edSdeAqkdxBVzA1yL1LrLCml1YjyCVvPMtMqJpbF+6K609tHe8V6sQUzFQSGcYNhcuhOceZtjvN32+mpIth30A==", "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.0", @@ -530,7 +530,7 @@ "tslib": "^2.6.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } }, "node_modules/@azure/msal-browser": { diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index 285b63128c4a..b4c41dac063d 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -2,7 +2,7 @@ "name": "copilot-chat", "displayName": "GitHub Copilot", "description": "AI chat features powered by Copilot", - "version": "0.69.0", + "version": "0.70.0", "build": "1", "completionsCoreVersion": "1.378.1799", "internalLargeStorageAriaKey": "ec712b3202c5462fb6877acae7f1f9d7-c19ad55e-3e3c-4f99-984b-827f6d95bd9e-6917", @@ -22,7 +22,7 @@ "icon": "assets/copilot.png", "pricing": "Trial", "engines": { - "vscode": "^1.141.0", + "vscode": "^1.142.0", "npm": ">=9.0.0", "node": ">=22.14.0" }, @@ -6866,6 +6866,14 @@ "copilotcli" ] }, + { + "path": "./assets/prompts/skills/migrate-customizations/SKILL.md", + "sessionTypes": [ + "copilotcli", + "claude", + "codex" + ] + }, { "path": "./assets/prompts/skills/init/SKILL.md", "sessionTypes": [ diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts index d12a82dea983..b55cb62887b2 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts @@ -826,8 +826,8 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS const fileSystemSetting = rawFileSystemSetting && typeof rawFileSystemSetting === 'object' ? rawFileSystemSetting as IAgentSandboxFileSystemSettings : undefined; - const allowedHosts = readStringArraySetting(this.configurationService, 'chat.agent.allowedNetworkDomains'); - const blockedHosts = readStringArraySetting(this.configurationService, 'chat.agent.deniedNetworkDomains'); + const allowedHosts = readStringArraySetting(this.configurationService, 'chat.agent.sandbox.network.allowedDomains'); + const blockedHosts = readStringArraySetting(this.configurationService, 'chat.agent.sandbox.network.deniedDomains'); return buildSandboxConfigForCLI(process.platform, sandboxSetting, fileSystemSetting, { allowedHosts, blockedHosts }); } diff --git a/extensions/copilot/src/extension/context/node/resolvers/extensionApi.tsx b/extensions/copilot/src/extension/context/node/resolvers/extensionApi.tsx index db57b8976ff4..46d7551ed382 100644 --- a/extensions/copilot/src/extension/context/node/resolvers/extensionApi.tsx +++ b/extensions/copilot/src/extension/context/node/resolvers/extensionApi.tsx @@ -32,8 +32,8 @@ export class ApiEmbeddingsIndex implements IApiEmbeddingsIndex { ) { const cacheVersion = sanitizeVSCodeVersion(envService.getEditorInfo().version); this.embeddingsCache = useRemoteCache ? - instantiationService.createInstance(RemoteEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'api', cacheVersion, EmbeddingType.text3small_512, RemoteCacheType.Api) : - instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'api', cacheVersion, EmbeddingType.text3small_512); + instantiationService.createInstance(RemoteEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'api', cacheVersion, EmbeddingType.metis_1024_I16_Binary, RemoteCacheType.Api) : + instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'api', cacheVersion, EmbeddingType.metis_1024_I16_Binary); } async updateIndex(): Promise { @@ -95,7 +95,7 @@ export class VSCodeAPIContextElement extends PromptElement = { + text3small: 'text-embedding-3-small', + metis: 'metis', +}; // Keep in sync with `BYOKUtilityModelDefault` in `src/vs/workbench/contrib/chat/common/constants.ts` and the `chat.byokUtilityModelDefault` enum in `chat.shared.contribution.ts`. const enum BYOKUtilityModelDefault { @@ -353,14 +357,14 @@ export class ProductionEndpointProvider extends Disposable implements IEndpointP async getEmbeddingsEndpoint(family?: EmbeddingsEndpointFamily): Promise { this._logService.trace(`Resolving embedding model`); - const modelMetadata = await this._modelFetcher.getEmbeddingsModel('text-embedding-3-small'); + const modelMetadata = await this._modelFetcher.getEmbeddingsModel(embeddingsModelFamilies[family ?? 'metis']); const model = await this.getOrCreateEmbeddingEndpointInstance(modelMetadata); this._logService.trace(`Resolved embedding model`); return model; } private async getOrCreateEmbeddingEndpointInstance(modelMetadata: IEmbeddingModelInformation): Promise { - const modelId = 'text-embedding-3-small'; + const modelId = modelMetadata.id; let embeddingEndpoint = this._embeddingEndpoints.get(modelId); if (!embeddingEndpoint) { embeddingEndpoint = this._instantiationService.createInstance(EmbeddingEndpoint, modelMetadata); diff --git a/extensions/copilot/src/extension/prompt/vscode-node/settingsEditorSearchServiceImpl.ts b/extensions/copilot/src/extension/prompt/vscode-node/settingsEditorSearchServiceImpl.ts index c319e4bb0a6e..43b942109108 100644 --- a/extensions/copilot/src/extension/prompt/vscode-node/settingsEditorSearchServiceImpl.ts +++ b/extensions/copilot/src/extension/prompt/vscode-node/settingsEditorSearchServiceImpl.ts @@ -32,7 +32,7 @@ export class SettingsEditorSearchServiceImpl implements ISettingsEditorSearchSer // Start searching for embedding results. let embeddingResult: Embeddings; try { - embeddingResult = await this.embeddingsComputer.computeEmbeddings(EmbeddingType.text3small_512, [query], {}, new TelemetryCorrelationId('SettingsEditorSearchServiceImpl::provideSettingsSearchResults'), token); + embeddingResult = await this.embeddingsComputer.computeEmbeddings(EmbeddingType.metis_1024_I16_Binary, [query], { inputType: 'query' }, new TelemetryCorrelationId('SettingsEditorSearchServiceImpl::provideSettingsSearchResults'), token); } catch { this.reportEmptyEmbeddingsResult(query, progress); if (!options.embeddingsOnly) { diff --git a/extensions/copilot/src/extension/prompts/node/panel/newWorkspace/newWorkspace.tsx b/extensions/copilot/src/extension/prompts/node/panel/newWorkspace/newWorkspace.tsx index c2865f146940..eb25085c33b1 100644 --- a/extensions/copilot/src/extension/prompts/node/panel/newWorkspace/newWorkspace.tsx +++ b/extensions/copilot/src/extension/prompts/node/panel/newWorkspace/newWorkspace.tsx @@ -105,7 +105,7 @@ export class NewWorkspacePrompt extends PromptElement { + async getEmbeddingsModel(family: 'text-embedding-3-small' | 'metis'): Promise { return { - id: 'text-embedding-3-small', + id: family, name: 'fake-name', vendor: 'fake-vendor', version: 'fake-version', @@ -68,7 +68,7 @@ class FakeModelMetadataFetcher implements IModelMetadataFetcher { capabilities: { type: 'embeddings', tokenizer: TokenizerType.O200K, - family: 'text-embedding-3-small', + family, limits: { max_inputs: 256 } } }; @@ -102,6 +102,26 @@ suite('Endpoint Class Test', function () { assert.strictEqual(endpoint.model, 'gpt-5.4-nano'); }); + + test('resolves embedding endpoints by family with Metis as the default', async function () { + const defaultEndpoint = await endpointProvider.getEmbeddingsEndpoint(); + const metisEndpoint = await endpointProvider.getEmbeddingsEndpoint('metis'); + const text3smallEndpoint = await endpointProvider.getEmbeddingsEndpoint('text3small'); + + assert.deepStrictEqual({ + defaultFamily: defaultEndpoint.family, + metisFamily: metisEndpoint.family, + text3smallFamily: text3smallEndpoint.family, + reusesMetisEndpoint: defaultEndpoint === metisEndpoint, + keepsFamiliesSeparate: metisEndpoint !== text3smallEndpoint, + }, { + defaultFamily: 'metis', + metisFamily: 'metis', + text3smallFamily: 'text-embedding-3-small', + reusesMetisEndpoint: true, + keepsFamiliesSeparate: true, + }); + }); }); class CopilotMatchableModelMetadataFetcher implements IModelMetadataFetcher { @@ -122,9 +142,9 @@ class CopilotMatchableModelMetadataFetcher implements IModelMetadataFetcher { async getChatModelFromCapiFamily(family: string): Promise { return makeChatModel(family); } - async getEmbeddingsModel(): Promise { + async getEmbeddingsModel(family: 'text-embedding-3-small' | 'metis'): Promise { return { - id: 'text-embedding-3-small', + id: family, name: 'fake-name', vendor: 'fake-vendor', version: 'fake-version', @@ -134,7 +154,7 @@ class CopilotMatchableModelMetadataFetcher implements IModelMetadataFetcher { capabilities: { type: 'embeddings', tokenizer: TokenizerType.O200K, - family: 'text-embedding-3-small', + family, limits: { max_inputs: 256 } } }; diff --git a/extensions/copilot/src/extension/tools/common/virtualTools/preComputedToolEmbeddingsCache.ts b/extensions/copilot/src/extension/tools/common/virtualTools/preComputedToolEmbeddingsCache.ts index 910d54325143..d2cc5a8fb1d3 100644 --- a/extensions/copilot/src/extension/tools/common/virtualTools/preComputedToolEmbeddingsCache.ts +++ b/extensions/copilot/src/extension/tools/common/virtualTools/preComputedToolEmbeddingsCache.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Embedding, EmbeddingType } from '../../../../platform/embeddings/common/embeddingsComputer'; +import { Embedding, EmbeddingType, EmbeddingVector } from '../../../../platform/embeddings/common/embeddingsComputer'; import { EmbeddingCacheType, IEmbeddingsCache, RemoteCacheType, RemoteEmbeddingsCache } from '../../../../platform/embeddings/common/embeddingsIndex'; import { IEnvService } from '../../../../platform/env/common/envService'; import { ILogService } from '../../../../platform/log/common/logService'; @@ -11,7 +11,11 @@ import { sanitizeVSCodeVersion } from '../../../../util/common/vscodeVersion'; import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation'; import { IToolEmbeddingsCache } from './toolEmbeddingsComputer'; -export const EMBEDDING_TYPE_FOR_TOOL_GROUPING = EmbeddingType.text3small_512; +export const EMBEDDING_TYPE_FOR_TOOL_GROUPING = EmbeddingType.metis_1024_I16_Binary; + +type ToolEmbeddingCacheData = + | Record + | readonly { readonly key: string; readonly embedding: EmbeddingVector }[]; export class PreComputedToolEmbeddingsCache implements IToolEmbeddingsCache { private readonly cache: IEmbeddingsCache; @@ -44,18 +48,21 @@ export class PreComputedToolEmbeddingsCache implements IToolEmbeddingsCache { private async _loadEmbeddings() { try { - const embeddingsData = await this.cache.getCache(); + const embeddingsData = await this.cache.getCache(); const embeddingsMap = new Map(); - if (embeddingsData) { - for (const [key, embeddingVector] of Object.entries(embeddingsData)) { - if (embeddingVector === undefined) { - this._logService.warn(`Tool embedding missing for key: ${key}`); - continue; - } + if (Array.isArray(embeddingsData)) { + for (const entry of embeddingsData) { + embeddingsMap.set(entry.key, { + type: this.embeddingType, + value: entry.embedding + }); + } + } else if (embeddingsData) { + for (const [key, entry] of Object.entries(embeddingsData)) { embeddingsMap.set(key, { type: this.embeddingType, - value: embeddingVector.embedding + value: entry.embedding }); } } @@ -67,5 +74,3 @@ export class PreComputedToolEmbeddingsCache implements IToolEmbeddingsCache { } } } - - diff --git a/extensions/copilot/src/extension/tools/common/virtualTools/toolEmbeddingsComputer.ts b/extensions/copilot/src/extension/tools/common/virtualTools/toolEmbeddingsComputer.ts index aa7d2e03778e..658adfd5ab7b 100644 --- a/extensions/copilot/src/extension/tools/common/virtualTools/toolEmbeddingsComputer.ts +++ b/extensions/copilot/src/extension/tools/common/virtualTools/toolEmbeddingsComputer.ts @@ -116,7 +116,7 @@ export class ToolEmbeddingsComputer implements IToolEmbeddingsComputer { return []; } - const queryEmbedding = await this._embeddingsComputer.computeEmbeddings(this._embeddingType, [query], {}, new TelemetryCorrelationId('ToolEmbeddingsComputer::searchToolsByQuery'), token); + const queryEmbedding = await this._embeddingsComputer.computeEmbeddings(this._embeddingType, [query], { inputType: 'query' }, new TelemetryCorrelationId('ToolEmbeddingsComputer::searchToolsByQuery'), token); if (!queryEmbedding || queryEmbedding.values.length === 0) { return []; } @@ -184,7 +184,7 @@ export class ToolEmbeddingsComputer implements IToolEmbeddingsComputer { const toolNames = tools.map(t => t.name + '\n\n' + t.description); const start = new StopWatch(); - const embeddings = await this._embeddingsComputer.computeEmbeddings(this._embeddingType, toolNames, {}, new TelemetryCorrelationId('ToolEmbeddingsComputer::computeEmbeddingsForTools'), token); + const embeddings = await this._embeddingsComputer.computeEmbeddings(this._embeddingType, toolNames, { inputType: 'document' }, new TelemetryCorrelationId('ToolEmbeddingsComputer::computeEmbeddingsForTools'), token); this._logService.trace(`[virtual-tools] Computed embeddings for ${toolNames.length} tools in ${start.elapsed()}ms`); if (embeddings?.values.length === 0 || embeddings?.values.length !== toolNames.length) { diff --git a/extensions/copilot/src/extension/tools/common/virtualTools/virtualToolGrouper.ts b/extensions/copilot/src/extension/tools/common/virtualTools/virtualToolGrouper.ts index 7a5de14a811f..523142464718 100644 --- a/extensions/copilot/src/extension/tools/common/virtualTools/virtualToolGrouper.ts +++ b/extensions/copilot/src/extension/tools/common/virtualTools/virtualToolGrouper.ts @@ -351,7 +351,7 @@ export class VirtualToolGrouper implements IToolCategorization { } // compute the embeddings for the query - const queryEmbedding = await this.embeddingsComputer.computeEmbeddings(EMBEDDING_TYPE_FOR_TOOL_GROUPING, [query], {}, new TelemetryCorrelationId('VirtualToolGrouper::_getPredictedTools'), token); + const queryEmbedding = await this.embeddingsComputer.computeEmbeddings(EMBEDDING_TYPE_FOR_TOOL_GROUPING, [query], { inputType: 'query' }, new TelemetryCorrelationId('VirtualToolGrouper::_getPredictedTools'), token); if (!queryEmbedding || queryEmbedding.values.length === 0) { return []; } diff --git a/extensions/copilot/src/extension/tools/test/node/virtualTools/preComputedToolEmbeddingsCache.spec.ts b/extensions/copilot/src/extension/tools/test/node/virtualTools/preComputedToolEmbeddingsCache.spec.ts new file mode 100644 index 000000000000..9afe478335f6 --- /dev/null +++ b/extensions/copilot/src/extension/tools/test/node/virtualTools/preComputedToolEmbeddingsCache.spec.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, test } from 'vitest'; +import { EmbeddingType, EmbeddingVector } from '../../../../../platform/embeddings/common/embeddingsComputer'; +import { IEmbeddingsCache } from '../../../../../platform/embeddings/common/embeddingsIndex'; +import { NullEnvService } from '../../../../../platform/env/common/nullEnvService'; +import { TestLogService } from '../../../../../platform/testing/common/testLogService'; +import { mock } from '../../../../../util/common/test/simpleMock'; +import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation'; +import { PreComputedToolEmbeddingsCache } from '../../../common/virtualTools/preComputedToolEmbeddingsCache'; + +function createCache(data: readonly { readonly key: string; readonly embedding: EmbeddingVector }[] | Record): PreComputedToolEmbeddingsCache { + const embeddingsCache = new class extends mock() { + override readonly embeddingType = EmbeddingType.metis_1024_I16_Binary; + + override async getCache(): Promise { + return data as T; + } + }(); + const instantiationService = new class extends mock() { }(); + instantiationService.createInstance = (() => embeddingsCache) as IInstantiationService['createInstance']; + return new PreComputedToolEmbeddingsCache(new TestLogService(), instantiationService, NullEnvService.Instance); +} + +describe('PreComputedToolEmbeddingsCache', () => { + test('loads published array entries by tool name', async () => { + const cache = createCache([{ key: 'click_element', embedding: [1, 0] }]); + + await cache.initialize(); + + expect(cache.get({ name: 'click_element' })).toEqual({ + type: EmbeddingType.metis_1024_I16_Binary, + value: [1, 0], + }); + }); + + test('loads legacy object entries by tool name', async () => { + const cache = createCache({ click_element: { embedding: [1, 0] } }); + + await cache.initialize(); + + expect(cache.get({ name: 'click_element' })).toEqual({ + type: EmbeddingType.metis_1024_I16_Binary, + value: [1, 0], + }); + }); +}); diff --git a/extensions/copilot/src/platform/embeddings/common/embeddingsIndex.ts b/extensions/copilot/src/platform/embeddings/common/embeddingsIndex.ts index 5fb2149307fb..f1067db12bb6 100644 --- a/extensions/copilot/src/platform/embeddings/common/embeddingsIndex.ts +++ b/extensions/copilot/src/platform/embeddings/common/embeddingsIndex.ts @@ -38,7 +38,7 @@ export enum RemoteCacheType { // These values are the blob storage container names where we publish computed embeddings enum RemoteEmbeddingsContainer { TEXT3SMALL = 'text-3-small', - METIS_1024_I16_BINARY = 'metis-1024-I16-Binary' + METIS_1024_I16_BINARY = 'metis-1024-i16-binary' } function embeddingsModelToRemoteContainer(embeddingType: EmbeddingType): RemoteEmbeddingsContainer { @@ -150,8 +150,8 @@ class EmbeddingsCache { await this.fileSystemService.stat(this.cacheStorageUri); await this.fileSystemService.delete(cacheFile, { useTrash: false }); } catch (e) { - if (e.code === 'ENOENT') { - throw new Error(`Cache file ${cacheFile} does not exist`); + if (e.code !== 'ENOENT' && e.code !== 'FileNotFound') { + throw e; } } } @@ -196,7 +196,7 @@ export class LocalEmbeddingsCache implements IEmbeddingsCache { this._embeddingsCache = instantiationService.createInstance( EmbeddingsCache, cacheType, - cacheKey, + `${cacheKey}-${embeddingType.id}`, cacheVersion ); } @@ -235,13 +235,14 @@ export class RemoteEmbeddingsCache implements IEmbeddingsCache { @IFetcherService protected readonly fetcherService: IFetcherService, @IInstantiationService instantiationService: IInstantiationService ) { + const modelCacheKey = `${cacheKey}-${embeddingType.id}`; this.embeddingsCache = instantiationService.createInstance( EmbeddingsCache, cacheType, - cacheKey, + modelCacheKey, cacheVersion ); - this.remoteCacheVersionKey = `${cacheKey}-version-remote`; + this.remoteCacheVersionKey = `${modelCacheKey}-version-remote`; } async clearCache(): Promise { diff --git a/extensions/copilot/src/platform/embeddings/common/remoteEmbeddingsComputer.ts b/extensions/copilot/src/platform/embeddings/common/remoteEmbeddingsComputer.ts index 8b99738731fb..a3a9a51db838 100644 --- a/extensions/copilot/src/platform/embeddings/common/remoteEmbeddingsComputer.ts +++ b/extensions/copilot/src/platform/embeddings/common/remoteEmbeddingsComputer.ts @@ -19,7 +19,7 @@ import { IEmbeddingsEndpoint, postRequest } from '../../networking/common/networ import { GenAiAttr, GenAiOperationName, GenAiProviderName } from '../../otel/common/genAiAttributes'; import { IOTelService, SpanKind, SpanStatusCode } from '../../otel/common/otelService'; import { ITelemetryService } from '../../telemetry/common/telemetry'; -import { ComputeEmbeddingsOptions, Embedding, EmbeddingType, EmbeddingTypeInfo, EmbeddingVector, Embeddings, IEmbeddingsComputer, getWellKnownEmbeddingTypeInfo } from './embeddingsComputer'; +import { ComputeEmbeddingsOptions, Embedding, EmbeddingInputType, EmbeddingType, EmbeddingTypeInfo, EmbeddingVector, Embeddings, IEmbeddingsComputer, LEGACY_EMBEDDING_MODEL_ID, getWellKnownEmbeddingTypeInfo } from './embeddingsComputer'; interface CAPIEmbeddingResults { readonly type: 'success'; @@ -72,7 +72,7 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { // Determine endpoint type: use CAPI for no-auth users, otherwise use GitHub const copilotToken = await this._authService.getCopilotToken(); if (copilotToken.isNoAuthUser) { - const embeddings = await this.computeCAPIEmbeddings(inputs, options, cancellationToken); + const embeddings = await this.computeCAPIEmbeddings(embeddingType, inputs, options, cancellationToken); return embeddings ?? { type: embeddingType, values: [] }; } @@ -168,19 +168,21 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { } private async computeCAPIEmbeddings( + embeddingType: EmbeddingType, inputs: readonly string[], options?: ComputeEmbeddingsOptions, cancellationToken?: CancellationToken, ) { - const typeInfo = getWellKnownEmbeddingTypeInfo(EmbeddingType.text3small_512); + const typeInfo = getWellKnownEmbeddingTypeInfo(embeddingType); if (!typeInfo) { - throw new Error(`Embeddings type info not found: ${EmbeddingType.text3small_512}`); + throw new Error(`Embeddings type info not found: ${embeddingType}`); } - const endpoint = await this._endpointProvider.getEmbeddingsEndpoint('text3small'); + const endpointFamily = typeInfo.model === LEGACY_EMBEDDING_MODEL_ID.Metis_I16_Binary ? 'metis' : 'text3small'; + const endpoint = await this._endpointProvider.getEmbeddingsEndpoint(endpointFamily); const batchSize = endpoint.maxBatchSize; // Open AI seems to allow 1 less than max tokens for the model requests. So if the max tokens is 8192, we can only send 8191 tokens. const maxTokens = endpoint.modelMaxPromptTokens - 1; - return this.fetchResponseWithBatches(typeInfo, endpoint, inputs, cancellationToken, maxTokens, batchSize); + return this.fetchResponseWithBatches(embeddingType, typeInfo, endpoint, inputs, options?.inputType ?? 'document', cancellationToken, maxTokens, batchSize); } /** @@ -191,9 +193,11 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { * @returns The embeddings */ private async fetchResponseWithBatches( + embeddingType: EmbeddingType, type: EmbeddingTypeInfo, endpoint: IEmbeddingsEndpoint, inputs: readonly string[], + inputType: EmbeddingInputType, cancellationToken: CancellationToken | undefined, maxTokens: number, batchSize: number, @@ -218,7 +222,7 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { return; } - const r = await this.rawEmbeddingsFetchWithTelemetry(type, endpoint, generateUuid(), currentBatch, cancellationToken); + const r = await this.rawEmbeddingsFetchWithTelemetry(type, endpoint, generateUuid(), currentBatch, inputType, cancellationToken); if (r.type === 'failed') { throw new Error('Embeddings request failed ' + r.reason); } @@ -241,7 +245,7 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { if (embeddings.length === 0) { return undefined; } - return { type: EmbeddingType.text3small_512, values: embeddings.map((value): Embedding => ({ type: EmbeddingType.text3small_512, value })) }; + return { type: embeddingType, values: embeddings.map((value): Embedding => ({ type: embeddingType, value })) }; } private async rawEmbeddingsFetchWithTelemetry( @@ -249,10 +253,11 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { endpoint: IEmbeddingsEndpoint, requestId: string, inputs: readonly string[], + inputType: EmbeddingInputType, cancellationToken: CancellationToken | undefined ) { const startTime = Date.now(); - const rawRequest = await this.rawEmbeddingsFetch(type, endpoint, requestId, inputs, cancellationToken); + const rawRequest = await this.rawEmbeddingsFetch(type, endpoint, requestId, inputs, inputType, cancellationToken); if (rawRequest.type === 'failed') { this._telemetryService.sendMSFTTelemetryErrorEvent('embedding.error', { type: rawRequest.type, @@ -281,12 +286,18 @@ export class RemoteEmbeddingsComputer implements IEmbeddingsComputer { endpoint: IEmbeddingsEndpoint, requestId: string, inputs: readonly string[], + inputType: EmbeddingInputType, cancellationToken: CancellationToken | undefined ): Promise { try { const token = await this._authService.getCopilotToken(); - const body = { input: inputs, model: type.model, dimensions: type.dimensions }; + const body = { + input: inputs, + input_type: inputType, + model: type.model, + dimensions: type.dimensions + }; endpoint.interceptBody?.(body); const response = await this._instantiationService.invokeFunction(postRequest, { endpointOrUrl: endpoint, diff --git a/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts b/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts index be2b2ba7b4d1..61897d6a8741 100644 --- a/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts +++ b/extensions/copilot/src/platform/embeddings/common/vscodeIndex.ts @@ -108,7 +108,7 @@ abstract class RelatedInformationProviderEmbeddingsIndex this.relatedInformationConfig.threshold) { results.push(this.toRelatedInformation(item, score)); } @@ -161,13 +161,13 @@ class CommandIdIndex extends RelatedInformationProviderEmbeddingsIndex() { + override readonly hasCopilotTokenSource = true; + + override async getCopilotToken(): Promise { + return new CopilotToken(createTestExtendedTokenInfo({ sku: 'no_auth_limited_copilot' })); + } +} + +class TestTokenizer extends mock() { + override async tokenLength(): Promise { + return 1; + } +} + +class TestEmbeddingsEndpoint extends mock() { + override readonly maxBatchSize = 100; + override readonly modelMaxPromptTokens = 8192; + + override acquireTokenizer(): ITokenizer { + return new TestTokenizer(); + } +} + +class TestEndpointProvider extends mock() { + readonly requestedFamilies: Array = []; + + override async getEmbeddingsEndpoint(family?: 'text3small' | 'metis'): Promise { + this.requestedFamilies.push(family); + return new TestEmbeddingsEndpoint(); + } +} + +describe('RemoteEmbeddingsComputer', () => { + function createComputer(endpointProvider: IEndpointProvider): RemoteEmbeddingsComputer { + return new RemoteEmbeddingsComputer( + new TestAuthenticationService(), + NullEnvService.Instance, + new TestLogService(), + new NullTelemetryService(), + endpointProvider, + new class extends mock() { }(), + new NoopOTelService(resolveOTelConfig({ env: {}, extensionVersion: '1.0.0', sessionId: 'test' })), + ); + } + + test('uses the Metis CAPI endpoint for Metis embeddings', async () => { + const endpointProvider = new TestEndpointProvider(); + const computer = createComputer(endpointProvider); + const fetch = vi.spyOn(computer, 'rawEmbeddingsFetch').mockResolvedValue({ + type: 'success', + embeddings: [[1, 0]], + }); + + const result = await computer.computeEmbeddings(EmbeddingType.metis_1024_I16_Binary, ['input'], { inputType: 'query' }); + + expect({ + requestedFamilies: endpointProvider.requestedFamilies, + requestedModel: fetch.mock.calls[0][0].model, + inputType: fetch.mock.calls[0][4], + resultType: result.type.id, + valueTypes: result.values.map(value => value.type.id), + }).toEqual({ + requestedFamilies: ['metis'], + requestedModel: LEGACY_EMBEDDING_MODEL_ID.Metis_I16_Binary, + inputType: 'query', + resultType: EmbeddingType.metis_1024_I16_Binary.id, + valueTypes: [EmbeddingType.metis_1024_I16_Binary.id], + }); + }); + + test('preserves the text3small endpoint for text3small embeddings', async () => { + const endpointProvider = new TestEndpointProvider(); + const computer = createComputer(endpointProvider); + const fetch = vi.spyOn(computer, 'rawEmbeddingsFetch').mockResolvedValue({ + type: 'success', + embeddings: [[1, 0]], + }); + + const result = await computer.computeEmbeddings(EmbeddingType.text3small_512, ['input']); + + expect({ + requestedFamilies: endpointProvider.requestedFamilies, + requestedModel: fetch.mock.calls[0][0].model, + inputType: fetch.mock.calls[0][4], + resultType: result.type.id, + }).toEqual({ + requestedFamilies: ['text3small'], + requestedModel: LEGACY_EMBEDDING_MODEL_ID.TEXT3SMALL, + inputType: 'document', + resultType: EmbeddingType.text3small_512.id, + }); + }); +}); diff --git a/extensions/copilot/src/platform/endpoint/node/modelMetadataFetcher.ts b/extensions/copilot/src/platform/endpoint/node/modelMetadataFetcher.ts index 601295b10a80..dce052e7f483 100644 --- a/extensions/copilot/src/platform/endpoint/node/modelMetadataFetcher.ts +++ b/extensions/copilot/src/platform/endpoint/node/modelMetadataFetcher.ts @@ -22,6 +22,8 @@ import { IExperimentationService } from '../../telemetry/common/nullExperimentat import { getModelCapabilityOverride } from '../common/chatModelCapabilities'; import { IChatModelInformation, ICompletionModelInformation, IEmbeddingModelInformation, IModelAPIResponse, isChatModelInformation, isCompletionModelInformation, isEmbeddingModelInformation } from '../common/endpointProvider'; +export type EmbeddingsModelFamily = 'text-embedding-3-small' | 'metis'; + export interface IModelMetadataFetcher { /** @@ -67,7 +69,7 @@ export interface IModelMetadataFetcher { * Retrieves an embeddings model by its family name * @param family The family of the model to fetch */ - getEmbeddingsModel(family: 'text-embedding-3-small'): Promise; + getEmbeddingsModel(family: EmbeddingsModelFamily): Promise; } /** @@ -219,7 +221,7 @@ export class ModelMetadataFetcher extends Disposable implements IModelMetadataFe return resolvedModel; } - public async getEmbeddingsModel(family: 'text-embedding-3-small'): Promise { + public async getEmbeddingsModel(family: EmbeddingsModelFamily): Promise { await this._taskSingler.getOrCreate(ModelMetadataFetcher.ALL_MODEL_KEY, this._fetchModels.bind(this)); const resolvedModel = this._familyMap.get(family)?.[0]; if (!resolvedModel || !isEmbeddingModelInformation(resolvedModel)) { diff --git a/extensions/copilot/src/platform/projectTemplatesIndex/common/projectTemplatesIndex.ts b/extensions/copilot/src/platform/projectTemplatesIndex/common/projectTemplatesIndex.ts index a557fa7071ce..54692f616938 100644 --- a/extensions/copilot/src/platform/projectTemplatesIndex/common/projectTemplatesIndex.ts +++ b/extensions/copilot/src/platform/projectTemplatesIndex/common/projectTemplatesIndex.ts @@ -37,8 +37,8 @@ export class ProjectTemplatesIndex implements IProjectTemplatesIndex { ) { const cacheVersion = sanitizeVSCodeVersion(envService.getEditorInfo().version); this.embeddingsCache = useRemoteCache ? - instantiationService.createInstance(RemoteEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'projectTemplateEmbeddings', cacheVersion, EmbeddingType.text3small_512, RemoteCacheType.ProjectTemplates) - : instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'projectTemplateEmbeddings', cacheVersion, EmbeddingType.text3small_512); + instantiationService.createInstance(RemoteEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'projectTemplateEmbeddings', cacheVersion, EmbeddingType.metis_1024_I16_Binary, RemoteCacheType.ProjectTemplates) + : instantiationService.createInstance(LocalEmbeddingsCache, EmbeddingCacheType.GLOBAL, 'projectTemplateEmbeddings', cacheVersion, EmbeddingType.metis_1024_I16_Binary); } async updateIndex(): Promise { diff --git a/extensions/copilot/src/platform/promptFiles/test/node/builtinSkills.spec.ts b/extensions/copilot/src/platform/promptFiles/test/node/builtinSkills.spec.ts index 3cc7a60db09e..6e519420008b 100644 --- a/extensions/copilot/src/platform/promptFiles/test/node/builtinSkills.spec.ts +++ b/extensions/copilot/src/platform/promptFiles/test/node/builtinSkills.spec.ts @@ -60,4 +60,47 @@ suite('built-in skills', () => { expect(skill).not.toContain('extensions_manage({'); expect(skill).not.toContain('run `/extensions`'); }); + + test('contributes the customization migration skill to Agent Host sessions', () => { + const manifest = JSON.parse(fs.readFileSync(path.join(copilotRoot, 'package.json'), 'utf-8')) as ICopilotPackage; + const contributions = manifest.contributes?.chatSkills?.filter(skill => skill.path.endsWith('/migrate-customizations/SKILL.md')); + const skill = fs.readFileSync( + path.join(copilotRoot, 'assets', 'prompts', 'skills', 'migrate-customizations', 'SKILL.md'), + 'utf-8', + ); + expect(skill).not.toContain('report_customization_migration'); + + expect({ + contributions, + requiredContent: [ + 'name: migrate-customizations', + 'user-invocable: true', + 'disable-model-invocation: true', + 'Agent Customizations > Migrations', + 'Do not ask the user to manually reconstruct or paste the missing context', + './references/migration-techniques.md', + 'migration-log.md', + 'restore.md', + 'pull request', + 'migration-results.json', + ].filter(anchor => skill.includes(anchor)), + }).toEqual({ + contributions: [{ + path: './assets/prompts/skills/migrate-customizations/SKILL.md', + sessionTypes: ['copilotcli', 'claude', 'codex'], + }], + requiredContent: [ + 'name: migrate-customizations', + 'user-invocable: true', + 'disable-model-invocation: true', + 'Agent Customizations > Migrations', + 'Do not ask the user to manually reconstruct or paste the missing context', + './references/migration-techniques.md', + 'migration-log.md', + 'restore.md', + 'pull request', + 'migration-results.json', + ], + }); + }); }); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 204f49245096..0ad28a71fa68 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -979,13 +979,18 @@ const coAuthorRegex = /^Co-authored-by:\s*(.+?)\s*<([^>]+)>\s*$/gim; export function parseCoAuthors(message: string): CoAuthor[] { const coAuthors: CoAuthor[] = []; + const emails = new Set(); let match; coAuthorRegex.lastIndex = 0; while ((match = coAuthorRegex.exec(message)) !== null) { const name = match[1].trim(); const email = match[2].trim(); - if (name && email) { + + // Squash merge commit messages repeat the trailers of each squashed commit + const emailKey = email.toLowerCase(); + if (name && email && !emails.has(emailKey)) { + emails.add(emailKey); coAuthors.push({ name, email }); } } diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index b9c08fb907fa..974afb66e7f8 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -642,6 +642,32 @@ suite('git', () => { [{ name: 'Jane Doe', email: 'jane@example.com' }] ); }); + + test('duplicate co-authors (squash merge)', function () { + assert.deepStrictEqual( + parseCoAuthors([ + 'Fix bug (#123)', + '', + '* First change', + '', + 'Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>', + '', + '* Second change', + '', + 'Co-authored-by: Jane Doe ', + 'Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>', + '', + '---------', + '', + 'Co-authored-by: Copilot <223556219+copilot@users.noreply.github.com>', + 'Co-authored-by: Jane ' + ].join('\n')), + [ + { name: 'Copilot', email: '223556219+Copilot@users.noreply.github.com' }, + { name: 'Jane Doe', email: 'jane@example.com' } + ] + ); + }); }); suite('splitInChunks', () => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/browser.tools.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/browser.tools.test.ts index ebf900ce78bb..f311a5f501cb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/browser.tools.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/browser.tools.test.ts @@ -168,10 +168,11 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string { }); const browserConfig = vscode.workspace.getConfiguration('workbench.browser'); const agentConfig = vscode.workspace.getConfiguration('chat.agent'); + const agentNetworkConfig = vscode.workspace.getConfiguration('chat.agent.sandbox.network'); try { - await agentConfig.update('allowedNetworkDomains', ['*'], vscode.ConfigurationTarget.Global); - await agentConfig.update('deniedNetworkDomains', ['localhost'], vscode.ConfigurationTarget.Global); + await agentNetworkConfig.update('allowedDomains', ['*'], vscode.ConfigurationTarget.Global); + await agentNetworkConfig.update('deniedDomains', ['localhost'], vscode.ConfigurationTarget.Global); await agentConfig.update('networkFilter', true, vscode.ConfigurationTarget.Global); await browserConfig.update('dataStorage', 'global', vscode.ConfigurationTarget.Global); @@ -233,8 +234,8 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string { } finally { await browserConfig.update('dataStorage', undefined, vscode.ConfigurationTarget.Global); await agentConfig.update('networkFilter', undefined, vscode.ConfigurationTarget.Global); - await agentConfig.update('allowedNetworkDomains', undefined, vscode.ConfigurationTarget.Global); - await agentConfig.update('deniedNetworkDomains', undefined, vscode.ConfigurationTarget.Global); + await agentNetworkConfig.update('allowedDomains', undefined, vscode.ConfigurationTarget.Global); + await agentNetworkConfig.update('deniedDomains', undefined, vscode.ConfigurationTarget.Global); await Promise.all(vscode.window.browserTabs.map(tab => tab.close())); await new Promise((resolve, reject) => { server.close(error => error ? reject(error) : resolve()); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.runInTerminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.runInTerminal.test.ts index e79388f20d7b..33907ee18821 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.runInTerminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.runInTerminal.test.ts @@ -319,7 +319,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string { this.timeout(60000); const configuration = vscode.workspace.getConfiguration(); - await configuration.update('chat.agent.allowedNetworkDomains', ['example.com'], vscode.ConfigurationTarget.Global); + await configuration.update('chat.agent.sandbox.network.allowedDomains', ['example.com'], vscode.ConfigurationTarget.Global); try { const output = await invokeRunInTerminal('curl -s --max-time 5 https://example.com'); const trimmed = output.trim(); @@ -329,7 +329,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string { ]; assert.ok(acceptable.some(value => trimmed.includes(value) || trimmed === value), `Unexpected output: ${JSON.stringify(trimmed)}`); } finally { - await configuration.update('chat.agent.allowedNetworkDomains', undefined, vscode.ConfigurationTarget.Global); + await configuration.update('chat.agent.sandbox.network.allowedDomains', undefined, vscode.ConfigurationTarget.Global); } }); diff --git a/package-lock.json b/package-lock.json index af47f54f3629..d3044e0bc7d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "code-oss-dev", - "version": "1.141.0", + "version": "1.142.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.141.0", + "version": "1.142.0", "hasInstallScript": true, "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.82.0", "@devcontainers/cli": "0.88.0", - "@github/copilot-sdk": "1.0.17-preview.0", + "@github/copilot-sdk": "1.0.17-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", @@ -1170,9 +1170,9 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.17-preview.0.tgz", - "integrity": "sha512-XYoM7Jv+KOw28i+SbZ8hddpkSNjFOeBGZX7aGl+dRWzRFGOUlS4wpmaH33I3EvywfXRj48CO/6/0FabX98fk7Q==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.17-preview.4.tgz", + "integrity": "sha512-tt5pyDLUjiaxJSMCU2HRDLRSFOA4mIFpI6+360EC0jqNDmhlaeJy88YwO6zDO77D21l8lGt8INBj+IYMbPb30Q==", "license": "MIT", "dependencies": { "koffi": "3.2.1", @@ -1183,20 +1183,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@github/copilot-sdk-darwin-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-darwin-x64": "1.0.17-preview.0", - "@github/copilot-sdk-linux-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-linux-x64": "1.0.17-preview.0", - "@github/copilot-sdk-linuxmusl-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-linuxmusl-x64": "1.0.17-preview.0", - "@github/copilot-sdk-win32-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-win32-x64": "1.0.17-preview.0" + "@github/copilot-sdk-darwin-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-darwin-x64": "1.0.17-preview.4", + "@github/copilot-sdk-linux-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-linux-x64": "1.0.17-preview.4", + "@github/copilot-sdk-linuxmusl-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-linuxmusl-x64": "1.0.17-preview.4", + "@github/copilot-sdk-win32-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-win32-x64": "1.0.17-preview.4" } }, "node_modules/@github/copilot-sdk-darwin-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-p7J5iCeq6ntw4GD42AK7Kmck9jyhVjKZFFH2W8G8B76fopYSBnvMX+LxdfLDkvOHb0OMY91rG0KA9EImZLkiiA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-cufab0CWxFH0wwIw1/dlPNx4gFP/PJoL4+tUQ/d6lZ+SWX4/2VQgRJCQ97qe9Nqql3YWDikiJTTHXQ55xkmmRA==", "cpu": [ "arm64" ], @@ -1207,9 +1207,9 @@ ] }, "node_modules/@github/copilot-sdk-darwin-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-5whrJOGWrfDZz97VH2TavhWPppNbf9E19IcGtF2DhxBCdWklLWhBfSDCMzrWvpiUnukHSJA925sKQCoThJDKZg==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-4kj7nU3oXCFFm24d6FijBkuiLTCBtfiI8qEHsDcSxfgPdPnwV+9osT/EAZRKPTh3DYGwqTO7i+bIHEHgogd+Wg==", "cpu": [ "x64" ], @@ -1220,9 +1220,9 @@ ] }, "node_modules/@github/copilot-sdk-linux-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-Dm1Q+/SIU4V1/+VBI2zUfTHcrKBAhuMY204WjuVxyry9BBdrAe98XuZuaGv8+ztbWOsIQ/Vhdcbj7PkEBHetrQ==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-aOHcV5YiFGhtxh+H3qhOuMzktyhwuepGl/jWkWu2vTLy2oztpMnxmoRgwJaj9NdNQoiM8PO13YjbFhbb3TGaOA==", "cpu": [ "arm64" ], @@ -1236,9 +1236,9 @@ ] }, "node_modules/@github/copilot-sdk-linux-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-UG37BcakUmUh5C7Av1ab2smsFJiitev9mWH3vohaAaCYujj/Zb4CRkTVezVA8KLkkdnq67R7AOQSvTFArb0NoA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-W98/SrLJXVk5xn8GgvUxNs8wHM10nPe5Ff3fU9vKb4Kb0aS2KyxoAhC4eoNn8VRB2XDW2wFTBvUJAutCiWGL+Q==", "cpu": [ "x64" ], @@ -1252,9 +1252,9 @@ ] }, "node_modules/@github/copilot-sdk-linuxmusl-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-DgwS8F4IAra48ML6jWprk1HeJAANf7FdjCRIViVAuhaVLvdjbPi/7pYTQGLtwTcxN63O4cG+/1mPKf2roz83YA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-hym02Fv/JuqjmmMqs4E+XFEgx8nzIpIdMLSr5CFpMLWVVKQYBhY8x6r+03qXhfutUWlcoL5f8gK/pdOR23k9jw==", "cpu": [ "arm64" ], @@ -1268,9 +1268,9 @@ ] }, "node_modules/@github/copilot-sdk-linuxmusl-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-9T2ik5qK5akLqVhIyvGVicjcumKGOivvuLaAPYU+210MV+t70u9BGQ0a3Vy3tlgDPGHos/js72ZL2RUo6hmFZg==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-IhjdLXtUSV8lSZiWsciGHD2iwv3nS2pAwLMywfgB1GozouBDIfWZJlbTUX5Jqv4JeZjE7lpvD5e+Sc11HL6kQQ==", "cpu": [ "x64" ], @@ -1284,9 +1284,9 @@ ] }, "node_modules/@github/copilot-sdk-win32-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-7nSjNxKXQWCEsf+iDQMEW8dr8q4SkQAimemb0U03fqD3jj7JA6Z/XvmAIFb/Cf+RWqTLexHw0O3HjFs9vF5MNQ==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-MxJJiT2DoDXi3a6dPY/DwMbJPweK75ywlIIbK6Agnof055euC4st3bKMUXz8Nuq4pWzXU3HEWFUVWnNr/63TNw==", "cpu": [ "arm64" ], @@ -1297,9 +1297,9 @@ ] }, "node_modules/@github/copilot-sdk-win32-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-4pa51WL3KZgouFrODCngtmGw3yavDIIEwiK+cGAA7jH45uFu5EpPnuVHlpFIrAU/fa80ZSbiX5KkiigP+EpLow==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-NGC8k/WHw6lP6O6h/nJiw3lBaaaMjb1hu+urr+Wpa9EP9a/PFL4rWXpxixgQT4qym7EAVY+EE1vSTA8jCi7cAA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 80d724d28101..cbabf9bd0f87 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.141.0", - "distro": "a620eb1a35f4f1994abbd67502c0b2c3b8e73ee0", + "version": "1.142.0", + "distro": "a0e7521b2a1e3289e4118fad155277b200aebd35", "author": { "name": "Microsoft Corporation" }, @@ -13,7 +13,7 @@ "test": "node -e \"console.error('Run a test script from the scripts folder, for example: ./scripts/test.sh --run .'); process.exit(1)\"", "test-browser": "npx playwright install && node test/unit/browser/index.js", "test-browser-no-install": "node test/unit/browser/index.js", - "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", + "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --exit", "test-extension": "vscode-test", "test-build-scripts": "cd build && npm run test", "test-agent-host-e2e": "node scripts/test-agent-host-e2e.ts", @@ -102,7 +102,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.82.0", "@devcontainers/cli": "0.88.0", - "@github/copilot-sdk": "1.0.17-preview.0", + "@github/copilot-sdk": "1.0.17-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", @@ -325,5 +325,5 @@ "@vscode/windows-ca-certs@0.3.4": true, "@vscode/windows-process-tree@0.8.0": true }, - "copilotRuntimeVersion": "1.0.92-0" + "copilotRuntimeVersion": "1.0.92-4" } diff --git a/remote/package-lock.json b/remote/package-lock.json index f0b8fce08d9d..0300e36651d4 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@devcontainers/cli": "0.88.0", - "@github/copilot-sdk": "1.0.17-preview.0", + "@github/copilot-sdk": "1.0.17-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/mxc-sdk": "0.9.0", @@ -73,9 +73,9 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.17-preview.0.tgz", - "integrity": "sha512-XYoM7Jv+KOw28i+SbZ8hddpkSNjFOeBGZX7aGl+dRWzRFGOUlS4wpmaH33I3EvywfXRj48CO/6/0FabX98fk7Q==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.17-preview.4.tgz", + "integrity": "sha512-tt5pyDLUjiaxJSMCU2HRDLRSFOA4mIFpI6+360EC0jqNDmhlaeJy88YwO6zDO77D21l8lGt8INBj+IYMbPb30Q==", "license": "MIT", "dependencies": { "koffi": "3.2.1", @@ -86,20 +86,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@github/copilot-sdk-darwin-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-darwin-x64": "1.0.17-preview.0", - "@github/copilot-sdk-linux-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-linux-x64": "1.0.17-preview.0", - "@github/copilot-sdk-linuxmusl-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-linuxmusl-x64": "1.0.17-preview.0", - "@github/copilot-sdk-win32-arm64": "1.0.17-preview.0", - "@github/copilot-sdk-win32-x64": "1.0.17-preview.0" + "@github/copilot-sdk-darwin-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-darwin-x64": "1.0.17-preview.4", + "@github/copilot-sdk-linux-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-linux-x64": "1.0.17-preview.4", + "@github/copilot-sdk-linuxmusl-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-linuxmusl-x64": "1.0.17-preview.4", + "@github/copilot-sdk-win32-arm64": "1.0.17-preview.4", + "@github/copilot-sdk-win32-x64": "1.0.17-preview.4" } }, "node_modules/@github/copilot-sdk-darwin-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-p7J5iCeq6ntw4GD42AK7Kmck9jyhVjKZFFH2W8G8B76fopYSBnvMX+LxdfLDkvOHb0OMY91rG0KA9EImZLkiiA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-cufab0CWxFH0wwIw1/dlPNx4gFP/PJoL4+tUQ/d6lZ+SWX4/2VQgRJCQ97qe9Nqql3YWDikiJTTHXQ55xkmmRA==", "cpu": [ "arm64" ], @@ -110,9 +110,9 @@ ] }, "node_modules/@github/copilot-sdk-darwin-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-5whrJOGWrfDZz97VH2TavhWPppNbf9E19IcGtF2DhxBCdWklLWhBfSDCMzrWvpiUnukHSJA925sKQCoThJDKZg==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-4kj7nU3oXCFFm24d6FijBkuiLTCBtfiI8qEHsDcSxfgPdPnwV+9osT/EAZRKPTh3DYGwqTO7i+bIHEHgogd+Wg==", "cpu": [ "x64" ], @@ -123,9 +123,9 @@ ] }, "node_modules/@github/copilot-sdk-linux-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-Dm1Q+/SIU4V1/+VBI2zUfTHcrKBAhuMY204WjuVxyry9BBdrAe98XuZuaGv8+ztbWOsIQ/Vhdcbj7PkEBHetrQ==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-aOHcV5YiFGhtxh+H3qhOuMzktyhwuepGl/jWkWu2vTLy2oztpMnxmoRgwJaj9NdNQoiM8PO13YjbFhbb3TGaOA==", "cpu": [ "arm64" ], @@ -139,9 +139,9 @@ ] }, "node_modules/@github/copilot-sdk-linux-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-UG37BcakUmUh5C7Av1ab2smsFJiitev9mWH3vohaAaCYujj/Zb4CRkTVezVA8KLkkdnq67R7AOQSvTFArb0NoA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-W98/SrLJXVk5xn8GgvUxNs8wHM10nPe5Ff3fU9vKb4Kb0aS2KyxoAhC4eoNn8VRB2XDW2wFTBvUJAutCiWGL+Q==", "cpu": [ "x64" ], @@ -155,9 +155,9 @@ ] }, "node_modules/@github/copilot-sdk-linuxmusl-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-DgwS8F4IAra48ML6jWprk1HeJAANf7FdjCRIViVAuhaVLvdjbPi/7pYTQGLtwTcxN63O4cG+/1mPKf2roz83YA==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-hym02Fv/JuqjmmMqs4E+XFEgx8nzIpIdMLSr5CFpMLWVVKQYBhY8x6r+03qXhfutUWlcoL5f8gK/pdOR23k9jw==", "cpu": [ "arm64" ], @@ -171,9 +171,9 @@ ] }, "node_modules/@github/copilot-sdk-linuxmusl-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-9T2ik5qK5akLqVhIyvGVicjcumKGOivvuLaAPYU+210MV+t70u9BGQ0a3Vy3tlgDPGHos/js72ZL2RUo6hmFZg==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-IhjdLXtUSV8lSZiWsciGHD2iwv3nS2pAwLMywfgB1GozouBDIfWZJlbTUX5Jqv4JeZjE7lpvD5e+Sc11HL6kQQ==", "cpu": [ "x64" ], @@ -187,9 +187,9 @@ ] }, "node_modules/@github/copilot-sdk-win32-arm64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.17-preview.0.tgz", - "integrity": "sha512-7nSjNxKXQWCEsf+iDQMEW8dr8q4SkQAimemb0U03fqD3jj7JA6Z/XvmAIFb/Cf+RWqTLexHw0O3HjFs9vF5MNQ==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.17-preview.4.tgz", + "integrity": "sha512-MxJJiT2DoDXi3a6dPY/DwMbJPweK75ywlIIbK6Agnof055euC4st3bKMUXz8Nuq4pWzXU3HEWFUVWnNr/63TNw==", "cpu": [ "arm64" ], @@ -200,9 +200,9 @@ ] }, "node_modules/@github/copilot-sdk-win32-x64": { - "version": "1.0.17-preview.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.17-preview.0.tgz", - "integrity": "sha512-4pa51WL3KZgouFrODCngtmGw3yavDIIEwiK+cGAA7jH45uFu5EpPnuVHlpFIrAU/fa80ZSbiX5KkiigP+EpLow==", + "version": "1.0.17-preview.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.17-preview.4.tgz", + "integrity": "sha512-NGC8k/WHw6lP6O6h/nJiw3lBaaaMjb1hu+urr+Wpa9EP9a/PFL4rWXpxixgQT4qym7EAVY+EE1vSTA8jCi7cAA==", "cpu": [ "x64" ], diff --git a/remote/package.json b/remote/package.json index 12797db32618..9f718de08d78 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@devcontainers/cli": "0.88.0", - "@github/copilot-sdk": "1.0.17-preview.0", + "@github/copilot-sdk": "1.0.17-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/mxc-sdk": "0.9.0", @@ -78,5 +78,5 @@ "@vscode/windows-ca-certs@0.3.4": true, "@vscode/windows-process-tree@0.8.0": true }, - "copilotRuntimeVersion": "1.0.92-0" + "copilotRuntimeVersion": "1.0.92-4" } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index cf5fd39b7389..d455ebf06940 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -1293,7 +1293,7 @@ export class Emitter { let stack: Stacktrace | undefined; if (this._leakWarningThreshold !== undefined && this._size >= Math.ceil(this._leakWarningThreshold * 0.2)) { const leakageMon = this._getLeakageMonitor(); - if (leakageMon) { + if (leakageMon && this._size + 1 >= leakageMon.threshold) { // check and record this emitter for potential leakage contained.stack = Stacktrace.create(); removeMonitor = leakageMon.check(contained.stack, this._size + 1); diff --git a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts index 7b1971d0aa7e..f5187f617ea1 100644 --- a/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-browser/contextmenu.ts @@ -17,7 +17,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide const itemId = args[0] as number; const context = args[1] as IContextMenuEvent; const item = processedItems[itemId]; - item.click?.(context); + item?.click?.(context); }; ipcRenderer.once(onClickChannel, onClickChannelHandler); @@ -27,8 +27,6 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions, onHide return; } - ipcRenderer.removeListener(onClickChannel, onClickChannelHandler); - onHide?.(); }); diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 988c43285f14..a61213ef7103 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -20,6 +20,8 @@ export function registerContextMenuListener(): void { // It turns out that the menu gets GC'ed if not referenced anymore // As such we drag it into this scope so that it is not being GC'ed if (menu) { + // Complete the one-shot click listener even when no item was selected. + event.sender.send(onClickChannel, -1); event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId); } } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index abe335105607..515445a9c286 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -38,6 +38,7 @@ import { MessageController } from '../../message/browser/messageController.js'; import { PreferredPasteConfiguration } from './copyPasteContribution.js'; import { DefaultTextPasteOrDropEditProvider } from './defaultProviders.js'; import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from './edit.js'; +import { PasteEditSession } from './pasteEditSession.js'; import { PostEditWidgetManager } from './postEditWidget.js'; export const changePasteTypeCommandId = 'editor.changePasteType'; @@ -60,11 +61,6 @@ type PasteEditWithProvider = DocumentPasteEdit & { }; -interface DocumentPasteWithProviderEditsSession { - edits: readonly PasteEditWithProvider[]; - dispose(): void; -} - export type PastePreference = | { readonly only: HierarchicalKind } | { readonly preferences: readonly HierarchicalKind[] } @@ -339,7 +335,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const disposables = new DisposableStore(); const cts = disposables.add(new CancellationTokenSource(pToken)); - disposables.add(editorStateCts.token.onCancellationRequested(() => cts.cancel())); + const editorStateListener = disposables.add(editorStateCts.token.onCancellationRequested(() => cts.cancel())); const token = cts.token; try { @@ -359,8 +355,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi triggerKind: DocumentPasteTriggerKind.Automatic, }; - const editSession = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, token); - disposables.add(editSession); + const editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, token)); if (token.isCancellationRequested) { return; } @@ -372,7 +367,9 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (editSession.edits.length) { const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste'; - return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, async (edit, resolveToken) => { + // Applying the paste changes editor state itself. The widget manager handles cancellation during resolve. + disposables.delete(editorStateListener); + return await this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, async (edit, resolveToken) => { if (!edit.provider.resolveDocumentPasteEdit) { return edit; } @@ -388,7 +385,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi edit.additionalEdit = resolved.additionalEdit; } return edit; - }, token); + }, token, editSession); } await this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent); @@ -443,28 +440,26 @@ export class CopyPasteController extends Disposable implements IEditorContributi triggerKind: DocumentPasteTriggerKind.PasteAs, only: preference && 'only' in preference ? preference.only : undefined, }; - let editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token)); + const editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token)); if (tokenSource.token.isCancellationRequested) { return; } // Filter out any edits that don't match the requested kind + let edits = editSession.edits; if (preference) { - editSession = { - edits: editSession.edits.filter(edit => { - if ('only' in preference) { - return preference.only.contains(edit.kind); - } else if ('preferences' in preference) { - return preference.preferences.some(preference => preference.contains(edit.kind)); - } else { - return preference.providerId === edit.provider.id; - } - }), - dispose: editSession.dispose - }; + edits = edits.filter(edit => { + if ('only' in preference) { + return preference.only.contains(edit.kind); + } else if ('preferences' in preference) { + return preference.preferences.some(preference => preference.contains(edit.kind)); + } else { + return preference.providerId === edit.provider.id; + } + }); } - if (!editSession.edits.length) { + if (!edits.length) { if (preference) { this.showPasteAsNoEditMessage(selections, preference); } @@ -473,7 +468,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi let pickedEdit: DocumentPasteEdit | undefined; if (preference) { - pickedEdit = editSession.edits.at(0); + pickedEdit = edits.at(0); } else { type ItemWithEdit = IQuickPickItem & { edit?: DocumentPasteEdit }; const configureDefaultItem: ItemWithEdit = { @@ -484,7 +479,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const selected = await this._quickInputService.pick( [ - ...editSession.edits.map((edit): ItemWithEdit => ({ + ...edits.map((edit): ItemWithEdit => ({ label: edit.title, description: edit.kind?.value, edit, @@ -596,7 +591,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi } } - private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise { + private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise> { const disposables = new DisposableStore(); const results = await raceCancellation( @@ -618,10 +613,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const edits = coalesce(results ?? []).flat().filter(edit => { return !context.only || context.only.contains(edit.kind); }); - return { - edits: sortEditsByYieldTo(edits), - dispose: () => disposables.dispose() - }; + return new PasteEditSession(sortEditsByYieldTo(edits), disposables); } private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken, clipboardEvent: ClipboardEvent | undefined) { diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/pasteEditSession.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/pasteEditSession.ts new file mode 100644 index 000000000000..895841356d63 --- /dev/null +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/pasteEditSession.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { DocumentPasteEdit } from '../../../common/languages.js'; + +/** Owns cached paste edits until they are disposed or transferred to another session. */ +export class PasteEditSession extends Disposable { + private readonly _disposables = this._register(new MutableDisposable()); + + constructor(readonly edits: readonly T[], disposables: IDisposable | undefined) { + super(); + this._disposables.value = disposables; + } + + /** Transfers the edits to a new owner. Disposing this session will no longer release them. */ + take(): PasteEditSession { + return new PasteEditSession(this.edits, this._disposables.clearAndLeak()); + } +} diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts index 735768eb1758..0e09fd57b8f4 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts @@ -28,6 +28,7 @@ import { DocumentDropEdit, DocumentPasteEdit } from '../../../common/languages.j import { TrackedRangeStickiness } from '../../../common/model.js'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js'; import { createCombinedWorkspaceEdit } from './edit.js'; +import { PasteEditSession } from './pasteEditSession.js'; import './postEditWidget.css'; @@ -47,6 +48,8 @@ class PostEditWidget extends Dis readonly allowEditorOverflow = true; readonly suppressMouseDown = true; + private readonly _editSession: PasteEditSession | undefined; + private domNode!: HTMLElement; private button!: Button; @@ -59,14 +62,16 @@ class PostEditWidget extends Dis private readonly showCommand: ShowCommand, private readonly range: Range, private readonly edits: EditSet, - private readonly onSelectNewEdit: (editIndex: number) => void, + private readonly onSelectNewEdit: (editIndex: number, editSession: PasteEditSession | undefined) => void, private readonly additionalActions: readonly IAction[], + editSessionOwner: PasteEditSession | undefined, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IActionWidgetService private readonly _actionWidgetService: IActionWidgetService, ) { super(); + this._editSession = editSessionOwner ? this._register(editSessionOwner.take()) : undefined; this.create(); this.visibleContext = visibleContext.bindTo(contextKeyService); @@ -140,7 +145,8 @@ class PostEditWidget extends Dis const i = this.edits.allEdits.findIndex(edit => edit === item); if (i !== this.edits.activeEditIndex) { - return this.onSelectNewEdit(i); + // Transfer the edits before undo disposes this widget. + return this.onSelectNewEdit(i, this._editSession?.take()); } }, }, anchor, this.editor.getDomNode() ?? undefined, this.additionalActions); @@ -169,7 +175,7 @@ export class PostEditWidgetManager this.clear())); } - public async applyEditAndShowIfNeeded(ranges: readonly Range[], edits: EditSet, canShowWidget: boolean, resolve: (edit: T, token: CancellationToken) => Promise, token: CancellationToken) { + public async applyEditAndShowIfNeeded(ranges: readonly Range[], edits: EditSet, canShowWidget: boolean, resolve: (edit: T, token: CancellationToken) => Promise, token: CancellationToken, editSessionOwner?: PasteEditSession) { if (!ranges.length || !this._editor.hasModel()) { return; } @@ -180,14 +186,19 @@ export class PostEditWidgetManager { - const model = this._editor.getModel(); - if (!model) { - return; - } + const onDidSelectEdit = async (newEditIndex: number, editSession: PasteEditSession | undefined) => { + try { + this.clear(); + const model = this._editor.getModel(); + if (!model) { + return; + } - await model.undo(); - this.applyEditAndShowIfNeeded(ranges, { activeEditIndex: newEditIndex, allEdits: edits.allEdits }, canShowWidget, resolve, token); + await model.undo(); + await this.applyEditAndShowIfNeeded(ranges, { activeEditIndex: newEditIndex, allEdits: edits.allEdits }, canShowWidget, resolve, token, editSession); + } finally { + editSession?.dispose(); + } }; const handleError = (e: Error, message: string) => { @@ -197,7 +208,7 @@ export class PostEditWidgetManager 1) { - this.show(editRange ?? primaryRange, edits, onDidSelectEdit); + this.show(editRange ?? primaryRange, edits, onDidSelectEdit, editSessionOwner); } } - public show(range: Range, edits: EditSet, onDidSelectEdit: (newIndex: number) => void) { + public show(range: Range, edits: EditSet, onDidSelectEdit: (newIndex: number, editSession: PasteEditSession | undefined) => void, editSessionOwner?: PasteEditSession) { this.clear(); if (this._editor.hasModel()) { - this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit, this._getAdditionalActions()); + this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit, this._getAdditionalActions(), editSessionOwner); } } diff --git a/src/vs/editor/contrib/dropOrPasteInto/test/browser/copyPasteController.test.ts b/src/vs/editor/contrib/dropOrPasteInto/test/browser/copyPasteController.test.ts new file mode 100644 index 000000000000..7267431088b8 --- /dev/null +++ b/src/vs/editor/contrib/dropOrPasteInto/test/browser/copyPasteController.test.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import sinon from 'sinon'; +import { DeferredPromise } from '../../../../../base/common/async.js'; +import { createStringDataTransferItem, VSDataTransfer } from '../../../../../base/common/dataTransfer.js'; +import { HierarchicalKind } from '../../../../../base/common/hierarchicalKind.js'; +import { mock } from '../../../../../base/test/common/mock.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js'; +import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IProgressService } from '../../../../../platform/progress/common/progress.js'; +import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; +import { IBulkEditService, ResourceEdit, ResourceTextEdit } from '../../../../browser/services/bulkEditService.js'; +import { DocumentPasteEditProvider, WorkspaceEdit } from '../../../../common/languages.js'; +import { withAsyncTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; +import { SnippetParser } from '../../../snippet/browser/snippetParser.js'; +import { CopyPasteController } from '../../browser/copyPasteController.js'; + +suite('CopyPasteController - paste edit session', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + for (const endInteraction of ['dismiss', 'cursor', 'content', 'model', 'dispose', 'noSelector', 'singleEdit', 'cancelReplacement', 'failedUndo'] as const) { + let name = `resolves alternatives after the initial paste and releases edits on ${endInteraction}`; + switch (endInteraction) { + case 'noSelector': name = 'releases edits after paste when the selector is disabled'; break; + case 'singleEdit': name = 'releases edits after paste when there are no alternatives'; break; + case 'cancelReplacement': name = 'releases transferred edits when alternative resolution is cancelled'; break; + case 'failedUndo': name = 'releases transferred edits when undo fails'; break; + } + test(name, async () => { + await withAsyncTestCodeEditor('', { pasteAs: { enabled: true, showPasteSelector: endInteraction === 'noSelector' ? 'never' : 'afterPaste' } }, async (editor, _viewModel, instantiationService) => { + instantiationService.stub(createDecorator<{ add(): () => void }>('IEditorCancelService'), { add: () => () => { } }); + let selectEdit: ((index: number) => Promise) | undefined; + instantiationService.stub(IActionWidgetService, new class extends mock() { + override show: IActionWidgetService['show'] = (_user, _preview, items, delegate) => { + selectEdit = async index => { await delegate.onSelect(items[index].item!); }; + }; + override hide() { } + }); + instantiationService.stub(IBulkEditService, new class extends mock() { + override async apply(edit: ResourceEdit[] | WorkspaceEdit) { + const textEdits = (Array.isArray(edit) ? edit : edit.edits).map(edit => { + assert.ok(ResourceTextEdit.is(edit)); + return { range: edit.textEdit.range, text: new SnippetParser().parse(edit.textEdit.text).toString() }; + }); + editor.getModel().pushStackElement(); + editor.getModel().pushEditOperations(editor.getSelections(), textEdits, () => null); + editor.getModel().pushStackElement(); + return { isApplied: true, ariaSummary: '' }; + } + }); + instantiationService.stub(IQuickInputService, new class extends mock() { }); + instantiationService.stub(IProgressService, new class extends mock() { }); + + let released = 0; + let resolves = 0; + const resolveStarted = new DeferredPromise(); + const finishResolve = new DeferredPromise(); + const plainKind = new HierarchicalKind('text.plain'); + const provider: DocumentPasteEditProvider = { + copyMimeTypes: [], + pasteMimeTypes: ['text/plain'], + providedPasteEditKinds: [plainKind], + async provideDocumentPasteEdits() { + return { + edits: [ + { title: 'Resolve test', insertText: 'UNRESOLVED', kind: new HierarchicalKind('test'), yieldTo: [{ kind: plainKind }] }, + { title: 'Plain text', insertText: 'PLAIN', kind: plainKind } + ].slice(endInteraction === 'singleEdit' ? 1 : 0), + dispose: () => { ++released; } + }; + }, + async resolveDocumentPasteEdit(edit) { + assert.strictEqual(released, 0, 'Provider edits must still be cached during resolve'); + ++resolves; + if (endInteraction === 'cancelReplacement' && edit.title === 'Resolve test') { + resolveStarted.complete(); + await finishResolve.p; + } + return { ...edit, insertText: edit.title === 'Resolve test' ? 'RESOLVED' : edit.insertText }; + } + }; + const controller = disposables.add(instantiationService.createInstance(CopyPasteController, editor)); + const dataTransfer = new VSDataTransfer(); + dataTransfer.append('text/plain', createStringDataTransferItem('PLAIN')); + // The test editor has no clipboard view, so start the inline paste directly. + // eslint-disable-next-line local/code-no-bracket-notation-for-identifiers + controller['doPasteInline']([provider], editor.getSelections(), dataTransfer, undefined, undefined); + await controller.finishedPaste(); + if (endInteraction === 'noSelector' || endInteraction === 'singleEdit') { + assert.deepStrictEqual({ text: editor.getValue(), released, resolves }, { text: 'PLAIN', released: 1, resolves: 1 }); + return; + } + assert.deepStrictEqual({ text: editor.getValue(), released, resolves }, { text: 'PLAIN', released: 0, resolves: 1 }); + + controller.changePasteType(); + assert.ok(selectEdit); + if (endInteraction === 'failedUndo') { + const undo = sinon.stub(editor.getModel(), 'undo').rejects(new Error('Undo failed')); + try { + await assert.rejects(selectEdit(1), /Undo failed/); + } finally { + undo.restore(); + } + controller.clearWidgets(); + assert.strictEqual(released, 1); + return; + } + if (endInteraction === 'cancelReplacement') { + const selection = selectEdit(1); + await resolveStarted.p; + editor.getModel().setValue('changed'); + finishResolve.complete(); + await selection; + controller.clearWidgets(); + assert.deepStrictEqual({ text: editor.getValue(), released }, { text: 'changed', released: 1 }); + return; + } + await selectEdit(1); + assert.deepStrictEqual({ text: editor.getValue(), released, resolves }, { text: 'RESOLVED', released: 0, resolves: 2 }); + + controller.changePasteType(); + await selectEdit(0); + assert.deepStrictEqual({ text: editor.getValue(), released, resolves }, { text: 'PLAIN', released: 0, resolves: 3 }); + + switch (endInteraction) { + case 'dismiss': controller.clearWidgets(); break; + case 'cursor': editor.setPosition({ lineNumber: 1, column: 2 }); break; + case 'content': editor.getModel().setValue('changed'); break; + case 'model': editor.setModel(null); break; + case 'dispose': controller.dispose(); break; + } + controller.clearWidgets(); + assert.strictEqual(released, 1); + }); + }); + } +}); diff --git a/src/vs/platform/agentHost/browser/agentHostProtocolClient.ts b/src/vs/platform/agentHost/browser/agentHostProtocolClient.ts index 52fb661bb5c7..508e63c06e24 100644 --- a/src/vs/platform/agentHost/browser/agentHostProtocolClient.ts +++ b/src/vs/platform/agentHost/browser/agentHostProtocolClient.ts @@ -32,7 +32,7 @@ import { AGENT_HOST_SCHEME, agentHostAuthority, createAgentHostResourceUriMapper import { AgentHostResourceIdentity, AgentHostResourcePermissionError, IAgentHostResourceService, LOCAL_AGENT_HOST_RESOURCE_IDENTITY } from '../common/agentHostResourceService.js'; import type { ClientNotificationMap, CommandMap, JsonRpcErrorResponse, JsonRpcRequest, JsonRpcResponse } from '../common/state/protocol/messages.js'; import { ActionType, type ActionEnvelope, type ChatAction, type ClientAnnotationsAction, type ClientAutomationAction, type ClientAutomationRunAction, type ClientChangesetAction, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js'; -import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, isDefaultChatUri, isSessionStatusArchived, type ClientPluginCustomization, type Message, type RootState } from '../common/state/sessionState.js'; +import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, isDefaultChatUri, isSessionStatusArchived, isSessionStatusRead, type ClientPluginCustomization, type Message, type RootState } from '../common/state/sessionState.js'; import { normalizeLegacyActionEnvelope } from '../common/state/legacyProtocolCompatibility.js'; import { SUPPORTED_PROTOCOL_VERSIONS } from '../common/state/protocol/version/registry.js'; import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, ProtocolError, ReconnectResultType, type ProtocolMessage, type IStateSnapshot } from '../common/state/sessionProtocol.js'; @@ -1852,6 +1852,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect origin: chat.origin, ...(chat.interactivity !== undefined ? { interactivity: chat.interactivity } : {}), ...(isSessionStatusArchived(chat.status) || chat.archived === true ? { archived: true } : {}), + ...(chat.status !== undefined ? { isRead: isSessionStatusRead(chat.status) } : {}), ...(chat.changes !== undefined ? { changes: chat.changes } : {}), })), // Carry durable host provenance for sessions first materialized from a listing. diff --git a/src/vs/platform/agentHost/common/agent.ts b/src/vs/platform/agentHost/common/agent.ts index 0e95c118e6af..8eda7dede66d 100644 --- a/src/vs/platform/agentHost/common/agent.ts +++ b/src/vs/platform/agentHost/common/agent.ts @@ -193,6 +193,8 @@ export interface IAgentSessionChatMetadata { readonly origin?: ChatOrigin; readonly interactivity?: ChatInteractivity; readonly archived?: boolean; + /** Exact chat read state when known; absence means the provider did not supply it. */ + readonly isRead?: boolean; readonly changes?: ChangesSummary; } @@ -1044,6 +1046,9 @@ export interface IAgentToolPendingConfirmationSignal { export type AgentSubagentTaskModelSource = 'task_argument' | 'subagent_configuration' | 'custom_agent_definition' | 'unset'; +/** `task` is a delegated subagent; `fusionPhase` is a presentation-only chat for one HydraFusion phase of the parent turn. */ +export type AgentSubagentKind = 'task' | 'fusionPhase'; + /** * A subagent was spawned by a tool call. The host creates a child session * silently and routes subsequent inner-tool events to it. @@ -1059,6 +1064,10 @@ export interface IAgentSubagentStartedSignal { readonly agentDisplayName: string; readonly agentDescription?: string; readonly taskModelSource?: AgentSubagentTaskModelSource; + /** Absent means `task`. */ + readonly subagentKind?: AgentSubagentKind; + /** For telemetry, when the chat reports no usage of its own. */ + readonly model?: string; /** * The spawning Task tool's short (typically 3-5 word) `description` * input, e.g. "Review package.json structure". Distinct from diff --git a/src/vs/platform/agentHost/common/agentHostSessionType.ts b/src/vs/platform/agentHost/common/agentHostSessionType.ts index ff096518865a..2513c175d0a7 100644 --- a/src/vs/platform/agentHost/common/agentHostSessionType.ts +++ b/src/vs/platform/agentHost/common/agentHostSessionType.ts @@ -8,6 +8,18 @@ import { type AgentProvider } from './agent.js'; const LOCAL_AGENT_HOST_SESSION_TYPE_PREFIX = 'agent-host-'; const REMOTE_AGENT_HOST_SESSION_TYPE_PREFIX = 'remote-'; +/** Identifies the Copilot agent family without changing the host's provider ID or implying runtime capabilities. */ +export function isCopilotAgentHostProvider(provider: AgentProvider | undefined): boolean { + return provider === 'copilotcli' || provider === 'copilot'; +} + +/** Identifies local and remote Copilot agent-host session types, preserving their existing URI schemes. */ +export function isCopilotAgentHostSessionType(sessionType: string): boolean { + const provider = parseAgentHostHarness(sessionType); + return provider !== undefined && isCopilotAgentHostProvider(provider) + && (!isRemoteAgentHostSessionType(sessionType) || parseRemoteAgentHostSessionTypeAuthority(sessionType, provider) !== undefined); +} + /** * Builds the unique per-connection identifier for a remote agent host. * diff --git a/src/vs/platform/agentHost/common/agentService.ts b/src/vs/platform/agentHost/common/agentService.ts index a11b4896998e..72f57ecb9065 100644 --- a/src/vs/platform/agentHost/common/agentService.ts +++ b/src/vs/platform/agentHost/common/agentService.ts @@ -988,6 +988,7 @@ export interface IAgentService { * with {@link unsubscribe} when the subscription is released. When * provided, `isActive` is checked before registering the subscriber so a * request cancelled during asynchronous resolution cannot pin the resource. + * Callers must distinguish lost subscription ownership from a missing resource. */ subscribe(resource: URI, clientId: string, isActive?: () => boolean): Promise; diff --git a/src/vs/platform/agentHost/common/cloudSandboxAgentHost.ts b/src/vs/platform/agentHost/common/cloudSandboxAgentHost.ts index 9d4afd43e1e8..de0677e3b9db 100644 --- a/src/vs/platform/agentHost/common/cloudSandboxAgentHost.ts +++ b/src/vs/platform/agentHost/common/cloudSandboxAgentHost.ts @@ -17,6 +17,7 @@ import { createDecorator } from '../../instantiation/common/instantiation.js'; import { RemoteAgentHostsEnabledSettingId } from './remoteAgentHostService.js'; import { IReplayedTaskHistory } from './taskEventReplay.js'; import { SessionStatus } from './state/sessionState.js'; +import { SessionModelInfo } from './state/protocol/state.js'; /** Configuration key gating the cloud-sandbox connection path. Disabled by default. */ export const CloudSandboxEnabledSettingId = 'chat.agentHost.cloudSandbox.enabled'; @@ -122,6 +123,8 @@ export interface ICloudSandboxDiscoveredSession { readonly updatedAt?: string; /** Last reported activity; this does not establish environment availability or session flags. */ readonly status?: SessionStatus; + /** Mission Control's archive state, independent of the sandbox's AHP session flags. */ + readonly isArchived?: boolean; } /** Build the synthesized remote-agent-host address for a sandbox environment. */ @@ -233,6 +236,11 @@ export interface ICloudSandboxConnectionRequest { export const ICloudSandboxApiService = createDecorator('cloudSandboxApiService'); +export interface ICloudSandboxModelCatalog { + readonly models: readonly SessionModelInfo[]; + readonly defaultModel?: string; +} + /** Account identity and control-plane APIs for sandbox credentials, discovery, and persisted history. */ export interface ICloudSandboxApiService { readonly _serviceBrand: undefined; @@ -243,6 +251,9 @@ export interface ICloudSandboxApiService { /** Resolves an opaque, credential-free account key using the same identity as task requests. */ getAccountKey(): Promise; + /** Account-authorized cloud model catalog, available before a sandbox is provisioned. */ + listModels(token: CancellationToken): Promise; + /** * Mint a fresh client Web PubSub connection token for a new logical connection. May resolve to a * "waking" result the caller should retry. @@ -277,6 +288,9 @@ export interface ICloudSandboxApiService { /** Rename the Mission Control task without waking the sandbox. */ renameTask(taskId: string, title: string, token: CancellationToken): Promise; + /** Archive or unarchive the Mission Control task without waking the sandbox. */ + setTaskArchived(taskId: string, archived: boolean, token: CancellationToken): Promise; + /** * Read a task's persisted AHP history and fold it back into session and chat state. Served by * Mission Control's mirror, so it works without the sandbox. `undefined` when there is none. diff --git a/src/vs/platform/agentHost/common/meta/agentSlashCommandOutputMeta.ts b/src/vs/platform/agentHost/common/meta/agentSlashCommandOutputMeta.ts new file mode 100644 index 000000000000..97b72eef836d --- /dev/null +++ b/src/vs/platform/agentHost/common/meta/agentSlashCommandOutputMeta.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type { ISlashCommandResource } from './vscode/agentSlashCommandOutputMeta.js'; +export { readSlashCommandResource, toSlashCommandResourceMeta } from './vscode/agentSlashCommandOutputMeta.js'; diff --git a/src/vs/platform/agentHost/common/meta/copilotd/cloudSandboxProjectMeta.ts b/src/vs/platform/agentHost/common/meta/copilotd/cloudSandboxProjectMeta.ts index 9231c100531a..6fdf65b818c9 100644 --- a/src/vs/platform/agentHost/common/meta/copilotd/cloudSandboxProjectMeta.ts +++ b/src/vs/platform/agentHost/common/meta/copilotd/cloudSandboxProjectMeta.ts @@ -13,6 +13,7 @@ export interface ICloudSandboxProject { readonly path: string; readonly git: boolean; readonly status: 'ready' | 'cloning' | 'failed'; + readonly progress?: number; readonly remoteUrl?: string; readonly error?: string; } @@ -37,6 +38,9 @@ function readProject(value: unknown): ICloudSandboxProject | undefined { path: value.path, git: value.git, status, + progress: status === 'cloning' && typeof value.progress === 'number' && Number.isFinite(value.progress) + ? Math.min(100, Math.max(0, value.progress)) + : undefined, remoteUrl: typeof value.remoteUrl === 'string' ? value.remoteUrl : undefined, error: typeof value.error === 'string' ? value.error : undefined, }; diff --git a/src/vs/platform/agentHost/common/meta/mcpCustomizationMeta.ts b/src/vs/platform/agentHost/common/meta/mcpCustomizationMeta.ts index 682826484180..3d849ec889c8 100644 --- a/src/vs/platform/agentHost/common/meta/mcpCustomizationMeta.ts +++ b/src/vs/platform/agentHost/common/meta/mcpCustomizationMeta.ts @@ -4,4 +4,4 @@ *--------------------------------------------------------------------------------------------*/ export type { McpServerSource } from './vscode/mcpCustomizationMeta.js'; -export { readMcpServerSource, withMcpServerSourceMeta } from './vscode/mcpCustomizationMeta.js'; +export { readMcpServerDisplayName, readMcpServerSource, withMcpServerDisplayNameMeta, withMcpServerSourceMeta } from './vscode/mcpCustomizationMeta.js'; diff --git a/src/vs/platform/agentHost/common/meta/vscode/agentSandboxPolicyMeta.ts b/src/vs/platform/agentHost/common/meta/vscode/agentSandboxPolicyMeta.ts index 97542935ee5f..3a5584e76b11 100644 --- a/src/vs/platform/agentHost/common/meta/vscode/agentSandboxPolicyMeta.ts +++ b/src/vs/platform/agentHost/common/meta/vscode/agentSandboxPolicyMeta.ts @@ -16,20 +16,30 @@ export interface ISessionSandboxPolicy { readonly allowDevToolAccess?: boolean; readonly sandboxMcpServers?: boolean; readonly sandboxLspServers?: boolean; + readonly authenticateGit?: boolean; + readonly authenticateGh?: boolean; readonly failClosed?: boolean; } -/** Missing or unsupported metadata carries no assertion about the host's policy. */ +/** Reads trusted host policy metadata; missing or non-object metadata carries no policy assertion. */ export function readSessionSandboxPolicy(source: { readonly _meta?: Record } | undefined): ISessionSandboxPolicy | undefined { const value = source?._meta?.[sandboxPolicyKey]; if (!isObject(value)) { return undefined; } - const { enabled, allowBypass, allowOutbound, allowLocalNetwork, allowDevToolAccess, sandboxMcpServers, sandboxLspServers, failClosed } = value as Record; - if (typeof enabled !== 'boolean' || (allowBypass !== undefined && typeof allowBypass !== 'boolean') || (allowOutbound !== undefined && typeof allowOutbound !== 'boolean') || (allowLocalNetwork !== undefined && typeof allowLocalNetwork !== 'boolean') || (allowDevToolAccess !== undefined && typeof allowDevToolAccess !== 'boolean') || (sandboxMcpServers !== undefined && typeof sandboxMcpServers !== 'boolean') || (sandboxLspServers !== undefined && typeof sandboxLspServers !== 'boolean') || (failClosed !== undefined && typeof failClosed !== 'boolean')) { - return undefined; - } - return { enabled, ...(allowBypass !== undefined ? { allowBypass } : {}), ...(allowOutbound !== undefined ? { allowOutbound } : {}), ...(allowLocalNetwork !== undefined ? { allowLocalNetwork } : {}), ...(allowDevToolAccess !== undefined ? { allowDevToolAccess } : {}), ...(sandboxMcpServers !== undefined ? { sandboxMcpServers } : {}), ...(sandboxLspServers !== undefined ? { sandboxLspServers } : {}), ...(failClosed !== undefined ? { failClosed } : {}) }; + const { enabled, allowBypass, allowOutbound, allowLocalNetwork, allowDevToolAccess, sandboxMcpServers, sandboxLspServers, authenticateGit, authenticateGh, failClosed } = value as ISessionSandboxPolicy; + return { + enabled, + ...(allowBypass !== undefined ? { allowBypass } : {}), + ...(allowOutbound !== undefined ? { allowOutbound } : {}), + ...(allowLocalNetwork !== undefined ? { allowLocalNetwork } : {}), + ...(allowDevToolAccess !== undefined ? { allowDevToolAccess } : {}), + ...(sandboxMcpServers !== undefined ? { sandboxMcpServers } : {}), + ...(sandboxLspServers !== undefined ? { sandboxLspServers } : {}), + ...(authenticateGit !== undefined ? { authenticateGit } : {}), + ...(authenticateGh !== undefined ? { authenticateGh } : {}), + ...(failClosed !== undefined ? { failClosed } : {}), + }; } export function withSessionSandboxPolicy(meta: Record | undefined, policy: ISessionSandboxPolicy | undefined): Record { @@ -43,6 +53,8 @@ export function withSessionSandboxPolicy(meta: Record | undefin ...(policy.allowDevToolAccess !== undefined ? { allowDevToolAccess: policy.allowDevToolAccess } : {}), ...(policy.sandboxMcpServers !== undefined ? { sandboxMcpServers: policy.sandboxMcpServers } : {}), ...(policy.sandboxLspServers !== undefined ? { sandboxLspServers: policy.sandboxLspServers } : {}), + ...(policy.authenticateGit !== undefined ? { authenticateGit: policy.authenticateGit } : {}), + ...(policy.authenticateGh !== undefined ? { authenticateGh: policy.authenticateGh } : {}), ...(policy.failClosed !== undefined ? { failClosed: policy.failClosed } : {}), } : undefined, }; diff --git a/src/vs/platform/agentHost/common/meta/vscode/agentSlashCommandOutputMeta.ts b/src/vs/platform/agentHost/common/meta/vscode/agentSlashCommandOutputMeta.ts new file mode 100644 index 000000000000..daddfb659488 --- /dev/null +++ b/src/vs/platform/agentHost/common/meta/vscode/agentSlashCommandOutputMeta.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isObject } from '../../../../../base/common/types.js'; +import { URI } from '../../../../../base/common/uri.js'; + +const slashCommandResourceKey = 'vscode.slashCommandResource'; + +export interface ISlashCommandResource { + readonly resource: URI; + readonly preview: boolean; +} + +/** Reads an optional resource to open for a user-invoked slash command. */ +export function readSlashCommandResource(source: { readonly _meta?: Record }): ISlashCommandResource | undefined { + const value = source._meta?.[slashCommandResourceKey]; + if (!isObject(value)) { + return undefined; + } + const { resource, preview } = value as Record; + if (typeof resource !== 'string' || typeof preview !== 'boolean') { + return undefined; + } + try { + return { resource: URI.parse(resource, true), preview }; + } catch { + return undefined; + } +} + +export function toSlashCommandResourceMeta(resource: URI, preview: boolean): Record { + return { [slashCommandResourceKey]: { resource: resource.toString(), preview } }; +} diff --git a/src/vs/platform/agentHost/common/meta/vscode/agentToolCallMeta.ts b/src/vs/platform/agentHost/common/meta/vscode/agentToolCallMeta.ts index d3f8741c28a0..096b44ea3598 100644 --- a/src/vs/platform/agentHost/common/meta/vscode/agentToolCallMeta.ts +++ b/src/vs/platform/agentHost/common/meta/vscode/agentToolCallMeta.ts @@ -5,6 +5,7 @@ import type { Mutable } from '../../../../../base/common/types.js'; import { hasAgentMetadata } from '../metadata.js'; +import { ISandboxNetworkRestrictions, isSandboxNetworkRestrictions } from '../../../../sandbox/common/sandboxSettingsResolutionHelper.js'; /** Anything carrying a tool call's `_meta` bag (persisted state or wire actions). */ interface IHasToolCallMeta { @@ -18,6 +19,8 @@ interface IHasToolCallMeta { * wrong-typed values. */ export interface IToolCallMeta { + /** Trusted Copilot host snapshot for integrated-browser client tools. Absent for other tools and harnesses. */ + readonly 'vscode.copilotSandboxNetworkRestrictions'?: ISandboxNetworkRestrictions; readonly 'agentHost.sandboxBypass'?: boolean; /** * VS Code rendering hint. `terminal` routes the call to the command/output @@ -62,6 +65,7 @@ export type AgentFusionPhaseStatus = typeof fusionPhaseStatuses[number]; const knownFusionPhaseStatuses: ReadonlySet = new Set(fusionPhaseStatuses); const toolCallMetaKeys = [ + 'vscode.copilotSandboxNetworkRestrictions', 'agentHost.sandboxBypass', 'toolKind', 'language', 'subagentDescription', 'subagentAgentName', 'subagentChatUri', 'mcpServerName', 'mcpToolName', 'autoApproveBySetting', 'autoApproveRuleResolvable', 'toolSearchCandidates', 'progressMessage', 'fusionPhase', ] as const; @@ -167,6 +171,10 @@ export function readToolCallMeta(source: IHasToolCallMeta): IToolCallMeta { return {}; } const result: Mutable = {}; + const networkRestrictions = meta['vscode.copilotSandboxNetworkRestrictions']; + if (isSandboxNetworkRestrictions(networkRestrictions)) { + result['vscode.copilotSandboxNetworkRestrictions'] = networkRestrictions; + } if (typeof meta['agentHost.sandboxBypass'] === 'boolean') { result['agentHost.sandboxBypass'] = meta['agentHost.sandboxBypass']; } if (isToolKind(meta.toolKind)) { result.toolKind = meta.toolKind; } if (typeof meta.language === 'string') { result.language = meta.language; } diff --git a/src/vs/platform/agentHost/common/meta/vscode/mcpCustomizationMeta.ts b/src/vs/platform/agentHost/common/meta/vscode/mcpCustomizationMeta.ts index 257cf48a442c..5b854324187e 100644 --- a/src/vs/platform/agentHost/common/meta/vscode/mcpCustomizationMeta.ts +++ b/src/vs/platform/agentHost/common/meta/vscode/mcpCustomizationMeta.ts @@ -6,6 +6,8 @@ import type { McpServerCustomization } from '../../state/protocol/state.js'; const sourceKey = 'agentHost.mcpServerSource'; +const displayNameKey = 'vscode.mcpServerDisplayName'; +const maxDisplayNameLength = 512; export type McpServerSource = | 'user' // Defined in user-level configuration. @@ -36,3 +38,22 @@ export function withMcpServerSourceMeta(meta: Record | undefine } return { ...(meta ?? {}), [sourceKey]: source }; } + +/** Reads an optional Connector catalog name without changing the MCP server's runtime identity. */ +export function readMcpServerDisplayName(customization: McpServerCustomization | undefined): string | undefined { + return normalizeMcpServerDisplayName(customization?._meta?.[displayNameKey]); +} + +/** Records an optional Connector catalog name while preserving every other metadata entry. */ +export function withMcpServerDisplayNameMeta(meta: Record | undefined, displayName: string | undefined): Record | undefined { + const normalized = normalizeMcpServerDisplayName(displayName); + return normalized === undefined ? meta : { ...(meta ?? {}), [displayNameKey]: normalized }; +} + +function normalizeMcpServerDisplayName(value: unknown): string | undefined { + if (typeof value !== 'string') { + return undefined; + } + const trimmed = value.trim(); + return trimmed.length > 0 && trimmed.length <= maxDisplayNameLength ? trimmed : undefined; +} diff --git a/src/vs/platform/agentHost/common/sandboxConfigSchema.ts b/src/vs/platform/agentHost/common/sandboxConfigSchema.ts index 6dd55b2bd595..ae7bff150697 100644 --- a/src/vs/platform/agentHost/common/sandboxConfigSchema.ts +++ b/src/vs/platform/agentHost/common/sandboxConfigSchema.ts @@ -32,6 +32,8 @@ export const enum AgentHostSandboxKey { AllowUnsandboxedCommands = 'allowUnsandboxedCommands', SandboxMcpServers = 'sandboxMcpServers', SandboxLspServers = 'sandboxLspServers', + AuthenticateGit = 'authenticateGit', + AuthenticateGh = 'authenticateGh', AllowDevToolAccess = 'allowDevToolAccess', UserConfiguredPaths = 'fileSystem.userConfiguredPaths', LinuxFileSystem = 'fileSystem.linux', @@ -50,6 +52,8 @@ export type ISandboxConfigValue = Partial<{ [AgentHostSandboxKey.AllowUnsandboxedCommands]: boolean; [AgentHostSandboxKey.SandboxMcpServers]: boolean; [AgentHostSandboxKey.SandboxLspServers]: boolean; + [AgentHostSandboxKey.AuthenticateGit]: boolean; + [AgentHostSandboxKey.AuthenticateGh]: boolean; [AgentHostSandboxKey.AllowDevToolAccess]: boolean; [AgentHostSandboxKey.UserConfiguredPaths]: IAgentSandboxUserConfiguredPaths; [AgentHostSandboxKey.LinuxFileSystem]: IAgentSandboxFileSystemSetting; @@ -97,6 +101,14 @@ export const sandboxConfigSchema = createSchema({ type: 'boolean', title: localize('agentHost.config.sandbox.sandboxLspServers.title', "Sandbox LSP Servers"), }, + [AgentHostSandboxKey.AuthenticateGit]: { + type: 'boolean', + title: localize('agentHost.config.sandbox.authenticateGit.title', "Authenticate git"), + }, + [AgentHostSandboxKey.AuthenticateGh]: { + type: 'boolean', + title: localize('agentHost.config.sandbox.authenticateGh.title', "Authenticate gh"), + }, [AgentHostSandboxKey.AllowDevToolAccess]: { type: 'boolean', title: localize('agentHost.config.sandbox.allowDevToolAccess.title', "Allow Dev Tool Access"), @@ -166,6 +178,8 @@ export const sandboxSettingIdToAgentHostKey: Readonly