diff --git a/deps/patches/vscode/jetbrains.patch b/deps/patches/vscode/jetbrains.patch new file mode 100644 index 0000000000..f800804a32 --- /dev/null +++ b/deps/patches/vscode/jetbrains.patch @@ -0,0 +1,1832 @@ +From 84ef3dc1d9bf973d8bcaae8b91653bbfe74c2dcf Mon Sep 17 00:00:00 2001 +From: hongyu9 +Date: Tue, 29 Jul 2025 21:50:18 +0800 +Subject: [PATCH] fix: update callback type and add default export for start + +- Change callback in extHostConsoleForwarder.ts to accept (err?: Error | null) => void for better TypeScript compatibility +- Add a default export for the start function in extensionHostProcess.ts to allow easier importing and usage +--- + src/main.ts | 719 ------------------ + src/vs/base/common/uri.ts | 1 + + src/vs/base/parts/ipc/common/ipc.net.ts | 16 +- + .../workbench/api/common/extHost.api.impl.ts | 11 +- + .../api/common/extHostConfiguration.ts | 1 + + .../api/common/extHostExtensionActivator.ts | 4 + + .../api/common/extHostExtensionService.ts | 17 +- + src/vs/workbench/api/common/extHostWebview.ts | 1 + + .../api/common/extHostWebviewView.ts | 5 + + .../workbench/api/common/extHostWorkspace.ts | 1 + + .../workbench/api/common/extensionHostMain.ts | 248 +++--- + .../api/node/extHostConsoleForwarder.ts | 2 +- + .../api/node/extensionHostProcess.ts | 13 +- + .../contrib/webview/common/webview.ts | 11 +- + .../common/abstractExtensionService.ts | 1 + + .../common/fileRPCProtocolLogger.ts | 246 ++++++ + .../services/extensions/common/rpcProtocol.ts | 4 +- + 17 files changed, 453 insertions(+), 848 deletions(-) + delete mode 100644 src/main.ts + create mode 100644 src/vs/workbench/services/extensions/common/fileRPCProtocolLogger.ts + +diff --git a/src/main.ts b/src/main.ts +deleted file mode 100644 +index 1af3c941e00..00000000000 +--- a/src/main.ts ++++ /dev/null +@@ -1,719 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import * as path from 'path'; +-import * as fs from 'original-fs'; +-import * as os from 'os'; +-import { performance } from 'perf_hooks'; +-import { configurePortable } from './bootstrap-node.js'; +-import { bootstrapESM } from './bootstrap-esm.js'; +-import { fileURLToPath } from 'url'; +-import { app, protocol, crashReporter, Menu, contentTracing } from 'electron'; +-import minimist from 'minimist'; +-import { product } from './bootstrap-meta.js'; +-import { parse } from './vs/base/common/jsonc.js'; +-import { getUserDataPath } from './vs/platform/environment/node/userDataPath.js'; +-import * as perf from './vs/base/common/performance.js'; +-import { resolveNLSConfiguration } from './vs/base/node/nls.js'; +-import { getUNCHost, addUNCHostToAllowlist } from './vs/base/node/unc.js'; +-import { INLSConfiguration } from './vs/nls.js'; +-import { NativeParsedArgs } from './vs/platform/environment/common/argv.js'; +- +-const __dirname = path.dirname(fileURLToPath(import.meta.url)); +- +-perf.mark('code/didStartMain'); +- +-perf.mark('code/willLoadMainBundle', { +- // When built, the main bundle is a single JS file with all +- // dependencies inlined. As such, we mark `willLoadMainBundle` +- // as the start of the main bundle loading process. +- startTime: Math.floor(performance.timeOrigin) +-}); +-perf.mark('code/didLoadMainBundle'); +- +-// Enable portable support +-const portable = configurePortable(product); +- +-const args = parseCLIArgs(); +-// Configure static command line arguments +-const argvConfig = configureCommandlineSwitchesSync(args); +-// Enable sandbox globally unless +-// 1) disabled via command line using either +-// `--no-sandbox` or `--disable-chromium-sandbox` argument. +-// 2) argv.json contains `disable-chromium-sandbox: true`. +-if (args['sandbox'] && +- !args['disable-chromium-sandbox'] && +- !argvConfig['disable-chromium-sandbox']) { +- app.enableSandbox(); +-} else if (app.commandLine.hasSwitch('no-sandbox') && +- !app.commandLine.hasSwitch('disable-gpu-sandbox')) { +- // Disable GPU sandbox whenever --no-sandbox is used. +- app.commandLine.appendSwitch('disable-gpu-sandbox'); +-} else { +- app.commandLine.appendSwitch('no-sandbox'); +- app.commandLine.appendSwitch('disable-gpu-sandbox'); +-} +- +-// Set userData path before app 'ready' event +-const userDataPath = getUserDataPath(args, product.nameShort ?? 'code-oss-dev'); +-if (process.platform === 'win32') { +- const userDataUNCHost = getUNCHost(userDataPath); +- if (userDataUNCHost) { +- addUNCHostToAllowlist(userDataUNCHost); // enables to use UNC paths in userDataPath +- } +-} +-app.setPath('userData', userDataPath); +- +-// Resolve code cache path +-const codeCachePath = getCodeCachePath(); +- +-// Disable default menu (https://github.com/electron/electron/issues/35512) +-Menu.setApplicationMenu(null); +- +-// Configure crash reporter +-perf.mark('code/willStartCrashReporter'); +-// If a crash-reporter-directory is specified we store the crash reports +-// in the specified directory and don't upload them to the crash server. +-// +-// Appcenter crash reporting is enabled if +-// * enable-crash-reporter runtime argument is set to 'true' +-// * --disable-crash-reporter command line parameter is not set +-// +-// Disable crash reporting in all other cases. +-if (args['crash-reporter-directory'] || (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) { +- configureCrashReporter(); +-} +-perf.mark('code/didStartCrashReporter'); +- +-// Set logs path before app 'ready' event if running portable +-// to ensure that no 'logs' folder is created on disk at a +-// location outside of the portable directory +-// (https://github.com/microsoft/vscode/issues/56651) +-if (portable && portable.isPortable) { +- app.setAppLogsPath(path.join(userDataPath, 'logs')); +-} +- +-// Register custom schemes with privileges +-protocol.registerSchemesAsPrivileged([ +- { +- scheme: 'vscode-webview', +- privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, allowServiceWorkers: true, codeCache: true } +- }, +- { +- scheme: 'vscode-file', +- privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true, codeCache: true } +- } +-]); +- +-// Global app listeners +-registerListeners(); +- +-/** +- * We can resolve the NLS configuration early if it is defined +- * in argv.json before `app.ready` event. Otherwise we can only +- * resolve NLS after `app.ready` event to resolve the OS locale. +- */ +-let nlsConfigurationPromise: Promise | undefined = undefined; +- +-// Use the most preferred OS language for language recommendation. +-// The API might return an empty array on Linux, such as when +-// the 'C' locale is the user's only configured locale. +-// No matter the OS, if the array is empty, default back to 'en'. +-const osLocale = processZhLocale((app.getPreferredSystemLanguages()?.[0] ?? 'en').toLowerCase()); +-const userLocale = getUserDefinedLocale(argvConfig); +-if (userLocale) { +- nlsConfigurationPromise = resolveNLSConfiguration({ +- userLocale, +- osLocale, +- commit: product.commit, +- userDataPath, +- nlsMetadataPath: __dirname +- }); +-} +- +-// Pass in the locale to Electron so that the +-// Windows Control Overlay is rendered correctly on Windows. +-// For now, don't pass in the locale on macOS due to +-// https://github.com/microsoft/vscode/issues/167543. +-// If the locale is `qps-ploc`, the Microsoft +-// Pseudo Language Language Pack is being used. +-// In that case, use `en` as the Electron locale. +- +-if (process.platform === 'win32' || process.platform === 'linux') { +- const electronLocale = (!userLocale || userLocale === 'qps-ploc') ? 'en' : userLocale; +- app.commandLine.appendSwitch('lang', electronLocale); +-} +- +-// Load our code once ready +-app.once('ready', function () { +- if (args['trace']) { +- let traceOptions: Electron.TraceConfig | Electron.TraceCategoriesAndOptions; +- if (args['trace-memory-infra']) { +- const customCategories = args['trace-category-filter']?.split(',') || []; +- customCategories.push('disabled-by-default-memory-infra', 'disabled-by-default-memory-infra.v8.code_stats'); +- traceOptions = { +- included_categories: customCategories, +- excluded_categories: ['*'], +- memory_dump_config: { +- allowed_dump_modes: ['light', 'detailed'], +- triggers: [ +- { +- type: 'periodic_interval', +- mode: 'detailed', +- min_time_between_dumps_ms: 10000 +- }, +- { +- type: 'periodic_interval', +- mode: 'light', +- min_time_between_dumps_ms: 1000 +- } +- ] +- } +- }; +- } else { +- traceOptions = { +- categoryFilter: args['trace-category-filter'] || '*', +- traceOptions: args['trace-options'] || 'record-until-full,enable-sampling' +- }; +- } +- +- contentTracing.startRecording(traceOptions).finally(() => onReady()); +- } else { +- onReady(); +- } +-}); +- +-async function onReady() { +- perf.mark('code/mainAppReady'); +- +- try { +- const [, nlsConfig] = await Promise.all([ +- mkdirpIgnoreError(codeCachePath), +- resolveNlsConfiguration() +- ]); +- +- await startup(codeCachePath, nlsConfig); +- } catch (error) { +- console.error(error); +- } +-} +- +-/** +- * Main startup routine +- */ +-async function startup(codeCachePath: string | undefined, nlsConfig: INLSConfiguration): Promise { +- process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); +- process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || ''; +- +- // Bootstrap ESM +- await bootstrapESM(); +- +- // Load Main +- await import('./vs/code/electron-main/main.js'); +- perf.mark('code/didRunMainBundle'); +-} +- +-function configureCommandlineSwitchesSync(cliArgs: NativeParsedArgs) { +- const SUPPORTED_ELECTRON_SWITCHES = [ +- +- // alias from us for --disable-gpu +- 'disable-hardware-acceleration', +- +- // override for the color profile to use +- 'force-color-profile', +- +- // disable LCD font rendering, a Chromium flag +- 'disable-lcd-text', +- +- // bypass any specified proxy for the given semi-colon-separated list of hosts +- 'proxy-bypass-list' +- ]; +- +- if (process.platform === 'linux') { +- +- // Force enable screen readers on Linux via this flag +- SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); +- +- // override which password-store is used on Linux +- SUPPORTED_ELECTRON_SWITCHES.push('password-store'); +- } +- +- const SUPPORTED_MAIN_PROCESS_SWITCHES = [ +- +- // Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775 +- 'enable-proposed-api', +- +- // Log level to use. Default is 'info'. Allowed values are 'error', 'warn', 'info', 'debug', 'trace', 'off'. +- 'log-level', +- +- // Use an in-memory storage for secrets +- 'use-inmemory-secretstorage' +- ]; +- +- // Read argv config +- const argvConfig = readArgvConfigSync(); +- +- Object.keys(argvConfig).forEach(argvKey => { +- const argvValue = argvConfig[argvKey]; +- +- // Append Electron flags to Electron +- if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) !== -1) { +- if (argvValue === true || argvValue === 'true') { +- if (argvKey === 'disable-hardware-acceleration') { +- app.disableHardwareAcceleration(); // needs to be called explicitly +- } else { +- app.commandLine.appendSwitch(argvKey); +- } +- } else if (typeof argvValue === 'string' && argvValue) { +- if (argvKey === 'password-store') { +- // Password store +- // TODO@TylerLeonhardt: Remove this migration in 3 months +- let migratedArgvValue = argvValue; +- if (argvValue === 'gnome' || argvValue === 'gnome-keyring') { +- migratedArgvValue = 'gnome-libsecret'; +- } +- app.commandLine.appendSwitch(argvKey, migratedArgvValue); +- } else { +- app.commandLine.appendSwitch(argvKey, argvValue); +- } +- } +- } +- +- // Append main process flags to process.argv +- else if (SUPPORTED_MAIN_PROCESS_SWITCHES.indexOf(argvKey) !== -1) { +- switch (argvKey) { +- case 'enable-proposed-api': +- if (Array.isArray(argvValue)) { +- argvValue.forEach(id => id && typeof id === 'string' && process.argv.push('--enable-proposed-api', id)); +- } else { +- console.error(`Unexpected value for \`enable-proposed-api\` in argv.json. Expected array of extension ids.`); +- } +- break; +- +- case 'log-level': +- if (typeof argvValue === 'string') { +- process.argv.push('--log', argvValue); +- } else if (Array.isArray(argvValue)) { +- for (const value of argvValue) { +- process.argv.push('--log', value); +- } +- } +- break; +- +- case 'use-inmemory-secretstorage': +- if (argvValue) { +- process.argv.push('--use-inmemory-secretstorage'); +- } +- break; +- } +- } +- }); +- +- // Following features are enabled from the runtime: +- // `DocumentPolicyIncludeJSCallStacksInCrashReports` - https://www.electronjs.org/docs/latest/api/web-frame-main#framecollectjavascriptcallstack-experimental +- // `EarlyEstablishGpuChannel` - Refs https://issues.chromium.org/issues/40208065 +- // `EstablishGpuChannelAsync` - Refs https://issues.chromium.org/issues/40208065 +- const featuresToEnable = +- `DocumentPolicyIncludeJSCallStacksInCrashReports,EarlyEstablishGpuChannel,EstablishGpuChannelAsync,${app.commandLine.getSwitchValue('enable-features')}`; +- app.commandLine.appendSwitch('enable-features', featuresToEnable); +- +- // Following features are disabled from the runtime: +- // `CalculateNativeWinOcclusion` - Disable native window occlusion tracker (https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ) +- const featuresToDisable = +- `CalculateNativeWinOcclusion,${app.commandLine.getSwitchValue('disable-features')}`; +- app.commandLine.appendSwitch('disable-features', featuresToDisable); +- +- // Blink features to configure. +- // `FontMatchingCTMigration` - Siwtch font matching on macOS to Appkit (Refs https://github.com/microsoft/vscode/issues/224496#issuecomment-2270418470). +- // `StandardizedBrowserZoom` - Disable zoom adjustment for bounding box (https://github.com/microsoft/vscode/issues/232750#issuecomment-2459495394) +- const blinkFeaturesToDisable = +- `FontMatchingCTMigration,StandardizedBrowserZoom,${app.commandLine.getSwitchValue('disable-blink-features')}`; +- app.commandLine.appendSwitch('disable-blink-features', blinkFeaturesToDisable); +- +- // Support JS Flags +- const jsFlags = getJSFlags(cliArgs); +- if (jsFlags) { +- app.commandLine.appendSwitch('js-flags', jsFlags); +- } +- +- // Use portal version 4 that supports current_folder option +- // to address https://github.com/microsoft/vscode/issues/213780 +- // Runtime sets the default version to 3, refs https://github.com/electron/electron/pull/44426 +- app.commandLine.appendSwitch('xdg-portal-required-version', '4'); +- +- return argvConfig; +-} +- +-interface IArgvConfig { +- [key: string]: string | string[] | boolean | undefined; +- readonly locale?: string; +- readonly 'disable-lcd-text'?: boolean; +- readonly 'proxy-bypass-list'?: string; +- readonly 'disable-hardware-acceleration'?: boolean; +- readonly 'force-color-profile'?: string; +- readonly 'enable-crash-reporter'?: boolean; +- readonly 'crash-reporter-id'?: string; +- readonly 'enable-proposed-api'?: string[]; +- readonly 'log-level'?: string | string[]; +- readonly 'disable-chromium-sandbox'?: boolean; +- readonly 'use-inmemory-secretstorage'?: boolean; +-} +- +-function readArgvConfigSync(): IArgvConfig { +- +- // Read or create the argv.json config file sync before app('ready') +- const argvConfigPath = getArgvConfigPath(); +- let argvConfig: IArgvConfig | undefined = undefined; +- try { +- argvConfig = parse(fs.readFileSync(argvConfigPath).toString()); +- } catch (error) { +- if (error && error.code === 'ENOENT') { +- createDefaultArgvConfigSync(argvConfigPath); +- } else { +- console.warn(`Unable to read argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`); +- } +- } +- +- // Fallback to default +- if (!argvConfig) { +- argvConfig = {}; +- } +- +- return argvConfig; +-} +- +-function createDefaultArgvConfigSync(argvConfigPath: string): void { +- try { +- +- // Ensure argv config parent exists +- const argvConfigPathDirname = path.dirname(argvConfigPath); +- if (!fs.existsSync(argvConfigPathDirname)) { +- fs.mkdirSync(argvConfigPathDirname); +- } +- +- // Default argv content +- const defaultArgvConfigContent = [ +- '// This configuration file allows you to pass permanent command line arguments to VS Code.', +- '// Only a subset of arguments is currently supported to reduce the likelihood of breaking', +- '// the installation.', +- '//', +- '// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT', +- '//', +- '// NOTE: Changing this file requires a restart of VS Code.', +- '{', +- ' // Use software rendering instead of hardware accelerated rendering.', +- ' // This can help in cases where you see rendering issues in VS Code.', +- ' // "disable-hardware-acceleration": true', +- '}' +- ]; +- +- // Create initial argv.json with default content +- fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n')); +- } catch (error) { +- console.error(`Unable to create argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`); +- } +-} +- +-function getArgvConfigPath(): string { +- const vscodePortable = process.env['VSCODE_PORTABLE']; +- if (vscodePortable) { +- return path.join(vscodePortable, 'argv.json'); +- } +- +- let dataFolderName = product.dataFolderName; +- if (process.env['VSCODE_DEV']) { +- dataFolderName = `${dataFolderName}-dev`; +- } +- +- return path.join(os.homedir(), dataFolderName!, 'argv.json'); +-} +- +-function configureCrashReporter(): void { +- let crashReporterDirectory = args['crash-reporter-directory']; +- let submitURL = ''; +- if (crashReporterDirectory) { +- crashReporterDirectory = path.normalize(crashReporterDirectory); +- +- if (!path.isAbsolute(crashReporterDirectory)) { +- console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); +- app.exit(1); +- } +- +- if (!fs.existsSync(crashReporterDirectory)) { +- try { +- fs.mkdirSync(crashReporterDirectory, { recursive: true }); +- } catch (error) { +- console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); +- app.exit(1); +- } +- } +- +- // Crashes are stored in the crashDumps directory by default, so we +- // need to change that directory to the provided one +- console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); +- app.setPath('crashDumps', crashReporterDirectory); +- } +- +- // Otherwise we configure the crash reporter from product.json +- else { +- const appCenter = product.appCenter; +- if (appCenter) { +- const isWindows = (process.platform === 'win32'); +- const isLinux = (process.platform === 'linux'); +- const isDarwin = (process.platform === 'darwin'); +- const crashReporterId = argvConfig['crash-reporter-id']; +- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +- if (crashReporterId && uuidPattern.test(crashReporterId)) { +- if (isWindows) { +- switch (process.arch) { +- case 'x64': +- submitURL = appCenter['win32-x64']; +- break; +- case 'arm64': +- submitURL = appCenter['win32-arm64']; +- break; +- } +- } else if (isDarwin) { +- if (product.darwinUniversalAssetId) { +- submitURL = appCenter['darwin-universal']; +- } else { +- switch (process.arch) { +- case 'x64': +- submitURL = appCenter['darwin']; +- break; +- case 'arm64': +- submitURL = appCenter['darwin-arm64']; +- break; +- } +- } +- } else if (isLinux) { +- submitURL = appCenter['linux-x64']; +- } +- submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); +- // Send the id for child node process that are explicitly starting crash reporter. +- // For vscode this is ExtensionHost process currently. +- const argv = process.argv; +- const endOfArgsMarkerIndex = argv.indexOf('--'); +- if (endOfArgsMarkerIndex === -1) { +- argv.push('--crash-reporter-id', crashReporterId); +- } else { +- // if the we have an argument "--" (end of argument marker) +- // we cannot add arguments at the end. rather, we add +- // arguments before the "--" marker. +- argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId); +- } +- } +- } +- } +- +- // Start crash reporter for all processes +- const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; +- const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; +- const uploadToServer = Boolean(!process.env['VSCODE_DEV'] && submitURL && !crashReporterDirectory); +- crashReporter.start({ +- companyName, +- productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, +- submitURL, +- uploadToServer, +- compress: true +- }); +-} +- +-function getJSFlags(cliArgs: NativeParsedArgs): string | null { +- const jsFlags: string[] = []; +- +- // Add any existing JS flags we already got from the command line +- if (cliArgs['js-flags']) { +- jsFlags.push(cliArgs['js-flags']); +- } +- +- if (process.platform === 'linux') { +- // Fix cppgc crash on Linux with 16KB page size. +- // Refs https://issues.chromium.org/issues/378017037 +- // The fix from https://github.com/electron/electron/commit/6c5b2ef55e08dc0bede02384747549c1eadac0eb +- // only affects non-renderer process. +- // The following will ensure that the flag will be +- // applied to the renderer process as well. +- // TODO(deepak1556): Remove this once we update to +- // Chromium >= 134. +- jsFlags.push('--nodecommit_pooled_pages'); +- } +- +- return jsFlags.length > 0 ? jsFlags.join(' ') : null; +-} +- +-function parseCLIArgs(): NativeParsedArgs { +- return minimist(process.argv, { +- string: [ +- 'user-data-dir', +- 'locale', +- 'js-flags', +- 'crash-reporter-directory' +- ], +- boolean: [ +- 'disable-chromium-sandbox', +- ], +- default: { +- 'sandbox': true +- }, +- alias: { +- 'no-sandbox': 'sandbox' +- } +- }); +-} +- +-function registerListeners(): void { +- +- /** +- * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before +- * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. +- */ +- const macOpenFiles: string[] = []; +- (globalThis as any)['macOpenFiles'] = macOpenFiles; +- app.on('open-file', function (event, path) { +- macOpenFiles.push(path); +- }); +- +- /** +- * macOS: react to open-url requests. +- */ +- const openUrls: string[] = []; +- const onOpenUrl = +- function (event: { preventDefault: () => void }, url: string) { +- event.preventDefault(); +- +- openUrls.push(url); +- }; +- +- app.on('will-finish-launching', function () { +- app.on('open-url', onOpenUrl); +- }); +- +- (globalThis as any)['getOpenUrls'] = function () { +- app.removeListener('open-url', onOpenUrl); +- +- return openUrls; +- }; +-} +- +-function getCodeCachePath(): string | undefined { +- +- // explicitly disabled via CLI args +- if (process.argv.indexOf('--no-cached-data') > 0) { +- return undefined; +- } +- +- // running out of sources +- if (process.env['VSCODE_DEV']) { +- return undefined; +- } +- +- // require commit id +- const commit = product.commit; +- if (!commit) { +- return undefined; +- } +- +- return path.join(userDataPath, 'CachedData', commit); +-} +- +-async function mkdirpIgnoreError(dir: string | undefined): Promise { +- if (typeof dir === 'string') { +- try { +- await fs.promises.mkdir(dir, { recursive: true }); +- +- return dir; +- } catch (error) { +- // ignore +- } +- } +- +- return undefined; +-} +- +-//#region NLS Support +- +-function processZhLocale(appLocale: string): string { +- if (appLocale.startsWith('zh')) { +- const region = appLocale.split('-')[1]; +- +- // On Windows and macOS, Chinese languages returned by +- // app.getPreferredSystemLanguages() start with zh-hans +- // for Simplified Chinese or zh-hant for Traditional Chinese, +- // so we can easily determine whether to use Simplified or Traditional. +- // However, on Linux, Chinese languages returned by that same API +- // are of the form zh-XY, where XY is a country code. +- // For China (CN), Singapore (SG), and Malaysia (MY) +- // country codes, assume they use Simplified Chinese. +- // For other cases, assume they use Traditional. +- if (['hans', 'cn', 'sg', 'my'].includes(region)) { +- return 'zh-cn'; +- } +- +- return 'zh-tw'; +- } +- +- return appLocale; +-} +- +-/** +- * Resolve the NLS configuration +- */ +-async function resolveNlsConfiguration(): Promise { +- +- // First, we need to test a user defined locale. +- // If it fails we try the app locale. +- // If that fails we fall back to English. +- +- const nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined; +- if (nlsConfiguration) { +- return nlsConfiguration; +- } +- +- // Try to use the app locale which is only valid +- // after the app ready event has been fired. +- +- let userLocale = app.getLocale(); +- if (!userLocale) { +- return { +- userLocale: 'en', +- osLocale, +- resolvedLanguage: 'en', +- defaultMessagesFile: path.join(__dirname, 'nls.messages.json'), +- +- // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated +- locale: 'en', +- availableLanguages: {} +- }; +- } +- +- // See above the comment about the loader and case sensitiveness +- userLocale = processZhLocale(userLocale.toLowerCase()); +- +- return resolveNLSConfiguration({ +- userLocale, +- osLocale, +- commit: product.commit, +- userDataPath, +- nlsMetadataPath: __dirname +- }); +-} +- +-/** +- * Language tags are case insensitive however an ESM loader is case sensitive +- * To make this work on case preserving & insensitive FS we do the following: +- * the language bundles have lower case language tags and we always lower case +- * the locale we receive from the user or OS. +- */ +-function getUserDefinedLocale(argvConfig: IArgvConfig): string | undefined { +- const locale = args['locale']; +- if (locale) { +- return locale.toLowerCase(); // a directly provided --locale always wins +- } +- +- return typeof argvConfig?.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined; +-} +- +-//#endregion +diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts +index 73a3aa6cd49..404070f038f 100644 +--- a/src/vs/base/common/uri.ts ++++ b/src/vs/base/common/uri.ts +@@ -21,6 +21,7 @@ function _validateUri(ret: URI, _strict?: boolean): void { + + // scheme, https://tools.ietf.org/html/rfc3986#section-3.1 + // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ++ // console.log('validate uri', ret.scheme); + if (ret.scheme && !_schemePattern.test(ret.scheme)) { + throw new Error('[UriError]: Scheme contains illegal characters.'); + } +diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts +index 1bc63ba6878..d14cd29de5d 100644 +--- a/src/vs/base/parts/ipc/common/ipc.net.ts ++++ b/src/vs/base/parts/ipc/common/ipc.net.ts +@@ -3,6 +3,9 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + ++import { fileLoggerGlobal } from '../../../../../../../src/extension.js'; ++import { FileRPCProtocolLogger } from '../../../../workbench/services/extensions/common/fileRPCProtocolLogger.js'; ++import { RequestInitiator } from '../../../../workbench/services/extensions/common/rpcProtocol.js'; + import { VSBuffer } from '../../../common/buffer.js'; + import { Emitter, Event } from '../../../common/event.js'; + import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js'; +@@ -281,7 +284,7 @@ function protocolMessageTypeToString(messageType: ProtocolMessageType) { + } + + export const enum ProtocolConstants { +- HeaderLength = 13, ++HeaderLength = 13, + /** + * Send an Acknowledge message at most 2 seconds later... + */ +@@ -353,13 +356,14 @@ class ProtocolReader extends Disposable { + + public acceptChunk(data: VSBuffer | null): void { + if (!data || data.byteLength === 0) { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'Accept chunk: empty buffer'); + return; + } ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'Accept chunk: ' + data.byteLength + ', read head: ' + this._state.readHead + ', read len: ' + this._state.readLen); + + this.lastReadTime = Date.now(); + + this._incomingData.acceptChunk(data); +- + while (this._incomingData.byteLength >= this._state.readLen) { + + const buff = this._incomingData.read(this._state.readLen); +@@ -373,6 +377,7 @@ class ProtocolReader extends Disposable { + this._state.messageType = buff.readUInt8(0); + this._state.id = buff.readUInt32BE(1); + this._state.ack = buff.readUInt32BE(5); ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'Protocol header read: ' + this._state.id); + + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderRead, { messageType: protocolMessageTypeToString(this._state.messageType), id: this._state.id, ack: this._state.ack, messageSize: this._state.readLen }); + +@@ -388,6 +393,7 @@ class ProtocolReader extends Disposable { + this._state.messageType = ProtocolMessageType.None; + this._state.id = 0; + this._state.ack = 0; ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'Protocol message read: ' + id + ', type: ' + messageType + ', ack: ' + ack); + + this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageRead, buff); + +@@ -832,6 +838,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { + private _socketReader: ProtocolReader; + // eslint-disable-next-line local/code-no-potentially-unsafe-disposables + private _socketDisposables: DisposableStore; ++ private _fileLogger: FileRPCProtocolLogger; + + private readonly _loadEstimator: ILoadEstimator; + private readonly _shouldSendKeepAlive: boolean; +@@ -878,7 +885,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { + this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket)); + this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg))); + this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e))); +- ++ this._fileLogger = new FileRPCProtocolLogger('PersistentProtocol'); + if (opts.initialChunk) { + this._socketReader.acceptChunk(opts.initialChunk); + } +@@ -944,6 +951,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { + } + + public beginAcceptReconnection(socket: ISocket, initialDataChunk: VSBuffer | null): void { ++ this._fileLogger.logIncoming(0, 0, RequestInitiator.LocalSide, 'Begin accept reconnection'); + this._isReconnecting = true; + + this._socketDisposables.dispose(); +@@ -966,6 +974,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { + } + + public endAcceptReconnection(): void { ++ this._fileLogger.logIncoming(0, 0, RequestInitiator.LocalSide, 'End accept reconnection'); + this._isReconnecting = false; + + // After a reconnection, let the other party know (again) which messages have been received. +@@ -987,6 +996,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { + } + + private _receiveMessage(msg: ProtocolMessage): void { ++ this._fileLogger.logIncoming(0, 0, RequestInitiator.LocalSide, 'Receive message: ' + msg.type + ', id: ' + msg.id + ', ack: ' + msg.ack + ', data: ' + msg.data.byteLength + ', _incomingMsgId: ' + this._incomingMsgId); + if (msg.ack > this._outgoingAckId) { + this._outgoingAckId = msg.ack; + do { +diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts +index 79a1112b410..cb4f3bce86d 100644 +--- a/src/vs/workbench/api/common/extHost.api.impl.ts ++++ b/src/vs/workbench/api/common/extHost.api.impl.ts +@@ -1241,14 +1241,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I + checkProposedApiEnabled(extension, 'canonicalUriProvider'); + return extHostWorkspace.provideCanonicalUri(uri, options, token); + }, +- decode(content: Uint8Array, uri: vscode.Uri | undefined, options?: { encoding: string }) { ++ decode: function(content: Uint8Array): Thenable { + checkProposedApiEnabled(extension, 'textDocumentEncoding'); +- return extHostWorkspace.decode(content, uri, options); ++ return extHostWorkspace.decode(content, undefined, undefined); + }, +- encode(content: string, uri: vscode.Uri | undefined, options?: { encoding: string }) { ++ encode: function(content: string): Thenable { + checkProposedApiEnabled(extension, 'textDocumentEncoding'); +- return extHostWorkspace.encode(content, uri, options); +- } ++ return extHostWorkspace.encode(content, undefined, undefined); ++ }, + }; + + // namespace: scm +@@ -1837,3 +1837,4 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I + }; + }; + } ++ +diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts +index f0d9124a0da..943bda43fe3 100644 +--- a/src/vs/workbench/api/common/extHostConfiguration.ts ++++ b/src/vs/workbench/api/common/extHostConfiguration.ts +@@ -159,6 +159,7 @@ export class ExtHostConfigProvider { + } + + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionDescription?: IExtensionDescription): vscode.WorkspaceConfiguration { ++ console.log('getConfiguration', section, scope, extensionDescription); + const overrides = scopeToOverrides(scope) || {}; + const config = this._toReadonlyValue(this._configuration.getValue(section, overrides, this._extHostWorkspace.workspace)); + +diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts +index 20f8efbfdbd..72d45f6b758 100644 +--- a/src/vs/workbench/api/common/extHostExtensionActivator.ts ++++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts +@@ -11,6 +11,8 @@ import { ExtensionIdentifier, ExtensionIdentifierMap } from '../../../platform/e + import { ExtensionActivationReason, MissingExtensionDependency } from '../../services/extensions/common/extensions.js'; + import { ILogService } from '../../../platform/log/common/log.js'; + import { Barrier } from '../../../base/common/async.js'; ++import { fileLoggerGlobal } from '../../../../../../src/extension.js'; ++import { RequestInitiator } from '../../services/extensions/common/rpcProtocol.js'; + + /** + * Represents the source code (module) of an extension. +@@ -229,7 +231,9 @@ export class ExtensionsActivator implements IDisposable { + } + + public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateById start: ' + extensionId.value); + const desc = this._registry.getExtensionDescription(extensionId); ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateById desc: ' + desc); + if (!desc) { + throw new Error(`Extension '${extensionId.value}' is not known`); + } +diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts +index 03aabb46045..32a8fd18af6 100644 +--- a/src/vs/workbench/api/common/extHostExtensionService.ts ++++ b/src/vs/workbench/api/common/extHostExtensionService.ts +@@ -48,6 +48,8 @@ import { StopWatch } from '../../../base/common/stopwatch.js'; + import { isCI, setTimeout0 } from '../../../base/common/platform.js'; + import { IExtHostManagedSockets } from './extHostManagedSockets.js'; + import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; ++import { fileLoggerGlobal } from '../../../../../../src/extension.js'; ++import { RequestInitiator } from '../../services/extensions/common/rpcProtocol.js'; + + interface ITestRunner { + /** Old test runner API, as exported from `vscode/lib/testrunner` */ +@@ -298,6 +300,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + private _activateByEvent(activationEvent: string, startup: boolean): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, '_activateByEvent: ' + activationEvent); + return this._activator.activateByEvent(activationEvent, startup); + } + +@@ -647,15 +650,19 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + private _activateAllStartupFinished(): void { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateAllStartupFinished start'); + // startup is considered finished + this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks()); + + this._extHostConfiguration.getConfigProvider().then((configProvider) => { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateAllStartupFinished getConfigProvider'); + const shouldDeferActivation = configProvider.getConfiguration('extensions.experimental').get('deferredStartupFinishedActivation'); + const allExtensionDescriptions = this._myRegistry.getAllExtensionDescriptions(); + if (shouldDeferActivation) { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateAllStartupFinished shouldDeferActivation'); + this._activateAllStartupFinishedDeferred(allExtensionDescriptions); + } else { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'activateAllStartupFinished !shouldDeferActivation'); + for (const desc of allExtensionDescriptions) { + if (desc.activationEvents) { + for (const activationEvent of desc.activationEvents) { +@@ -671,6 +678,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + + // Handle "eager" activation extensions + private _handleEagerExtensions(): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'handleEagerExtensions start'); + const starActivation = this._activateByEvent('*', true).then(undefined, (err) => { + this._logService.error(err); + }); +@@ -689,6 +697,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'handleWorkspaceContainsEagerExtensions start: ' + folders.length); + if (folders.length === 0) { + return Promise.resolve(undefined); + } +@@ -697,7 +706,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + this._myRegistry.getAllExtensionDescriptions().map((desc) => { + return this._handleWorkspaceContainsEagerExtension(folders, desc); + }) +- ).then(() => { }); ++ ).then(() => { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'handleWorkspaceContainsEagerExtensions end'); ++ }); + } + + private async _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { +@@ -726,6 +737,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + private async _handleRemoteResolverEagerExtensions(): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'handleRemoteResolverEagerExtensions start'); + if (this._initData.remote.authority) { + return this._activateByEvent(`onResolveRemoteAuthority:${this._initData.remote.authority}`, false); + } +@@ -803,7 +815,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + private _startExtensionHost(): Promise { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'startExtensionHost start'); + if (this._started) { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'Extension host is already started!'); + throw new Error(`Extension host is already started!`); + } + this._started = true; +@@ -1036,6 +1050,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme + } + + public async $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { ++ console.log('activate', extensionId, reason); + await this._readyToRunExtensions.wait(); + if (!this._myRegistry.getExtensionDescription(extensionId)) { + // unknown extension => ignore +diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts +index 435df4b03fc..1b85d76d832 100644 +--- a/src/vs/workbench/api/common/extHostWebview.ts ++++ b/src/vs/workbench/api/common/extHostWebview.ts +@@ -222,6 +222,7 @@ export class ExtHostWebviews extends Disposable implements extHostProtocol.ExtHo + jsonMessage: string, + buffers: SerializableObjectWithBuffers + ): void { ++ console.log('onMessage', handle, jsonMessage, buffers); + const webview = this.getWebview(handle); + if (webview) { + const { message } = deserializeWebviewMessage(jsonMessage, buffers.value); +diff --git a/src/vs/workbench/api/common/extHostWebviewView.ts b/src/vs/workbench/api/common/extHostWebviewView.ts +index 4696f33c5fa..87d6300330c 100644 +--- a/src/vs/workbench/api/common/extHostWebviewView.ts ++++ b/src/vs/workbench/api/common/extHostWebviewView.ts +@@ -12,6 +12,8 @@ import { ViewBadge } from './extHostTypeConverters.js'; + import type * as vscode from 'vscode'; + import * as extHostProtocol from './extHost.protocol.js'; + import * as extHostTypes from './extHostTypes.js'; ++import { fileLoggerGlobal } from '../../../../../../src/extension.js'; ++import { RequestInitiator } from '../../services/extensions/common/rpcProtocol.js'; + + /* eslint-disable local/code-no-native-private */ + +@@ -162,6 +164,7 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS + retainContextWhenHidden?: boolean; + }, + ): vscode.Disposable { ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'registerWebviewViewProvider start: ' + viewType); + if (this._viewProviders.has(viewType)) { + throw new Error(`View provider for '${viewType}' already registered`); + } +@@ -185,6 +188,8 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS + state: any, + cancellation: CancellationToken, + ): Promise { ++ console.log('resolveWebviewView', webviewHandle, viewType, title, state); ++ fileLoggerGlobal.logIncoming(0, 0, RequestInitiator.LocalSide, 'resolveWebviewView start: ' + viewType); + const entry = this._viewProviders.get(viewType); + if (!entry) { + throw new Error(`No view provider found for '${viewType}'`); +diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts +index 12af1c17f95..2e2b55b1fa4 100644 +--- a/src/vs/workbench/api/common/extHostWorkspace.ts ++++ b/src/vs/workbench/api/common/extHostWorkspace.ts +@@ -227,6 +227,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac + } + + $initializeWorkspace(data: IWorkspaceData | null, trusted: boolean): void { ++ console.log('initializeWorkspace', data, trusted); + this._trusted = trusted; + this.$acceptWorkspaceData(data); + this._barrier.open(); +diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts +index 11980f9aafe..24d9a7aeca4 100644 +--- a/src/vs/workbench/api/common/extensionHostMain.ts ++++ b/src/vs/workbench/api/common/extensionHostMain.ts +@@ -3,220 +3,236 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +-import * as errors from '../../../base/common/errors.js'; +-import * as performance from '../../../base/common/performance.js'; +-import { URI } from '../../../base/common/uri.js'; +-import { IURITransformer } from '../../../base/common/uriIpc.js'; +-import { IMessagePassingProtocol } from '../../../base/parts/ipc/common/ipc.js'; +-import { MainContext, MainThreadConsoleShape } from './extHost.protocol.js'; +-import { IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js'; +-import { RPCProtocol } from '../../services/extensions/common/rpcProtocol.js'; +-import { ExtensionError, ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; +-import { ILogService } from '../../../platform/log/common/log.js'; +-import { getSingletonServiceDescriptors } from '../../../platform/instantiation/common/extensions.js'; +-import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js'; +-import { IExtHostInitDataService } from './extHostInitDataService.js'; +-import { InstantiationService } from '../../../platform/instantiation/common/instantiationService.js'; +-import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; +-import { IExtHostRpcService, ExtHostRpcService } from './extHostRpcService.js'; +-import { IURITransformerService, URITransformerService } from './extHostUriTransformerService.js'; +-import { IExtHostExtensionService, IHostUtils } from './extHostExtensionService.js'; +-import { IExtHostTelemetry } from './extHostTelemetry.js'; +-import { Mutable } from '../../../base/common/types.js'; ++import * as errors from "../../../base/common/errors.js" ++import * as performance from "../../../base/common/performance.js" ++import { URI } from "../../../base/common/uri.js" ++import { IURITransformer } from "../../../base/common/uriIpc.js" ++import { IMessagePassingProtocol } from "../../../base/parts/ipc/common/ipc.js" ++import { MainContext, MainThreadConsoleShape } from "./extHost.protocol.js" ++import { IExtensionHostInitData } from "../../services/extensions/common/extensionHostProtocol.js" ++import { RPCProtocol } from "../../services/extensions/common/rpcProtocol.js" ++import { ++ ExtensionError, ++ ExtensionIdentifier, ++ IExtensionDescription, ++} from "../../../platform/extensions/common/extensions.js" ++import { ILogService } from "../../../platform/log/common/log.js" ++import { getSingletonServiceDescriptors } from "../../../platform/instantiation/common/extensions.js" ++import { ServiceCollection } from "../../../platform/instantiation/common/serviceCollection.js" ++import { IExtHostInitDataService } from "./extHostInitDataService.js" ++import { InstantiationService } from "../../../platform/instantiation/common/instantiationService.js" ++import { IInstantiationService, ServicesAccessor } from "../../../platform/instantiation/common/instantiation.js" ++import { IExtHostRpcService, ExtHostRpcService } from "./extHostRpcService.js" ++import { IURITransformerService, URITransformerService } from "./extHostUriTransformerService.js" ++import { IExtHostExtensionService, IHostUtils } from "./extHostExtensionService.js" ++import { IExtHostTelemetry } from "./extHostTelemetry.js" ++import { Mutable } from "../../../base/common/types.js" ++import { FileRPCProtocolLogger } from "../../services/extensions/common/fileRPCProtocolLogger.js" + + export interface IExitFn { +- (code?: number): any; ++ (code?: number): any + } + + export interface IConsolePatchFn { +- (mainThreadConsole: MainThreadConsoleShape): any; ++ (mainThreadConsole: MainThreadConsoleShape): any + } + + export abstract class ErrorHandler { +- + static async installEarlyHandler(accessor: ServicesAccessor): Promise { +- + // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) +- Error.stackTraceLimit = 100; ++ Error.stackTraceLimit = 100 + + // does NOT dependent of extension information, can be installed immediately, and simply forwards + // to the log service and main thread errors +- const logService = accessor.get(ILogService); +- const rpcService = accessor.get(IExtHostRpcService); +- const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors); +- +- errors.setUnexpectedErrorHandler(err => { +- logService.error(err); +- const data = errors.transformErrorForSerialization(err); +- mainThreadErrors.$onUnexpectedError(data); +- }); ++ const logService = accessor.get(ILogService) ++ const rpcService = accessor.get(IExtHostRpcService) ++ const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors) ++ ++ errors.setUnexpectedErrorHandler((err) => { ++ logService.error(err) ++ const data = errors.transformErrorForSerialization(err) ++ mainThreadErrors.$onUnexpectedError(data) ++ }) + } + + static async installFullHandler(accessor: ServicesAccessor): Promise { + // uses extension knowledges to correlate errors with extensions + +- const logService = accessor.get(ILogService); +- const rpcService = accessor.get(IExtHostRpcService); +- const extensionService = accessor.get(IExtHostExtensionService); +- const extensionTelemetry = accessor.get(IExtHostTelemetry); ++ const logService = accessor.get(ILogService) ++ const rpcService = accessor.get(IExtHostRpcService) ++ const extensionService = accessor.get(IExtHostExtensionService) ++ const extensionTelemetry = accessor.get(IExtHostTelemetry) + +- const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService); +- const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors); ++ const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService) ++ const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors) + +- const map = await extensionService.getExtensionPathIndex(); +- const extensionErrors = new WeakMap(); ++ const map = await extensionService.getExtensionPathIndex() ++ const extensionErrors = new WeakMap< ++ Error, ++ { extensionIdentifier: ExtensionIdentifier | undefined; stack: string } ++ >() + + // PART 1 + // set the prepareStackTrace-handle and use it as a side-effect to associate errors + // with extensions - this works by looking up callsites in the extension path index + function prepareStackTraceAndFindExtension(error: Error, stackTrace: errors.V8CallSite[]) { + if (extensionErrors.has(error)) { +- return extensionErrors.get(error)!.stack; ++ return extensionErrors.get(error)!.stack + } +- let stackTraceMessage = ''; +- let extension: IExtensionDescription | undefined; +- let fileName: string | null; ++ let stackTraceMessage = "" ++ let extension: IExtensionDescription | undefined ++ let fileName: string | null + for (const call of stackTrace) { +- stackTraceMessage += `\n\tat ${call.toString()}`; +- fileName = call.getFileName(); ++ stackTraceMessage += `\n\tat ${call.toString()}` ++ fileName = call.getFileName() + if (!extension && fileName) { +- extension = map.findSubstr(URI.file(fileName)); ++ extension = map.findSubstr(URI.file(fileName)) + } + } +- const result = `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`; +- extensionErrors.set(error, { extensionIdentifier: extension?.identifier, stack: result }); +- return result; ++ const result = `${error.name || "Error"}: ${error.message || ""}${stackTraceMessage}` ++ extensionErrors.set(error, { extensionIdentifier: extension?.identifier, stack: result }) ++ return result + } + +- const _wasWrapped = Symbol('prepareStackTrace wrapped'); +- let _prepareStackTrace = prepareStackTraceAndFindExtension; ++ const _wasWrapped = Symbol("prepareStackTrace wrapped") ++ let _prepareStackTrace = prepareStackTraceAndFindExtension + +- Object.defineProperty(Error, 'prepareStackTrace', { ++ Object.defineProperty(Error, "prepareStackTrace", { + configurable: false, + get() { +- return _prepareStackTrace; ++ return _prepareStackTrace + }, + set(v) { + if (v === prepareStackTraceAndFindExtension || !v || v[_wasWrapped]) { +- _prepareStackTrace = v || prepareStackTraceAndFindExtension; +- return; ++ _prepareStackTrace = v || prepareStackTraceAndFindExtension ++ return + } + + _prepareStackTrace = function (error, stackTrace) { +- prepareStackTraceAndFindExtension(error, stackTrace); +- return v.call(Error, error, stackTrace); +- }; ++ prepareStackTraceAndFindExtension(error, stackTrace) ++ return v.call(Error, error, stackTrace) ++ } + +- Object.assign(_prepareStackTrace, { [_wasWrapped]: true }); ++ Object.assign(_prepareStackTrace, { [_wasWrapped]: true }) + }, +- }); ++ }) + + // PART 2 + // set the unexpectedErrorHandler and check for extensions that have been identified as + // having caused the error. Note that the runtime order is actually reversed, the code + // below accesses the stack-property which triggers the code above +- errors.setUnexpectedErrorHandler(err => { +- logService.error(err); ++ errors.setUnexpectedErrorHandler((err) => { ++ logService.error(err) + +- const errorData = errors.transformErrorForSerialization(err); ++ const errorData = errors.transformErrorForSerialization(err) + +- let extension: ExtensionIdentifier | undefined; ++ let extension: ExtensionIdentifier | undefined + if (err instanceof ExtensionError) { +- extension = err.extension; ++ extension = err.extension + } else { +- const stackData = extensionErrors.get(err); +- extension = stackData?.extensionIdentifier; ++ const stackData = extensionErrors.get(err) ++ extension = stackData?.extensionIdentifier + } + + if (extension) { +- mainThreadExtensions.$onExtensionRuntimeError(extension, errorData); +- const reported = extensionTelemetry.onExtensionError(extension, err); +- logService.trace('forwarded error to extension?', reported, extension); ++ mainThreadExtensions.$onExtensionRuntimeError(extension, errorData) ++ const reported = extensionTelemetry.onExtensionError(extension, err) ++ logService.trace("forwarded error to extension?", reported, extension) + } +- }); ++ }) + +- errors.errorHandler.addListener(err => { +- mainThreadErrors.$onUnexpectedError(err); +- }); ++ errors.errorHandler.addListener((err) => { ++ const data = errors.transformErrorForSerialization(err) ++ mainThreadErrors.$onUnexpectedError(data) ++ }) + } + } + + export class ExtensionHostMain { +- +- private readonly _hostUtils: IHostUtils; +- private readonly _rpcProtocol: RPCProtocol; +- private readonly _extensionService: IExtHostExtensionService; +- private readonly _logService: ILogService; ++ private readonly _hostUtils: IHostUtils ++ private readonly _rpcProtocol: RPCProtocol ++ private readonly _extensionService: IExtHostExtensionService ++ private readonly _logService: ILogService + + constructor( + protocol: IMessagePassingProtocol, + initData: IExtensionHostInitData, + hostUtils: IHostUtils, + uriTransformer: IURITransformer | null, +- messagePorts?: ReadonlyMap ++ messagePorts?: ReadonlyMap, + ) { +- this._hostUtils = hostUtils; +- this._rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); ++ this._hostUtils = hostUtils ++ this._rpcProtocol = new RPCProtocol(protocol, new FileRPCProtocolLogger("extension_protocol"), uriTransformer) + + // ensure URIs are transformed and revived +- initData = ExtensionHostMain._transform(initData, this._rpcProtocol); ++ initData = ExtensionHostMain._transform(initData, this._rpcProtocol) + + // bootstrap services +- const services = new ServiceCollection(...getSingletonServiceDescriptors()); +- services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts }); +- services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol)); +- services.set(IURITransformerService, new URITransformerService(uriTransformer)); +- services.set(IHostUtils, hostUtils); ++ const services = new ServiceCollection(...getSingletonServiceDescriptors()) ++ services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts }) ++ services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol)) ++ services.set(IURITransformerService, new URITransformerService(uriTransformer)) ++ services.set(IHostUtils, hostUtils) + +- const instaService: IInstantiationService = new InstantiationService(services, true); ++ const instaService: IInstantiationService = new InstantiationService(services, true) + +- instaService.invokeFunction(ErrorHandler.installEarlyHandler); ++ instaService.invokeFunction(ErrorHandler.installEarlyHandler) + + // ugly self - inject +- this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService)); ++ this._logService = instaService.invokeFunction((accessor) => accessor.get(ILogService)) + +- performance.mark(`code/extHost/didCreateServices`); ++ performance.mark(`code/extHost/didCreateServices`) + if (this._hostUtils.pid) { +- this._logService.info(`Extension host with pid ${this._hostUtils.pid} started`); ++ this._logService.info(`Extension host with pid ${this._hostUtils.pid} started`) + } else { +- this._logService.info(`Extension host started`); ++ this._logService.info(`Extension host started`) + } +- this._logService.trace('initData', initData); ++ this._logService.trace("initData", initData) + + // ugly self - inject + // must call initialize *after* creating the extension service + // because `initialize` itself creates instances that depend on it +- this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)); +- this._extensionService.initialize(); ++ this._extensionService = instaService.invokeFunction((accessor) => accessor.get(IExtHostExtensionService)) ++ this._extensionService.initialize() + + // install error handler that is extension-aware +- instaService.invokeFunction(ErrorHandler.installFullHandler); ++ instaService.invokeFunction(ErrorHandler.installFullHandler) + } + + async asBrowserUri(uri: URI): Promise { +- const mainThreadExtensionsProxy = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService); +- return URI.revive(await mainThreadExtensionsProxy.$asBrowserUri(uri)); ++ const mainThreadExtensionsProxy = this._rpcProtocol.getProxy(MainContext.MainThreadExtensionService) ++ return URI.revive(await mainThreadExtensionsProxy.$asBrowserUri(uri)) + } + + terminate(reason: string): void { +- this._extensionService.terminate(reason); ++ this._extensionService.terminate(reason) + } + + private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData { + initData.extensions.allExtensions.forEach((ext) => { +- (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); +- }); +- initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); +- const extDevLocs = initData.environment.extensionDevelopmentLocationURI; ++ ;(>ext).extensionLocation = URI.revive( ++ rpcProtocol.transformIncomingURIs(ext.extensionLocation), ++ ) ++ }) ++ initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)) ++ const extDevLocs = initData.environment.extensionDevelopmentLocationURI + if (extDevLocs) { +- initData.environment.extensionDevelopmentLocationURI = extDevLocs.map(url => URI.revive(rpcProtocol.transformIncomingURIs(url))); ++ initData.environment.extensionDevelopmentLocationURI = extDevLocs.map((url) => ++ URI.revive(rpcProtocol.transformIncomingURIs(url)), ++ ) + } +- initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI)); +- initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome)); +- initData.environment.workspaceStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.workspaceStorageHome)); +- initData.nlsBaseUrl = URI.revive(rpcProtocol.transformIncomingURIs(initData.nlsBaseUrl)); +- initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation)); +- initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace); +- return initData; ++ initData.environment.extensionTestsLocationURI = URI.revive( ++ rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI), ++ ) ++ initData.environment.globalStorageHome = URI.revive( ++ rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome), ++ ) ++ initData.environment.workspaceStorageHome = URI.revive( ++ rpcProtocol.transformIncomingURIs(initData.environment.workspaceStorageHome), ++ ) ++ initData.nlsBaseUrl = URI.revive(rpcProtocol.transformIncomingURIs(initData.nlsBaseUrl)) ++ initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation)) ++ initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace) ++ return initData + } + } +diff --git a/src/vs/workbench/api/node/extHostConsoleForwarder.ts b/src/vs/workbench/api/node/extHostConsoleForwarder.ts +index aa2dbca286c..d9eb33a7043 100644 +--- a/src/vs/workbench/api/node/extHostConsoleForwarder.ts ++++ b/src/vs/workbench/api/node/extHostConsoleForwarder.ts +@@ -47,7 +47,7 @@ export class ExtHostConsoleForwarder extends AbstractExtHostConsoleForwarder { + + Object.defineProperty(stream, 'write', { + set: () => { }, +- get: () => (chunk: Uint8Array | string, encoding?: BufferEncoding, callback?: (err?: Error) => void) => { ++ get: () => (chunk: Uint8Array | string, encoding?: BufferEncoding, callback?: (err?: Error | null) => void) => { + if (!this._isMakingConsoleCall) { + buf += (chunk as any).toString(encoding); + const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf('\n'); +diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts +index 704a0dbb5bd..fa9a93ab32a 100644 +--- a/src/vs/workbench/api/node/extensionHostProcess.ts ++++ b/src/vs/workbench/api/node/extensionHostProcess.ts +@@ -29,6 +29,8 @@ import { IDisposable } from '../../../base/common/lifecycle.js'; + import '../common/extHost.common.services.js'; + import './extHost.node.services.js'; + import { createRequire } from 'node:module'; ++import { fileLoggerGlobal } from '../../../../../../src/extension.js'; ++import { RequestInitiator } from '../../services/extensions/common/rpcProtocol.js'; + const require = createRequire(import.meta.url); + + interface ParsedExtHostArgs { +@@ -186,6 +188,7 @@ function _createExtHostProtocol(): Promise { + socket = new WebSocketNodeSocket(new NodeSocket(handle, 'extHost-socket'), msg.permessageDeflate, inflateBytes, false); + } + if (protocol) { ++ fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, 'Reconnection case'); + // reconnection case + disconnectRunner1.cancel(); + disconnectRunner2.cancel(); +@@ -193,6 +196,7 @@ function _createExtHostProtocol(): Promise { + protocol.endAcceptReconnection(); + protocol.sendResume(); + } else { ++ fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, 'New connection case'); + clearTimeout(timer); + protocol = new PersistentProtocol({ socket, initialChunk: initialDataChunk }); + protocol.sendResume(); +@@ -207,6 +211,7 @@ function _createExtHostProtocol(): Promise { + } + } + if (msg && msg.type === 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME') { ++ fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, 'Reduce grace time case'); + if (disconnectRunner2.isScheduled()) { + // we are disconnected and already running the short reconnection timer + return; +@@ -338,12 +343,14 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { + onTerminate = (reason: string) => extensionHostMain.terminate(reason); + } + +-startExtensionHostProcess().catch((err) => console.log(err)); ++function start() { ++ startExtensionHostProcess().catch((err) => console.log(err)); ++} ++ ++export default start; +\ No newline at end of file +diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts +index 95c65048fcd..31aaea4a722 100644 +--- a/src/vs/workbench/contrib/webview/common/webview.ts ++++ b/src/vs/workbench/contrib/webview/common/webview.ts +@@ -22,7 +22,7 @@ export const webviewResourceBaseHost = 'vscode-cdn.net'; + + export const webviewRootResourceAuthority = `vscode-resource.${webviewResourceBaseHost}`; + +-export const webviewGenericCspSource = `'self' https://*.${webviewResourceBaseHost}`; ++export const webviewGenericCspSource = `'self' https://*.${webviewResourceBaseHost} vscode-file://*`; + + /** + * Construct a uri that can load resources inside a webview +@@ -42,6 +42,15 @@ export function asWebviewUri(resource: URI, remoteInfo?: WebviewRemoteInfo): URI + return resource; + } + ++ if (resource.scheme === Schemas.file) { ++ return URI.from({ ++ scheme: "vscode-file", ++ path: resource.path, ++ fragment: resource.fragment, ++ query: resource.query, ++ }); ++ } ++ + if (remoteInfo && remoteInfo.authority && remoteInfo.isRemote && resource.scheme === Schemas.file) { + resource = URI.from({ + scheme: Schemas.vscodeRemote, +diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +index 382683f3cf7..4478914fdc6 100644 +--- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts ++++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +@@ -813,6 +813,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx + const disposableStore = new DisposableStore(); + disposableStore.add(processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal))); + disposableStore.add(processManager.onDidChangeResponsiveState((responsiveState) => { ++ console.log(`Extension host (${processManager.friendyName}) is ${responsiveState === ResponsiveState.Responsive ? 'responsive' : 'unresponsive'}.`); + this._logService.info(`Extension host (${processManager.friendyName}) is ${responsiveState === ResponsiveState.Responsive ? 'responsive' : 'unresponsive'}.`); + this._onDidChangeResponsiveChange.fire({ + extensionHostKind: processManager.kind, +diff --git a/src/vs/workbench/services/extensions/common/fileRPCProtocolLogger.ts b/src/vs/workbench/services/extensions/common/fileRPCProtocolLogger.ts +new file mode 100644 +index 00000000000..1b4fc52e001 +--- /dev/null ++++ b/src/vs/workbench/services/extensions/common/fileRPCProtocolLogger.ts +@@ -0,0 +1,246 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright (c) Microsoft Corporation. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++import * as fs from "fs" ++import * as path from "path" ++import * as os from "os" ++import { IRPCProtocolLogger, RequestInitiator } from "./rpcProtocol.js" ++ ++/** ++ * 文件RPC协议日志记录器,将RPC通信日志保存到文件中 ++ */ ++export class FileRPCProtocolLogger implements IRPCProtocolLogger { ++ private _totalIncoming = 0 ++ private _totalOutgoing = 0 ++ private _logDir: string | undefined ++ private _logFile: string | undefined ++ private _writeStream: fs.WriteStream | null = null ++ private _logQueue: string[] = [] ++ private _isInitialized = false ++ private _isDisposed = false ++ private _processInterval: NodeJS.Timeout | null = null ++ private _isEnabled = false ++ ++ constructor(suffix?: string) { ++ if(!this._isEnabled) { ++ return ++ } ++ this._logDir = path.join(os.homedir(), ".ext_host", "log") ++ this._ensureLogDirectoryExists() ++ ++ // 创建日志文件名,使用时间戳确保唯一性 ++ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19) ++ ++ // 如果提供了后缀,在文件名中添加 ++ const suffixPart = suffix ? `_${suffix}` : ''; ++ this._logFile = path.join(this._logDir, `rpc${suffixPart}_${timestamp}.log`) ++ ++ try { ++ // 创建日志文件写入流 ++ this._writeStream = fs.createWriteStream(this._logFile, { flags: "a" }) ++ ++ // 生成精确到毫秒的时间戳 ++ const now = new Date() ++ const startTime = this._formatTimestampWithMilliseconds(now) ++ ++ // 写入日志头 ++ const header = [ ++ "-------------------------------------------------------------", ++ "Extension Host RPC Protocol Logger", ++ `Started at: ${startTime}`, ++ `Log file: ${this._logFile}`, ++ "-------------------------------------------------------------", ++ "" ++ ].join("\n") ++ ++ this._logQueue.push(header) ++ ++ // 启动日志处理定时器 ++ this._startProcessingQueue() ++ ++ this._isInitialized = true ++ console.log(`FileRPCProtocolLogger initialized, log file: ${this._logFile}`) ++ } catch (e) { ++ console.error("Failed to initialize FileRPCProtocolLogger", e) ++ } ++ } ++ ++ /** ++ * 确保日志目录存在 ++ */ ++ private _ensureLogDirectoryExists(): void { ++ if(!this._logDir) { ++ return ++ } ++ try { ++ if (!fs.existsSync(this._logDir)) { ++ fs.mkdirSync(this._logDir, { recursive: true }) ++ } ++ } catch (e) { ++ console.error("Failed to create log directory", e) ++ } ++ } ++ ++ /** ++ * 启动队列处理定时器 ++ */ ++ private _startProcessingQueue(): void { ++ this._processInterval = setInterval(() => { ++ this._processQueue() ++ }, 100) // 100毫秒处理一次队列 ++ } ++ ++ /** ++ * 处理日志队列 ++ */ ++ private _processQueue(): void { ++ if (this._isDisposed || !this._writeStream || this._logQueue.length === 0) { ++ return ++ } ++ ++ try { ++ // 批量写入日志条目 ++ const entries = this._logQueue.splice(0, Math.min(50, this._logQueue.length)) ++ for (const entry of entries) { ++ this._writeStream.write(entry + "\n") ++ } ++ } catch (e) { ++ console.error("Failed to write log entries", e) ++ } ++ } ++ ++ /** ++ * 记录传入消息日志 ++ */ ++ logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void { ++ if (!this._isInitialized) { ++ return ++ } ++ ++ this._totalIncoming += msgLength ++ this._logMessage("IDEA → Ext", this._totalIncoming, msgLength, req, initiator, str, data) ++ } ++ ++ /** ++ * 记录传出消息日志 ++ */ ++ logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void { ++ if (!this._isInitialized) { ++ return ++ } ++ ++ this._totalOutgoing += msgLength ++ this._logMessage("Ext → IDEA", this._totalOutgoing, msgLength, req, initiator, str, data) ++ } ++ ++ /** ++ * 记录消息 ++ */ ++ private _logMessage( ++ direction: string, ++ totalLength: number, ++ msgLength: number, ++ req: number, ++ initiator: RequestInitiator, ++ str: string, ++ data: any ++ ): void { ++ try { ++ const now = new Date() ++ const timestamp = this._formatTimestampWithMilliseconds(now) ++ ++ const initiatorStr = initiator === RequestInitiator.LocalSide ? "Local" : "Other" ++ ++ let logEntry = `[${timestamp}] ` ++ logEntry += `[${direction}] ` ++ logEntry += `[Total: ${String(totalLength).padStart(7)}] ` ++ logEntry += `[Len: ${String(msgLength).padStart(5)}] ` ++ logEntry += `[${String(req).padStart(5)}] ` ++ logEntry += `[${initiatorStr}] ` ++ logEntry += str ++ ++ if (data !== undefined) { ++ const dataStr = /\($/.test(str) ? `${this._stringify(data)})` : this._stringify(data) ++ logEntry += ` ${dataStr}` ++ } ++ ++ this._logQueue.push(logEntry) ++ } catch (e) { ++ console.error("Failed to format log message", e) ++ } ++ } ++ ++ /** ++ * 安全地将数据转换为字符串 ++ */ ++ private _stringify(data: any): string { ++ try { ++ return JSON.stringify(data, null, 0) ++ } catch (e) { ++ return String(data) ++ } ++ } ++ ++ /** ++ * 释放资源 ++ */ ++ dispose(): void { ++ if (this._isDisposed) { ++ return ++ } ++ ++ this._isDisposed = true ++ ++ try { ++ // 清除定时器 ++ if (this._processInterval) { ++ clearInterval(this._processInterval) ++ this._processInterval = null ++ } ++ ++ // 处理剩余的队列条目 ++ this._processQueue() ++ ++ // 生成精确到毫秒的时间戳 ++ const now = new Date() ++ const endTime = this._formatTimestampWithMilliseconds(now) ++ ++ // 写入日志尾 ++ const footer = [ ++ "-------------------------------------------------------------", ++ "Extension Host RPC Protocol Logger", ++ `Ended at: ${endTime}`, ++ `Total incoming: ${this._totalIncoming} bytes`, ++ `Total outgoing: ${this._totalOutgoing} bytes`, ++ "-------------------------------------------------------------" ++ ].join("\n") ++ ++ // 直接写入,不经过队列 ++ if (this._writeStream) { ++ this._writeStream.write(footer + "\n") ++ this._writeStream.end() ++ this._writeStream = null ++ } ++ ++ console.log("FileRPCProtocolLogger disposed") ++ } catch (e) { ++ console.error("Failed to dispose FileRPCProtocolLogger", e) ++ } ++ } ++ ++ /** ++ * 格式化时间戳为毫秒级别 ++ */ ++ private _formatTimestampWithMilliseconds(date: Date): string { ++ const year = date.getFullYear() ++ const month = String(date.getMonth() + 1).padStart(2, '0') ++ const day = String(date.getDate()).padStart(2, '0') ++ const hours = String(date.getHours()).padStart(2, '0') ++ const minutes = String(date.getMinutes()).padStart(2, '0') ++ const seconds = String(date.getSeconds()).padStart(2, '0') ++ const milliseconds = String(date.getMilliseconds()).padStart(3, '0') ++ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}` ++ } ++} +\ No newline at end of file +diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts +index 6467e585843..eeb99f77f4a 100644 +--- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts ++++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts +@@ -288,6 +288,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { + const buff = MessageBuffer.read(rawmsg, 0); + const messageType = buff.readUInt8(); + const req = buff.readUInt32(); ++ this._logger?.logIncoming(msgLength, req, RequestInitiator.OtherSide, `receiveMessage: ${messageType}, req: ${req}, msgType: ${messageType}`); + + switch (messageType) { + case MessageType.RequestJSONArgs: +@@ -309,7 +310,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { + break; + } + case MessageType.Acknowledged: { +- this._logger?.logIncoming(msgLength, req, RequestInitiator.LocalSide, `ack`); ++ // this._logger?.logIncoming(msgLength, req, RequestInitiator.LocalSide, `ack`); + this._onDidReceiveAcknowledge(req); + break; + } +@@ -442,6 +443,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { + try { + return Promise.resolve(this._doInvokeHandler(rpcId, methodName, args)); + } catch (err) { ++ console.error('invokeHandler error:', err); + return Promise.reject(err); + } + } +-- +2.49.0 diff --git a/jetbrains/.gitignore b/jetbrains/.gitignore new file mode 100644 index 0000000000..381d328f82 --- /dev/null +++ b/jetbrains/.gitignore @@ -0,0 +1,2 @@ +### Debug resources +resources/* \ No newline at end of file diff --git a/jetbrains/README.md b/jetbrains/README.md new file mode 100644 index 0000000000..e87fdd4666 --- /dev/null +++ b/jetbrains/README.md @@ -0,0 +1,379 @@ +# JetBrains Plugin Development Setup + +This directory contains the JetBrains plugin implementation for Kilo Code, including both the IntelliJ plugin (Kotlin) and the Extension Host (Node.js/TypeScript). + +## Prerequisites + +Before building the JetBrains plugin, ensure all dependencies are properly configured. Use the provided dependency check script to verify your setup. + +### Required Dependencies + +#### 1. Java Development Kit (JDK) 17 + +- **Required Version**: Java 17 (LTS) +- **Why**: The plugin build system requires Java 17 for compilation and runtime compatibility +- **Recommended Installation** (SDKMAN - works on macOS/Linux): + + ```bash + # Install SDKMAN + curl -s "https://get.sdkman.io" | bash + source ~/.sdkman/bin/sdkman-init.sh + + # Install and use Java 17 + sdk install java 17.0.12-tem + sdk use java 17.0.12-tem + ``` + +- **Alternative Installation**: + - macOS: `brew install openjdk@17` + - Linux: `sudo apt install openjdk-17-jdk` or equivalent + - Windows: Download from [Oracle](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) or [OpenJDK](https://openjdk.org/projects/jdk/17/) + +#### 2. VSCode Submodule + +- **Location**: `deps/vscode/` +- **Purpose**: Provides VSCode runtime dependencies and APIs for the Extension Host +- **Initialization**: Must be initialized before building + +#### 3. Node.js and pnpm + +- **Node.js**: Version 20.x (as specified in package.json) +- **pnpm**: For workspace management and dependency installation + +## Quick Setup + +The dependency check runs automatically as part of the build process, but you can also run it manually: + +```bash +# Run dependency check manually +./jetbrains/scripts/check-dependencies.sh + +# Or as part of JetBrains host build process +cd jetbrains/host && pnpm run deps:check +``` + +**Note**: The dependency check is automatically integrated into the Turbo build system and runs before JetBrains builds to ensure all dependencies are properly configured. + +### Quick Fixes for Common Issues + +- **"Unsupported class file major version 68"**: [Install Java 17](#java-version-issues) +- **"slice is not valid mach-o file"**: [Rebuild native modules](#native-module-architecture-mismatch) +- **"platform.zip file does not exist"**: [Generate platform files](#missing-platformzip) + +## Manual Setup + +If you prefer to set up dependencies manually: + +### 1. Initialize VSCode Submodule + +```bash +# From project root +git submodule update --init --recursive +``` + +### 2. Verify Java Version + +```bash +java -version +# Should show Java 17.x.x + +javac -version +# Should show javac 17.x.x +``` + +### 3. Install Node Dependencies + +```bash +# From project root +pnpm install +``` + +## Project Structure + +``` +jetbrains/ +├── host/ # Extension Host (Node.js/TypeScript) +│ ├── src/ # TypeScript source code +│ ├── package.json # Node.js dependencies +│ ├── tsconfig.json # TypeScript configuration +│ └── turbo.json # Turbo build configuration +├── plugin/ # IntelliJ Plugin (Kotlin/Java) +│ ├── src/main/kotlin/ # Kotlin source code +│ ├── src/main/resources/ # Plugin resources and themes +│ ├── build.gradle.kts # Gradle build configuration +│ ├── gradle.properties # Plugin version and platform settings +│ ├── genPlatform.gradle # VSCode platform generation +│ └── scripts/ # Build and utility scripts +├── resources/ # Runtime resources (generated) +└── README.md # This file +``` + +## Build Modes + +The plugin supports three build modes controlled by the `debugMode` property: + +### 1. Development Mode (`debugMode=idea`) + +```bash +./gradlew prepareSandbox -PdebugMode=idea +``` + +- Used for local development and debugging +- Creates `.env` file for Extension Host +- Copies theme resources to debug location +- Enables hot-reloading for VSCode plugin integration + +### 2. Release Mode (`debugMode=release`) + +```bash +./gradlew prepareSandbox -PdebugMode=release +``` + +- Used for production builds +- Requires `platform.zip` file (generated via `genPlatform` task) +- Creates fully self-contained deployment package +- Includes all runtime dependencies and node_modules + +### 3. Lightweight Mode (`debugMode=none`, default) + +```bash +./gradlew prepareSandbox +``` + +- Used for testing and CI +- Minimal resource preparation +- No VSCode runtime dependencies +- Suitable for static analysis and unit tests + +## Building the Plugin + +### Development Build + +```bash +# From project root +pnpm jetbrains:run + +# Or manually: +cd jetbrains/plugin +./gradlew runIde -PdebugMode=idea +``` + +### Production Build + +```bash +# Generate platform files first (if needed) +cd jetbrains/plugin +./gradlew genPlatform + +# Build plugin +./gradlew buildPlugin -PdebugMode=release +``` + +### Extension Host Only + +```bash +# From jetbrains/host directory +pnpm build + +# Or with Turbo from project root +pnpm --filter @kilo-code/jetbrains-host build +``` + +## Turbo Integration + +The project uses Turborepo for efficient builds and caching: + +- **`jetbrains:bundle`**: Builds the complete plugin bundle +- **`jetbrains:run-bundle`**: Runs the plugin with bundle mode +- **`jetbrains:run`**: Runs the plugin in development mode + +Turbo automatically handles: + +- VSCode submodule initialization (`deps:check`) +- Dependency patching (`deps:patch`) +- Build caching and parallelization + +## Common Issues and Troubleshooting + +### Java Version Issues + +**Problem**: Build fails with "Unsupported class file major version 68" or similar Java version errors +**Root Cause**: Running Java 24+ instead of required Java 17 + +**Solution**: + +#### Option 1: Using SDKMAN (Recommended for macOS/Linux) + +```bash +# Install SDKMAN if not already installed +curl -s "https://get.sdkman.io" | bash +source ~/.sdkman/bin/sdkman-init.sh + +# Install and use Java 17 +sdk install java 17.0.12-tem +sdk use java 17.0.12-tem + +# Make Java 17 default (optional) +sdk default java 17.0.12-tem + +# Verify version +java -version # Should show OpenJDK 17.x.x +``` + +#### Option 2: Using Homebrew (macOS Alternative) + +```bash +# Install Java 17 +brew install openjdk@17 + +# Set JAVA_HOME for current session +export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home + +# Add to shell profile for persistence +echo 'export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home' >> ~/.zshrc + +# Verify version +java -version +``` + +#### Option 3: Manual JAVA_HOME Setup + +```bash +# Find Java 17 installation +/usr/libexec/java_home -V + +# Set JAVA_HOME to Java 17 path +export JAVA_HOME=$(/usr/libexec/java_home -v 17) +``` + +### VSCode Submodule Not Initialized + +**Problem**: Build fails with missing VSCode dependencies +**Solution**: + +```bash +# Initialize submodule +git submodule update --init --recursive + +# Verify submodule is populated +ls deps/vscode/src # Should contain VSCode source files +``` + +### Missing platform.zip + +**Problem**: Release build fails with "platform.zip file does not exist" +**Solution**: + +```bash +cd jetbrains/plugin +./gradlew genPlatform # This will download and generate platform.zip +``` + +### Node.js Version Mismatch + +**Problem**: Extension Host build fails with Node.js compatibility errors +**Solution**: + +```bash +# Use Node.js 20.x +nvm use 20 # if using nvm +# or +node --version # should show v20.x.x +``` + +### Native Module Architecture Mismatch + +**Problem**: Plugin fails to load with "slice is not valid mach-o file" errors for native modules like `@vscode/spdlog` or `native-watchdog` +**Root Cause**: Native Node.js modules were compiled for wrong CPU architecture (e.g., x86_64 vs ARM64) + +**Solution**: + +```bash +# Navigate to resources directory and rebuild native modules +cd jetbrains/resources + +# Clean existing modules +rm -rf node_modules package-lock.json + +# Copy package.json from host +cp ../host/package.json . + +# Install dependencies with npm (not pnpm to avoid workspace conflicts) +npm install + +# Verify native modules are built for correct architecture +file node_modules/@vscode/spdlog/build/Release/spdlog.node +file node_modules/native-watchdog/build/Release/watchdog.node +# Should show "Mach-O 64-bit bundle arm64" on Apple Silicon or appropriate arch + +# Update production dependency list +cd ../plugin +npm ls --omit=dev --all --parseable --prefix ../resources > ./prodDep.txt + +# Rebuild plugin +./gradlew buildPlugin -PdebugMode=none +``` + +**Prevention**: When updating dependencies or switching architectures, always rebuild native modules in the `jetbrains/resources/` directory. + +### Gradle Build Issues + +**Problem**: Gradle tasks fail or hang +**Solution**: + +```bash +# Clean and rebuild +./gradlew clean +./gradlew build --refresh-dependencies + +# Check Gradle daemon +./gradlew --stop +./gradlew build +``` + +## Development Workflow + +1. **Initial Setup**: Dependencies are automatically checked when you run any JetBrains build command +2. **Development**: Use `pnpm jetbrains:run` for live development (includes automatic dependency check) +3. **Testing**: Build with `debugMode=none` for CI/testing +4. **Release**: Generate platform files and build with `debugMode=release` + +**Automatic Dependency Management**: The build system now automatically verifies and sets up all required dependencies (Java 17, VSCode submodule, Node.js, etc.) before each build, ensuring a smooth development experience. + +## Environment Variables + +The plugin respects these environment variables: + +- `JAVA_HOME`: Java installation directory +- `debugMode`: Build mode (idea/release/none) +- `vscodePlugin`: Plugin name (default: kilocode) +- `vscodeVersion`: VSCode version for platform generation (default: 1.100.0) + +## Platform Support + +The plugin supports multiple platforms through the platform generation system: + +- **Windows**: x64 +- **macOS**: x64 and ARM64 (Apple Silicon) +- **Linux**: x64 + +Platform-specific dependencies are automatically handled during the build process. + +**Multi-Architecture Support**: The platform generation system now includes enhanced architecture-aware native module handling, automatically creating runtime loaders that detect the current platform and load the correct native modules for each architecture. + +## Contributing + +When making changes to the JetBrains plugin: + +1. Ensure all dependencies are properly set up +2. Test in development mode first (`debugMode=idea`) +3. Verify builds work in all three modes +4. Update this README if adding new dependencies or requirements +5. Run the dependency check script to validate setup + +## Scripts + +- `jetbrains/scripts/check-dependencies.sh`: Comprehensive dependency verification and setup +- `jetbrains/plugin/scripts/sync_version.js`: Version synchronization utility + +For more detailed build information, see the individual `package.json` and `build.gradle.kts` files in the respective directories. diff --git a/jetbrains/host/.gitignore b/jetbrains/host/.gitignore new file mode 100644 index 0000000000..f1cb5f4bb9 --- /dev/null +++ b/jetbrains/host/.gitignore @@ -0,0 +1,5 @@ +### Dependencies +deps/* + +### Build +dist \ No newline at end of file diff --git a/jetbrains/host/bootstrap-cli.ts b/jetbrains/host/bootstrap-cli.ts new file mode 100644 index 0000000000..224cc5f874 --- /dev/null +++ b/jetbrains/host/bootstrap-cli.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Delete `VSCODE_CWD` very early. We have seen +// reports where `code .` would use the wrong +// current working directory due to our variable +// somehow escaping to the parent shell +// (https://github.com/microsoft/vscode/issues/126399) +delete process.env["VSCODE_CWD"] diff --git a/jetbrains/host/bootstrap-esm.ts b/jetbrains/host/bootstrap-esm.ts new file mode 100644 index 0000000000..28b2668586 --- /dev/null +++ b/jetbrains/host/bootstrap-esm.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from "path" +import * as fs from "fs" +import { fileURLToPath } from "url" +import { createRequire, register } from "node:module" +import { product, pkg } from "./bootstrap-meta.js" +import "./bootstrap-node.js" +import * as performance from "./deps/vscode/vs/base/common/performance.js" +import { INLSConfiguration } from "./deps/vscode/vs/nls.js" + +const require = createRequire(import.meta.url) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// Install a hook to module resolution to map 'fs' to 'original-fs' +if (process.env["ELECTRON_RUN_AS_NODE"] || process.versions["electron"]) { + const jsCode = ` + export async function resolve(specifier, context, nextResolve) { + if (specifier === 'fs') { + return { + format: 'builtin', + shortCircuit: true, + url: 'node:original-fs' + }; + } + + // Defer to the next hook in the chain, which would be the + // Node.js default resolve if this is the last user-specified loader. + return nextResolve(specifier, context); + }` + register(`data:text/javascript;base64,${Buffer.from(jsCode).toString("base64")}`, import.meta.url) +} + +// Prepare globals that are needed for running +globalThis._VSCODE_PRODUCT_JSON = { ...product } +if (process.env["VSCODE_DEV"]) { + try { + const overrides: unknown = require("../product.overrides.json") + globalThis._VSCODE_PRODUCT_JSON = Object.assign(globalThis._VSCODE_PRODUCT_JSON, overrides) + } catch (error) { + /* ignore */ + } +} +globalThis._VSCODE_PACKAGE_JSON = { ...pkg } +globalThis._VSCODE_FILE_ROOT = __dirname + +//#region NLS helpers + +let setupNLSResult: Promise | undefined = undefined + +function setupNLS(): Promise { + if (!setupNLSResult) { + setupNLSResult = doSetupNLS() + } + + return setupNLSResult +} + +async function doSetupNLS(): Promise { + performance.mark("code/willLoadNls") + + let nlsConfig: INLSConfiguration | undefined = undefined + + let messagesFile: string | undefined + if (process.env["VSCODE_NLS_CONFIG"]) { + try { + nlsConfig = JSON.parse(process.env["VSCODE_NLS_CONFIG"]) + if (nlsConfig?.languagePack?.messagesFile) { + messagesFile = nlsConfig.languagePack.messagesFile + } else if (nlsConfig?.defaultMessagesFile) { + messagesFile = nlsConfig.defaultMessagesFile + } + + globalThis._VSCODE_NLS_LANGUAGE = nlsConfig?.resolvedLanguage + } catch (e) { + console.error(`Error reading VSCODE_NLS_CONFIG from environment: ${e}`) + } + } + + if ( + process.env["VSCODE_DEV"] || // no NLS support in dev mode + !messagesFile // no NLS messages file + ) { + return undefined + } + + try { + globalThis._VSCODE_NLS_MESSAGES = JSON.parse((await fs.promises.readFile(messagesFile)).toString()) + } catch (error) { + console.error(`Error reading NLS messages file ${messagesFile}: ${error}`) + + // Mark as corrupt: this will re-create the language pack cache next startup + if (nlsConfig?.languagePack?.corruptMarkerFile) { + try { + await fs.promises.writeFile(nlsConfig.languagePack.corruptMarkerFile, "corrupted") + } catch (error) { + console.error(`Error writing corrupted NLS marker file: ${error}`) + } + } + + // Fallback to the default message file to ensure english translation at least + if (nlsConfig?.defaultMessagesFile && nlsConfig.defaultMessagesFile !== messagesFile) { + try { + globalThis._VSCODE_NLS_MESSAGES = JSON.parse( + (await fs.promises.readFile(nlsConfig.defaultMessagesFile)).toString(), + ) + } catch (error) { + console.error(`Error reading default NLS messages file ${nlsConfig.defaultMessagesFile}: ${error}`) + } + } + } + + performance.mark("code/didLoadNls") + + return nlsConfig +} + +//#endregion + +export async function bootstrapESM(): Promise { + // NLS + await setupNLS() +} diff --git a/jetbrains/host/bootstrap-fork.ts b/jetbrains/host/bootstrap-fork.ts new file mode 100644 index 0000000000..56c5a2baef --- /dev/null +++ b/jetbrains/host/bootstrap-fork.ts @@ -0,0 +1,251 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as performance from "./deps/vscode/vs/base/common/performance.js" +import { removeGlobalNodeJsModuleLookupPaths, devInjectNodeModuleLookupPath } from "./bootstrap-node.js" +import { bootstrapESM } from "./bootstrap-esm.js" + +performance.mark("code/fork/start") + +//#region Helpers + +function pipeLoggingToParent(): void { + const MAX_STREAM_BUFFER_LENGTH = 1024 * 1024 + const MAX_LENGTH = 100000 + + /** + * Prevent circular stringify and convert arguments to real array + */ + function safeToString(args: ArrayLike): string { + const seen: unknown[] = [] + const argsArray: unknown[] = [] + + // Massage some arguments with special treatment + if (args.length) { + for (let i = 0; i < args.length; i++) { + let arg = args[i] + + // Any argument of type 'undefined' needs to be specially treated because + // JSON.stringify will simply ignore those. We replace them with the string + // 'undefined' which is not 100% right, but good enough to be logged to console + if (typeof arg === "undefined") { + arg = "undefined" + } + + // Any argument that is an Error will be changed to be just the error stack/message + // itself because currently cannot serialize the error over entirely. + else if (arg instanceof Error) { + const errorObj = arg + if (errorObj.stack) { + arg = errorObj.stack + } else { + arg = errorObj.toString() + } + } + + argsArray.push(arg) + } + } + + try { + const res = JSON.stringify(argsArray, function (key, value: unknown) { + // Objects get special treatment to prevent circles + if (isObject(value) || Array.isArray(value)) { + if (seen.indexOf(value) !== -1) { + return "[Circular]" + } + + seen.push(value) + } + + return value + }) + + if (res.length > MAX_LENGTH) { + return "Output omitted for a large object that exceeds the limits" + } + + return res + } catch (error) { + return `Output omitted for an object that cannot be inspected ('${error.toString()}')` + } + } + + function safeSend(arg: { type: string; severity: string; arguments: string }): void { + try { + if (process.send) { + process.send(arg) + } + } catch (error) { + // Can happen if the parent channel is closed meanwhile + } + } + + function isObject(obj: unknown): boolean { + return ( + typeof obj === "object" && + obj !== null && + !Array.isArray(obj) && + !(obj instanceof RegExp) && + !(obj instanceof Date) + ) + } + + function safeSendConsoleMessage(severity: "log" | "warn" | "error", args: string): void { + safeSend({ type: "__$console", severity, arguments: args }) + } + + /** + * Wraps a console message so that it is transmitted to the renderer. + * + * The wrapped property is not defined with `writable: false` to avoid + * throwing errors, but rather a no-op setting. See https://github.com/microsoft/vscode-extension-telemetry/issues/88 + */ + function wrapConsoleMethod(method: "log" | "info" | "warn" | "error", severity: "log" | "warn" | "error"): void { + Object.defineProperty(console, method, { + set: () => {}, + get: () => + function () { + safeSendConsoleMessage(severity, safeToString(arguments)) + }, + }) + } + + /** + * Wraps process.stderr/stdout.write() so that it is transmitted to the + * renderer or CLI. It both calls through to the original method as well + * as to console.log with complete lines so that they're made available + * to the debugger/CLI. + */ + function wrapStream(streamName: "stdout" | "stderr", severity: "log" | "warn" | "error"): void { + const stream = process[streamName] + const original = stream.write + + let buf = "" + + Object.defineProperty(stream, "write", { + set: () => {}, + get: + () => + ( + chunk: string | Buffer | Uint8Array, + encoding: BufferEncoding | undefined, + callback: ((err?: Error | undefined) => void) | undefined, + ) => { + buf += chunk.toString(encoding) + const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf("\n") + if (eol !== -1) { + console[severity](buf.slice(0, eol)) + buf = buf.slice(eol + 1) + } + + original.call(stream, chunk, encoding, callback) + }, + }) + } + + // Pass console logging to the outside so that we have it in the main side if told so + if (process.env["VSCODE_VERBOSE_LOGGING"] === "true") { + wrapConsoleMethod("info", "log") + wrapConsoleMethod("log", "log") + wrapConsoleMethod("warn", "warn") + wrapConsoleMethod("error", "error") + } else { + console.log = function () { + /* ignore */ + } + console.warn = function () { + /* ignore */ + } + console.info = function () { + /* ignore */ + } + wrapConsoleMethod("error", "error") + } + + wrapStream("stderr", "error") + wrapStream("stdout", "log") +} + +function handleExceptions(): void { + // Handle uncaught exceptions + process.on("uncaughtException", function (err) { + console.error("Uncaught Exception: ", err) + }) + + // Handle unhandled promise rejections + process.on("unhandledRejection", function (reason) { + console.error("Unhandled Promise Rejection: ", reason) + }) +} + +function terminateWhenParentTerminates(): void { + const parentPid = Number(process.env["VSCODE_PARENT_PID"]) + + if (typeof parentPid === "number" && !isNaN(parentPid)) { + setInterval(function () { + try { + process.kill(parentPid, 0) // throws an exception if the main process doesn't exist anymore. + } catch (e) { + process.exit() + } + }, 5000) + } +} + +function configureCrashReporter(): void { + const crashReporterProcessType = process.env["VSCODE_CRASH_REPORTER_PROCESS_TYPE"] + if (crashReporterProcessType) { + try { + //@ts-ignore + if ( + process["crashReporter"] && + typeof process["crashReporter"].addExtraParameter === "function" /* Electron only */ + ) { + //@ts-ignore + process["crashReporter"].addExtraParameter("processType", crashReporterProcessType) + } + } catch (error) { + console.error(error) + } + } +} + +//#endregion + +// Crash reporter +configureCrashReporter() + +// Remove global paths from the node module lookup (node.js only) +removeGlobalNodeJsModuleLookupPaths() + +if (process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"]) { + devInjectNodeModuleLookupPath(process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"]) +} + +// Configure: pipe logging to parent process +if (!!process.send && process.env["VSCODE_PIPE_LOGGING"] === "true") { + pipeLoggingToParent() +} + +// Handle Exceptions +if (!process.env["VSCODE_HANDLES_UNCAUGHT_ERRORS"]) { + handleExceptions() +} + +// Terminate when parent terminates +if (process.env["VSCODE_PARENT_PID"]) { + terminateWhenParentTerminates() +} + +// Bootstrap ESM +await bootstrapESM() + +// Load ESM entry point +await import( + [`./${process.env["VSCODE_ESM_ENTRYPOINT"]}.js`].join( + "/", + ) /* workaround: esbuild prints some strange warnings when trying to inline? */ +) diff --git a/jetbrains/host/bootstrap-import.ts b/jetbrains/host/bootstrap-import.ts new file mode 100644 index 0000000000..c7e56906dd --- /dev/null +++ b/jetbrains/host/bootstrap-import.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// ********************************************************************* +// * * +// * We need this to redirect to node_modules from the remote-folder. * +// * This ONLY applies when running out of source. * +// * * +// ********************************************************************* + +import { fileURLToPath, pathToFileURL } from "node:url" +import { promises } from "node:fs" +import { join } from "node:path" + +// SEE https://nodejs.org/docs/latest/api/module.html#initialize + +const _specifierToUrl: Record = {} + +export async function initialize(injectPath: string): Promise { + // populate mappings + + const injectPackageJSONPath = fileURLToPath(new URL("../package.json", pathToFileURL(injectPath))) + const packageJSON = JSON.parse(String(await promises.readFile(injectPackageJSONPath))) + + for (const [name] of Object.entries(packageJSON.dependencies)) { + try { + const path = join(injectPackageJSONPath, `../node_modules/${name}/package.json`) + let { main } = JSON.parse(String(await promises.readFile(path))) + + if (!main) { + main = "index.js" + } + if (!main.endsWith(".js")) { + main += ".js" + } + const mainPath = join(injectPackageJSONPath, `../node_modules/${name}/${main}`) + _specifierToUrl[name] = pathToFileURL(mainPath).href + } catch (err) { + console.error(name) + console.error(err) + } + } + + console.log(`[bootstrap-import] Initialized node_modules redirector for: ${injectPath}`) +} + +export async function resolve(specifier: string | number, context: any, nextResolve: (arg0: any, arg1: any) => any) { + const newSpecifier = _specifierToUrl[specifier] + if (newSpecifier !== undefined) { + return { + format: "commonjs", + shortCircuit: true, + url: newSpecifier, + } + } + + // Defer to the next hook in the chain, which would be the + // Node.js default resolve if this is the last user-specified loader. + return nextResolve(specifier, context) +} diff --git a/jetbrains/host/bootstrap-meta.ts b/jetbrains/host/bootstrap-meta.ts new file mode 100644 index 0000000000..152e24d751 --- /dev/null +++ b/jetbrains/host/bootstrap-meta.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createRequire } from "node:module" +import type { IProductConfiguration } from "./deps/vscode/vs/base/common/product.js" + +const require = createRequire(import.meta.url) + +let productObj: Partial & { BUILD_INSERT_PRODUCT_CONFIGURATION?: string } = { + BUILD_INSERT_PRODUCT_CONFIGURATION: "BUILD_INSERT_PRODUCT_CONFIGURATION", +} // DO NOT MODIFY, PATCHED DURING BUILD +if (productObj["BUILD_INSERT_PRODUCT_CONFIGURATION"]) { + productObj = require("../product.json") // Running out of sources +} + +let pkgObj = { BUILD_INSERT_PACKAGE_CONFIGURATION: "BUILD_INSERT_PACKAGE_CONFIGURATION" } // DO NOT MODIFY, PATCHED DURING BUILD +if (pkgObj["BUILD_INSERT_PACKAGE_CONFIGURATION"]) { + pkgObj = require("../package.json") // Running out of sources +} + +export const product = productObj +export const pkg = pkgObj diff --git a/jetbrains/host/bootstrap-node.ts b/jetbrains/host/bootstrap-node.ts new file mode 100644 index 0000000000..1500ca2dbc --- /dev/null +++ b/jetbrains/host/bootstrap-node.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from "path" +import * as fs from "fs" +import { fileURLToPath } from "url" +import { createRequire } from "node:module" +import type { IProductConfiguration } from "./deps/vscode/vs/base/common/product.js" + +const require = createRequire(import.meta.url) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const isWindows = process.platform === "win32" + +// increase number of stack frames(from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) +Error.stackTraceLimit = 100 + +if (!process.env["VSCODE_HANDLES_SIGPIPE"]) { + // Workaround for Electron not installing a handler to ignore SIGPIPE + // (https://github.com/electron/electron/issues/13254) + let didLogAboutSIGPIPE = false + process.on("SIGPIPE", () => { + // See https://github.com/microsoft/vscode-remote-release/issues/6543 + // In certain situations, the console itself can be in a broken pipe state + // so logging SIGPIPE to the console will cause an infinite async loop + if (!didLogAboutSIGPIPE) { + didLogAboutSIGPIPE = true + console.error(new Error(`Unexpected SIGPIPE`)) + } + }) +} + +// Setup current working directory in all our node & electron processes +// - Windows: call `process.chdir()` to always set application folder as cwd +// - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups +function setupCurrentWorkingDirectory(): void { + try { + // Store the `process.cwd()` inside `VSCODE_CWD` + // for consistent lookups, but make sure to only + // do this once unless defined already from e.g. + // a parent process. + if (typeof process.env["VSCODE_CWD"] !== "string") { + process.env["VSCODE_CWD"] = process.cwd() + } + + // Windows: always set application folder as current working dir + if (process.platform === "win32") { + process.chdir(path.dirname(process.execPath)) + } + } catch (err) { + console.error(err) + } +} + +setupCurrentWorkingDirectory() + +/** + * Add support for redirecting the loading of node modules + * + * Note: only applies when running out of sources. + */ +export function devInjectNodeModuleLookupPath(injectPath: string): void { + if (!process.env["VSCODE_DEV"]) { + return // only applies running out of sources + } + + if (!injectPath) { + throw new Error("Missing injectPath") + } + + // register a loader hook + const Module = require("node:module") + Module.register("./bootstrap-import.js", { parentURL: import.meta.url, data: injectPath }) +} + +export function removeGlobalNodeJsModuleLookupPaths(): void { + if (typeof process?.versions?.electron === "string") { + return // Electron disables global search paths in https://github.com/electron/electron/blob/3186c2f0efa92d275dc3d57b5a14a60ed3846b0e/shell/common/node_bindings.cc#L653 + } + + const Module = require("module") + const globalPaths = Module.globalPaths + + const originalResolveLookupPaths = Module._resolveLookupPaths + + Module._resolveLookupPaths = function (moduleName: string, parent: any): string[] { + const paths = originalResolveLookupPaths(moduleName, parent) + if (Array.isArray(paths)) { + let commonSuffixLength = 0 + while ( + commonSuffixLength < paths.length && + paths[paths.length - 1 - commonSuffixLength] === + globalPaths[globalPaths.length - 1 - commonSuffixLength] + ) { + commonSuffixLength++ + } + + return paths.slice(0, paths.length - commonSuffixLength) + } + + return paths + } + + const originalNodeModulePaths = Module._nodeModulePaths + Module._nodeModulePaths = function (from: string): string[] { + let paths: string[] = originalNodeModulePaths(from) + if (!isWindows) { + return paths + } + + // On Windows, remove drive(s) and users' home directory from search paths, + // UNLESS 'from' is explicitly set to one of those. + const isDrive = (p: string) => p.length >= 3 && p.endsWith(":\\") + + if (!isDrive(from)) { + paths = paths.filter((p) => !isDrive(path.dirname(p))) + } + + if (process.env.HOMEDRIVE && process.env.HOMEPATH) { + const userDir = path.dirname(path.join(process.env.HOMEDRIVE, process.env.HOMEPATH)) + + const isUsersDir = (p: string) => path.relative(p, userDir).length === 0 + + // Check if 'from' is the same as 'userDir' + if (!isUsersDir(from)) { + paths = paths.filter((p) => !isUsersDir(path.dirname(p))) + } + } + + return paths + } +} + +/** + * Helper to enable portable mode. + */ +export function configurePortable(product: Partial): { + portableDataPath: string + isPortable: boolean +} { + const appRoot = path.dirname(__dirname) + + function getApplicationPath(): string { + if (process.env["VSCODE_DEV"]) { + return appRoot + } + + if (process.platform === "darwin") { + return path.dirname(path.dirname(path.dirname(appRoot))) + } + + return path.dirname(path.dirname(appRoot)) + } + + function getPortableDataPath(): string { + if (process.env["VSCODE_PORTABLE"]) { + return process.env["VSCODE_PORTABLE"] + } + + if (process.platform === "win32" || process.platform === "linux") { + return path.join(getApplicationPath(), "data") + } + + const portableDataName = product.portable || `${product.applicationName}-portable-data` + return path.join(path.dirname(getApplicationPath()), portableDataName) + } + + const portableDataPath = getPortableDataPath() + const isPortable = !("target" in product) && fs.existsSync(portableDataPath) + const portableTempPath = path.join(portableDataPath, "tmp") + const isTempPortable = isPortable && fs.existsSync(portableTempPath) + + if (isPortable) { + process.env["VSCODE_PORTABLE"] = portableDataPath + } else { + delete process.env["VSCODE_PORTABLE"] + } + + if (isTempPortable) { + if (process.platform === "win32") { + process.env["TMP"] = portableTempPath + process.env["TEMP"] = portableTempPath + } else { + process.env["TMPDIR"] = portableTempPath + } + } + + return { + portableDataPath, + isPortable, + } +} diff --git a/jetbrains/host/bootstrap-server.ts b/jetbrains/host/bootstrap-server.ts new file mode 100644 index 0000000000..2b973985b6 --- /dev/null +++ b/jetbrains/host/bootstrap-server.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. + *--------------------------------------------------------------------------------------------*/ + +// Keep bootstrap-esm.js from redefining 'fs'. +delete process.env["ELECTRON_RUN_AS_NODE"] diff --git a/jetbrains/host/bootstrap-window.ts b/jetbrains/host/bootstrap-window.ts new file mode 100644 index 0000000000..9d24dded9f --- /dev/null +++ b/jetbrains/host/bootstrap-window.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +;(function () { + type ISandboxConfiguration = + import("./deps/vscode/vs/base/parts/sandbox/common/sandboxTypes.js").ISandboxConfiguration + type ILoadResult< + M, + T extends ISandboxConfiguration, + > = import("./deps/vscode/vs/platform/window/electron-sandbox/window.js").ILoadResult + type ILoadOptions = + import("./deps/vscode/vs/platform/window/electron-sandbox/window.js").ILoadOptions + type IMainWindowSandboxGlobals = + import("./deps/vscode/vs/base/parts/sandbox/electron-sandbox/globals.js").IMainWindowSandboxGlobals + + const preloadGlobals: IMainWindowSandboxGlobals = (window as any).vscode // defined by preload.ts + const safeProcess = preloadGlobals.process + + async function load( + esModule: string, + options: ILoadOptions, + ): Promise> { + // Window Configuration from Preload Script + const configuration = await resolveWindowConfiguration() + + // Signal before import() + options?.beforeImport?.(configuration) + + // Developer settings + const { + enableDeveloperKeybindings, + removeDeveloperKeybindingsAfterLoad, + developerDeveloperKeybindingsDisposable, + forceDisableShowDevtoolsOnError, + } = setupDeveloperKeybindings(configuration, options) + + // NLS + setupNLS(configuration) + + // Compute base URL and set as global + const baseUrl = new URL( + `${fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === "win32", scheme: "vscode-file", fallbackAuthority: "vscode-app" })}/out/`, + ) + globalThis._VSCODE_FILE_ROOT = baseUrl.toString() + + // Dev only: CSS import map tricks + setupCSSImportMaps(configuration, baseUrl) + + // ESM Import + try { + const result = await import(new URL(`${esModule}.js`, baseUrl).href) + + if (developerDeveloperKeybindingsDisposable && removeDeveloperKeybindingsAfterLoad) { + developerDeveloperKeybindingsDisposable() + } + + return { result, configuration } + } catch (error) { + onUnexpectedError(error, enableDeveloperKeybindings && !forceDisableShowDevtoolsOnError) + + throw error + } + } + + async function resolveWindowConfiguration() { + const timeout = setTimeout(() => { + console.error( + `[resolve window config] Could not resolve window configuration within 10 seconds, but will continue to wait...`, + ) + }, 10000) + performance.mark("code/willWaitForWindowConfig") + + const configuration = (await preloadGlobals.context.resolveConfiguration()) as T + performance.mark("code/didWaitForWindowConfig") + + clearTimeout(timeout) + + return configuration + } + + function setupDeveloperKeybindings(configuration: T, options: ILoadOptions) { + const { + forceEnableDeveloperKeybindings, + disallowReloadKeybinding, + removeDeveloperKeybindingsAfterLoad, + forceDisableShowDevtoolsOnError, + } = + typeof options?.configureDeveloperSettings === "function" + ? options.configureDeveloperSettings(configuration) + : { + forceEnableDeveloperKeybindings: false, + disallowReloadKeybinding: false, + removeDeveloperKeybindingsAfterLoad: false, + forceDisableShowDevtoolsOnError: false, + } + + const isDev = !!safeProcess.env["VSCODE_DEV"] + const enableDeveloperKeybindings = Boolean(isDev || forceEnableDeveloperKeybindings) + let developerDeveloperKeybindingsDisposable: Function | undefined = undefined + if (enableDeveloperKeybindings) { + developerDeveloperKeybindingsDisposable = registerDeveloperKeybindings(disallowReloadKeybinding) + } + + return { + enableDeveloperKeybindings, + removeDeveloperKeybindingsAfterLoad, + developerDeveloperKeybindingsDisposable, + forceDisableShowDevtoolsOnError, + } + } + + function registerDeveloperKeybindings(disallowReloadKeybinding: boolean | undefined): Function { + const ipcRenderer = preloadGlobals.ipcRenderer + + const extractKey = function (e: KeyboardEvent) { + return [ + e.ctrlKey ? "ctrl-" : "", + e.metaKey ? "meta-" : "", + e.altKey ? "alt-" : "", + e.shiftKey ? "shift-" : "", + e.keyCode, + ].join("") + } + + // Devtools & reload support + const TOGGLE_DEV_TOOLS_KB = safeProcess.platform === "darwin" ? "meta-alt-73" : "ctrl-shift-73" // mac: Cmd-Alt-I, rest: Ctrl-Shift-I + const TOGGLE_DEV_TOOLS_KB_ALT = "123" // F12 + const RELOAD_KB = safeProcess.platform === "darwin" ? "meta-82" : "ctrl-82" // mac: Cmd-R, rest: Ctrl-R + + let listener: ((e: KeyboardEvent) => void) | undefined = function (e) { + const key = extractKey(e) + if (key === TOGGLE_DEV_TOOLS_KB || key === TOGGLE_DEV_TOOLS_KB_ALT) { + ipcRenderer.send("vscode:toggleDevTools") + } else if (key === RELOAD_KB && !disallowReloadKeybinding) { + ipcRenderer.send("vscode:reloadWindow") + } + } + + window.addEventListener("keydown", listener) + + return function () { + if (listener) { + window.removeEventListener("keydown", listener) + listener = undefined + } + } + } + + function setupNLS(configuration: T): void { + globalThis._VSCODE_NLS_MESSAGES = configuration.nls.messages + globalThis._VSCODE_NLS_LANGUAGE = configuration.nls.language + + let language = configuration.nls.language || "en" + if (language === "zh-tw") { + language = "zh-Hant" + } else if (language === "zh-cn") { + language = "zh-Hans" + } + + window.document.documentElement.setAttribute("lang", language) + } + + function onUnexpectedError(error: string | Error, showDevtoolsOnError: boolean): void { + if (showDevtoolsOnError) { + const ipcRenderer = preloadGlobals.ipcRenderer + ipcRenderer.send("vscode:openDevTools") + } + + console.error(`[uncaught exception]: ${error}`) + + if (error && typeof error !== "string" && error.stack) { + console.error(error.stack) + } + } + + function fileUriFromPath( + path: string, + config: { isWindows?: boolean; scheme?: string; fallbackAuthority?: string }, + ): string { + // Since we are building a URI, we normalize any backslash + // to slashes and we ensure that the path begins with a '/'. + let pathName = path.replace(/\\/g, "/") + if (pathName.length > 0 && pathName.charAt(0) !== "/") { + pathName = `/${pathName}` + } + + let uri: string + + // Windows: in order to support UNC paths (which start with '//') + // that have their own authority, we do not use the provided authority + // but rather preserve it. + if (config.isWindows && pathName.startsWith("//")) { + uri = encodeURI(`${config.scheme || "file"}:${pathName}`) + } + + // Otherwise we optionally add the provided authority if specified + else { + uri = encodeURI(`${config.scheme || "file"}://${config.fallbackAuthority || ""}${pathName}`) + } + + return uri.replace(/#/g, "%23") + } + + function setupCSSImportMaps(configuration: T, baseUrl: URL) { + // DEV --------------------------------------------------------------------------------------- + // DEV: This is for development and enables loading CSS via import-statements via import-maps. + // DEV: For each CSS modules that we have we defined an entry in the import map that maps to + // DEV: a blob URL that loads the CSS via a dynamic @import-rule. + // DEV --------------------------------------------------------------------------------------- + + if (Array.isArray(configuration.cssModules) && configuration.cssModules.length > 0) { + performance.mark("code/willAddCssLoader") + + const style = document.createElement("style") + style.type = "text/css" + style.media = "screen" + style.id = "vscode-css-loading" + document.head.appendChild(style) + + globalThis._VSCODE_CSS_LOAD = function (url) { + style.textContent += `@import url(${url});\n` + } + + const importMap: { imports: Record } = { imports: {} } + for (const cssModule of configuration.cssModules) { + const cssUrl = new URL(cssModule, baseUrl).href + const jsSrc = `globalThis._VSCODE_CSS_LOAD('${cssUrl}');\n` + const blob = new Blob([jsSrc], { type: "application/javascript" }) + importMap.imports[cssUrl] = URL.createObjectURL(blob) + } + + const ttp = window.trustedTypes?.createPolicy("vscode-bootstrapImportMap", { + createScript(value) { + return value + }, + }) + const importMapSrc = JSON.stringify(importMap, undefined, 2) + const importMapScript = document.createElement("script") + importMapScript.type = "importmap" + importMapScript.setAttribute("nonce", "0c6a828f1297") + // @ts-ignore + importMapScript.textContent = ttp?.createScript(importMapSrc) ?? importMapSrc + document.head.appendChild(importMapScript) + + performance.mark("code/didAddCssLoader") + } + } + + ;(globalThis as any).MonacoBootstrapWindow = { load } +})() diff --git a/jetbrains/host/cli.ts b/jetbrains/host/cli.ts new file mode 100644 index 0000000000..e2b491fa95 --- /dev/null +++ b/jetbrains/host/cli.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import "./bootstrap-cli.js" // this MUST come before other imports as it changes global state +import { dirname } from "path" +import { fileURLToPath } from "url" +import { configurePortable } from "./bootstrap-node.js" +import { bootstrapESM } from "./bootstrap-esm.js" +import { resolveNLSConfiguration } from "./deps/vscode/vs/base/node/nls.js" +import { product } from "./bootstrap-meta.js" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// NLS +const nlsConfiguration = await resolveNLSConfiguration({ + userLocale: "en", + osLocale: "en", + commit: product.commit, + userDataPath: "", + nlsMetadataPath: __dirname, +}) +process.env["VSCODE_NLS_CONFIG"] = JSON.stringify(nlsConfiguration) // required for `bootstrap-esm` to pick up NLS messages + +// Enable portable support +configurePortable(product) + +// Signal processes that we got launched as CLI +process.env["VSCODE_CLI"] = "1" + +// Bootstrap ESM +await bootstrapESM() + +// Load Server +await import("./deps/vscode/vs/code/node/cli.js") diff --git a/jetbrains/host/electron.d.ts b/jetbrains/host/electron.d.ts new file mode 100644 index 0000000000..722568577f --- /dev/null +++ b/jetbrains/host/electron.d.ts @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import { MessageEvent } from "electron" + +declare global { + interface Process { + /** + * Electron's process.crash() method + * This method is used to crash the process + */ + crash(): void + + /** + * Electron's IPC (Inter-Process Communication) port + * Used for communication between renderer and main processes + */ + parentPort: { + /** + * Register a listener for a specific channel + */ + on(channel: string, listener: (event: MessageEvent) => void): void + + /** + * Register a one-time listener for a specific channel + */ + once(channel: string, listener: (event: MessageEvent) => void): void + + /** + * Send a message to the parent process + */ + postMessage(message: any): void + } + } +} + +export {} diff --git a/jetbrains/host/eslint.config.mjs b/jetbrains/host/eslint.config.mjs new file mode 100644 index 0000000000..d9ae2c0eda --- /dev/null +++ b/jetbrains/host/eslint.config.mjs @@ -0,0 +1,42 @@ +import { config } from "@roo-code/config-eslint/base" + +/** @type {import("eslint").Linter.Config} */ +export default [ + ...config, + { + rules: { + // TODO: These should be fixed and the rules re-enabled. + "no-regex-spaces": "off", + "no-useless-escape": "off", + "no-empty": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "no-var": "off", + + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-unsafe-function-type": "off", + "@typescript-eslint/no-unnecessary-type-constraint": "off", + "@typescript-eslint/no-misused-new": "off", + "@typescript-eslint/no-empty-object-type": "off", + }, + }, + { + files: ["__mocks__/**/*.js"], + rules: { + "no-undef": "off", + }, + }, + { + files: ["**/__test_cases__/**/*"], + rules: { + "no-undef": "off", + "no-const-assign": "off", + }, + }, + { + ignores: ["dist", "deps"], + }, +] \ No newline at end of file diff --git a/jetbrains/host/extension.ts b/jetbrains/host/extension.ts new file mode 100644 index 0000000000..7180e575df --- /dev/null +++ b/jetbrains/host/extension.ts @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import net from "net" + +// Save original process methods +const originalProcessOn = process.on +const originalProcessSend = process.send || (() => false) + +// Store message event handlers +const messageHandlers: ((message: any, socket?: net.Socket) => void)[] = [] + +// Reconnection related variables +let isReconnecting = false +let reconnectAttempts = 0 +const MAX_RECONNECT_ATTEMPTS = 5 +const RECONNECT_DELAY = 1000 // 1 second + +// Override process.on +process.on = function (event: string, listener: (...args: any[]) => void): any { + if (event === "message") { + messageHandlers.push((message: any, socket?: net.Socket) => { + // Check the number of parameters for listener + const paramCount = listener.length + if (paramCount === 1) { + // If only one parameter, pass only message + listener(message) + } else { + // If multiple parameters, pass message and socket + listener(message, socket) + } + }) + } + return originalProcessOn.call(process, event, listener) +} + +// Override process.send +process.send = function (message: any): boolean { + if (message?.type === "VSCODE_EXTHOST_IPC_READY") { + console.log("Extension host process is ready to receive socket") + connect() + } + + // Call original process.send + return originalProcessSend.call(process, message) +} + +// Establish socket connection +function connect() { + if (isReconnecting) { + console.log("Already in reconnection process, skipping") + return + } + + try { + // Get socket server information from environment variables + const host = process.env.VSCODE_EXTHOST_SOCKET_HOST || "127.0.0.1" + const port = parseInt(process.env.VSCODE_EXTHOST_SOCKET_PORT || "0", 10) + + if (!port) { + throw new Error("Invalid socket port") + } + + console.log(`Attempting to connect to ${host}:${port}`) + + // Establish socket connection + const socket = net.createConnection(port, host) + // Set socket noDelay option + socket.setNoDelay(true) + + socket.on("connect", () => { + console.log("Connected to main server") + isReconnecting = false + reconnectAttempts = 0 + + // Prepare message to send to VSCode module + const socketMessage = { + type: "VSCODE_EXTHOST_IPC_SOCKET", + initialDataChunk: "", + skipWebSocketFrames: true, + permessageDeflate: false, + inflateBytes: "", + } + + // Call all saved message handlers + messageHandlers.forEach((handler) => { + try { + handler(socketMessage, socket) + } catch (error) { + console.error("Error in message handler:", error) + } + }) + }) + + socket.on("error", (error: Error) => { + console.error("Socket connection error:", error) + handleDisconnect() + }) + + socket.on("close", () => { + console.log("Socket connection closed") + handleDisconnect() + }) + } catch (error) { + console.error("Connection error:", error) + handleDisconnect() + } +} + +// Handle disconnection +async function handleDisconnect() { + if (isReconnecting) { + console.log("Already in reconnection process, skipping") + return + } + + isReconnecting = true + reconnectAttempts++ + + if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) { + console.error("Max reconnection attempts reached. Giving up.") + isReconnecting = false + return + } + + console.log(`Attempting to reconnect (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`) + + // Wait for a while before retrying + console.log(`Waiting ${RECONNECT_DELAY}ms before reconnecting...`) + await new Promise((resolve) => setTimeout(resolve, RECONNECT_DELAY)) + console.log("Reconnection delay finished, attempting to connect...") + connect() +} + +console.log("Starting extension host process...") + +import start from "./deps/vscode/vs/workbench/api/node/extensionHostProcess.js" + +// This line will trigger extension host related logic startup, actual logic is in extensionHostProcess, +// Do not handle specific plugin business logic in subsequent content of this file +start() diff --git a/jetbrains/host/package.json b/jetbrains/host/package.json new file mode 100644 index 0000000000..7790c58a5d --- /dev/null +++ b/jetbrains/host/package.json @@ -0,0 +1,178 @@ +{ + "name": "@kilo-code/jetbrains-host", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "deps:check": "node ../../jetbrains/scripts/check-dependencies.js", + "deps:patch": "npm run deps:check && cd ../../deps/vscode && git reset --hard HEAD && git clean -fd && git apply ../patches/vscode/jetbrains.patch", + "deps:clean": "rm -rf ./deps/vscode/* || true", + "deps:copy": "npm run deps:check && npx cpy '../../deps/vscode/src/**' './deps/vscode' --parents", + "clean": "del-cli ./dist", + "build": "tsc", + "build:clean": "npm run clean && npm run build", + "start": "node ./dist/src/main.js", + "dev": "tsc && node ./dist/src/main.js", + "watch:tsc": "tsc --watch", + "watch": "tsc --watch", + "bundle:package": "cp ./package.json ./dist/package.json", + "bundle:build": "tsc --noEmit && tsup", + "lint": "eslint . --ext=ts --max-warnings=0" + }, + "dependencies": { + "@c4312/eventsource-umd": "^3.0.5", + "@microsoft/1ds-core-js": "^3.2.13", + "@microsoft/1ds-post-js": "^3.2.13", + "@parcel/watcher": "2.5.1", + "@types/semver": "^7.5.8", + "@vscode/deviceid": "^0.1.1", + "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/policy-watcher": "^1.3.2", + "@vscode/proxy-agent": "^0.32.0", + "@vscode/ripgrep": "^1.15.11", + "@vscode/spdlog": "^0.15.0", + "@vscode/sqlite3": "5.1.8-vscode", + "@vscode/sudo-prompt": "9.3.1", + "@vscode/tree-sitter-wasm": "^0.1.4", + "@vscode/vscode-languagedetection": "1.0.21", + "@vscode/windows-mutex": "^0.5.0", + "@vscode/windows-process-tree": "^0.6.0", + "@vscode/windows-registry": "^1.1.0", + "@xterm/addon-clipboard": "^0.2.0-beta.82", + "@xterm/addon-image": "^0.9.0-beta.99", + "@xterm/addon-ligatures": "^0.10.0-beta.99", + "@xterm/addon-progress": "^0.2.0-beta.5", + "@xterm/addon-search": "^0.16.0-beta.99", + "@xterm/addon-serialize": "^0.14.0-beta.99", + "@xterm/addon-unicode11": "^0.9.0-beta.99", + "@xterm/addon-webgl": "^0.19.0-beta.99", + "@xterm/headless": "^5.6.0-beta.99", + "@xterm/xterm": "^5.6.0-beta.99", + "all": "^0.0.0", + "debug": "^4.4.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "jschardet": "3.1.4", + "kerberos": "2.1.1", + "minimist": "^1.2.6", + "native-is-elevated": "0.7.0", + "native-keymap": "^3.3.5", + "native-watchdog": "^1.4.1", + "node-pty": "^1.1.0-beta33", + "open": "^8.4.2", + "tas-client-umd": "0.2.0", + "undici": "^7.13.0", + "undici-types": "^7.15.0", + "v8-inspect-profiler": "^0.1.1", + "vscode-oniguruma": "1.7.0", + "vscode-regexpp": "^3.1.0", + "vscode-textmate": "9.2.0", + "yauzl": "^3.0.0", + "yazl": "^2.4.3" + }, + "devDependencies": { + "@roo-code/config-eslint": "file:../../packages/config-eslint", + "@playwright/test": "^1.50.0", + "@types/cookie": "^0.3.3", + "@types/debug": "^4.1.5", + "@types/gulp-svgmin": "^1.2.1", + "@types/http-proxy-agent": "^2.0.1", + "@types/kerberos": "^1.1.2", + "@types/minimist": "^1.2.1", + "@types/mocha": "^9.1.1", + "@types/node": "20.x", + "@types/sinon": "^10.0.2", + "@types/sinon-test": "^2.4.2", + "@types/trusted-types": "^1.0.6", + "@types/vscode-notebook-renderer": "^1.72.0", + "@types/webpack": "^5.28.5", + "@types/wicg-file-system-access": "^2020.9.6", + "@types/windows-foreground-love": "^0.3.0", + "@types/winreg": "^1.2.30", + "@types/yauzl": "^2.10.0", + "@types/yazl": "^2.4.2", + "@vscode/gulp-electron": "^1.36.0", + "@vscode/l10n-dev": "0.0.35", + "@vscode/telemetry-extractor": "^1.10.2", + "@vscode/test-cli": "^0.0.6", + "@vscode/test-electron": "^2.4.0", + "@vscode/test-web": "^0.0.62", + "@vscode/v8-heap-parser": "^0.1.0", + "@vscode/vscode-perf": "^0.0.19", + "@webgpu/types": "^0.1.44", + "ansi-colors": "^3.2.3", + "asar": "^3.0.3", + "chromium-pickle-js": "^0.2.0", + "cookie": "^0.7.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.9.1", + "cssnano": "^6.0.3", + "debounce": "^1.0.0", + "deemon": "^1.11.0", + "electron": "34.4.1", + "event-stream": "3.3.4", + "fancy-log": "^1.3.3", + "file-loader": "^6.2.0", + "cpy-cli": "^5.0.0", + "glob": "^7.2.3", + "gulp": "^4.0.0", + "gulp-azure-storage": "^0.12.1", + "gulp-bom": "^3.0.0", + "gulp-buffer": "0.0.2", + "gulp-filter": "^5.1.0", + "gulp-flatmap": "^1.0.2", + "gulp-gunzip": "^1.0.0", + "gulp-gzip": "^1.4.2", + "gulp-json-editor": "^2.5.0", + "gulp-plumber": "^1.2.0", + "gulp-rename": "^1.2.0", + "gulp-replace": "^0.5.4", + "gulp-sourcemaps": "^3.0.0", + "gulp-svgmin": "^4.1.0", + "gulp-untar": "^0.0.7", + "husky": "^0.13.1", + "innosetup": "^6.4.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^6.0.1", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", + "lazy.js": "^0.4.2", + "merge-options": "^1.0.1", + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "minimist": "^1.2.6", + "mocha": "^10.8.2", + "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", + "npm-run-all": "^4.1.5", + "os-browserify": "^0.3.0", + "p-all": "^1.0.0", + "path-browserify": "^1.0.1", + "postcss": "^8.4.33", + "postcss-nesting": "^12.0.2", + "pump": "^1.0.1", + "rcedit": "^1.1.0", + "del-cli": "^5.1.0", + "sinon": "^12.0.1", + "sinon-test": "^3.1.3", + "source-map": "0.6.1", + "source-map-support": "^0.3.2", + "style-loader": "^3.3.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.1", + "tslib": "^2.6.3", + "tsup": "^8.5.0", + "util": "^0.12.4", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4", + "webpack-stream": "^7.0.0", + "xml2js": "^0.5.0", + "yaserver": "^0.4.0" + }, + "overrides": { + "node-gyp-build": "4.8.1", + "kerberos@2.1.1": { + "node-addon-api": "7.1.0" + } + } +} diff --git a/jetbrains/host/pnpm-lock.yaml b/jetbrains/host/pnpm-lock.yaml new file mode 100644 index 0000000000..2506d03fae --- /dev/null +++ b/jetbrains/host/pnpm-lock.yaml @@ -0,0 +1,12318 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@c4312/eventsource-umd': + specifier: ^3.0.5 + version: 3.0.5 + '@microsoft/1ds-core-js': + specifier: ^3.2.13 + version: 3.2.18(tslib@2.8.1) + '@microsoft/1ds-post-js': + specifier: ^3.2.13 + version: 3.2.18(tslib@2.8.1) + '@parcel/watcher': + specifier: 2.5.1 + version: 2.5.1 + '@types/semver': + specifier: ^7.5.8 + version: 7.7.0 + '@vscode/deviceid': + specifier: ^0.1.1 + version: 0.1.2 + '@vscode/iconv-lite-umd': + specifier: 0.7.0 + version: 0.7.0 + '@vscode/policy-watcher': + specifier: ^1.3.2 + version: 1.3.2 + '@vscode/proxy-agent': + specifier: ^0.32.0 + version: 0.32.0 + '@vscode/ripgrep': + specifier: ^1.15.11 + version: 1.15.14 + '@vscode/spdlog': + specifier: ^0.15.0 + version: 0.15.2 + '@vscode/sqlite3': + specifier: 5.1.8-vscode + version: 5.1.8-vscode + '@vscode/sudo-prompt': + specifier: 9.3.1 + version: 9.3.1 + '@vscode/tree-sitter-wasm': + specifier: ^0.1.4 + version: 0.1.4 + '@vscode/vscode-languagedetection': + specifier: 1.0.21 + version: 1.0.21 + '@vscode/windows-mutex': + specifier: ^0.5.0 + version: 0.5.0 + '@vscode/windows-process-tree': + specifier: ^0.6.0 + version: 0.6.0 + '@vscode/windows-registry': + specifier: ^1.1.0 + version: 1.1.0 + '@xterm/addon-clipboard': + specifier: ^0.2.0-beta.82 + version: 0.2.0-beta.97(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-image': + specifier: ^0.9.0-beta.99 + version: 0.9.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-ligatures': + specifier: ^0.10.0-beta.99 + version: 0.10.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-progress': + specifier: ^0.2.0-beta.5 + version: 0.2.0-beta.20(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-search': + specifier: ^0.16.0-beta.99 + version: 0.16.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-serialize': + specifier: ^0.14.0-beta.99 + version: 0.14.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-unicode11': + specifier: ^0.9.0-beta.99 + version: 0.9.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/addon-webgl': + specifier: ^0.19.0-beta.99 + version: 0.19.0-beta.114(@xterm/xterm@5.6.0-beta.114) + '@xterm/headless': + specifier: ^5.6.0-beta.99 + version: 5.6.0-beta.114 + '@xterm/xterm': + specifier: ^5.6.0-beta.99 + version: 5.6.0-beta.114 + all: + specifier: ^0.0.0 + version: 0.0.0 + http-proxy-agent: + specifier: ^7.0.0 + version: 7.0.2 + https-proxy-agent: + specifier: ^7.0.2 + version: 7.0.6 + jschardet: + specifier: 3.1.4 + version: 3.1.4 + kerberos: + specifier: 2.1.1 + version: 2.1.1 + minimist: + specifier: ^1.2.6 + version: 1.2.8 + native-is-elevated: + specifier: 0.7.0 + version: 0.7.0 + native-keymap: + specifier: ^3.3.5 + version: 3.3.5 + native-watchdog: + specifier: ^1.4.1 + version: 1.4.2 + node-pty: + specifier: ^1.1.0-beta33 + version: 1.1.0-beta9 + open: + specifier: ^8.4.2 + version: 8.4.2 + tas-client-umd: + specifier: 0.2.0 + version: 0.2.0 + v8-inspect-profiler: + specifier: ^0.1.1 + version: 0.1.1 + vscode-oniguruma: + specifier: 1.7.0 + version: 1.7.0 + vscode-regexpp: + specifier: ^3.1.0 + version: 3.1.0 + vscode-textmate: + specifier: 9.2.0 + version: 9.2.0 + yauzl: + specifier: ^3.0.0 + version: 3.2.0 + yazl: + specifier: ^2.4.3 + version: 2.5.1 + devDependencies: + '@playwright/test': + specifier: ^1.50.0 + version: 1.54.1 + '@types/cookie': + specifier: ^0.3.3 + version: 0.3.3 + '@types/debug': + specifier: ^4.1.5 + version: 4.1.12 + '@types/gulp-svgmin': + specifier: ^1.2.1 + version: 1.2.4 + '@types/http-proxy-agent': + specifier: ^2.0.1 + version: 2.0.2 + '@types/kerberos': + specifier: ^1.1.2 + version: 1.1.5 + '@types/minimist': + specifier: ^1.2.1 + version: 1.2.5 + '@types/mocha': + specifier: ^9.1.1 + version: 9.1.1 + '@types/node': + specifier: 20.x + version: 20.19.9 + '@types/sinon': + specifier: ^10.0.2 + version: 10.0.20 + '@types/sinon-test': + specifier: ^2.4.2 + version: 2.4.6 + '@types/trusted-types': + specifier: ^1.0.6 + version: 1.0.6 + '@types/vscode-notebook-renderer': + specifier: ^1.72.0 + version: 1.72.3 + '@types/webpack': + specifier: ^5.28.5 + version: 5.28.5(esbuild@0.25.8)(webpack-cli@5.1.4) + '@types/wicg-file-system-access': + specifier: ^2020.9.6 + version: 2020.9.8 + '@types/windows-foreground-love': + specifier: ^0.3.0 + version: 0.3.1 + '@types/winreg': + specifier: ^1.2.30 + version: 1.2.36 + '@types/yauzl': + specifier: ^2.10.0 + version: 2.10.3 + '@types/yazl': + specifier: ^2.4.2 + version: 2.4.6 + '@vscode/gulp-electron': + specifier: ^1.36.0 + version: 1.38.1 + '@vscode/l10n-dev': + specifier: 0.0.35 + version: 0.0.35 + '@vscode/telemetry-extractor': + specifier: ^1.10.2 + version: 1.17.0 + '@vscode/test-cli': + specifier: ^0.0.6 + version: 0.0.6 + '@vscode/test-electron': + specifier: ^2.4.0 + version: 2.5.2 + '@vscode/test-web': + specifier: ^0.0.62 + version: 0.0.62 + '@vscode/v8-heap-parser': + specifier: ^0.1.0 + version: 0.1.0 + '@vscode/vscode-perf': + specifier: ^0.0.19 + version: 0.0.19 + '@webgpu/types': + specifier: ^0.1.44 + version: 0.1.64 + ansi-colors: + specifier: ^3.2.3 + version: 3.2.4 + asar: + specifier: ^3.0.3 + version: 3.2.0 + chromium-pickle-js: + specifier: ^0.2.0 + version: 0.2.0 + cookie: + specifier: ^0.7.2 + version: 0.7.2 + copy-webpack-plugin: + specifier: ^11.0.0 + version: 11.0.0(webpack@5.100.2) + css-loader: + specifier: ^6.9.1 + version: 6.11.0(webpack@5.100.2) + cssnano: + specifier: ^6.0.3 + version: 6.1.2(postcss@8.5.6) + debounce: + specifier: ^1.0.0 + version: 1.2.1 + deemon: + specifier: ^1.11.0 + version: 1.13.5 + electron: + specifier: 34.4.1 + version: 34.4.1 + event-stream: + specifier: 3.3.4 + version: 3.3.4 + fancy-log: + specifier: ^1.3.3 + version: 1.3.3 + file-loader: + specifier: ^6.2.0 + version: 6.2.0(webpack@5.100.2) + glob: + specifier: ^5.0.13 + version: 5.0.15 + gulp: + specifier: ^4.0.0 + version: 4.0.2 + gulp-azure-storage: + specifier: ^0.12.1 + version: 0.12.1 + gulp-bom: + specifier: ^3.0.0 + version: 3.0.0(gulp@4.0.2) + gulp-buffer: + specifier: 0.0.2 + version: 0.0.2 + gulp-filter: + specifier: ^5.1.0 + version: 5.1.0 + gulp-flatmap: + specifier: ^1.0.2 + version: 1.0.2 + gulp-gunzip: + specifier: ^1.0.0 + version: 1.1.0 + gulp-gzip: + specifier: ^1.4.2 + version: 1.4.2 + gulp-json-editor: + specifier: ^2.5.0 + version: 2.6.0 + gulp-plumber: + specifier: ^1.2.0 + version: 1.2.1 + gulp-rename: + specifier: ^1.2.0 + version: 1.4.0 + gulp-replace: + specifier: ^0.5.4 + version: 0.5.4 + gulp-sourcemaps: + specifier: ^3.0.0 + version: 3.0.0 + gulp-svgmin: + specifier: ^4.1.0 + version: 4.1.0 + gulp-untar: + specifier: ^0.0.7 + version: 0.0.7 + husky: + specifier: ^0.13.1 + version: 0.13.4 + innosetup: + specifier: ^6.4.1 + version: 6.4.1 + istanbul-lib-coverage: + specifier: ^3.2.0 + version: 3.2.2 + istanbul-lib-instrument: + specifier: ^6.0.1 + version: 6.0.3 + istanbul-lib-report: + specifier: ^3.0.0 + version: 3.0.1 + istanbul-lib-source-maps: + specifier: ^4.0.1 + version: 4.0.1 + istanbul-reports: + specifier: ^3.1.5 + version: 3.1.7 + lazy.js: + specifier: ^0.4.2 + version: 0.4.3 + merge-options: + specifier: ^1.0.1 + version: 1.0.1 + mime: + specifier: ^1.4.1 + version: 1.6.0 + minimatch: + specifier: ^3.0.4 + version: 3.1.2 + mocha: + specifier: ^10.8.2 + version: 10.8.2 + mocha-junit-reporter: + specifier: ^2.2.1 + version: 2.2.1(mocha@10.8.2) + mocha-multi-reporters: + specifier: ^1.5.1 + version: 1.5.1(mocha@10.8.2) + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + os-browserify: + specifier: ^0.3.0 + version: 0.3.0 + p-all: + specifier: ^1.0.0 + version: 1.0.0 + path-browserify: + specifier: ^1.0.1 + version: 1.0.1 + postcss: + specifier: ^8.4.33 + version: 8.5.6 + postcss-nesting: + specifier: ^12.0.2 + version: 12.1.5(postcss@8.5.6) + pump: + specifier: ^1.0.1 + version: 1.0.3 + rcedit: + specifier: ^1.1.0 + version: 1.1.2 + rimraf: + specifier: ^2.2.8 + version: 2.7.1 + sinon: + specifier: ^12.0.1 + version: 12.0.1 + sinon-test: + specifier: ^3.1.3 + version: 3.1.6(sinon@12.0.1) + source-map: + specifier: 0.6.1 + version: 0.6.1 + source-map-support: + specifier: ^0.3.2 + version: 0.3.3 + style-loader: + specifier: ^3.3.2 + version: 3.3.4(webpack@5.100.2) + ts-loader: + specifier: ^9.5.1 + version: 9.5.2(typescript@4.9.5)(webpack@5.100.2) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.9)(typescript@4.9.5) + tslib: + specifier: ^2.6.3 + version: 2.8.1 + tsup: + specifier: ^8.5.0 + version: 8.5.0(postcss@8.5.6)(typescript@4.9.5) + util: + specifier: ^0.12.4 + version: 0.12.5 + webpack: + specifier: ^5.94.0 + version: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + webpack-cli: + specifier: ^5.1.4 + version: 5.1.4(webpack@5.100.2) + webpack-stream: + specifier: ^7.0.0 + version: 7.0.0(webpack@5.100.2) + xml2js: + specifier: ^0.5.0 + version: 0.5.0 + yaserver: + specifier: ^0.4.0 + version: 0.4.0 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@azure-rest/ai-translation-text@1.0.1': + resolution: {integrity: sha512-lUs1FfBXjik6EReUEYP1ogkhaSPHZdUV+EB215y7uejuyHgG1RXD2aLsqXQrluZwXcLMdN+bTzxylKBc5xDhgQ==} + engines: {node: '>=18.0.0'} + + '@azure-rest/core-client@2.5.0': + resolution: {integrity: sha512-KMVIPxG6ygcQ1M2hKHahF7eddKejYsWTjoLIfTWiqnaj42dBkYzj4+S8rK9xxmlOaEHKZHcMrRbm0NfN4kgwHw==} + engines: {node: '>=20.0.0'} + + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.10.0': + resolution: {integrity: sha512-88Djs5vBvGbHQHf5ZZcaoNHo6Y8BKZkt3cw2iuJIQzLEgH4Ox6Tm4hjFhbqOxyYsgIG/eJbFEHpxRIfEEWv5Ow==} + engines: {node: '>=20.0.0'} + + '@azure/core-client@1.10.0': + resolution: {integrity: sha512-O4aP3CLFNodg8eTHXECaH3B3CjicfzkxVtnrfLkOq0XNP7TIECGfHpK/C6vADZkWP75wzmdBnsIA8ksuJMk18g==} + engines: {node: '>=20.0.0'} + + '@azure/core-http-compat@2.3.0': + resolution: {integrity: sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw==} + engines: {node: '>=18.0.0'} + + '@azure/core-lro@2.7.2': + resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==} + engines: {node: '>=18.0.0'} + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.22.0': + resolution: {integrity: sha512-OKHmb3/Kpm06HypvB3g6Q3zJuvyXcpxDpCS1PnU8OV6AJgSFaee/covXBcPbWc6XDDxtEPlbi3EMQ6nUiPaQtw==} + engines: {node: '>=20.0.0'} + + '@azure/core-tracing@1.3.0': + resolution: {integrity: sha512-+XvmZLLWPe67WXNZo9Oc9CrPj/Tm8QnHR92fFAFdnbzwNdCH1h+7UdpaQgRSBsMY+oW1kHXNUZQLdZ1gHX3ROw==} + engines: {node: '>=20.0.0'} + + '@azure/core-util@1.13.0': + resolution: {integrity: sha512-o0psW8QWQ58fq3i24Q1K2XfS/jYTxr7O1HRcyUE9bV9NttLU+kYOH82Ixj8DGlMTOWgxm1Sss2QAfKK5UkSPxw==} + engines: {node: '>=20.0.0'} + + '@azure/core-xml@1.5.0': + resolution: {integrity: sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==} + engines: {node: '>=20.0.0'} + + '@azure/logger@1.3.0': + resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} + engines: {node: '>=20.0.0'} + + '@azure/storage-blob@12.28.0': + resolution: {integrity: sha512-VhQHITXXO03SURhDiGuHhvc/k/sD2WvJUS7hqhiVNbErVCuQoLtWql7r97fleBlIRKHJaa9R7DpBjfE0pfLYcA==} + engines: {node: '>=20.0.0'} + + '@azure/storage-common@12.0.0': + resolution: {integrity: sha512-QyEWXgi4kdRo0wc1rHum9/KnaWZKCdQGZK1BjU4fFL6Jtedp7KLbQihgTTVxldFy1z1ZPtuDPx8mQ5l3huPPbA==} + engines: {node: '>=20.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@c4312/eventsource-umd@3.0.5': + resolution: {integrity: sha512-0QhLg51eFB+SS/a4Pv5tHaRSnjJBpdFsjT3WN/Vfh6qzeFXqvaE+evVIIToYvr2lRBLg1NIB635ip8ML+/84Sg==} + engines: {node: '>=18.0.0'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@csstools/selector-resolve-nested@1.1.0': + resolution: {integrity: sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@csstools/selector-specificity@3.1.1': + resolution: {integrity: sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@electron/get@4.0.2': + resolution: {integrity: sha512-n9fRt/nzzOOZdDtTP3kT6GVdo0ro9FgMKCoS520kQMIiKBhpGmPny6yK/lER3tOCKr+wLYW1O25D9oI6ZinwCA==} + engines: {node: '>=22.12.0'} + + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@gulp-sourcemaps/identity-map@2.0.1': + resolution: {integrity: sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==} + engines: {node: '>= 0.10'} + + '@gulp-sourcemaps/map-sources@1.0.0': + resolution: {integrity: sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==} + engines: {node: '>= 0.10'} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.10': + resolution: {integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@koa/cors@5.0.0': + resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} + engines: {node: '>= 14.0.0'} + + '@koa/router@13.1.1': + resolution: {integrity: sha512-JQEuMANYRVHs7lm7KY9PCIjkgJk73h4m4J+g2mkw2Vo1ugPZ17UJVqEH8F+HeAdjKz5do1OaLe7ArDz+z308gw==} + engines: {node: '>= 18'} + + '@malept/cross-spawn-promise@1.1.1': + resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} + engines: {node: '>= 10'} + + '@microsoft/1ds-core-js@3.2.18': + resolution: {integrity: sha512-ytlFv3dfb8OGqvbZP8tSIlNvn3QNYxdsF0k6ikRMWSr6CmBxBi1sliaxc2Q5KuYOuaeWkd8WRm25Rx/UtHcyMg==} + + '@microsoft/1ds-post-js@3.2.18': + resolution: {integrity: sha512-Tzjcja4SMyws3UP58kD2edFPNb7BJtx5uCgwf/PWXwDyfbUY1/crsTQdEyR98wy/vorvLDZdQlcL++VMChfYnQ==} + + '@microsoft/applicationinsights-core-js@2.8.18': + resolution: {integrity: sha512-yPHRZFLpnEO0uSgFPM1BLMRRwjoten9YBbn4pJRbCT4PigLnj748knmWsMwXIdcehtkRTYz78kPYa/LWP7nvmA==} + peerDependencies: + tslib: '*' + + '@microsoft/applicationinsights-shims@2.0.2': + resolution: {integrity: sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==} + + '@microsoft/dynamicproto-js@1.1.11': + resolution: {integrity: sha512-gNw9z9LbqLV+WadZ6/MMrWwO3e0LuoUH1wve/1iPsBNbgqeVCiB0EZFNNj2lysxS2gkqoF9hmyVaG3MoM1BkxA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.3': + resolution: {integrity: sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.0': + resolution: {integrity: sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.1': + resolution: {integrity: sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + + '@octokit/plugin-paginate-rest@13.1.1': + resolution: {integrity: sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@6.0.0': + resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@16.0.0': + resolution: {integrity: sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@7.0.0': + resolution: {integrity: sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.3': + resolution: {integrity: sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==} + engines: {node: '>= 20'} + + '@octokit/rest@22.0.0': + resolution: {integrity: sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA==} + engines: {node: '>= 20'} + + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/browser-chromium@1.54.1': + resolution: {integrity: sha512-GFiRk7OvwlPrUXM3JGm5QgmzA0w2nyke0sYwigDL+rriQ+Ok7Vub0F3lIsxjHPEp5pfq+KQvzSWCMDXs0efMKQ==} + engines: {node: '>=18'} + + '@playwright/test@1.54.1': + resolution: {integrity: sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==} + engines: {node: '>=18'} + hasBin: true + + '@rollup/rollup-android-arm-eabi@4.46.1': + resolution: {integrity: sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.1': + resolution: {integrity: sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.1': + resolution: {integrity: sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.1': + resolution: {integrity: sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.1': + resolution: {integrity: sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.1': + resolution: {integrity: sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.1': + resolution: {integrity: sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.1': + resolution: {integrity: sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.1': + resolution: {integrity: sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.1': + resolution: {integrity: sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.1': + resolution: {integrity: sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.1': + resolution: {integrity: sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.1': + resolution: {integrity: sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.1': + resolution: {integrity: sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.1': + resolution: {integrity: sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.1': + resolution: {integrity: sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.1': + resolution: {integrity: sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.1': + resolution: {integrity: sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.1': + resolution: {integrity: sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.1': + resolution: {integrity: sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==} + cpu: [x64] + os: [win32] + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@sindresorhus/is@7.0.2': + resolution: {integrity: sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==} + engines: {node: '>=18'} + + '@sinonjs/commons@1.8.6': + resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@11.3.1': + resolution: {integrity: sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==} + + '@sinonjs/fake-timers@8.1.0': + resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} + + '@sinonjs/samsam@6.1.3': + resolution: {integrity: sha512-nhOb2dWPeb1sd3IQXL/dVPnKHDOAFfvichtBf4xV00/rU1QbPCQqKMbvIheIjqwVjh7qIgf2AHTHi391yMOMpQ==} + + '@sinonjs/text-encoding@0.7.3': + resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + + '@tootallnate/once@3.0.0': + resolution: {integrity: sha512-OAdBVB7rlwvLD+DiecSAyVKzKVmSfXbouCyM5I6wHGi4MGXIyFqErg1IvyJ7PI1e+GYZuZh7cCHV/c4LA8SKMw==} + engines: {node: '>= 10'} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@ts-morph/common@0.26.1': + resolution: {integrity: sha512-Sn28TGl/4cFpcM+jwsH1wLncYq3FtN/BIpem+HOygfBWPT5pAeS5dB4VFVzV8FbnOKHpDLZmvAl4AjPEev5idA==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/cookie@0.3.3': + resolution: {integrity: sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/expect@1.20.4': + resolution: {integrity: sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==} + + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + + '@types/gulp-svgmin@1.2.4': + resolution: {integrity: sha512-xoKhseGy8g1Kf1zQEX20Pkcc+qaLHpbLOktkPH0WlGHRUPLU8BRVoCQkabGUUKwjC02CAkeAKpE5Sn4Y31oIGw==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/http-proxy-agent@2.0.2': + resolution: {integrity: sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/kerberos@1.1.5': + resolution: {integrity: sha512-eljovuC0f1+6a4R8CSGwlP8P7OGygDoYJ4Yo0PtKYN4NOQEOkLH7tCQ3humCMz3lsGd0hOTyyjxHP+S3N/KtFg==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/minimatch@6.0.0': + resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} + deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + + '@types/mocha@9.1.1': + resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.9': + resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/semver@7.7.0': + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + + '@types/sinon-test@2.4.6': + resolution: {integrity: sha512-ggz+mnObtOEM64FL6PjJvfw4ratfcXv7O0yp1e1aRDLPTeWn5K19pmtEXrnZeCyxY+J31361HHsccyL0sbup8Q==} + + '@types/sinon@10.0.20': + resolution: {integrity: sha512-2APKKruFNCAZgx3daAyACGzWuJ028VVCUDk6o2rw/Z4PXT0ogwdV4KUegW0MwVs0Zu59auPXbbuBJHF12Sx1Eg==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/svgo@1.3.6': + resolution: {integrity: sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug==} + + '@types/trusted-types@1.0.6': + resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==} + + '@types/vinyl@2.0.12': + resolution: {integrity: sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==} + + '@types/vscode-notebook-renderer@1.72.3': + resolution: {integrity: sha512-MfmEI3A2McbUV2WaijoTgLOAs9chwHN4WmqOedl3jdtlbzJBWIQ9ZFmQdzPa3lYr5j8DJhRg3KB5AIM/BBfg9Q==} + + '@types/webpack@5.28.5': + resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} + + '@types/wicg-file-system-access@2020.9.8': + resolution: {integrity: sha512-ggMz8nOygG7d/stpH40WVaNvBwuyYLnrg5Mbyf6bmsj/8+gb6Ei4ZZ9/4PNpcPNTT8th9Q8sM8wYmWGjMWLX/A==} + + '@types/windows-foreground-love@0.3.1': + resolution: {integrity: sha512-aN6wdcro6KOUinabIbY8EeWnk4BzXLbUTcS3YGUDIh5lmHYif0z3gsaqi3Dg2Nk7GpRIjqVutwPMeAdn/YfChA==} + deprecated: This is a stub types definition. windows-foreground-love provides its own type definitions, so you do not need this installed. + + '@types/winreg@1.2.36': + resolution: {integrity: sha512-DtafHy5A8hbaosXrbr7YdjQZaqVewXmiasRS5J4tYMzt3s1gkh40ixpxgVFfKiQ0JIYetTJABat47v9cpr/sQg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@types/yazl@2.4.6': + resolution: {integrity: sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg==} + + '@typespec/ts-http-runtime@0.3.0': + resolution: {integrity: sha512-sOx1PKSuFwnIl7z4RN0Ls7N9AQawmR9r66eI5rFCzLDIs8HTIYrIpH9QjYWoX0lkgGrkLxXhi4QnK7MizPRrIg==} + engines: {node: '>=20.0.0'} + + '@vscode/deviceid@0.1.2': + resolution: {integrity: sha512-QZsbcKGd5JMBVoKIVT+HD9o8YWWqcmKRNWvR3qWj3iXuDo8fKaZXY3k3ZGMdOAK36fCgL0zGTDkt9vWmeBwNvQ==} + + '@vscode/gulp-electron@1.38.1': + resolution: {integrity: sha512-GKkK/Mx2Zpe6zSsraumwDmAUNkVgUfkSsRla7nIA+BpgxxXXJijY1c8QOrbrv+piD7SL66t8ewDQZu8KvJKz1w==} + engines: {node: '>=22'} + + '@vscode/iconv-lite-umd@0.7.0': + resolution: {integrity: sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==} + + '@vscode/l10n-dev@0.0.35': + resolution: {integrity: sha512-s6uzBXsVDSL69Z85HSqpc5dfKswQkeucY8L00t1TWzGalw7wkLQUKMRwuzqTq+AMwQKrRd7Po14cMoTcd11iDw==} + hasBin: true + + '@vscode/policy-watcher@1.3.2': + resolution: {integrity: sha512-fmNPYysU2ioH99uCaBPiRblEZSnir5cTmc7w91hAxAoYoGpHt2PZPxT5eIOn7FGmPOsjLdQcd6fduFJGYVD4Mw==} + + '@vscode/proxy-agent@0.32.0': + resolution: {integrity: sha512-n6h2+WVMJ3ByfGUakDbBNpR25J2JpLQabofiTKHIcLpXfxhT5TQSEH4OcjesZZfqw1zDpd7oBgcgqToWIiaBrQ==} + + '@vscode/ripgrep@1.15.14': + resolution: {integrity: sha512-/G1UJPYlm+trBWQ6cMO3sv6b8D1+G16WaJH1/DSqw32JOVlzgZbLkDxRyzIpTpv30AcYGMkCf5tUqGlW6HbDWw==} + + '@vscode/spdlog@0.15.2': + resolution: {integrity: sha512-8RQ7JEs81x5IFONYGtFhYtaF2a3IPtNtgMdp+MFLxTDokJQBAVittx0//EN38BYhlzeVqEPgusRsOA8Yulaysg==} + + '@vscode/sqlite3@5.1.8-vscode': + resolution: {integrity: sha512-9Ku18yZej1kxS7mh6dhCWxkCof043HljcLIdq+RRJr65QdOeAqPOUJ2i6qXRL63l1Kd72uXV/zLA2SBwhfgiOw==} + + '@vscode/sudo-prompt@9.3.1': + resolution: {integrity: sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA==} + + '@vscode/telemetry-extractor@1.17.0': + resolution: {integrity: sha512-HnSLeeHqR3vyXjhLaIn7oPbPOKZxjhR5CLGkgwbVGSAZAB8Vl9xH0mF3MZRL7tn9BujujlIuXuHznxffOOTtZw==} + hasBin: true + + '@vscode/test-cli@0.0.6': + resolution: {integrity: sha512-4i61OUv5PQr3GxhHOuUgHdgBDfIO/kXTPCsEyFiMaY4SOqQTgkTmyZLagHehjOgCfsXdcrJa3zgQ7zoc+Dh6hQ==} + hasBin: true + + '@vscode/test-electron@2.5.2': + resolution: {integrity: sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==} + engines: {node: '>=16'} + + '@vscode/test-web@0.0.62': + resolution: {integrity: sha512-Ypug5PvhPOPFbuHVilai7t23tm3Wm5geIpC2DB09Gy9o0jZCduramiSdPf+YN7yhkFy1usFYtN3Eaks1XoBrOQ==} + engines: {node: '>=16'} + hasBin: true + + '@vscode/tree-sitter-wasm@0.1.4': + resolution: {integrity: sha512-kQVVg/CamCYDM+/XYCZuNTQyixjZd8ts/Gf84UzjEY0eRnbg6kiy5I9z2/2i3XdqwhI87iG07rkMR2KwhqcSbA==} + + '@vscode/v8-heap-parser@0.1.0': + resolution: {integrity: sha512-3EvQak7EIOLyIGz+IP9qSwRmP08ZRWgTeoRgAXPVkkDXZ8riqJ7LDtkgx++uHBiJ3MUaSdlUYPZcLFFw7E6zGg==} + + '@vscode/vscode-languagedetection@1.0.21': + resolution: {integrity: sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g==} + hasBin: true + + '@vscode/vscode-perf@0.0.19': + resolution: {integrity: sha512-E/I0S+71K3Jo4kiMYbeKM8mUG3K8cHlj5MFVfPYVAvlp7KuIZTM914E7osp+jx8XgMLN6fChxnFmntm1GtVrKA==} + engines: {node: '>= 16'} + hasBin: true + + '@vscode/windows-ca-certs@0.3.3': + resolution: {integrity: sha512-C0Iq5RcH+H31GUZ8bsMORsX3LySVkGAqe4kQfUSVcCqJ0QOhXkhgwUMU7oCiqYLXaQWyXFp6Fj6eMdt05uK7VA==} + os: [win32] + + '@vscode/windows-mutex@0.5.0': + resolution: {integrity: sha512-iD29L9AUscpn07aAvhP2QuhrXzuKc1iQpPF6u7ybtvRbR+o+RotfbuKqqF1RDlDDrJZkL+3AZTy4D01U4nEe5A==} + + '@vscode/windows-process-tree@0.6.0': + resolution: {integrity: sha512-7/DjBKKUtlmKNiAet2GRbdvfjgMKmfBeWVClIgONv8aqxGnaKca5N85eIDxh6rLMy2hKvFqIIsqgxs1Q26TWwg==} + + '@vscode/windows-registry@1.1.0': + resolution: {integrity: sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@webgpu/types@0.1.64': + resolution: {integrity: sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==} + + '@webpack-cli/configtest@2.1.1': + resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + + '@webpack-cli/info@2.0.2': + resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + + '@webpack-cli/serve@2.0.5': + resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + webpack-dev-server: '*' + peerDependenciesMeta: + webpack-dev-server: + optional: true + + '@xmldom/xmldom@0.8.10': + resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} + engines: {node: '>=10.0.0'} + + '@xterm/addon-clipboard@0.2.0-beta.97': + resolution: {integrity: sha512-k/CCkQQvpGNTNvTFQVPDV7wd49Oxgh2pd8REKmoHcSgGZW/6jMRKElWAYv3hXujKml+fsS48XD3cbrkG52nFjw==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-image@0.9.0-beta.114': + resolution: {integrity: sha512-qY7yclSaZ+1fc/ZejNxRpa4GdvLq2cA+iaq0YBSL9OFHXejJCKwck4ZvqRwqb/WvoZSUNlqLff+2So4tFKQ51A==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-ligatures@0.10.0-beta.114': + resolution: {integrity: sha512-LTRV+EIqTMyZyko9IAlSyRdZY36aNHwLxj9uNkKDN0jIjrT67uFWL+YT4WN48XCn5Naz8nSCLam5xKldBe+o5g==} + engines: {node: '>8.0.0'} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-progress@0.2.0-beta.20': + resolution: {integrity: sha512-1RHok9oVZp1lx157GM5ebVxvF32ErEQ4ukst4GScDQBrt/qN08LdsBLbue2wDn5Z8RX/CofQ235NDThdAUCyRA==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-search@0.16.0-beta.114': + resolution: {integrity: sha512-SspxCxerty6qVd6H27zQueecLzTbx8mQ7TGjBa3a51yjZG7ee97iG+OgFxyOVOwR0xUag5vWLanWt9Pd6FtiBQ==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-serialize@0.14.0-beta.114': + resolution: {integrity: sha512-wv+QHAuAS7Be0Nr/HNQ+WrPQJN6lazpJykrFcSK1LRWYEQ1j7/OaR3UKuj0BE62sWkIazhLMx5GAJ+FY32G+4w==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-unicode11@0.9.0-beta.114': + resolution: {integrity: sha512-urjLk1DCxQ+LG1ARVl88OvD77vWmx0ySEFjeZDiVm+RO4vcwdclGyKIxWGe1wRg1y5p36zJPUnE1fbPwLr1Etg==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/addon-webgl@0.19.0-beta.114': + resolution: {integrity: sha512-6n2naFuMe65WXuet2kx7ndNNwRVW27AJfeDe7BwTX6y/t+HGO3X5G0Lji1W2fqX1vw0KRx7LTv+7qrtEvTjtQg==} + peerDependencies: + '@xterm/xterm': ^5.6.0-beta.114 + + '@xterm/headless@5.6.0-beta.114': + resolution: {integrity: sha512-RViy4ZMTgKK8bo92N3KZb2+g4nUlkJ8fiAtlDhnrczjy0lxk/0Zgoh3s2srjA3iNoGybTYqpGH4hbZrH0Ljo1g==} + + '@xterm/xterm@5.6.0-beta.114': + resolution: {integrity: sha512-OW0Pz64qQU03xCv+56mscuxBeZH5cV7u4KrRrYxfFYn6VdbuY9pkWgR0yr5IfJ0lRfazVhZyzIo9/lm2uPPk4g==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@6.4.2: + resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + all@0.0.0: + resolution: {integrity: sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==} + + amdefine@1.0.1: + resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} + engines: {node: '>=0.4.2'} + + ansi-colors@1.1.0: + resolution: {integrity: sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==} + engines: {node: '>=0.10.0'} + + ansi-colors@3.2.4: + resolution: {integrity: sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==} + engines: {node: '>=6'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-cyan@0.1.1: + resolution: {integrity: sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==} + engines: {node: '>=0.10.0'} + + ansi-gray@0.1.1: + resolution: {integrity: sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==} + engines: {node: '>=0.10.0'} + + ansi-red@0.1.1: + resolution: {integrity: sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==} + engines: {node: '>=0.10.0'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansi-wrap@0.1.0: + resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} + engines: {node: '>=0.10.0'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@2.0.0: + resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-buffer@1.0.2: + resolution: {integrity: sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==} + engines: {node: '>=0.10.0'} + + archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + arr-diff@1.1.0: + resolution: {integrity: sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==} + engines: {node: '>=0.10.0'} + + arr-diff@4.0.0: + resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} + engines: {node: '>=0.10.0'} + + arr-filter@1.1.2: + resolution: {integrity: sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==} + engines: {node: '>=0.10.0'} + + arr-flatten@1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + + arr-map@2.0.2: + resolution: {integrity: sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==} + engines: {node: '>=0.10.0'} + + arr-union@2.1.0: + resolution: {integrity: sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==} + engines: {node: '>=0.10.0'} + + arr-union@3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-differ@1.0.0: + resolution: {integrity: sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==} + engines: {node: '>=0.10.0'} + + array-each@1.0.1: + resolution: {integrity: sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==} + engines: {node: '>=0.10.0'} + + array-initial@1.1.0: + resolution: {integrity: sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==} + engines: {node: '>=0.10.0'} + + array-last@1.3.0: + resolution: {integrity: sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==} + engines: {node: '>=0.10.0'} + + array-slice@0.2.3: + resolution: {integrity: sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==} + engines: {node: '>=0.10.0'} + + array-slice@1.1.0: + resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==} + engines: {node: '>=0.10.0'} + + array-sort@1.0.0: + resolution: {integrity: sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==} + engines: {node: '>=0.10.0'} + + array-union@1.0.2: + resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} + engines: {node: '>=0.10.0'} + + array-uniq@1.0.3: + resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} + engines: {node: '>=0.10.0'} + + array-unique@0.3.2: + resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} + engines: {node: '>=0.10.0'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + asar@3.2.0: + resolution: {integrity: sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==} + engines: {node: '>=10.12.0'} + deprecated: Please use @electron/asar moving forward. There is no API change, just a package name change + hasBin: true + + assign-symbols@1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + + async-done@1.3.2: + resolution: {integrity: sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==} + engines: {node: '>= 0.10'} + + async-each@1.0.6: + resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async-settle@1.0.0: + resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==} + engines: {node: '>= 0.10'} + + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + bach@1.2.0: + resolution: {integrity: sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==} + engines: {node: '>= 0.10'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.6.0: + resolution: {integrity: sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==} + + bare-fs@4.1.6: + resolution: {integrity: sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + base@0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + + binary-extensions@1.13.1: + resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} + engines: {node: '>=0.10.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + binaryextensions@1.0.1: + resolution: {integrity: sha512-xnG0l4K3ghM62rFzDi2jcNEuICl6uQ4NgvGpqQsY7HgW8gPDeAWGOxHI/k+qZfXfMANytzrArGNPXidaCwtbmA==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + block-stream@0.0.9: + resolution: {integrity: sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==} + engines: {node: 0.4 || >=0.5.8} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + browserify-zlib@0.1.4: + resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} + + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal@1.0.1: + resolution: {integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==} + engines: {node: '>=0.4'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + c8@9.1.0: + resolution: {integrity: sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==} + engines: {node: '>=14.14.0'} + hasBin: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cache-base@1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + + cache-content-type@1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + cacheable-request@12.0.1: + resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} + engines: {node: '>=18'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@3.0.0: + resolution: {integrity: sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==} + engines: {node: '>=0.10.0'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + chokidar@2.1.8: + resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chrome-remote-interface@0.33.3: + resolution: {integrity: sha512-zNnn0prUL86Teru6UCAZ1yU1XeXljHl3gj7OrfPcarEfU62OUU4IujDPdTDW3dAWwRqN3ZMG/Chhkh2gPL/wiw==} + hasBin: true + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + chromium-pickle-js@0.2.0: + resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + + ci-info@1.6.0: + resolution: {integrity: sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==} + + class-utils@0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cliui@3.2.0: + resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-buffer@1.0.0: + resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} + engines: {node: '>= 0.10'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + clone-stats@0.0.1: + resolution: {integrity: sha512-dhUqc57gSMCo6TX85FLfe51eC/s+Im2MLkAgJwfaRRexR2tA4dd3eLEW4L6efzHc2iNorrRRXITifnDLlRrhaA==} + + clone-stats@1.0.0: + resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + cloneable-readable@1.1.3: + resolution: {integrity: sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + + code-point-at@1.1.0: + resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} + engines: {node: '>=0.10.0'} + + collection-map@1.0.0: + resolution: {integrity: sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==} + engines: {node: '>=0.10.0'} + + collection-visit@1.0.0: + resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} + engines: {node: '>=0.10.0'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + command-line-args@6.0.1: + resolution: {integrity: sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==} + engines: {node: '>=12.20'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@2.11.0: + resolution: {integrity: sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + + copy-descriptor@0.1.1: + resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} + engines: {node: '>=0.10.0'} + + copy-props@2.0.5: + resolution: {integrity: sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==} + + copy-webpack-plugin@11.0.0: + resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} + engines: {node: '>= 14.15.0'} + peerDependencies: + webpack: ^5.1.0 + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn-windows-exe@1.2.0: + resolution: {integrity: sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==} + engines: {node: '>= 10'} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + css-declaration-sorter@7.2.0: + resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.0.9 + + css-loader@6.11.0: + resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} + engines: {node: '>= 12.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + css@3.0.0: + resolution: {integrity: sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-default@6.1.2: + resolution: {integrity: sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano-utils@4.0.2: + resolution: {integrity: sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano@6.1.2: + resolution: {integrity: sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug-fabulous@1.1.0: + resolution: {integrity: sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deemon@1.13.5: + resolution: {integrity: sha512-mmQi4Rz7ehx/xmUoDKzYAfzoEIv0HIGuDL88aj7iIxxYq0OwxRc/edFqso+aUdgame1ZBGLoV4Ucd/5/WCmu4w==} + engines: {node: '>=10'} + hasBin: true + + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deepmerge-json@1.5.0: + resolution: {integrity: sha512-jZRrDmBKjmGcqMFEUJ14FjMJwm05Qaked+1vxaALRtF0UAl7lPU8OLWXFxvoeg3jbQM249VPFVn8g2znaQkEtA==} + engines: {node: '>=4.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-compare@1.0.0: + resolution: {integrity: sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==} + engines: {node: '>=0.10.0'} + + default-resolution@2.0.0: + resolution: {integrity: sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==} + engines: {node: '>= 0.10'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + define-property@0.2.5: + resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} + engines: {node: '>=0.10.0'} + + define-property@1.0.0: + resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} + engines: {node: '>=0.10.0'} + + define-property@2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + + delayed-stream@0.0.6: + resolution: {integrity: sha512-Si7mB08fdumvLNFddq3HQOoYf8BUxfITyZi+0RBn1sbojFm8c4gD1+3se7qVEji1uiVVLYE0Np0laaS9E+j6ag==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + detect-newline@2.1.0: + resolution: {integrity: sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==} + engines: {node: '>=0.10.0'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + + each-props@1.3.2: + resolution: {integrity: sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.191: + resolution: {integrity: sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==} + + electron@34.4.1: + resolution: {integrity: sha512-iYzeLBdCrAR3i0RVSLa+mzuFZwH6HGxTGKsI+SS41sg2anZj4R5mHjOiHsxcZ50/ih47NJbuVRJgPIVlTF+USg==} + engines: {node: '>= 12.20.55'} + hasBin: true + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.18.2: + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + envinfo@7.14.0: + resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==} + engines: {node: '>=4'} + hasBin: true + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + eventsource-parser@3.0.3: + resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} + engines: {node: '>=20.0.0'} + + expand-brackets@2.1.4: + resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} + engines: {node: '>=0.10.0'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + extend-shallow@1.1.4: + resolution: {integrity: sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==} + engines: {node: '>=0.10.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend-shallow@3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extglob@2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fancy-log@1.3.3: + resolution: {integrity: sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==} + engines: {node: '>= 0.10'} + + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@1.1.4: + resolution: {integrity: sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==} + + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-loader@6.2.0: + resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@4.0.0: + resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} + engines: {node: '>=0.10.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-parent-dir@0.3.1: + resolution: {integrity: sha512-o4UcykWV/XN9wm+jMEtWLPlV8RXCZnMhQI6F6OdHeSez7iiJWePw8ijOlskJZMsaQoGR/b7dH6lO02HhaTN7+A==} + + find-replace@5.0.2: + resolution: {integrity: sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==} + engines: {node: '>=14'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true + + find-up@1.1.2: + resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} + engines: {node: '>=0.10.0'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + findup-sync@2.0.0: + resolution: {integrity: sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==} + engines: {node: '>= 0.10'} + + findup-sync@3.0.0: + resolution: {integrity: sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==} + engines: {node: '>= 0.10'} + + fined@1.2.0: + resolution: {integrity: sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==} + engines: {node: '>= 0.10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flagged-respawn@1.0.1: + resolution: {integrity: sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==} + engines: {node: '>= 0.10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + flush-write-stream@1.1.1: + resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==} + + font-finder@1.1.0: + resolution: {integrity: sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==} + engines: {node: '>8.0.0'} + + font-ligatures@1.4.1: + resolution: {integrity: sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==} + engines: {node: '>8.0.0'} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + + for-own@1.0.0: + resolution: {integrity: sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==} + engines: {node: '>=0.10.0'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data-encoder@4.1.0: + resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} + engines: {node: '>= 18'} + + fragment-cache@0.2.1: + resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} + engines: {node: '>=0.10.0'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-mkdirp-stream@1.0.0: + resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} + engines: {node: '>= 0.10'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@1.2.13: + resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} + engines: {node: '>= 4.0'} + os: [darwin] + deprecated: Upgrade to fsevents v2 to mitigate potential security issues + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@1.0.3: + resolution: {integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stdin@7.0.0: + resolution: {integrity: sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==} + engines: {node: '>=8'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-system-fonts@2.0.2: + resolution: {integrity: sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==} + engines: {node: '>8.0.0'} + + get-value@2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob-parent@3.1.0: + resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-stream@6.1.0: + resolution: {integrity: sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==} + engines: {node: '>= 0.10'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob-watcher@5.0.5: + resolution: {integrity: sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==} + engines: {node: '>= 0.10'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + glob@5.0.15: + resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + glogg@1.0.2: + resolution: {integrity: sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==} + engines: {node: '>= 0.10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + got@14.4.7: + resolution: {integrity: sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==} + engines: {node: '>=20'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + gulp-azure-storage@0.12.1: + resolution: {integrity: sha512-n/hx8bbGsqrcizruqDTX6zy2FUdkTDGAz04IdopNxNTZivZmizf8u9WLYJreUE6/qCnSJnyjS1HP82+mLk7rjg==} + hasBin: true + + gulp-bom@3.0.0: + resolution: {integrity: sha512-iw/J94F+MVlxG64Q17BSkHsyjpY17qHl3N3A/jDdrL77zQBkhKtTiKLqM4di9CUX/qFToyyeDsOWwH+rESBgmA==} + engines: {node: '>=8'} + peerDependencies: + gulp: '>=4' + peerDependenciesMeta: + gulp: + optional: true + + gulp-buffer@0.0.2: + resolution: {integrity: sha512-EBkbjjTH2gRr2B8KBAcomdTemfZHqiKs8CxSYdaW0Hq3zxltQFrCg9BBmKVHC9cfxX/3l2BZK5oiGHYNJ/gcVw==} + + gulp-cli@2.3.0: + resolution: {integrity: sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==} + engines: {node: '>= 0.10'} + hasBin: true + + gulp-filter@5.1.0: + resolution: {integrity: sha512-ZERu1ipbPmjrNQ2dQD6lL4BjrJQG66P/c5XiyMMBqV+tUAJ+fLOyYIL/qnXd2pHmw/G/r7CLQb9ttANvQWbpfQ==} + engines: {node: '>=4'} + + gulp-flatmap@1.0.2: + resolution: {integrity: sha512-xm+Ax2vPL/xiMBqLFI++wUyPtncm3b55ztGHewmRcoG/sYb0OUTatjSacOud3fee77rnk+jOgnDEHhwBtMHgFA==} + engines: {node: '>=0.10.0'} + + gulp-gunzip@1.1.0: + resolution: {integrity: sha512-3INeprGyz5fUtAs75k6wVslGuRZIjKAoQp39xA7Bz350ReqkrfYaLYqjZ67XyIfLytRXdzeX04f+DnBduYhQWw==} + + gulp-gzip@1.4.2: + resolution: {integrity: sha512-ZIxfkUwk2XmZPTT9pPHrHUQlZMyp9nPhg2sfoeN27mBGpi7OaHnOD+WCN41NXjfJQ69lV1nQ9LLm1hYxx4h3UQ==} + engines: {node: '>= 0.10.0'} + + gulp-json-editor@2.6.0: + resolution: {integrity: sha512-Ni0ZUpNrhesHiTlHQth/Nv1rXCn0LUicEvzA5XuGy186C4PVeNoRjfuAIQrbmt3scKv8dgGbCs0hd77ScTw7hA==} + + gulp-plumber@1.2.1: + resolution: {integrity: sha512-mctAi9msEAG7XzW5ytDVZ9PxWMzzi1pS2rBH7lA095DhMa6KEXjm+St0GOCc567pJKJ/oCvosVAZEpAey0q2eQ==} + engines: {node: '>=0.10', npm: '>=1.2.10'} + + gulp-rename@1.2.2: + resolution: {integrity: sha512-qhfUlYwq5zIP4cpRjx+np2vZVnr0xCRQrF3RsGel8uqL47Gu3yjmllSfnvJyl/39zYuxS68e1nnxImbm7388vw==} + engines: {node: '>=0.10.0', npm: '>=1.2.10'} + + gulp-rename@1.4.0: + resolution: {integrity: sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==} + engines: {node: '>=4'} + + gulp-replace@0.5.4: + resolution: {integrity: sha512-lHL+zKJN8uV95UkONnfRkoj2yJxPPupt2SahxA4vo5c+Ee3+WaIiMdWbOyUhg8BhAROQrWKnnxKOWPdVrnBwGw==} + engines: {node: '>=0.10'} + + gulp-sourcemaps@3.0.0: + resolution: {integrity: sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==} + engines: {node: '>= 6'} + + gulp-svgmin@4.1.0: + resolution: {integrity: sha512-WKpif+yu3+oIlp1e11CQi5F64YddP699l2mFmxpz8swv8/P8dhxVcMKdCPFWouArlVyn7Ma1eWCJHw5gx4NMtw==} + + gulp-symdest@1.3.0: + resolution: {integrity: sha512-n1VaNYMpyOq4GfyQyIwRZhXOOsQVdEy56BCFxL4hu+stKwYeSQcZxLX5FOZL6jZUlBYXCWlXL+E5JU13ZMldIw==} + + gulp-untar@0.0.7: + resolution: {integrity: sha512-0QfbCH2a1k2qkTLWPqTX+QO4qNsHn3kC546YhAP3/n0h+nvtyGITDuDrYBMDZeW4WnFijmkOvBWa5HshTic1tw==} + + gulp-vinyl-zip@2.5.0: + resolution: {integrity: sha512-KPi5/2SUmkXXDvKU4L2U1dkPOP03SbhONTOgNZlL23l9Yopt+euJ1bBXwWrSMbsyh3JLW/TYuC8CI4c4Kq4qrw==} + engines: {node: '>= 10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + gulp@4.0.2: + resolution: {integrity: sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==} + engines: {node: '>= 0.10'} + hasBin: true + + gulplog@1.0.0: + resolution: {integrity: sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==} + engines: {node: '>= 0.10'} + + gunzip-maybe@1.4.2: + resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} + hasBin: true + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-value@0.3.1: + resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} + engines: {node: '>=0.10.0'} + + has-value@1.0.0: + resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} + engines: {node: '>=0.10.0'} + + has-values@0.1.4: + resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} + engines: {node: '>=0.10.0'} + + has-values@1.0.0: + resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} + engines: {node: '>=0.10.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + husky@0.13.4: + resolution: {integrity: sha512-kafsK/82ndSVKJe1IoR/z7NKkiI2LYM6H+VNI/YlKOeoOXEJTpD65TNu05Zx7pzSZzLuAdMt4fHgpUsnd6HJ7A==} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + innosetup@6.4.1: + resolution: {integrity: sha512-Bh/dNlyyFsFKBGoTf19n1msPnm4Njfb0CWVirw2CV1MSJXYy0ajRI+xjo/Yr9DtZFLLafhnwm1ZsxTu309F8lQ==} + engines: {node: '>= 0.8.0'} + hasBin: true + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + + interpret@3.1.1: + resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} + engines: {node: '>=10.13.0'} + + invert-kv@1.0.0: + resolution: {integrity: sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==} + engines: {node: '>=0.10.0'} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + + is-accessor-descriptor@1.0.1: + resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} + engines: {node: '>= 0.10'} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@1.0.1: + resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} + engines: {node: '>=0.10.0'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-ci@1.2.1: + resolution: {integrity: sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==} + hasBin: true + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-descriptor@1.0.1: + resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-deflate@1.0.0: + resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} + + is-descriptor@0.1.7: + resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} + engines: {node: '>= 0.4'} + + is-descriptor@1.0.3: + resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@1.0.0: + resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@3.1.0: + resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-gzip@1.0.0: + resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} + engines: {node: '>=0.10.0'} + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negated-glob@1.0.0: + resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==} + engines: {node: '>=0.10.0'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@3.0.0: + resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} + engines: {node: '>=0.10.0'} + + is-number@4.0.0: + resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + + is-valid-glob@1.0.0: + resolution: {integrity: sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==} + engines: {node: '>=0.10.0'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@2.1.0: + resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} + engines: {node: '>=0.10.0'} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + istextorbinary@1.0.2: + resolution: {integrity: sha512-qZ5ptUDuni2pdCngFTraYa5kalQ0mX47Mhn08tT0DZZv/7yhX1eMb9lFtXVbWhFtgRtpLG/UdqVAjh9teO5x+w==} + engines: {node: '>=0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jschardet@3.1.4: + resolution: {integrity: sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==} + engines: {node: '>=0.1.90'} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + just-debounce@1.1.0: + resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==} + + just-extend@6.2.0: + resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} + + kerberos@2.1.1: + resolution: {integrity: sha512-414s1G/qgK2T60cXnZsHbtRj8Ynjg0DBlQWeY99tkyqQ2e8vGgFHvxRdvjTlLHg/SxBA0zLQcGE6Pk6Dfq/BCA==} + engines: {node: '>=12.9.0'} + + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@1.1.0: + resolution: {integrity: sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==} + engines: {node: '>=0.10.0'} + + kind-of@3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + + kind-of@4.0.0: + resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} + engines: {node: '>=0.10.0'} + + kind-of@5.1.0: + resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} + engines: {node: '>=0.10.0'} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa-convert@2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + + koa-morgan@1.0.1: + resolution: {integrity: sha512-JOUdCNlc21G50afBXfErUrr1RKymbgzlrO5KURY+wmDG1Uvd2jmxUJcHgylb/mYXy2SjiNZyYim/ptUBGsIi3A==} + + koa-mount@4.2.0: + resolution: {integrity: sha512-2iHQc7vbA9qLeVq5gKAYh3m5DOMMlMfIKjW/REPAS18Mf63daCJHHVXY9nbu7ivrnYn5PiPC4CE523Tf5qvjeQ==} + engines: {node: '>= 7.6.0'} + + koa-send@5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + + koa-static@5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + + koa@2.16.1: + resolution: {integrity: sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + + last-run@1.1.1: + resolution: {integrity: sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==} + engines: {node: '>= 0.10'} + + lazy.js@0.4.3: + resolution: {integrity: sha512-kHcnVaCZzhv6P+YgC4iRZFw62+biYIcBYU8qqKzJysC7cdKwPgb3WRtcBPyINTSLZwsjyFdBtd97sHbkseTZKw==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lcid@1.0.0: + resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==} + engines: {node: '>=0.10.0'} + + lead@1.0.0: + resolution: {integrity: sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==} + engines: {node: '>= 0.10'} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + liftoff@3.1.0: + resolution: {integrity: sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==} + engines: {node: '>= 0.8'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + load-json-file@1.1.0: + resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} + engines: {node: '>=0.10.0'} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + loader-utils@2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clone@4.5.0: + resolution: {integrity: sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==} + deprecated: This package is deprecated. Use structuredClone instead. + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.some@4.6.0: + resolution: {integrity: sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + make-iterator@1.0.1: + resolution: {integrity: sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==} + engines: {node: '>=0.10.0'} + + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + + map-visit@1.0.0: + resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} + engines: {node: '>=0.10.0'} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + matchdep@2.0.0: + resolution: {integrity: sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==} + engines: {node: '>= 0.10.0'} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memoizee@0.4.17: + resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} + engines: {node: '>=0.12'} + + memory-fs@0.5.0: + resolution: {integrity: sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==} + engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + merge-options@1.0.1: + resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==} + engines: {node: '>=4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@3.1.10: + resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==} + engines: {node: '>=0.10.0'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mixin-deep@1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + mocha-junit-reporter@2.2.1: + resolution: {integrity: sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==} + peerDependencies: + mocha: '>=2.2.5' + + mocha-multi-reporters@1.5.1: + resolution: {integrity: sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==} + engines: {node: '>=6.0.0'} + peerDependencies: + mocha: '>=3.1.2' + + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + + morgan@1.10.1: + resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} + engines: {node: '>= 0.8.0'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multimatch@2.1.0: + resolution: {integrity: sha512-0mzK8ymiWdehTBiJh0vClAzGyQbdtyWqzSVx//EK4N/D+599RFlGfTAsKw2zMSABtDG9C6Ul2+t8f2Lbdjf5mA==} + engines: {node: '>=0.10.0'} + + mute-stdout@1.0.1: + resolution: {integrity: sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==} + engines: {node: '>= 0.10'} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nan@2.23.0: + resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanomatch@1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + native-is-elevated@0.7.0: + resolution: {integrity: sha512-tp8hUqK7vexBiyIWKMvmRxdG6kqUtO+3eay9iB0i16NYgvCqE5wMe1Y0guHilpkmRgvVXEWNW4et1+qqcwpLBA==} + + native-keymap@3.3.5: + resolution: {integrity: sha512-7XDOLPNX1FnUFC/cX3cioBz2M+dO212ai9DuwpfKFzkPu3xTmEzOm5xewOMLXE4V9YoRhNPxvq1H2YpPWDgSsg==} + + native-watchdog@1.4.2: + resolution: {integrity: sha512-iT3Uj6FFdrW5vHbQ/ybiznLus9oiUoMJ8A8nyugXv9rV3EBhIodmGs+mztrwQyyBc+PB5/CrskAH/WxaUVRRSQ==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + nise@5.1.9: + resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==} + + node-abi@3.75.0: + resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + engines: {node: '>=10'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + node-addon-api@7.1.0: + resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} + engines: {node: ^16 || ^18 || >= 20} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + engines: {node: ^18 || ^20 || >= 21} + + node-fetch@2.6.8: + resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-html-markdown@1.3.0: + resolution: {integrity: sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g==} + engines: {node: '>=10.0.0'} + + node-html-parser@6.1.13: + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} + + node-pty@1.1.0-beta9: + resolution: {integrity: sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@1.0.0: + resolution: {integrity: sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA==} + engines: {node: '>=0.10.0'} + + normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + normalize-url@8.0.2: + resolution: {integrity: sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==} + engines: {node: '>=14.16'} + + now-and-later@2.0.1: + resolution: {integrity: sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==} + engines: {node: '>= 0.10'} + + npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + number-is-nan@1.0.1: + resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-copy@0.1.0: + resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@0.4.0: + resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object-visit@1.0.1: + resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} + engines: {node: '>=0.10.0'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.defaults@1.1.0: + resolution: {integrity: sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==} + engines: {node: '>=0.10.0'} + + object.map@1.0.1: + resolution: {integrity: sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==} + engines: {node: '>=0.10.0'} + + object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + + object.reduce@1.0.1: + resolution: {integrity: sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==} + engines: {node: '>=0.10.0'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + only@0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + opentype.js@0.8.0: + resolution: {integrity: sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg==} + hasBin: true + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + ordered-read-streams@1.0.1: + resolution: {integrity: sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==} + + os-browserify@0.3.0: + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + + os-locale@1.4.0: + resolution: {integrity: sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-all@1.0.0: + resolution: {integrity: sha512-OtbznqfGjQT+i89LK9C9YPh1G8d6n8xgsJ8yRVXrx6PRXrlOthNJhP+dHxrPopty8fugYb1DodpwrzP7z0Mtvw==} + engines: {node: '>=4'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-cancelable@4.0.1: + resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} + engines: {node: '>=14.16'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@1.2.0: + resolution: {integrity: sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==} + engines: {node: '>=4'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parse-filepath@1.0.2: + resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} + engines: {node: '>=0.8'} + + parse-json@2.2.0: + resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} + engines: {node: '>=0.10.0'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascalcase@0.1.1: + resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} + engines: {node: '>=0.10.0'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-dirname@1.0.2: + resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} + + path-exists@2.1.0: + resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} + engines: {node: '>=0.10.0'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-root-regex@0.1.2: + resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==} + engines: {node: '>=0.10.0'} + + path-root@0.1.1: + resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} + engines: {node: '>=0.10.0'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@1.1.0: + resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} + engines: {node: '>=0.10.0'} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + + peek-stream@1.1.3: + resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@0.2.1: + resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + playwright-core@1.54.1: + resolution: {integrity: sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.54.1: + resolution: {integrity: sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==} + engines: {node: '>=18'} + hasBin: true + + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + + plugin-error@0.1.2: + resolution: {integrity: sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==} + engines: {node: '>=0.10.0'} + + plugin-error@1.0.1: + resolution: {integrity: sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==} + engines: {node: '>= 0.10'} + + plugin-error@2.0.1: + resolution: {integrity: sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==} + engines: {node: '>=10.13.0'} + + posix-character-classes@0.1.1: + resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} + engines: {node: '>=0.10.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-calc@9.0.1: + resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.2.2 + + postcss-colormin@6.1.0: + resolution: {integrity: sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-convert-values@6.1.0: + resolution: {integrity: sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-comments@6.0.2: + resolution: {integrity: sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-duplicates@6.0.3: + resolution: {integrity: sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-empty@6.0.3: + resolution: {integrity: sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-overridden@6.0.2: + resolution: {integrity: sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-merge-longhand@6.0.5: + resolution: {integrity: sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-merge-rules@6.1.1: + resolution: {integrity: sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-font-values@6.1.0: + resolution: {integrity: sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-gradients@6.0.3: + resolution: {integrity: sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-params@6.1.0: + resolution: {integrity: sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-selectors@6.0.4: + resolution: {integrity: sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-nesting@12.1.5: + resolution: {integrity: sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-normalize-charset@6.0.2: + resolution: {integrity: sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-display-values@6.0.2: + resolution: {integrity: sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-positions@6.0.2: + resolution: {integrity: sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-repeat-style@6.0.2: + resolution: {integrity: sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-string@6.0.2: + resolution: {integrity: sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-timing-functions@6.0.2: + resolution: {integrity: sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-unicode@6.1.0: + resolution: {integrity: sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-url@6.0.2: + resolution: {integrity: sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-whitespace@6.0.2: + resolution: {integrity: sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-ordered-values@6.0.2: + resolution: {integrity: sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-initial@6.1.0: + resolution: {integrity: sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-transforms@6.0.2: + resolution: {integrity: sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss-svgo@6.0.3: + resolution: {integrity: sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==} + engines: {node: ^14 || ^16 || >= 18} + peerDependencies: + postcss: ^8.4.31 + + postcss-unique-selectors@6.0.4: + resolution: {integrity: sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@7.0.39: + resolution: {integrity: sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==} + engines: {node: '>=6.0.0'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + pretty-hrtime@1.0.3: + resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} + engines: {node: '>= 0.8'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@1.1.8: + resolution: {integrity: sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==} + engines: {node: '>=0.4.0'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-stream-reader@1.0.1: + resolution: {integrity: sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==} + engines: {node: '>8.0.0'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + pseudo-localization@2.4.0: + resolution: {integrity: sha512-ISYMOKY8+f+PmiXMFw2y6KLY74LBrv/8ml/VjjoVEV2k+MS+OJZz7ydciK5ntJwxPrKQPTU1+oXq9Mx2b0zEzg==} + hasBin: true + + pump@1.0.3: + resolution: {integrity: sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==} + + pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + pumpify@1.5.1: + resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue@3.1.0: + resolution: {integrity: sha512-z3XqdkYJ/YVQuAAoAKLcePEk2BZDMZR2jv2hTrpQb0K5M0dUbiwADr48N1F63M4ChD/GwPc/LeaA9VC5dJFfTA==} + + queue@4.5.1: + resolution: {integrity: sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + rcedit@1.1.2: + resolution: {integrity: sha512-z2ypB4gbINhI6wVe0JJMmdpmOpmNc4g90sE6/6JSuch5kYnjfz9CxvVPqqhShgR6GIkmtW3W2UlfiXhWljA0Fw==} + + rcedit@4.0.1: + resolution: {integrity: sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg==} + engines: {node: '>= 14.0.0'} + + read-pkg-up@1.0.1: + resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} + engines: {node: '>=0.10.0'} + + read-pkg@1.1.0: + resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} + engines: {node: '>=0.10.0'} + + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + + readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@2.2.1: + resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} + engines: {node: '>=0.10'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regex-not@1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remove-bom-buffer@3.0.0: + resolution: {integrity: sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==} + engines: {node: '>=0.10.0'} + + remove-bom-stream@1.2.0: + resolution: {integrity: sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==} + engines: {node: '>= 0.10'} + + remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + + repeat-element@1.1.4: + resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} + engines: {node: '>=0.10.0'} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + replace-ext@0.0.1: + resolution: {integrity: sha512-AFBWBy9EVRTa/LhEcG8QDP3FvpwZqmvN2QFDuJswFeaVhWnZMp8q3E6Zd90SR04PlIwfGdyVjNyLPyen/ek5CQ==} + engines: {node: '>= 0.4'} + + replace-ext@1.0.1: + resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==} + engines: {node: '>= 0.10'} + + replace-ext@2.0.0: + resolution: {integrity: sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==} + engines: {node: '>= 10'} + + replace-homedir@1.0.0: + resolution: {integrity: sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==} + engines: {node: '>= 0.10'} + + replacestream@4.0.3: + resolution: {integrity: sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@1.0.1: + resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-options@1.1.0: + resolution: {integrity: sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==} + engines: {node: '>= 0.10'} + + resolve-path@1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + + resolve-url@0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + rollup@4.46.1: + resolution: {integrity: sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex@1.1.0: + resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.2: + resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + engines: {node: '>= 10.13.0'} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver-greatest-satisfied-range@1.1.0: + resolution: {integrity: sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==} + engines: {node: '>= 0.10'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + sinon-test@3.1.6: + resolution: {integrity: sha512-3jBJGf61sS2EN3M+YuIiIbeutKrubP6SFolceTcJrubG+4s+zq3rey/y0huSEwU2ECKOcyCs7EkzANnwqHWPjA==} + peerDependencies: + sinon: '>= 2.x' + + sinon@12.0.1: + resolution: {integrity: sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==} + deprecated: 16.1.1 + + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + snapdragon-node@2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + + snapdragon-util@3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + + snapdragon@0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.6: + resolution: {integrity: sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-resolve@0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + + source-map-resolve@0.6.0: + resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + + source-map-support@0.3.3: + resolution: {integrity: sha512-9O4+y9n64RewmFoKUZ/5Tx9IHIcXM6Q+RTSw6ehnqybUz4a7iwR3Eaw80uLtqqQ5D0C+5H03D4KKGo9PdP33Gg==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map-url@0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + + source-map@0.1.32: + resolution: {integrity: sha512-htQyLrrRLkQ87Zfrir4/yN+vAUd6DNjVayEjTSHXu29AYQJw57I4/xEL/M6p6E/woPNJwvZt6rVlzc7gFEJccQ==} + engines: {node: '>=0.8.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + sparkles@1.0.1: + resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==} + engines: {node: '>= 0.10'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + + split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + static-extend@0.1.2: + resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} + engines: {node: '>=0.10.0'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + + stream-exhaust@1.0.2: + resolution: {integrity: sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + + streamfilter@1.0.7: + resolution: {integrity: sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==} + + streamifier@0.1.1: + resolution: {integrity: sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==} + engines: {node: '>=0.10'} + + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + + string-width@1.0.2: + resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.padend@3.1.6: + resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-bom@2.0.0: + resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} + engines: {node: '>=0.10.0'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + + style-loader@3.3.4: + resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + + stylehacks@6.1.1: + resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==} + engines: {node: ^14 || ^16 || >=18.0} + peerDependencies: + postcss: ^8.4.31 + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + sver-compat@1.5.0: + resolution: {integrity: sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar-fs@2.1.3: + resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + tar@2.2.2: + resolution: {integrity: sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==} + deprecated: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap. + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tas-client-umd@0.2.0: + resolution: {integrity: sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + temp@0.8.4: + resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} + engines: {node: '>=6.0.0'} + + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.43.1: + resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + textextensions@1.0.2: + resolution: {integrity: sha512-jm9KjEWiDmtGLBrTqXEduGzlYTTlPaoDKdq5YRQhD0rYjo61ZNTYKZ/x5J4ajPSBH9wIYY5qm9GNG5otIKjtOA==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through2-filter@3.0.0: + resolution: {integrity: sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==} + + through2@0.4.2: + resolution: {integrity: sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==} + + through2@2.0.3: + resolution: {integrity: sha512-tmNYYHFqXmaKSSlOU4ZbQ82cxmFQa5LRWKFtWCNkGIiZ3/VHmOffCeWfBRZZRyXAhNP9itVMR+cuvomBOPlm8g==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + through2@3.0.2: + resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + time-stamp@1.1.0: + resolution: {integrity: sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==} + engines: {node: '>=0.10.0'} + + timers-ext@0.1.8: + resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} + engines: {node: '>=0.12'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + to-absolute-glob@2.0.2: + resolution: {integrity: sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==} + engines: {node: '>=0.10.0'} + + to-object-path@0.3.0: + resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} + engines: {node: '>=0.10.0'} + + to-regex-range@2.1.1: + resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} + engines: {node: '>=0.10.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + to-regex@3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + + to-through@2.0.0: + resolution: {integrity: sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==} + engines: {node: '>= 0.10'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-loader@9.5.2: + resolution: {integrity: sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + ts-morph@25.0.1: + resolution: {integrity: sha512-QJEiTdnz1YjrB3JFhd626gX4rKHDLSjSVMvGGG4v7ONc3RBwa0Eei98G9AT9uNFDMtV54JyuXsFeC+OH0n6bXQ==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + tsup@8.5.0: + resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + + undertaker-registry@1.0.1: + resolution: {integrity: sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==} + engines: {node: '>= 0.10'} + + undertaker@1.3.0: + resolution: {integrity: sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==} + engines: {node: '>= 0.10'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@7.12.0: + resolution: {integrity: sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==} + engines: {node: '>=20.18.1'} + + union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + + unique-stream@2.3.1: + resolution: {integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unset-value@1.0.0: + resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + engines: {node: '>=0.10.0'} + + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urix@0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + + use@3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-inspect-profiler@0.1.1: + resolution: {integrity: sha512-GB3X9w7w+y9v4gq85olmf/bM3F2hj2DjjwvVpDXIziW5JBy8cDcIQ/K7m8xJVDWiFemxRX2Dxoo1k6JDvLMTig==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + v8flags@3.2.0: + resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} + engines: {node: '>= 0.10'} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + value-or-function@3.0.0: + resolution: {integrity: sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vinyl-fs@3.0.3: + resolution: {integrity: sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==} + engines: {node: '>= 0.10'} + + vinyl-sourcemap@1.1.0: + resolution: {integrity: sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==} + engines: {node: '>= 0.10'} + + vinyl@1.2.0: + resolution: {integrity: sha512-Ci3wnR2uuSAWFMSglZuB8Z2apBdtOyz8CV7dC6/U1XbltXBC+IuutUkXQISz01P+US2ouBuesSbV6zILZ6BuzQ==} + engines: {node: '>= 0.9'} + + vinyl@2.0.2: + resolution: {integrity: sha512-ViPXqulxjb1yXxaf/kQZfLHkd2ppnVBWPq4XmvW377vcBTxHFtHR5NRfYsdXsiKpWndKRoCdn11DfEnoCz1Inw==} + engines: {node: '>= 0.10'} + + vinyl@2.2.1: + resolution: {integrity: sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==} + engines: {node: '>= 0.10'} + + vinyl@3.0.1: + resolution: {integrity: sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==} + engines: {node: '>=10.13.0'} + + vscode-oniguruma@1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + + vscode-regexpp@3.1.0: + resolution: {integrity: sha512-pqtN65VC1jRLawfluX4Y80MMG0DHJydWhe5ZwMHewZD6sys4LbU6lHwFAHxeuaVE6Y6+xZOtAw+9hvq7/0ejkg==} + engines: {node: '>=8'} + + vscode-textmate@9.2.0: + resolution: {integrity: sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + watchpack@2.4.4: + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + engines: {node: '>=10.13.0'} + + web-tree-sitter@0.20.8: + resolution: {integrity: sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + webpack-cli@5.1.4: + resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} + engines: {node: '>=14.15.0'} + hasBin: true + peerDependencies: + '@webpack-cli/generators': '*' + webpack: 5.x.x + webpack-bundle-analyzer: '*' + webpack-dev-server: '*' + peerDependenciesMeta: + '@webpack-cli/generators': + optional: true + webpack-bundle-analyzer: + optional: true + webpack-dev-server: + optional: true + + webpack-merge@5.10.0: + resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} + engines: {node: '>=10.0.0'} + + webpack-sources@3.3.3: + resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + engines: {node: '>=10.13.0'} + + webpack-stream@7.0.0: + resolution: {integrity: sha512-XoAQTHyCaYMo6TS7Atv1HYhtmBgKiVLONJbzLBl2V3eibXQ2IT/MCRM841RW/r3vToKD5ivrTJFWgd/ghoxoRg==} + engines: {node: '>= 10.0.0'} + peerDependencies: + webpack: ^5.21.2 + + webpack@5.100.2: + resolution: {integrity: sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@1.0.0: + resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wildcard@2.0.1: + resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + + windows-foreground-love@0.5.0: + resolution: {integrity: sha512-yjBwmKEmQBDk3Z7yg/U9hizGWat8C6Pe4MQWl5bN6mvPU81Bt6HV2k/6mGlK3ETJLW1hCLhYx2wcGh+ykUUCyA==} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@2.1.0: + resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + + xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + xtend@2.1.2: + resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} + engines: {node: '>=0.4'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@3.2.2: + resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-parser@5.0.1: + resolution: {integrity: sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yargs@7.1.2: + resolution: {integrity: sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==} + + yaserver@0.4.0: + resolution: {integrity: sha512-98Vj4sgqB1fLcpf2wK7h3dFCaabISHU9CXZHaAx3QLkvTTCD31MzMcNbw5V5jZFBK7ffkFqfWig6B20KQt4wtA==} + hasBin: true + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yauzl@3.2.0: + resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + engines: {node: '>=12'} + + yazl@2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + + ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} + engines: {node: '>= 4.0.0'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@azure-rest/ai-translation-text@1.0.1': + dependencies: + '@azure-rest/core-client': 2.5.0 + '@azure/core-auth': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure-rest/core-client@2.5.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@typespec/ts-http-runtime': 0.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.10.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.13.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-client@1.10.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@azure/core-util': 1.13.0 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-http-compat@2.3.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-client': 1.10.0 + '@azure/core-rest-pipeline': 1.22.0 + transitivePeerDependencies: + - supports-color + + '@azure/core-lro@2.7.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.13.0 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.22.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-tracing': 1.3.0 + '@azure/core-util': 1.13.0 + '@azure/logger': 1.3.0 + '@typespec/ts-http-runtime': 0.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.3.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.13.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@typespec/ts-http-runtime': 0.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-xml@1.5.0': + dependencies: + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@azure/logger@1.3.0': + dependencies: + '@typespec/ts-http-runtime': 0.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/storage-blob@12.28.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-client': 1.10.0 + '@azure/core-http-compat': 2.3.0 + '@azure/core-lro': 2.7.2 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@azure/core-util': 1.13.0 + '@azure/core-xml': 1.5.0 + '@azure/logger': 1.3.0 + '@azure/storage-common': 12.0.0 + events: 3.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/storage-common@12.0.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.0 + '@azure/core-http-compat': 2.3.0 + '@azure/core-rest-pipeline': 1.22.0 + '@azure/core-tracing': 1.3.0 + '@azure/core-util': 1.13.0 + '@azure/logger': 1.3.0 + events: 3.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.28.2 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.2': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@0.2.3': {} + + '@c4312/eventsource-umd@3.0.5': + dependencies: + eventsource-parser: 3.0.3 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@csstools/selector-specificity@3.1.1(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@discoveryjs/json-ext@0.5.7': {} + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.1(supports-color@8.1.1) + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/get@4.0.2': + dependencies: + debug: 4.4.1(supports-color@8.1.1) + env-paths: 3.0.0 + got: 14.4.7 + graceful-fs: 4.2.11 + progress: 2.0.3 + semver: 7.7.2 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + + '@gulp-sourcemaps/identity-map@2.0.1': + dependencies: + acorn: 6.4.2 + normalize-path: 3.0.0 + postcss: 7.0.39 + source-map: 0.6.1 + through2: 3.0.2 + + '@gulp-sourcemaps/map-sources@1.0.0': + dependencies: + normalize-path: 2.1.1 + through2: 2.0.5 + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.10': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@koa/cors@5.0.0': + dependencies: + vary: 1.1.2 + + '@koa/router@13.1.1': + dependencies: + debug: 4.4.1(supports-color@8.1.1) + http-errors: 2.0.0 + koa-compose: 4.1.0 + path-to-regexp: 6.3.0 + transitivePeerDependencies: + - supports-color + + '@malept/cross-spawn-promise@1.1.1': + dependencies: + cross-spawn: 7.0.6 + + '@microsoft/1ds-core-js@3.2.18(tslib@2.8.1)': + dependencies: + '@microsoft/applicationinsights-core-js': 2.8.18(tslib@2.8.1) + '@microsoft/applicationinsights-shims': 2.0.2 + '@microsoft/dynamicproto-js': 1.1.11 + transitivePeerDependencies: + - tslib + + '@microsoft/1ds-post-js@3.2.18(tslib@2.8.1)': + dependencies: + '@microsoft/1ds-core-js': 3.2.18(tslib@2.8.1) + '@microsoft/applicationinsights-shims': 2.0.2 + '@microsoft/dynamicproto-js': 1.1.11 + transitivePeerDependencies: + - tslib + + '@microsoft/applicationinsights-core-js@2.8.18(tslib@2.8.1)': + dependencies: + '@microsoft/applicationinsights-shims': 2.0.2 + '@microsoft/dynamicproto-js': 1.1.11 + tslib: 2.8.1 + + '@microsoft/applicationinsights-shims@2.0.2': {} + + '@microsoft/dynamicproto-js@1.1.11': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.3': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.1 + '@octokit/request': 10.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.0': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.1': + dependencies: + '@octokit/request': 10.0.3 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@25.1.0': {} + + '@octokit/plugin-paginate-rest@13.1.1(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/types': 14.1.0 + + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + + '@octokit/plugin-rest-endpoint-methods@16.0.0(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/types': 14.1.0 + + '@octokit/request-error@7.0.0': + dependencies: + '@octokit/types': 14.1.0 + + '@octokit/request@10.0.3': + dependencies: + '@octokit/endpoint': 11.0.0 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + + '@octokit/rest@22.0.0': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/plugin-paginate-rest': 13.1.1(@octokit/core@7.0.3) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.3) + '@octokit/plugin-rest-endpoint-methods': 16.0.0(@octokit/core@7.0.3) + + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + + '@one-ini/wasm@0.1.1': {} + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/browser-chromium@1.54.1': + dependencies: + playwright-core: 1.54.1 + + '@playwright/test@1.54.1': + dependencies: + playwright: 1.54.1 + + '@rollup/rollup-android-arm-eabi@4.46.1': + optional: true + + '@rollup/rollup-android-arm64@4.46.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.1': + optional: true + + '@rollup/rollup-darwin-x64@4.46.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.1': + optional: true + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/is@4.6.0': {} + + '@sindresorhus/is@7.0.2': {} + + '@sinonjs/commons@1.8.6': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@11.3.1': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@8.1.0': + dependencies: + '@sinonjs/commons': 1.8.6 + + '@sinonjs/samsam@6.1.3': + dependencies: + '@sinonjs/commons': 1.8.6 + lodash.get: 4.4.2 + type-detect: 4.1.0 + + '@sinonjs/text-encoding@0.7.3': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@szmarczak/http-timer@5.0.1': + dependencies: + defer-to-connect: 2.0.1 + + '@tootallnate/once@3.0.0': {} + + '@trysound/sax@0.2.0': {} + + '@ts-morph/common@0.26.1': + dependencies: + fast-glob: 3.3.3 + minimatch: 9.0.5 + path-browserify: 1.0.1 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 20.19.9 + '@types/responselike': 1.0.3 + + '@types/cookie@0.3.3': {} + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/expect@1.20.4': {} + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 6.0.0 + '@types/node': 20.19.9 + optional: true + + '@types/gulp-svgmin@1.2.4': + dependencies: + '@types/node': 20.19.9 + '@types/svgo': 1.3.6 + '@types/vinyl': 2.0.12 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/http-proxy-agent@2.0.2': + dependencies: + '@types/node': 20.19.9 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/kerberos@1.1.5': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.19.9 + + '@types/minimatch@6.0.0': + dependencies: + minimatch: 3.1.2 + optional: true + + '@types/minimist@1.2.5': {} + + '@types/mocha@10.0.10': {} + + '@types/mocha@9.1.1': {} + + '@types/ms@2.1.0': {} + + '@types/node@20.19.9': + dependencies: + undici-types: 6.21.0 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.19.9 + + '@types/semver@7.7.0': {} + + '@types/sinon-test@2.4.6': + dependencies: + '@types/sinon': 10.0.20 + + '@types/sinon@10.0.20': + dependencies: + '@types/sinonjs__fake-timers': 8.1.5 + + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/svgo@1.3.6': {} + + '@types/trusted-types@1.0.6': {} + + '@types/vinyl@2.0.12': + dependencies: + '@types/expect': 1.20.4 + '@types/node': 20.19.9 + + '@types/vscode-notebook-renderer@1.72.3': {} + + '@types/webpack@5.28.5(esbuild@0.25.8)(webpack-cli@5.1.4)': + dependencies: + '@types/node': 20.19.9 + tapable: 2.2.2 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@types/wicg-file-system-access@2020.9.8': {} + + '@types/windows-foreground-love@0.3.1': + dependencies: + windows-foreground-love: 0.5.0 + + '@types/winreg@1.2.36': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.9 + + '@types/yazl@2.4.6': + dependencies: + '@types/node': 20.19.9 + + '@typespec/ts-http-runtime@0.3.0': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@vscode/deviceid@0.1.2': + dependencies: + fs-extra: 11.3.0 + uuid: 9.0.1 + + '@vscode/gulp-electron@1.38.1': + dependencies: + '@electron/get': 4.0.2 + '@octokit/rest': 22.0.0 + event-stream: 3.3.4 + gulp-filter: 5.1.0 + gulp-rename: 1.2.2 + gulp-symdest: 1.3.0 + gulp-vinyl-zip: 2.5.0 + mkdirp: 0.5.6 + plist: 3.1.0 + progress: 1.1.8 + rcedit: 4.0.1 + rimraf: 2.7.1 + semver: 7.7.2 + sumchecker: 3.0.1 + temp: 0.8.4 + vinyl: 3.0.1 + vinyl-fs: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@vscode/iconv-lite-umd@0.7.0': {} + + '@vscode/l10n-dev@0.0.35': + dependencies: + '@azure-rest/ai-translation-text': 1.0.1 + debug: 4.4.1(supports-color@8.1.1) + deepmerge-json: 1.5.0 + glob: 10.4.5 + markdown-it: 14.1.0 + node-html-markdown: 1.3.0 + pseudo-localization: 2.4.0 + web-tree-sitter: 0.20.8 + xml2js: 0.5.0 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + '@vscode/policy-watcher@1.3.2': + dependencies: + bindings: 1.5.0 + node-addon-api: 8.5.0 + + '@vscode/proxy-agent@0.32.0': + dependencies: + '@tootallnate/once': 3.0.0 + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + socks-proxy-agent: 8.0.5 + undici: 7.12.0 + optionalDependencies: + '@vscode/windows-ca-certs': 0.3.3 + transitivePeerDependencies: + - supports-color + + '@vscode/ripgrep@1.15.14': + dependencies: + https-proxy-agent: 7.0.6 + proxy-from-env: 1.1.0 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + + '@vscode/spdlog@0.15.2': + dependencies: + bindings: 1.5.0 + mkdirp: 1.0.4 + node-addon-api: 7.1.0 + + '@vscode/sqlite3@5.1.8-vscode': + dependencies: + node-addon-api: 8.5.0 + tar: 6.2.1 + + '@vscode/sudo-prompt@9.3.1': {} + + '@vscode/telemetry-extractor@1.17.0': + dependencies: + '@vscode/ripgrep': 1.15.14 + command-line-args: 6.0.1 + ts-morph: 25.0.1 + transitivePeerDependencies: + - '@75lb/nature' + - supports-color + + '@vscode/test-cli@0.0.6': + dependencies: + '@types/mocha': 10.0.10 + c8: 9.1.0 + chokidar: 3.6.0 + enhanced-resolve: 5.18.2 + glob: 10.4.5 + minimatch: 9.0.5 + mocha: 10.8.2 + supports-color: 9.4.0 + yargs: 17.7.2 + + '@vscode/test-electron@2.5.2': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + jszip: 3.10.1 + ora: 8.2.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + '@vscode/test-web@0.0.62': + dependencies: + '@koa/cors': 5.0.0 + '@koa/router': 13.1.1 + '@playwright/browser-chromium': 1.54.1 + glob: 11.0.3 + gunzip-maybe: 1.4.2 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + koa: 2.16.1 + koa-morgan: 1.0.1 + koa-mount: 4.2.0 + koa-static: 5.0.0 + minimist: 1.2.8 + playwright: 1.54.1 + tar-fs: 3.1.0 + vscode-uri: 3.1.0 + transitivePeerDependencies: + - bare-buffer + - supports-color + + '@vscode/tree-sitter-wasm@0.1.4': {} + + '@vscode/v8-heap-parser@0.1.0': {} + + '@vscode/vscode-languagedetection@1.0.21': {} + + '@vscode/vscode-perf@0.0.19': + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + cookie: 0.7.2 + js-base64: 3.7.7 + node-fetch: 2.6.8 + playwright: 1.54.1 + transitivePeerDependencies: + - encoding + + '@vscode/windows-ca-certs@0.3.3': + dependencies: + node-addon-api: 8.5.0 + optional: true + + '@vscode/windows-mutex@0.5.0': + dependencies: + bindings: 1.5.0 + node-addon-api: 7.1.0 + + '@vscode/windows-process-tree@0.6.0': + dependencies: + node-addon-api: 7.1.0 + + '@vscode/windows-registry@1.1.0': {} + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@webgpu/types@0.1.64': {} + + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.100.2)': + dependencies: + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack@5.100.2) + + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.100.2)': + dependencies: + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack@5.100.2) + + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.100.2)': + dependencies: + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack@5.100.2) + + '@xmldom/xmldom@0.8.10': {} + + '@xterm/addon-clipboard@0.2.0-beta.97(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + js-base64: 3.7.7 + + '@xterm/addon-image@0.9.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/addon-ligatures@0.10.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + font-finder: 1.1.0 + font-ligatures: 1.4.1 + + '@xterm/addon-progress@0.2.0-beta.20(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/addon-search@0.16.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/addon-serialize@0.14.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/addon-unicode11@0.9.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/addon-webgl@0.19.0-beta.114(@xterm/xterm@5.6.0-beta.114)': + dependencies: + '@xterm/xterm': 5.6.0-beta.114 + + '@xterm/headless@5.6.0-beta.114': {} + + '@xterm/xterm@5.6.0-beta.114': {} + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abbrev@2.0.0: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-import-phases@1.0.4(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@6.4.2: {} + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv-keywords@5.1.0(ajv@8.17.1): + dependencies: + ajv: 8.17.1 + fast-deep-equal: 3.1.3 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + all@0.0.0: {} + + amdefine@1.0.1: {} + + ansi-colors@1.1.0: + dependencies: + ansi-wrap: 0.1.0 + + ansi-colors@3.2.4: {} + + ansi-colors@4.1.3: {} + + ansi-cyan@0.1.1: + dependencies: + ansi-wrap: 0.1.0 + + ansi-gray@0.1.1: + dependencies: + ansi-wrap: 0.1.0 + + ansi-red@0.1.1: + dependencies: + ansi-wrap: 0.1.0 + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@2.2.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + ansi-wrap@0.1.0: {} + + any-promise@1.3.0: {} + + anymatch@2.0.0: + dependencies: + micromatch: 3.1.10 + normalize-path: 2.1.1 + transitivePeerDependencies: + - supports-color + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + append-buffer@1.0.2: + dependencies: + buffer-equal: 1.0.1 + + archy@1.0.0: {} + + arg@4.1.3: {} + + argparse@2.0.1: {} + + arr-diff@1.1.0: + dependencies: + arr-flatten: 1.1.0 + array-slice: 0.2.3 + + arr-diff@4.0.0: {} + + arr-filter@1.1.2: + dependencies: + make-iterator: 1.0.1 + + arr-flatten@1.1.0: {} + + arr-map@2.0.2: + dependencies: + make-iterator: 1.0.1 + + arr-union@2.1.0: {} + + arr-union@3.1.0: {} + + array-back@6.2.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-differ@1.0.0: {} + + array-each@1.0.1: {} + + array-initial@1.1.0: + dependencies: + array-slice: 1.1.0 + is-number: 4.0.0 + + array-last@1.3.0: + dependencies: + is-number: 4.0.0 + + array-slice@0.2.3: {} + + array-slice@1.1.0: {} + + array-sort@1.0.0: + dependencies: + default-compare: 1.0.0 + get-value: 2.0.6 + kind-of: 5.1.0 + + array-union@1.0.2: + dependencies: + array-uniq: 1.0.3 + + array-uniq@1.0.3: {} + + array-unique@0.3.2: {} + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + arrify@1.0.1: {} + + asar@3.2.0: + dependencies: + chromium-pickle-js: 0.2.0 + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + optionalDependencies: + '@types/glob': 7.2.0 + + assign-symbols@1.0.0: {} + + async-done@1.3.2: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + process-nextick-args: 2.0.1 + stream-exhaust: 1.0.2 + + async-each@1.0.6: {} + + async-function@1.0.0: {} + + async-settle@1.0.0: + dependencies: + async-done: 1.3.2 + + atob@2.1.2: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + b4a@1.6.7: {} + + bach@1.2.0: + dependencies: + arr-filter: 1.1.2 + arr-flatten: 1.1.0 + arr-map: 2.0.2 + array-each: 1.0.1 + array-initial: 1.1.0 + array-last: 1.3.0 + async-done: 1.3.2 + async-settle: 1.0.0 + now-and-later: 2.0.1 + + balanced-match@1.0.2: {} + + bare-events@2.6.0: + optional: true + + bare-fs@4.1.6: + dependencies: + bare-events: 2.6.0 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.6.0) + optional: true + + bare-os@3.6.1: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.1 + optional: true + + bare-stream@2.6.5(bare-events@2.6.0): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.6.0 + optional: true + + base64-js@1.5.1: {} + + base@0.11.2: + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.1 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + + before-after-hook@4.0.0: {} + + big.js@5.2.2: {} + + binary-extensions@1.13.1: {} + + binary-extensions@2.3.0: {} + + binaryextensions@1.0.1: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + block-stream@0.0.9: + dependencies: + inherits: 2.0.4 + + boolbase@1.0.0: {} + + boolean@3.2.0: + optional: true + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@2.3.2: + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.4 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-stdout@1.3.1: {} + + browserify-zlib@0.1.4: + dependencies: + pako: 0.2.9 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.191 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + buffer-crc32@0.2.13: {} + + buffer-equal@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-require@5.1.0(esbuild@0.25.8): + dependencies: + esbuild: 0.25.8 + load-tsconfig: 0.2.5 + + bytes@3.1.2: {} + + c8@9.1.0: + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 3.3.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.7 + test-exclude: 6.0.0 + v8-to-istanbul: 9.3.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + + cac@6.7.14: {} + + cache-base@1.0.1: + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.1 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + + cache-content-type@1.0.1: + dependencies: + mime-types: 2.1.35 + ylru: 1.4.0 + + cacheable-lookup@5.0.4: {} + + cacheable-lookup@7.0.0: {} + + cacheable-request@12.0.1: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 9.0.1 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.2 + responselike: 3.0.0 + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@3.0.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.25.1 + caniuse-lite: 1.0.30001727 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001727: {} + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.4.1: {} + + charenc@0.0.2: {} + + chokidar@2.1.8: + dependencies: + anymatch: 2.0.0 + async-each: 1.0.6 + braces: 2.3.2 + glob-parent: 3.1.0 + inherits: 2.0.4 + is-binary-path: 1.0.1 + is-glob: 4.0.3 + normalize-path: 3.0.0 + path-is-absolute: 1.0.1 + readdirp: 2.2.1 + upath: 1.2.0 + optionalDependencies: + fsevents: 1.2.13 + transitivePeerDependencies: + - supports-color + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@1.1.4: {} + + chownr@2.0.0: {} + + chrome-remote-interface@0.33.3: + dependencies: + commander: 2.11.0 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + chrome-trace-event@1.0.4: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@1.6.0: {} + + class-utils@0.3.6: + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cliui@3.2.0: + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + wrap-ansi: 2.1.0 + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-buffer@1.0.0: {} + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clone-stats@0.0.1: {} + + clone-stats@1.0.0: {} + + clone@1.0.4: {} + + clone@2.1.2: {} + + cloneable-readable@1.1.3: + dependencies: + inherits: 2.0.4 + process-nextick-args: 2.0.1 + readable-stream: 2.3.8 + + co@4.6.0: {} + + code-block-writer@13.0.3: {} + + code-point-at@1.1.0: {} + + collection-map@1.0.0: + dependencies: + arr-map: 2.0.2 + for-own: 1.0.0 + make-iterator: 1.0.1 + + collection-visit@1.0.0: + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + colord@2.9.3: {} + + colorette@2.0.20: {} + + command-line-args@6.0.1: + dependencies: + array-back: 6.2.2 + find-replace: 5.0.2 + lodash.camelcase: 4.3.0 + typical: 7.3.0 + + commander@10.0.1: {} + + commander@2.11.0: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@5.1.0: {} + + commander@7.2.0: {} + + commander@9.5.0: {} + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + confbox@0.1.8: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + consola@3.4.2: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cookie@0.7.2: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + + copy-descriptor@0.1.1: {} + + copy-props@2.0.5: + dependencies: + each-props: 1.3.2 + is-plain-object: 5.0.0 + + copy-webpack-plugin@11.0.0(webpack@5.100.2): + dependencies: + fast-glob: 3.3.3 + glob-parent: 6.0.2 + globby: 13.2.2 + normalize-path: 3.0.0 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + core-util-is@1.0.3: {} + + create-require@1.1.1: {} + + cross-spawn-windows-exe@1.2.0: + dependencies: + '@malept/cross-spawn-promise': 1.1.1 + is-wsl: 2.2.0 + which: 2.0.2 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + css-declaration-sorter@7.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + css-loader@6.11.0(webpack@5.100.2): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) + postcss-value-parser: 4.2.0 + semver: 7.7.2 + optionalDependencies: + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + css@3.0.0: + dependencies: + inherits: 2.0.4 + source-map: 0.6.1 + source-map-resolve: 0.6.0 + + cssesc@3.0.0: {} + + cssnano-preset-default@6.1.2(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + css-declaration-sorter: 7.2.0(postcss@8.5.6) + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-calc: 9.0.1(postcss@8.5.6) + postcss-colormin: 6.1.0(postcss@8.5.6) + postcss-convert-values: 6.1.0(postcss@8.5.6) + postcss-discard-comments: 6.0.2(postcss@8.5.6) + postcss-discard-duplicates: 6.0.3(postcss@8.5.6) + postcss-discard-empty: 6.0.3(postcss@8.5.6) + postcss-discard-overridden: 6.0.2(postcss@8.5.6) + postcss-merge-longhand: 6.0.5(postcss@8.5.6) + postcss-merge-rules: 6.1.1(postcss@8.5.6) + postcss-minify-font-values: 6.1.0(postcss@8.5.6) + postcss-minify-gradients: 6.0.3(postcss@8.5.6) + postcss-minify-params: 6.1.0(postcss@8.5.6) + postcss-minify-selectors: 6.0.4(postcss@8.5.6) + postcss-normalize-charset: 6.0.2(postcss@8.5.6) + postcss-normalize-display-values: 6.0.2(postcss@8.5.6) + postcss-normalize-positions: 6.0.2(postcss@8.5.6) + postcss-normalize-repeat-style: 6.0.2(postcss@8.5.6) + postcss-normalize-string: 6.0.2(postcss@8.5.6) + postcss-normalize-timing-functions: 6.0.2(postcss@8.5.6) + postcss-normalize-unicode: 6.1.0(postcss@8.5.6) + postcss-normalize-url: 6.0.2(postcss@8.5.6) + postcss-normalize-whitespace: 6.0.2(postcss@8.5.6) + postcss-ordered-values: 6.0.2(postcss@8.5.6) + postcss-reduce-initial: 6.1.0(postcss@8.5.6) + postcss-reduce-transforms: 6.0.2(postcss@8.5.6) + postcss-svgo: 6.0.3(postcss@8.5.6) + postcss-unique-selectors: 6.0.4(postcss@8.5.6) + + cssnano-utils@4.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssnano@6.1.2(postcss@8.5.6): + dependencies: + cssnano-preset-default: 6.1.2(postcss@8.5.6) + lilconfig: 3.1.3 + postcss: 8.5.6 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debounce@1.2.1: {} + + debug-fabulous@1.1.0: + dependencies: + debug: 3.2.7 + memoizee: 0.4.17 + object-assign: 4.1.1 + transitivePeerDependencies: + - supports-color + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@1.2.0: {} + + decamelize@4.0.0: {} + + decode-uri-component@0.2.2: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deemon@1.13.5: + dependencies: + bl: 4.1.0 + tree-kill: 1.2.2 + + deep-equal@1.0.1: {} + + deep-extend@0.6.0: {} + + deepmerge-json@1.5.0: {} + + deepmerge@4.3.1: {} + + default-compare@1.0.0: + dependencies: + kind-of: 5.1.0 + + default-resolution@2.0.0: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + define-property@0.2.5: + dependencies: + is-descriptor: 0.1.7 + + define-property@1.0.0: + dependencies: + is-descriptor: 1.0.3 + + define-property@2.0.2: + dependencies: + is-descriptor: 1.0.3 + isobject: 3.0.1 + + delayed-stream@0.0.6: {} + + delegates@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-file@1.0.0: {} + + detect-indent@6.1.0: {} + + detect-libc@1.0.3: {} + + detect-libc@2.0.4: {} + + detect-newline@2.1.0: {} + + detect-node@2.1.0: + optional: true + + diff@4.0.2: {} + + diff@5.2.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer@0.1.2: {} + + duplexify@3.7.1: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 + + each-props@1.3.2: + dependencies: + is-plain-object: 2.0.4 + object.defaults: 1.1.0 + + eastasianwidth@0.2.0: {} + + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.2 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.191: {} + + electron@34.4.1: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.9 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + emojis-list@3.0.0: {} + + encodeurl@1.0.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + entities@2.2.0: {} + + entities@4.5.0: {} + + env-paths@2.2.1: {} + + env-paths@3.0.0: {} + + envinfo@7.14.0: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-error@4.1.1: + optional: true + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + es6-weak-map@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + + events@3.3.0: {} + + eventsource-parser@3.0.3: {} + + expand-brackets@2.1.4: + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + expand-template@2.0.3: {} + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + ext@1.7.0: + dependencies: + type: 2.7.3 + + extend-shallow@1.1.4: + dependencies: + kind-of: 1.1.0 + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + + extend@3.0.2: {} + + extglob@2.0.4: + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + extract-zip@2.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fancy-log@1.3.3: + dependencies: + ansi-gray: 0.1.1 + color-support: 1.1.3 + parse-node-version: 1.0.1 + time-stamp: 1.1.0 + + fast-content-type-parse@3.0.0: {} + + fast-deep-equal@3.1.3: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@1.1.4: {} + + fast-uri@3.0.6: {} + + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.1 + + fastest-levenshtein@1.0.16: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-loader@6.2.0(webpack@5.100.2): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + file-uri-to-path@1.0.0: {} + + fill-range@4.0.0: + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-parent-dir@0.3.1: {} + + find-replace@5.0.2: {} + + find-up@1.1.2: + dependencies: + path-exists: 2.1.0 + pinkie-promise: 2.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + findup-sync@2.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 3.1.0 + micromatch: 3.1.10 + resolve-dir: 1.0.1 + transitivePeerDependencies: + - supports-color + + findup-sync@3.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 3.1.10 + resolve-dir: 1.0.1 + transitivePeerDependencies: + - supports-color + + fined@1.2.0: + dependencies: + expand-tilde: 2.0.2 + is-plain-object: 2.0.4 + object.defaults: 1.1.0 + object.pick: 1.3.0 + parse-filepath: 1.0.2 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.17 + mlly: 1.7.4 + rollup: 4.46.1 + + flagged-respawn@1.0.1: {} + + flat@5.0.2: {} + + flush-write-stream@1.1.1: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + + font-finder@1.1.0: + dependencies: + get-system-fonts: 2.0.2 + promise-stream-reader: 1.0.1 + + font-ligatures@1.4.1: + dependencies: + font-finder: 1.1.0 + lru-cache: 6.0.0 + opentype.js: 0.8.0 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + for-in@1.0.2: {} + + for-own@1.0.0: + dependencies: + for-in: 1.0.2 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@4.1.0: {} + + fragment-cache@0.2.1: + dependencies: + map-cache: 0.2.2 + + fresh@0.5.2: {} + + from@0.1.7: {} + + fs-constants@1.0.0: {} + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs-mkdirp-stream@1.0.0: + dependencies: + graceful-fs: 4.2.11 + through2: 2.0.5 + + fs.realpath@1.0.0: {} + + fsevents@1.2.13: + dependencies: + bindings: 1.5.0 + nan: 2.23.0 + optional: true + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@1.0.3: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stdin@7.0.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-system-fonts@2.0.2: {} + + get-value@2.0.6: {} + + github-from-package@0.0.0: {} + + glob-parent@3.1.0: + dependencies: + is-glob: 3.1.0 + path-dirname: 1.0.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-stream@6.1.0: + dependencies: + extend: 3.0.2 + glob: 7.2.3 + glob-parent: 3.1.0 + is-negated-glob: 1.0.0 + ordered-read-streams: 1.0.1 + pumpify: 1.5.1 + readable-stream: 2.3.8 + remove-trailing-separator: 1.1.0 + to-absolute-glob: 2.0.2 + unique-stream: 2.3.1 + + glob-to-regexp@0.4.1: {} + + glob-watcher@5.0.5: + dependencies: + anymatch: 2.0.0 + async-done: 1.3.2 + chokidar: 2.1.8 + is-negated-glob: 1.0.0 + just-debounce: 1.1.0 + normalize-path: 3.0.0 + object.defaults: 1.1.0 + transitivePeerDependencies: + - supports-color + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + glob@5.0.15: + dependencies: + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.2 + serialize-error: 7.0.1 + optional: true + + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 4.0.0 + + glogg@1.0.2: + dependencies: + sparkles: 1.0.1 + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + got@14.4.7: + dependencies: + '@sindresorhus/is': 7.0.2 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 12.0.1 + decompress-response: 6.0.0 + form-data-encoder: 4.1.0 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 4.0.1 + responselike: 3.0.0 + type-fest: 4.41.0 + + graceful-fs@4.2.11: {} + + gulp-azure-storage@0.12.1: + dependencies: + '@azure/storage-blob': 12.28.0 + delayed-stream: 0.0.6 + event-stream: 3.3.4 + mime: 1.6.0 + progress: 1.1.8 + queue: 3.1.0 + streamifier: 0.1.1 + vinyl: 2.2.1 + vinyl-fs: 3.0.3 + yargs: 15.4.1 + transitivePeerDependencies: + - supports-color + + gulp-bom@3.0.0(gulp@4.0.2): + dependencies: + plugin-error: 1.0.1 + through2: 3.0.2 + optionalDependencies: + gulp: 4.0.2 + + gulp-buffer@0.0.2: + dependencies: + through2: 0.4.2 + + gulp-cli@2.3.0: + dependencies: + ansi-colors: 1.1.0 + archy: 1.0.0 + array-sort: 1.0.0 + color-support: 1.1.3 + concat-stream: 1.6.2 + copy-props: 2.0.5 + fancy-log: 1.3.3 + gulplog: 1.0.0 + interpret: 1.4.0 + isobject: 3.0.1 + liftoff: 3.1.0 + matchdep: 2.0.0 + mute-stdout: 1.0.1 + pretty-hrtime: 1.0.3 + replace-homedir: 1.0.0 + semver-greatest-satisfied-range: 1.1.0 + v8flags: 3.2.0 + yargs: 7.1.2 + transitivePeerDependencies: + - supports-color + + gulp-filter@5.1.0: + dependencies: + multimatch: 2.1.0 + plugin-error: 0.1.2 + streamfilter: 1.0.7 + + gulp-flatmap@1.0.2: + dependencies: + plugin-error: 0.1.2 + through2: 2.0.3 + + gulp-gunzip@1.1.0: + dependencies: + through2: 2.0.5 + vinyl: 2.0.2 + + gulp-gzip@1.4.2: + dependencies: + ansi-colors: 1.1.0 + bytes: 3.1.2 + fancy-log: 1.3.3 + plugin-error: 1.0.1 + stream-to-array: 2.3.0 + through2: 2.0.5 + + gulp-json-editor@2.6.0: + dependencies: + deepmerge: 4.3.1 + detect-indent: 6.1.0 + js-beautify: 1.15.4 + plugin-error: 2.0.1 + through2: 4.0.2 + + gulp-plumber@1.2.1: + dependencies: + chalk: 1.1.3 + fancy-log: 1.3.3 + plugin-error: 0.1.2 + through2: 2.0.5 + + gulp-rename@1.2.2: {} + + gulp-rename@1.4.0: {} + + gulp-replace@0.5.4: + dependencies: + istextorbinary: 1.0.2 + readable-stream: 2.3.8 + replacestream: 4.0.3 + + gulp-sourcemaps@3.0.0: + dependencies: + '@gulp-sourcemaps/identity-map': 2.0.1 + '@gulp-sourcemaps/map-sources': 1.0.0 + acorn: 6.4.2 + convert-source-map: 1.9.0 + css: 3.0.0 + debug-fabulous: 1.1.0 + detect-newline: 2.1.0 + graceful-fs: 4.2.11 + source-map: 0.6.1 + strip-bom-string: 1.0.0 + through2: 2.0.5 + transitivePeerDependencies: + - supports-color + + gulp-svgmin@4.1.0: + dependencies: + lodash.clonedeep: 4.5.0 + plugin-error: 1.0.1 + svgo: 2.8.0 + + gulp-symdest@1.3.0: + dependencies: + event-stream: 3.3.4 + mkdirp: 0.5.6 + queue: 3.1.0 + vinyl-fs: 3.0.3 + + gulp-untar@0.0.7: + dependencies: + event-stream: 3.3.4 + streamifier: 0.1.1 + tar: 2.2.2 + through2: 2.0.5 + vinyl: 1.2.0 + + gulp-vinyl-zip@2.5.0: + dependencies: + queue: 4.5.1 + through: 2.3.8 + through2: 2.0.5 + vinyl: 2.2.1 + vinyl-fs: 3.0.3 + yauzl: 2.10.0 + yazl: 2.5.1 + + gulp@4.0.2: + dependencies: + glob-watcher: 5.0.5 + gulp-cli: 2.3.0 + undertaker: 1.3.0 + vinyl-fs: 3.0.3 + transitivePeerDependencies: + - supports-color + + gulplog@1.0.0: + dependencies: + glogg: 1.0.2 + + gunzip-maybe@1.4.2: + dependencies: + browserify-zlib: 0.1.4 + is-deflate: 1.0.0 + is-gzip: 1.0.0 + peek-stream: 1.1.3 + pumpify: 1.5.1 + through2: 2.0.5 + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-value@0.3.1: + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + + has-value@1.0.0: + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + + has-values@0.1.4: {} + + has-values@1.0.0: + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hosted-git-info@2.8.9: {} + + html-escaper@2.0.2: {} + + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-cache-semantics@4.2.0: {} + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + husky@0.13.4: + dependencies: + chalk: 1.1.3 + find-parent-dir: 0.3.1 + is-ci: 1.2.1 + normalize-path: 1.0.0 + + icss-utils@5.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + immediate@3.0.6: {} + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + innosetup@6.4.1: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + interpret@1.4.0: {} + + interpret@3.1.1: {} + + invert-kv@1.0.0: {} + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + + is-accessor-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@1.0.1: + dependencies: + binary-extensions: 1.13.1 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-buffer@1.1.6: {} + + is-callable@1.2.7: {} + + is-ci@1.2.1: + dependencies: + ci-info: 1.6.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-deflate@1.0.0: {} + + is-descriptor@0.1.7: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-descriptor@1.0.3: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-docker@2.2.1: {} + + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@1.0.0: + dependencies: + number-is-nan: 1.0.1 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@3.1.0: + dependencies: + is-extglob: 2.1.1 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-gzip@1.0.0: {} + + is-interactive@2.0.0: {} + + is-map@2.0.3: {} + + is-negated-glob@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@3.0.0: + dependencies: + kind-of: 3.2.2 + + is-number@4.0.0: {} + + is-number@7.0.0: {} + + is-plain-obj@1.1.0: {} + + is-plain-obj@2.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-plain-object@5.0.0: {} + + is-promise@2.2.2: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@1.1.0: {} + + is-stream@4.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-utf8@0.2.1: {} + + is-valid-glob@1.0.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@0.0.1: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@2.1.0: + dependencies: + isarray: 1.0.0 + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.0 + '@babel/parser': 7.28.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + istextorbinary@1.0.2: + dependencies: + binaryextensions: 1.0.1 + textextensions: 1.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.19.9 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + joycon@3.1.1: {} + + js-base64@3.7.7: {} + + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.4.5 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@1.1.0: {} + + jschardet@3.1.4: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + just-debounce@1.1.0: {} + + just-extend@6.2.0: {} + + kerberos@2.1.1: + dependencies: + bindings: 1.5.0 + node-addon-api: 6.1.0 + prebuild-install: 7.1.3 + + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@1.1.0: {} + + kind-of@3.2.2: + dependencies: + is-buffer: 1.1.6 + + kind-of@4.0.0: + dependencies: + is-buffer: 1.1.6 + + kind-of@5.1.0: {} + + kind-of@6.0.3: {} + + koa-compose@4.1.0: {} + + koa-convert@2.0.0: + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + + koa-morgan@1.0.1: + dependencies: + morgan: 1.10.1 + transitivePeerDependencies: + - supports-color + + koa-mount@4.2.0: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + koa-compose: 4.1.0 + transitivePeerDependencies: + - supports-color + + koa-send@5.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + + koa-static@5.0.0: + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + + koa@2.16.1: + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + debug: 4.4.1(supports-color@8.1.1) + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.1.0 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + last-run@1.1.1: + dependencies: + default-resolution: 2.0.0 + es6-weak-map: 2.0.3 + + lazy.js@0.4.3: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lcid@1.0.0: + dependencies: + invert-kv: 1.0.0 + + lead@1.0.0: + dependencies: + flush-write-stream: 1.1.1 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + liftoff@3.1.0: + dependencies: + extend: 3.0.2 + findup-sync: 3.0.0 + fined: 1.2.0 + flagged-respawn: 1.0.1 + is-plain-object: 2.0.4 + object.map: 1.0.1 + rechoir: 0.6.2 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + load-json-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 2.2.0 + pify: 2.3.0 + pinkie-promise: 2.0.1 + strip-bom: 2.0.0 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + load-tsconfig@0.2.5: {} + + loader-runner@4.3.0: {} + + loader-utils@2.0.4: + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.3 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.clone@4.5.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.get@4.4.2: {} + + lodash.memoize@4.1.2: {} + + lodash.some@4.6.0: {} + + lodash.sortby@4.7.0: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-symbols@6.0.0: + dependencies: + chalk: 5.4.1 + is-unicode-supported: 1.3.0 + + lowercase-keys@2.0.0: {} + + lowercase-keys@3.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@11.1.0: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-queue@0.1.0: + dependencies: + es5-ext: 0.10.64 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-error@1.3.6: {} + + make-iterator@1.0.1: + dependencies: + kind-of: 6.0.3 + + map-cache@0.2.2: {} + + map-stream@0.1.0: {} + + map-visit@1.0.0: + dependencies: + object-visit: 1.0.1 + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + matchdep@2.0.0: + dependencies: + findup-sync: 2.0.0 + micromatch: 3.1.10 + resolve: 1.22.10 + stack-trace: 0.0.10 + transitivePeerDependencies: + - supports-color + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + + mdn-data@2.0.14: {} + + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + mdurl@2.0.0: {} + + media-typer@0.3.0: {} + + memoizee@0.4.17: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.8 + + memory-fs@0.5.0: + dependencies: + errno: 0.1.8 + readable-stream: 2.3.8 + + memorystream@0.3.1: {} + + merge-options@1.0.1: + dependencies: + is-plain-obj: 1.1.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@3.1.10: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 6.0.3 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-function@5.0.1: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + mimic-response@4.0.0: {} + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mixin-deep@1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + + mkdirp-classic@0.5.3: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + mkdirp@3.0.1: {} + + mlly@1.7.4: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mocha-junit-reporter@2.2.1(mocha@10.8.2): + dependencies: + debug: 4.4.1(supports-color@8.1.1) + md5: 2.3.0 + mkdirp: 3.0.1 + mocha: 10.8.2 + strip-ansi: 6.0.1 + xml: 1.0.1 + transitivePeerDependencies: + - supports-color + + mocha-multi-reporters@1.5.1(mocha@10.8.2): + dependencies: + debug: 4.4.1(supports-color@8.1.1) + lodash: 4.17.21 + mocha: 10.8.2 + transitivePeerDependencies: + - supports-color + + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.1(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + morgan@1.10.1: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.1.0 + transitivePeerDependencies: + - supports-color + + ms@2.0.0: {} + + ms@2.1.3: {} + + multimatch@2.1.0: + dependencies: + array-differ: 1.0.0 + array-union: 1.0.2 + arrify: 1.0.1 + minimatch: 3.1.2 + + mute-stdout@1.0.1: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nan@2.23.0: + optional: true + + nanoid@3.3.11: {} + + nanomatch@1.2.13: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.3 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + napi-build-utils@2.0.0: {} + + native-is-elevated@0.7.0: {} + + native-keymap@3.3.5: {} + + native-watchdog@1.4.2: {} + + negotiator@0.6.3: {} + + neo-async@2.6.2: {} + + next-tick@1.1.0: {} + + nice-try@1.0.5: {} + + nise@5.1.9: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 11.3.1 + '@sinonjs/text-encoding': 0.7.3 + just-extend: 6.2.0 + path-to-regexp: 6.3.0 + + node-abi@3.75.0: + dependencies: + semver: 7.7.2 + + node-addon-api@6.1.0: {} + + node-addon-api@7.1.0: {} + + node-addon-api@7.1.1: {} + + node-addon-api@8.5.0: {} + + node-fetch@2.6.8: + dependencies: + whatwg-url: 5.0.0 + + node-html-markdown@1.3.0: + dependencies: + node-html-parser: 6.1.13 + + node-html-parser@6.1.13: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + + node-pty@1.1.0-beta9: + dependencies: + node-addon-api: 7.1.1 + + node-releases@2.0.19: {} + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.10 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-path@1.0.0: {} + + normalize-path@2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + normalize-url@8.0.2: {} + + now-and-later@2.0.1: + dependencies: + once: 1.4.0 + + npm-run-all@4.1.5: + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.6 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.3 + string.prototype.padend: 3.1.6 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + number-is-nan@1.0.1: {} + + object-assign@4.1.1: {} + + object-copy@0.1.0: + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + + object-inspect@1.13.4: {} + + object-keys@0.4.0: {} + + object-keys@1.1.1: {} + + object-visit@1.0.1: + dependencies: + isobject: 3.0.1 + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.defaults@1.1.0: + dependencies: + array-each: 1.0.1 + array-slice: 1.1.0 + for-own: 1.0.0 + isobject: 3.0.1 + + object.map@1.0.1: + dependencies: + for-own: 1.0.0 + make-iterator: 1.0.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + + object.reduce@1.0.1: + dependencies: + for-own: 1.0.0 + make-iterator: 1.0.1 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + only@0.0.2: {} + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + opentype.js@0.8.0: + dependencies: + tiny-inflate: 1.0.3 + + ora@8.2.0: + dependencies: + chalk: 5.4.1 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + ordered-read-streams@1.0.1: + dependencies: + readable-stream: 2.3.8 + + os-browserify@0.3.0: {} + + os-locale@1.4.0: + dependencies: + lcid: 1.0.0 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-all@1.0.0: + dependencies: + p-map: 1.2.0 + + p-cancelable@2.1.1: {} + + p-cancelable@4.0.1: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@1.2.0: {} + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + pako@0.2.9: {} + + pako@1.0.11: {} + + parse-filepath@1.0.2: + dependencies: + is-absolute: 1.0.0 + map-cache: 0.2.2 + path-root: 0.1.1 + + parse-json@2.2.0: + dependencies: + error-ex: 1.3.2 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + + parse-node-version@1.0.1: {} + + parse-passwd@1.0.0: {} + + parseurl@1.3.3: {} + + pascalcase@0.1.1: {} + + path-browserify@1.0.1: {} + + path-dirname@1.0.2: {} + + path-exists@2.1.0: + dependencies: + pinkie-promise: 2.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-root-regex@0.1.2: {} + + path-root@0.1.1: + dependencies: + path-root-regex: 0.1.2 + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + + path-to-regexp@6.3.0: {} + + path-type@1.1.0: + dependencies: + graceful-fs: 4.2.11 + pify: 2.3.0 + pinkie-promise: 2.0.1 + + path-type@3.0.0: + dependencies: + pify: 3.0.0 + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + + peek-stream@1.1.3: + dependencies: + buffer-from: 1.1.2 + duplexify: 3.7.1 + through2: 2.0.5 + + pend@1.2.0: {} + + picocolors@0.2.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.3.1: {} + + pify@2.3.0: {} + + pify@3.0.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + playwright-core@1.54.1: {} + + playwright@1.54.1: + dependencies: + playwright-core: 1.54.1 + optionalDependencies: + fsevents: 2.3.2 + + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.10 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + plugin-error@0.1.2: + dependencies: + ansi-cyan: 0.1.1 + ansi-red: 0.1.1 + arr-diff: 1.1.0 + arr-union: 2.1.0 + extend-shallow: 1.1.4 + + plugin-error@1.0.1: + dependencies: + ansi-colors: 1.1.0 + arr-diff: 4.0.0 + arr-union: 3.1.0 + extend-shallow: 3.0.2 + + plugin-error@2.0.1: + dependencies: + ansi-colors: 1.1.0 + + posix-character-classes@0.1.1: {} + + possible-typed-array-names@1.1.0: {} + + postcss-calc@9.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-colormin@6.1.0(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-convert-values@6.1.0(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-discard-comments@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-discard-duplicates@6.0.3(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-discard-empty@6.0.3(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-discard-overridden@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-load-config@6.0.1(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.6 + + postcss-merge-longhand@6.0.5(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + stylehacks: 6.1.1(postcss@8.5.6) + + postcss-merge-rules@6.1.1(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-minify-font-values@6.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@6.0.3(postcss@8.5.6): + dependencies: + colord: 2.9.3 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-params@6.1.0(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@6.0.4(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.6): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + + postcss-modules-values@4.0.0(postcss@8.5.6): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-nesting@12.1.5(postcss@8.5.6): + dependencies: + '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.2) + '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-normalize-charset@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-normalize-display-values@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@6.1.0(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@6.0.2(postcss@8.5.6): + dependencies: + cssnano-utils: 4.0.2(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@6.1.0(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + postcss: 8.5.6 + + postcss-reduce-transforms@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-svgo@6.0.3(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + svgo: 3.3.2 + + postcss-unique-selectors@6.0.4(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-value-parser@4.2.0: {} + + postcss@7.0.39: + dependencies: + picocolors: 0.2.1 + source-map: 0.6.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.75.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.3 + tunnel-agent: 0.6.0 + + pretty-hrtime@1.0.3: {} + + process-nextick-args@2.0.1: {} + + progress@1.1.8: {} + + progress@2.0.3: {} + + promise-stream-reader@1.0.1: {} + + proto-list@1.2.4: {} + + proxy-from-env@1.1.0: {} + + prr@1.0.1: {} + + pseudo-localization@2.4.0: + dependencies: + flat: 5.0.2 + get-stdin: 7.0.0 + typescript: 4.9.5 + yargs: 17.7.2 + + pump@1.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pump@2.0.1: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pumpify@1.5.1: + dependencies: + duplexify: 3.7.1 + inherits: 2.0.4 + pump: 2.0.1 + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + queue@3.1.0: + dependencies: + inherits: 2.0.4 + + queue@4.5.1: + dependencies: + inherits: 2.0.4 + + quick-lru@5.1.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + rcedit@1.1.2: {} + + rcedit@4.0.1: + dependencies: + cross-spawn-windows-exe: 1.2.0 + + read-pkg-up@1.0.1: + dependencies: + find-up: 1.1.2 + read-pkg: 1.1.0 + + read-pkg@1.1.0: + dependencies: + load-json-file: 1.1.0 + normalize-package-data: 2.5.0 + path-type: 1.1.0 + + read-pkg@3.0.0: + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + + readable-stream@1.0.34: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@2.2.1: + dependencies: + graceful-fs: 4.2.11 + micromatch: 3.1.10 + readable-stream: 2.3.8 + transitivePeerDependencies: + - supports-color + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + rechoir@0.6.2: + dependencies: + resolve: 1.22.10 + + rechoir@0.8.0: + dependencies: + resolve: 1.22.10 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regex-not@1.0.2: + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + remove-bom-buffer@3.0.0: + dependencies: + is-buffer: 1.1.6 + is-utf8: 0.2.1 + + remove-bom-stream@1.2.0: + dependencies: + remove-bom-buffer: 3.0.0 + safe-buffer: 5.2.1 + through2: 2.0.5 + + remove-trailing-separator@1.1.0: {} + + repeat-element@1.1.4: {} + + repeat-string@1.6.1: {} + + replace-ext@0.0.1: {} + + replace-ext@1.0.1: {} + + replace-ext@2.0.0: {} + + replace-homedir@1.0.0: + dependencies: + homedir-polyfill: 1.0.3 + is-absolute: 1.0.0 + remove-trailing-separator: 1.1.0 + + replacestream@4.0.3: + dependencies: + escape-string-regexp: 1.0.5 + object-assign: 4.1.1 + readable-stream: 2.3.8 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@1.0.1: {} + + require-main-filename@2.0.0: {} + + resolve-alpn@1.2.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + + resolve-from@5.0.0: {} + + resolve-options@1.1.0: + dependencies: + value-or-function: 3.0.0 + + resolve-path@1.4.0: + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + + resolve-url@0.2.1: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + responselike@3.0.0: + dependencies: + lowercase-keys: 3.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + ret@0.1.15: {} + + reusify@1.1.0: {} + + rimraf@2.6.3: + dependencies: + glob: 7.2.3 + + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + rollup@4.46.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.1 + '@rollup/rollup-android-arm64': 4.46.1 + '@rollup/rollup-darwin-arm64': 4.46.1 + '@rollup/rollup-darwin-x64': 4.46.1 + '@rollup/rollup-freebsd-arm64': 4.46.1 + '@rollup/rollup-freebsd-x64': 4.46.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.1 + '@rollup/rollup-linux-arm-musleabihf': 4.46.1 + '@rollup/rollup-linux-arm64-gnu': 4.46.1 + '@rollup/rollup-linux-arm64-musl': 4.46.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.1 + '@rollup/rollup-linux-ppc64-gnu': 4.46.1 + '@rollup/rollup-linux-riscv64-gnu': 4.46.1 + '@rollup/rollup-linux-riscv64-musl': 4.46.1 + '@rollup/rollup-linux-s390x-gnu': 4.46.1 + '@rollup/rollup-linux-x64-gnu': 4.46.1 + '@rollup/rollup-linux-x64-musl': 4.46.1 + '@rollup/rollup-win32-arm64-msvc': 4.46.1 + '@rollup/rollup-win32-ia32-msvc': 4.46.1 + '@rollup/rollup-win32-x64-msvc': 4.46.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex@1.1.0: + dependencies: + ret: 0.1.15 + + sax@1.4.1: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + schema-utils@4.3.2: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) + + semver-compare@1.0.0: + optional: true + + semver-greatest-satisfied-range@1.1.0: + dependencies: + sver-compat: 1.5.0 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + + setimmediate@1.0.5: {} + + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + sinon-test@3.1.6(sinon@12.0.1): + dependencies: + sinon: 12.0.1 + + sinon@12.0.1: + dependencies: + '@sinonjs/commons': 1.8.6 + '@sinonjs/fake-timers': 8.1.0 + '@sinonjs/samsam': 6.1.3 + diff: 5.2.0 + nise: 5.1.9 + supports-color: 7.2.0 + + slash@4.0.0: {} + + smart-buffer@4.2.0: {} + + snapdragon-node@2.1.1: + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + + snapdragon-util@3.0.1: + dependencies: + kind-of: 3.2.2 + + snapdragon@0.8.2: + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.3 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + socks: 2.8.6 + transitivePeerDependencies: + - supports-color + + socks@2.8.6: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + + source-map-js@1.2.1: {} + + source-map-resolve@0.5.3: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + + source-map-resolve@0.6.0: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + + source-map-support@0.3.3: + dependencies: + source-map: 0.1.32 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-url@0.4.1: {} + + source-map@0.1.32: + dependencies: + amdefine: 1.0.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + sparkles@1.0.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.21 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 + + spdx-license-ids@3.0.21: {} + + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + + split@0.3.3: + dependencies: + through: 2.3.8 + + sprintf-js@1.1.3: {} + + stable@0.1.8: {} + + stack-trace@0.0.10: {} + + static-extend@0.1.2: + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + stdin-discarder@0.2.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + + stream-exhaust@1.0.2: {} + + stream-shift@1.0.3: {} + + stream-to-array@2.3.0: + dependencies: + any-promise: 1.3.0 + + streamfilter@1.0.7: + dependencies: + readable-stream: 2.3.8 + + streamifier@0.1.1: {} + + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.6.0 + + string-width@1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string.prototype.padend@3.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@0.10.31: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom-string@1.0.0: {} + + strip-bom@2.0.0: + dependencies: + is-utf8: 0.2.1 + + strip-bom@3.0.0: {} + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + strnum@2.1.1: {} + + style-loader@3.3.4(webpack@5.100.2): + dependencies: + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + stylehacks@6.1.1(postcss@8.5.6): + dependencies: + browserslist: 4.25.1 + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + sumchecker@3.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + supports-color@2.0.0: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-color@9.4.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + sver-compat@1.5.0: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.2.2 + css-tree: 2.3.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + + tapable@2.2.2: {} + + tar-fs@2.1.3: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-fs@3.1.0: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.6 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.1 + + tar@2.2.2: + dependencies: + block-stream: 0.0.9 + fstream: 1.0.12 + inherits: 2.0.4 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tas-client-umd@0.2.0: {} + + teex@1.0.1: + dependencies: + streamx: 2.22.1 + + temp@0.8.4: + dependencies: + rimraf: 2.6.3 + + terser-webpack-plugin@5.3.14(esbuild@0.25.8)(webpack@5.100.2): + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.43.1 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + optionalDependencies: + esbuild: 0.25.8 + + terser@5.43.1: + dependencies: + '@jridgewell/source-map': 0.3.10 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + + textextensions@1.0.2: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2-filter@3.0.0: + dependencies: + through2: 2.0.5 + xtend: 4.0.2 + + through2@0.4.2: + dependencies: + readable-stream: 1.0.34 + xtend: 2.1.2 + + through2@2.0.3: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + through2@3.0.2: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + through@2.3.8: {} + + time-stamp@1.1.0: {} + + timers-ext@0.1.8: + dependencies: + es5-ext: 0.10.64 + next-tick: 1.1.0 + + tiny-inflate@1.0.3: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + to-absolute-glob@2.0.2: + dependencies: + is-absolute: 1.0.0 + is-negated-glob: 1.0.0 + + to-object-path@0.3.0: + dependencies: + kind-of: 3.2.2 + + to-regex-range@2.1.1: + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + to-regex@3.0.2: + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + + to-through@2.0.0: + dependencies: + through2: 2.0.5 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + ts-loader@9.5.2(typescript@4.9.5)(webpack@5.100.2): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.2 + micromatch: 4.0.8 + semver: 7.7.2 + source-map: 0.7.6 + typescript: 4.9.5 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + ts-morph@25.0.1: + dependencies: + '@ts-morph/common': 0.26.1 + code-block-writer: 13.0.3 + + ts-node@10.9.2(@types/node@20.19.9)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.9 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + + tsup@8.5.0(postcss@8.5.6)(typescript@4.9.5): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.8) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.1(supports-color@8.1.1) + esbuild: 0.25.8 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.6) + resolve-from: 5.0.0 + rollup: 4.46.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.6 + typescript: 4.9.5 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-detect@4.0.8: {} + + type-detect@4.1.0: {} + + type-fest@0.13.1: + optional: true + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type@2.7.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typedarray@0.0.6: {} + + typescript@4.9.5: {} + + typical@7.3.0: {} + + uc.micro@2.1.0: {} + + ufo@1.6.1: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unc-path-regex@0.1.2: {} + + undertaker-registry@1.0.1: {} + + undertaker@1.3.0: + dependencies: + arr-flatten: 1.1.0 + arr-map: 2.0.2 + bach: 1.2.0 + collection-map: 1.0.0 + es6-weak-map: 2.0.3 + fast-levenshtein: 1.1.4 + last-run: 1.1.1 + object.defaults: 1.1.0 + object.reduce: 1.0.1 + undertaker-registry: 1.0.1 + + undici-types@6.21.0: {} + + undici@7.12.0: {} + + union-value@1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + + unique-stream@2.3.1: + dependencies: + json-stable-stringify-without-jsonify: 1.0.1 + through2-filter: 3.0.0 + + universal-user-agent@7.0.3: {} + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unset-value@1.0.0: + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + + upath@1.2.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urix@0.1.0: {} + + use@3.1.1: {} + + util-deprecate@1.0.2: {} + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.0 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-inspect-profiler@0.1.1: + dependencies: + chrome-remote-interface: 0.33.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + v8flags@3.2.0: + dependencies: + homedir-polyfill: 1.0.3 + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + value-or-function@3.0.0: {} + + vary@1.1.2: {} + + vinyl-fs@3.0.3: + dependencies: + fs-mkdirp-stream: 1.0.0 + glob-stream: 6.1.0 + graceful-fs: 4.2.11 + is-valid-glob: 1.0.0 + lazystream: 1.0.1 + lead: 1.0.0 + object.assign: 4.1.7 + pumpify: 1.5.1 + readable-stream: 2.3.8 + remove-bom-buffer: 3.0.0 + remove-bom-stream: 1.2.0 + resolve-options: 1.1.0 + through2: 2.0.5 + to-through: 2.0.0 + value-or-function: 3.0.0 + vinyl: 2.2.1 + vinyl-sourcemap: 1.1.0 + + vinyl-sourcemap@1.1.0: + dependencies: + append-buffer: 1.0.2 + convert-source-map: 1.9.0 + graceful-fs: 4.2.11 + normalize-path: 2.1.1 + now-and-later: 2.0.1 + remove-bom-buffer: 3.0.0 + vinyl: 2.2.1 + + vinyl@1.2.0: + dependencies: + clone: 1.0.4 + clone-stats: 0.0.1 + replace-ext: 0.0.1 + + vinyl@2.0.2: + dependencies: + clone: 1.0.4 + clone-buffer: 1.0.0 + clone-stats: 1.0.0 + cloneable-readable: 1.1.3 + is-stream: 1.1.0 + remove-trailing-separator: 1.1.0 + replace-ext: 1.0.1 + + vinyl@2.2.1: + dependencies: + clone: 2.1.2 + clone-buffer: 1.0.0 + clone-stats: 1.0.0 + cloneable-readable: 1.1.3 + remove-trailing-separator: 1.1.0 + replace-ext: 1.0.1 + + vinyl@3.0.1: + dependencies: + clone: 2.1.2 + remove-trailing-separator: 1.1.0 + replace-ext: 2.0.0 + teex: 1.0.1 + + vscode-oniguruma@1.7.0: {} + + vscode-regexpp@3.1.0: {} + + vscode-textmate@9.2.0: {} + + vscode-uri@3.1.0: {} + + watchpack@2.4.4: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + web-tree-sitter@0.20.8: {} + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + webpack-cli@5.1.4(webpack@5.100.2): + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.100.2) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.100.2) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.100.2) + colorette: 2.0.20 + commander: 10.0.1 + cross-spawn: 7.0.6 + envinfo: 7.14.0 + fastest-levenshtein: 1.0.16 + import-local: 3.2.0 + interpret: 3.1.1 + rechoir: 0.8.0 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + webpack-merge: 5.10.0 + + webpack-merge@5.10.0: + dependencies: + clone-deep: 4.0.1 + flat: 5.0.2 + wildcard: 2.0.1 + + webpack-sources@3.3.3: {} + + webpack-stream@7.0.0(webpack@5.100.2): + dependencies: + fancy-log: 1.3.3 + lodash.clone: 4.5.0 + lodash.some: 4.6.0 + memory-fs: 0.5.0 + plugin-error: 1.0.1 + supports-color: 8.1.1 + through: 2.3.8 + vinyl: 2.2.1 + webpack: 5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4) + + webpack@5.100.2(esbuild@0.25.8)(webpack-cli@5.1.4): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.25.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.2 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(esbuild@0.25.8)(webpack@5.100.2) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + optionalDependencies: + webpack-cli: 5.1.4(webpack@5.100.2) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-module@1.0.0: {} + + which-module@2.0.1: {} + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wildcard@2.0.1: {} + + windows-foreground-love@0.5.0: {} + + workerpool@6.5.1: {} + + wrap-ansi@2.1.0: + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + xml2js@0.5.0: + dependencies: + sax: 1.4.1 + xmlbuilder: 11.0.1 + + xml@1.0.1: {} + + xmlbuilder@11.0.1: {} + + xmlbuilder@15.1.1: {} + + xtend@2.1.2: + dependencies: + object-keys: 0.4.0 + + xtend@4.0.2: {} + + y18n@3.2.2: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-parser@5.0.1: + dependencies: + camelcase: 3.0.0 + object.assign: 4.1.7 + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@7.1.2: + dependencies: + camelcase: 3.0.0 + cliui: 3.2.0 + decamelize: 1.2.0 + get-caller-file: 1.0.3 + os-locale: 1.4.0 + read-pkg-up: 1.0.1 + require-directory: 2.1.1 + require-main-filename: 1.0.1 + set-blocking: 2.0.0 + string-width: 1.0.2 + which-module: 1.0.0 + y18n: 3.2.2 + yargs-parser: 5.0.1 + + yaserver@0.4.0: {} + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + + yazl@2.5.1: + dependencies: + buffer-crc32: 0.2.13 + + ylru@1.4.0: {} + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/jetbrains/host/server-cli.ts b/jetbrains/host/server-cli.ts new file mode 100644 index 0000000000..67aa42f4df --- /dev/null +++ b/jetbrains/host/server-cli.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import "./bootstrap-server.js" // this MUST come before other imports as it changes global state +import { dirname, join } from "path" +import { fileURLToPath } from "url" +import { devInjectNodeModuleLookupPath } from "./bootstrap-node.js" +import { bootstrapESM } from "./bootstrap-esm.js" +import { resolveNLSConfiguration } from "./deps/vscode/vs/base/node/nls.js" +import { product } from "./bootstrap-meta.js" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +// NLS +const nlsConfiguration = await resolveNLSConfiguration({ + userLocale: "en", + osLocale: "en", + commit: product.commit, + userDataPath: "", + nlsMetadataPath: __dirname, +}) +process.env["VSCODE_NLS_CONFIG"] = JSON.stringify(nlsConfiguration) // required for `bootstrap-esm` to pick up NLS messages + +if (process.env["VSCODE_DEV"]) { + // When running out of sources, we need to load node modules from remote/node_modules, + // which are compiled against nodejs, not electron + process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] = + process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] || join(__dirname, "..", "remote", "node_modules") + devInjectNodeModuleLookupPath(process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"]) +} else { + delete process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] +} + +// Bootstrap ESM +await bootstrapESM() + +// Load Server +await import("./deps/vscode/vs/server/node/server.cli.js") diff --git a/jetbrains/host/server-main.ts b/jetbrains/host/server-main.ts new file mode 100644 index 0000000000..98d7382634 --- /dev/null +++ b/jetbrains/host/server-main.ts @@ -0,0 +1,328 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import "./bootstrap-server.js" // this MUST come before other imports as it changes global state +import * as path from "path" +import * as http from "http" +import { AddressInfo } from "net" +import * as os from "os" +import * as readline from "readline" +import { performance } from "perf_hooks" +import { fileURLToPath } from "url" +import minimist from "minimist" +import { devInjectNodeModuleLookupPath, removeGlobalNodeJsModuleLookupPaths } from "./bootstrap-node.js" +import { bootstrapESM } from "./bootstrap-esm.js" +import { resolveNLSConfiguration } from "./deps/vscode/vs/base/node/nls.js" +import { product } from "./bootstrap-meta.js" +import * as perf from "./deps/vscode/vs/base/common/performance.js" +import { INLSConfiguration } from "./deps/vscode/vs/nls.js" +import { IServerAPI } from "./deps/vscode/vs/server/node/remoteExtensionHostAgentServer.js" + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +perf.mark("code/server/start") +;(globalThis as any).vscodeServerStartTime = performance.now() + +// Do a quick parse to determine if a server or the cli needs to be started +const parsedArgs = minimist(process.argv.slice(2), { + boolean: [ + "start-server", + "list-extensions", + "print-ip-address", + "help", + "version", + "accept-server-license-terms", + "update-extensions", + ], + string: [ + "install-extension", + "install-builtin-extension", + "uninstall-extension", + "locate-extension", + "socket-path", + "host", + "port", + "compatibility", + ], + alias: { help: "h", version: "v" }, +}) +;["host", "port", "accept-server-license-terms"].forEach((e) => { + if (!parsedArgs[e]) { + const envValue = process.env[`VSCODE_SERVER_${e.toUpperCase().replace("-", "_")}`] + if (envValue) { + parsedArgs[e] = envValue + } + } +}) + +const extensionLookupArgs = ["list-extensions", "locate-extension"] +const extensionInstallArgs = [ + "install-extension", + "install-builtin-extension", + "uninstall-extension", + "update-extensions", +] + +const shouldSpawnCli = + parsedArgs.help || + parsedArgs.version || + extensionLookupArgs.some((a) => !!parsedArgs[a]) || + (extensionInstallArgs.some((a) => !!parsedArgs[a]) && !parsedArgs["start-server"]) + +const nlsConfiguration = await resolveNLSConfiguration({ + userLocale: "en", + osLocale: "en", + commit: product.commit, + userDataPath: "", + nlsMetadataPath: __dirname, +}) + +if (shouldSpawnCli) { + loadCode(nlsConfiguration).then((mod) => { + mod.spawnCli() + }) +} else { + let _remoteExtensionHostAgentServer: IServerAPI | null = null + let _remoteExtensionHostAgentServerPromise: Promise | null = null + const getRemoteExtensionHostAgentServer = () => { + if (!_remoteExtensionHostAgentServerPromise) { + _remoteExtensionHostAgentServerPromise = loadCode(nlsConfiguration).then(async (mod) => { + const server = await mod.createServer(address) + _remoteExtensionHostAgentServer = server + return server + }) + } + return _remoteExtensionHostAgentServerPromise + } + + if (Array.isArray(product.serverLicense) && product.serverLicense.length) { + console.log(product.serverLicense.join("\n")) + if (product.serverLicensePrompt && parsedArgs["accept-server-license-terms"] !== true) { + if (hasStdinWithoutTty()) { + console.log("To accept the license terms, start the server with --accept-server-license-terms") + process.exit(1) + } + try { + const accept = await prompt(product.serverLicensePrompt) + if (!accept) { + process.exit(1) + } + } catch (e) { + console.log(e) + process.exit(1) + } + } + } + + let firstRequest = true + let firstWebSocket = true + + let address: string | AddressInfo | null = null + const server = http.createServer(async (req, res) => { + if (firstRequest) { + firstRequest = false + perf.mark("code/server/firstRequest") + } + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + return remoteExtensionHostAgentServer.handleRequest(req, res) + }) + server.on("upgrade", async (req, socket) => { + if (firstWebSocket) { + firstWebSocket = false + perf.mark("code/server/firstWebSocket") + } + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + // @ts-ignore + return remoteExtensionHostAgentServer.handleUpgrade(req, socket) + }) + server.on("error", async (err) => { + const remoteExtensionHostAgentServer = await getRemoteExtensionHostAgentServer() + return remoteExtensionHostAgentServer.handleServerError(err) + }) + + const host = + sanitizeStringArg(parsedArgs["host"]) || (parsedArgs["compatibility"] !== "1.63" ? "localhost" : undefined) + const nodeListenOptions = parsedArgs["socket-path"] + ? { path: sanitizeStringArg(parsedArgs["socket-path"]) } + : { host, port: await parsePort(host, sanitizeStringArg(parsedArgs["port"])) } + server.listen(nodeListenOptions, async () => { + let output = + Array.isArray(product.serverGreeting) && product.serverGreeting.length + ? `\n\n${product.serverGreeting.join("\n")}\n\n` + : `` + + if (typeof nodeListenOptions.port === "number" && parsedArgs["print-ip-address"]) { + const ifaces = os.networkInterfaces() + Object.keys(ifaces).forEach(function (ifname) { + ifaces[ifname]?.forEach(function (iface) { + if (!iface.internal && iface.family === "IPv4") { + output += `IP Address: ${iface.address}\n` + } + }) + }) + } + + address = server.address() + if (address === null) { + throw new Error("Unexpected server address") + } + + output += `Server bound to ${typeof address === "string" ? address : `${address.address}:${address.port} (${address.family})`}\n` + // Do not change this line. VS Code looks for this in the output. + output += `Extension host agent listening on ${typeof address === "string" ? address : address.port}\n` + console.log(output) + + perf.mark("code/server/started") + ;(globalThis as any).vscodeServerListenTime = performance.now() + + await getRemoteExtensionHostAgentServer() + }) + + process.on("exit", () => { + server.close() + if (_remoteExtensionHostAgentServer) { + _remoteExtensionHostAgentServer.dispose() + } + }) +} + +function sanitizeStringArg(val: any): string | undefined { + if (Array.isArray(val)) { + // if an argument is passed multiple times, minimist creates an array + val = val.pop() // take the last item + } + return typeof val === "string" ? val : undefined +} + +/** + * If `--port` is specified and describes a single port, connect to that port. + * + * If `--port`describes a port range + * then find a free port in that range. Throw error if no + * free port available in range. + * + * In absence of specified ports, connect to port 8000. + */ +async function parsePort(host: string | undefined, strPort: string | undefined): Promise { + if (strPort) { + let range: { start: number; end: number } | undefined + if (strPort.match(/^\d+$/)) { + return parseInt(strPort, 10) + } else if ((range = parseRange(strPort))) { + const port = await findFreePort(host, range.start, range.end) + if (port !== undefined) { + return port + } + // Remote-SSH extension relies on this exact port error message, treat as an API + console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`) + process.exit(1) + } else { + console.warn( + `--port "${strPort}" is not a valid number or range. Ranges must be in the form 'from-to' with 'from' an integer larger than 0 and not larger than 'end'.`, + ) + process.exit(1) + } + } + return 8000 +} + +function parseRange(strRange: string): { start: number; end: number } | undefined { + const match = strRange.match(/^(\d+)-(\d+)$/) + if (match) { + const start = parseInt(match[1], 10), + end = parseInt(match[2], 10) + if (start > 0 && start <= end && end <= 65535) { + return { start, end } + } + } + return undefined +} + +/** + * Starting at the `start` port, look for a free port incrementing + * by 1 until `end` inclusive. If no free port is found, undefined is returned. + */ +async function findFreePort(host: string | undefined, start: number, end: number): Promise { + const testPort = (port: number) => { + return new Promise((resolve) => { + const server = http.createServer() + server + .listen(port, host, () => { + server.close() + resolve(true) + }) + .on("error", () => { + resolve(false) + }) + }) + } + for (let port = start; port <= end; port++) { + if (await testPort(port)) { + return port + } + } + return undefined +} + +async function loadCode(nlsConfiguration: INLSConfiguration) { + // required for `bootstrap-esm` to pick up NLS messages + process.env["VSCODE_NLS_CONFIG"] = JSON.stringify(nlsConfiguration) + + // See https://github.com/microsoft/vscode-remote-release/issues/6543 + // We would normally install a SIGPIPE listener in bootstrap-node.js + // But in certain situations, the console itself can be in a broken pipe state + // so logging SIGPIPE to the console will cause an infinite async loop + process.env["VSCODE_HANDLES_SIGPIPE"] = "true" + + if (process.env["VSCODE_DEV"]) { + // When running out of sources, we need to load node modules from remote/node_modules, + // which are compiled against nodejs, not electron + process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] = + process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] || + path.join(__dirname, "..", "remote", "node_modules") + devInjectNodeModuleLookupPath(process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"]) + } else { + delete process.env["VSCODE_DEV_INJECT_NODE_MODULE_LOOKUP_PATH"] + } + + // Remove global paths from the node module lookup (node.js only) + removeGlobalNodeJsModuleLookupPaths() + + // Bootstrap ESM + await bootstrapESM() + + // Load Server + return import("./deps/vscode/vs/server/node/server.main.js") +} + +function hasStdinWithoutTty(): boolean { + try { + return !process.stdin.isTTY // Via https://twitter.com/MylesBorins/status/782009479382626304 + } catch (error) { + // Windows workaround for https://github.com/nodejs/node/issues/11656 + } + return false +} + +function prompt(question: string): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + return new Promise((resolve, reject) => { + rl.question(question + " ", async function (data) { + rl.close() + const str = data.toString().trim().toLowerCase() + if (str === "" || str === "y" || str === "yes") { + resolve(true) + } else if (str === "n" || str === "no") { + resolve(false) + } else { + process.stdout.write("\nInvalid Response. Answer either yes (y, yes) or no (n, no)\n") + resolve(await prompt(question)) + } + }) + }) +} diff --git a/jetbrains/host/src/config.ts b/jetbrains/host/src/config.ts new file mode 100644 index 0000000000..475f4f88aa --- /dev/null +++ b/jetbrains/host/src/config.ts @@ -0,0 +1,6 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +export const DEBUG_PORT = 9229 diff --git a/jetbrains/host/src/extension.ts b/jetbrains/host/src/extension.ts new file mode 100644 index 0000000000..242d8e8565 --- /dev/null +++ b/jetbrains/host/src/extension.ts @@ -0,0 +1,284 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import net from "net" +import start from "../deps/vscode/vs/workbench/api/node/extensionHostProcess.js" +import { FileRPCProtocolLogger } from "../deps/vscode/vs/workbench/services/extensions/common/fileRPCProtocolLogger.js" +import { RequestInitiator } from "../deps/vscode/vs/workbench/services/extensions/common/rpcProtocol.js" + +// Create global logger instance and export for use by other modules +export const fileLoggerGlobal = new FileRPCProtocolLogger("extension") + +// Command line argument parsing +const args = process.argv.slice(2) +console.log("args:", args) +const LISTEN_MODE = args.includes("--listen") || process.env.VSCODE_EXTHOST_LISTEN === "true" +const PORT = parseInt( + args.find((arg) => arg.startsWith("--vscode-socket-port="))?.substring(21) || + process.env.VSCODE_EXTHOST_DEBUG_PORT || + "51234", + 10, +) +const SOCKET_HOST = + args.find((arg) => arg.startsWith("--vscode-socket-host="))?.substring(21) || + process.env.VSCODE_EXTHOST_SOCKET_HOST || + "127.0.0.1" +const WILL_SEND_SOCKET = + args.find((arg) => arg.startsWith("--vscode-will-send-socket="))?.substring(26) || + process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET || + "0" +const pipeName = process.env.VSCODE_EXTHOST_IPC_HOOK + +console.log(`Extension host starting in ${LISTEN_MODE ? "LISTEN" : "CONNECT"} mode`) +console.log("PORT:", PORT) +console.log("SOCKET_HOST:", SOCKET_HOST) +console.log("WILL_SEND_SOCKET:", WILL_SEND_SOCKET) +console.log("pipeName:", pipeName) + +if (pipeName) { + console.log("Using pipeName, connection will be handled by VSCode IPC") +} else { + // Reset parameter values back to environment variables + process.env.VSCODE_EXTHOST_SOCKET_PORT = PORT.toString() + process.env.VSCODE_EXTHOST_SOCKET_HOST = SOCKET_HOST + process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET = WILL_SEND_SOCKET + console.log("set send socket:", process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) + + // Save original process methods + const originalProcessOn = process.on + const originalProcessSend = process.send || (() => false) + + // Store message event handlers + const messageHandlers: ((message: any, socket?: net.Socket) => void)[] = [] + + // Reconnection related variables + let isReconnecting = false + let reconnectAttempts = 0 + const MAX_RECONNECT_ATTEMPTS = 5 + const RECONNECT_DELAY = 1000 // 1 second + + // Override process.on + process.on = function (event: string, listener: (...args: any[]) => void): any { + if (event === "message") { + messageHandlers.push((message: any, socket?: net.Socket) => { + // Check listener parameter count + const paramCount = listener.length + if (paramCount === 1) { + // If only one parameter, pass only message + listener(message) + } else { + // If multiple parameters, pass message and socket + listener(message, socket) + } + }) + } + return originalProcessOn.call(process, event, listener) + } + + // Override process.send + process.send = function (message: any): boolean { + if (message?.type === "VSCODE_EXTHOST_IPC_READY") { + console.log("Extension host process is ready to receive socket") + if (LISTEN_MODE) { + startServer() + } else { + connect() + } + } + + // Call original process.send + return originalProcessSend.call(process, message) + } + + // Start server mode (for debugging) + function startServer() { + const server = net.createServer((socket) => { + console.log("Main process connected to extension host") + socket.setNoDelay(true) + + // Prepare message to send to VSCode module + const socketMessage = { + type: "VSCODE_EXTHOST_IPC_SOCKET", + initialDataChunk: "", + skipWebSocketFrames: true, + permessageDeflate: false, + inflateBytes: "", + } + + // Call all saved message handlers + messageHandlers.forEach((handler) => { + try { + handler(socketMessage, socket) + } catch (error) { + console.error("Error in message handler:", error) + } + }) + + socket.on("error", (error) => { + console.error("Socket error:", error) + // Don't close server, wait for reconnection + fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, "Socket error:", error) + }) + + socket.on("close", () => { + console.log("Client connection closed, waiting for new connections...") + fileLoggerGlobal.logOutgoing( + 0, + 0, + RequestInitiator.LocalSide, + "Client connection closed, waiting for new connections...", + ) + }) + }) + + // Prevent server timeout closure, keep process active + const keepAliveInterval = setInterval(() => { + if (server.listening) { + console.log("Server still waiting for connections...") + } + }, 60000) // Print a log every minute to keep the process alive + + // Ensure timer cleanup on process exit + process.on("exit", () => { + clearInterval(keepAliveInterval) + }) + + server.listen(PORT, "127.0.0.1", () => { + console.log(`Extension host server listening on 127.0.0.1:${PORT}`) + console.log("Waiting for main process to connect...") + }) + + server.on("error", (error) => { + console.error("Server error:", error) + fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, "Server error:", error) + // No longer exit process, only log error + // Try to restart server + setTimeout(() => { + if (!server.listening) { + console.log("Attempting to restart server after error...") + try { + server.listen(PORT, "127.0.0.1") + } catch (e) { + console.error("Failed to restart server:", e) + } + } + }, 5000) + }) + } + + // Client mode (original behavior) + function connect() { + if (isReconnecting) { + console.log("Already in reconnection process, skipping") + return + } + + try { + // Check connection method + // console.log("get send socket:", process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET); + // const useSocket = process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET === "1"; + + // if (!useSocket) { + // throw new Error('No connection method specified. Please set either VSCODE_EXTHOST_IPC_HOOK or VSCODE_EXTHOST_WILL_SEND_SOCKET'); + // } + + // Use regular TCP Socket + const host = process.env.VSCODE_EXTHOST_SOCKET_HOST || "127.0.0.1" + const port = parseInt(process.env.VSCODE_EXTHOST_SOCKET_PORT || "0", 10) + + if (!port) { + throw new Error("Invalid socket port") + } + + console.log(`Attempting to connect to ${host}:${port}`) + + // Establish socket connection + const socket = net.createConnection(port, host) + // Set the noDelay option for the socket + socket.setNoDelay(true) + + socket.on("connect", () => { + console.log("Connected to main server") + isReconnecting = false + reconnectAttempts = 0 + + // Prepare the message to be sent to the VSCode module + const socketMessage = { + type: "VSCODE_EXTHOST_IPC_SOCKET", + initialDataChunk: "", + skipWebSocketFrames: true, + permessageDeflate: false, + inflateBytes: "", + } + + // Call all saved message handler functions + messageHandlers.forEach((handler) => { + try { + handler(socketMessage, socket) + } catch (error) { + console.error("Error in message handler:", error) + } + }) + }) + + socket.on("error", (error: Error) => { + console.error("Socket connection error:", error) + fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, "Socket connection error:", error) + handleDisconnect() + }) + + socket.on("close", () => { + console.log("Socket connection closed") + handleDisconnect() + }) + } catch (error) { + console.error("Connection error:", error) + fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, "Connection error:", error) + handleDisconnect() + } + } + + // Handle disconnection + async function handleDisconnect() { + if (isReconnecting) { + console.log("Already in reconnection process, skipping") + fileLoggerGlobal.logOutgoing(0, 0, RequestInitiator.LocalSide, "Already in reconnection process, skipping") + return + } + + if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { + console.error("Max reconnection attempts reached. Giving up.") + fileLoggerGlobal.logOutgoing( + 0, + 0, + RequestInitiator.LocalSide, + "Max reconnection attempts reached. Giving up.", + ) + return + } + + isReconnecting = true + reconnectAttempts++ + + console.log(`Attempting to reconnect (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`) + + // Retry after waiting for a period of time + console.log(`Waiting ${RECONNECT_DELAY}ms before reconnecting...`) + await new Promise((resolve) => setTimeout(resolve, RECONNECT_DELAY)) + console.log("Reconnection delay finished, attempting to connect...") + + // Reset reconnection state to allow new reconnection attempts + isReconnecting = false + connect() + } +} + +console.log("Starting extension host process...") + +// Adjust logic: only start directly in non-LISTEN mode +if (LISTEN_MODE) { + process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET = "1" +} +start() diff --git a/jetbrains/host/src/extensionManager.ts b/jetbrains/host/src/extensionManager.ts new file mode 100644 index 0000000000..4d93d13644 --- /dev/null +++ b/jetbrains/host/src/extensionManager.ts @@ -0,0 +1,101 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import { + ExtensionIdentifier, + IExtensionDescription, + TargetPlatform, +} from "../deps/vscode/vs/platform/extensions/common/extensions.js" +import { URI } from "../deps/vscode/vs/base/common/uri.js" +import { ExtHostContext } from "../deps/vscode/vs/workbench/api/common/extHost.protocol.js" +import { IRPCProtocol } from "../deps/vscode/vs/workbench/services/extensions/common/proxyIdentifier.js" +import * as fs from "fs" +import * as path from "path" + +export class ExtensionManager { + private extensionDescriptions: Map = new Map() + + /** + * Parse extension description information + * @param extensionPath Extension path + * @returns Extension description object + */ + private parseExtensionDescription(extensionPath: string): IExtensionDescription { + const packageJsonPath = path.join(extensionPath, "extension.package.json") + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) + + return { + identifier: new ExtensionIdentifier(packageJson.name), + name: packageJson.name, + displayName: packageJson.displayName, + description: packageJson.description, + version: packageJson.version, + publisher: packageJson.publisher, + main: "./extension.cjs", + activationEvents: packageJson.activationEvents || ["onStartupFinished"], + extensionLocation: URI.file(path.resolve(extensionPath)), + targetPlatform: TargetPlatform.UNIVERSAL, + isBuiltin: false, + isUserBuiltin: false, + isUnderDevelopment: false, + engines: packageJson.engines || { vscode: "^1.0.0" }, + preRelease: false, + capabilities: {}, + extensionDependencies: packageJson.extensionDependencies || [], + } + } + + /** + * Get all parsed extension descriptions + * @returns Extension description array + */ + public getAllExtensionDescriptions(): IExtensionDescription[] { + return Array.from(this.extensionDescriptions.values()) + } + + /** + * Get description information for specified extension + * @param extensionId Extension ID + * @returns Extension description object, or undefined if not exists + */ + public getExtensionDescription(extensionId: string): IExtensionDescription | undefined { + return this.extensionDescriptions.get(extensionId) + } + + /** + * Register an extension + * @param extensionPath Extension path + * @returns Extension description object + */ + public registerExtension(extensionPath: string): IExtensionDescription { + const extensionDescription = this.parseExtensionDescription(extensionPath) + this.extensionDescriptions.set(extensionDescription.identifier.value, extensionDescription) + return extensionDescription + } + + /** + * Activate a registered extension + * @param extensionId Extension ID + * @param protocol RPC protocol + */ + public async activateExtension(extensionId: string, protocol: IRPCProtocol): Promise { + const extensionDescription = this.extensionDescriptions.get(extensionId) + if (!extensionDescription) { + throw new Error(`Extension ${extensionId} is not registered`) + } + + try { + const extensionService = protocol.getProxy(ExtHostContext.ExtHostExtensionService) + await extensionService.$activate(extensionDescription.identifier, { + startup: true, + extensionId: extensionDescription.identifier, + activationEvent: "api", + }) + } catch (error) { + console.error(`Failed to activate extension ${extensionId}:`, error) + throw error + } + } +} diff --git a/jetbrains/host/src/main.ts b/jetbrains/host/src/main.ts new file mode 100644 index 0000000000..4715072557 --- /dev/null +++ b/jetbrains/host/src/main.ts @@ -0,0 +1,202 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import { fork } from "child_process" +import * as path from "path" +import { fileURLToPath } from "url" +import * as net from "net" +import { VSBuffer } from "../deps/vscode/vs/base/common/buffer.js" +import { NodeSocket } from "../deps/vscode/vs/base/parts/ipc/node/ipc.net.js" +import { PersistentProtocol } from "../deps/vscode/vs/base/parts/ipc/common/ipc.net.js" +import { DEBUG_PORT } from "./config.js" +import { + MessageType, + createMessageOfType, + isMessageOfType, + UIKind, + IExtensionHostInitData, +} from "../deps/vscode/vs/workbench/services/extensions/common/extensionHostProtocol.js" +import { SocketCloseEvent, SocketCloseEventType } from "../deps/vscode/vs/base/parts/ipc/common/ipc.net.js" +import { IDisposable } from "../deps/vscode/vs/base/common/lifecycle.js" +import { URI } from "../deps/vscode/vs/base/common/uri.js" +import { RPCManager } from "./rpcManager.js" +import { ExtensionManager } from "./extensionManager.js" + +// Get current file directory path +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +// Create ExtensionManager instance and register extension +const extensionManager = new ExtensionManager() +const rooCodeIdentifier = extensionManager.registerExtension("kilocode").identifier + +// Declare extension host process variables +let extHostProcess: ReturnType +let protocol: PersistentProtocol | null = null +let rpcManager: RPCManager | null = null + +// Create socket server +const server = net.createServer((socket) => { + console.log("Someone connected to main server") + + // Set socket noDelay option + socket.setNoDelay(true) + + // Wrap socket with NodeSocket + const nodeSocket = new NodeSocket(socket) + + // Listen for NodeSocket close events + const closeDisposable: IDisposable = nodeSocket.onClose((event: SocketCloseEvent | undefined) => { + console.log("NodeSocket close event received") + if (event?.type === SocketCloseEventType.NodeSocketCloseEvent) { + if (event.hadError) { + console.error("Socket closed with error:", event.error) + } else { + console.log("Socket closed normally") + } + } + closeDisposable.dispose() + }) + + // Create PersistentProtocol instance + protocol = new PersistentProtocol({ + socket: nodeSocket, + initialChunk: null, + }) + + // Set protocol message handler + protocol.onMessage((message) => { + if (isMessageOfType(message, MessageType.Ready)) { + console.log("Extension host is ready") + // Send initialization data + const initData: IExtensionHostInitData = { + commit: "development", + version: "1.0.0", + quality: undefined, + parentPid: process.pid, + environment: { + isExtensionDevelopmentDebug: false, + appName: "VSCodeAPIHook", + appHost: "node", + appLanguage: "en", + appUriScheme: "vscode", + appRoot: URI.file(__dirname), + globalStorageHome: URI.file(path.join(__dirname, "globalStorage")), + workspaceStorageHome: URI.file(path.join(__dirname, "workspaceStorage")), + extensionDevelopmentLocationURI: undefined, + extensionTestsLocationURI: undefined, + useHostProxy: false, + skipWorkspaceStorageLock: false, + isExtensionTelemetryLoggingOnly: false, + }, + workspace: { + id: "development-workspace", + name: "Development Workspace", + transient: false, + configuration: null, + isUntitled: false, + }, + remote: { + authority: undefined, + connectionData: null, + isRemote: false, + }, + extensions: { + versionId: 1, + allExtensions: extensionManager.getAllExtensionDescriptions(), + myExtensions: extensionManager.getAllExtensionDescriptions().map((ext) => ext.identifier), + activationEvents: extensionManager.getAllExtensionDescriptions().reduce( + (events, ext) => { + if (ext.activationEvents) { + events[ext.identifier.value] = ext.activationEvents + } + return events + }, + {} as { [extensionId: string]: string[] }, + ), + }, + telemetryInfo: { + sessionId: "development-session", + machineId: "development-machine", + sqmId: "", + devDeviceId: "", + firstSessionDate: new Date().toISOString(), + msftInternal: false, + }, + logLevel: 0, // Info level + loggers: [], + logsLocation: URI.file(path.join(__dirname, "logs")), + autoStart: true, + consoleForward: { + includeStack: false, + logNative: false, + }, + uiKind: UIKind.Desktop, + } + protocol?.send(VSBuffer.fromString(JSON.stringify(initData))) + } else if (isMessageOfType(message, MessageType.Initialized)) { + console.log("Extension host initialized") + // Create RPCManager instance + rpcManager = new RPCManager(protocol!, extensionManager) + + rpcManager.startInitialize() + + // Activate rooCode plugin + const rpcProtocol = rpcManager.getRPCProtocol() + if (rpcProtocol) { + extensionManager.activateExtension(rooCodeIdentifier.value, rpcProtocol).catch((error: Error) => { + console.error("Failed to load rooCode plugin:", error) + }) + } else { + console.error("Failed to get RPCProtocol from RPCManager") + } + } + }) +}) + +function startExtensionHostProcess() { + process.env.VSCODE_DEBUG = "true" + let nodeOptions = process.env.VSCODE_DEBUG ? `--inspect-brk=9229` : `--inspect=${DEBUG_PORT}` + console.log("will start extension host process with options:", nodeOptions) + + // Create extension host process and pass environment variables + extHostProcess = fork(path.join(__dirname, "extension.js"), [], { + env: { + ...process.env, + VSCODE_EXTHOST_WILL_SEND_SOCKET: "1", + VSCODE_EXTHOST_SOCKET_HOST: "127.0.0.1", + VSCODE_EXTHOST_SOCKET_PORT: (server.address() as net.AddressInfo)?.port?.toString() || "0", + NODE_OPTIONS: nodeOptions, + }, + }) + + // Handle extension host process exit + extHostProcess.on("exit", (code: number | null, signal: string | null) => { + console.log(`Extension host process exited with code ${code} and signal ${signal}`) + server.close() + }) +} + +// Listen on random port +server.listen(0, "127.0.0.1", () => { + const address = server.address() + if (address && typeof address !== "string") { + console.log(`Server listening on port ${address.port}`) + startExtensionHostProcess() + } +}) + +// Handle process exit +process.on("SIGINT", () => { + console.log("Cleaning up...") + if (protocol) { + protocol.send(createMessageOfType(MessageType.Terminate)) + } + server.close() + if (extHostProcess) { + extHostProcess.kill() + } + process.exit(0) +}) diff --git a/jetbrains/host/src/rpcManager.ts b/jetbrains/host/src/rpcManager.ts new file mode 100644 index 0000000000..a9f6d28113 --- /dev/null +++ b/jetbrains/host/src/rpcManager.ts @@ -0,0 +1,997 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import { RPCProtocol } from "../deps/vscode/vs/workbench/services/extensions/common/rpcProtocol.js" +import { IRPCProtocol } from "../deps/vscode/vs/workbench/services/extensions/common/proxyIdentifier.js" +import { PersistentProtocol } from "../deps/vscode/vs/base/parts/ipc/common/ipc.net.js" +import { MainContext, ExtHostContext } from "../deps/vscode/vs/workbench/api/common/extHost.protocol.js" +import { + IRPCProtocolLogger, + RequestInitiator, +} from "../deps/vscode/vs/workbench/services/extensions/common/rpcProtocol.js" +import { UriComponents, UriDto } from "../deps/vscode/vs/base/common/uri.js" +import { LogLevel } from "../deps/vscode/vs/platform/log/common/log.js" +import { ILoggerResource } from "../deps/vscode/vs/platform/log/common/log.js" +import { TerminalLaunchConfig } from "../deps/vscode/vs/workbench/api/common/extHost.protocol.js" +import { IRawFileMatch2 } from "../deps/vscode/vs/workbench/services/search/common/search.js" +import { VSBuffer } from "../deps/vscode/vs/base/common/buffer.js" +import { SerializedError, transformErrorFromSerialization } from "../deps/vscode/vs/base/common/errors.js" +import { IRemoteConsoleLog } from "../deps/vscode/vs/base/common/console.js" +import { FileType, FilePermission, FileSystemProviderErrorCode } from "../deps/vscode/vs/platform/files/common/files.js" +import * as fs from "fs" +import { promisify } from "util" +import { ConfigurationModel } from "../deps/vscode/vs/platform/configuration/common/configurationModels.js" +import { NullLogService } from "../deps/vscode/vs/platform/log/common/log.js" +import { ExtensionIdentifier } from "../deps/vscode/vs/platform/extensions/common/extensions.js" +import { ExtensionActivationReason } from "../deps/vscode/vs/workbench/services/extensions/common/extensions.js" +import { IExtensionDescription } from "../deps/vscode/vs/platform/extensions/common/extensions.js" +import { Dto } from "../deps/vscode/vs/workbench/services/extensions/common/proxyIdentifier.js" +import { ExtensionManager } from "./extensionManager.js" +import { WebViewManager } from "./webViewManager.js" + +// Promisify Node.js fs functions +const fsStat = promisify(fs.stat) +const fsReadDir = promisify(fs.readdir) +const fsReadFile = promisify(fs.readFile) +const fsWriteFile = promisify(fs.writeFile) +const fsRename = promisify(fs.rename) +const fsCopyFile = promisify(fs.copyFile) +const fsUnlink = promisify(fs.unlink) +const fsLstat = promisify(fs.lstat) +const fsMkdir = promisify(fs.mkdir) + +class RPCLogger implements IRPCProtocolLogger { + logIncoming(msgLength: number, req: number, initiator: RequestInitiator, msg: string, data?: any): void { + if (msg == "ack") { + return + } + console.log(`[RPC] ExtHost: ${msg}`) + } + + logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, msg: string, data?: any): void { + if (msg == "ack" || msg == "reply:") { + return + } + console.log(`[RPC] Main: ${msg}`) + } +} + +export class RPCManager { + private rpcProtocol: IRPCProtocol + private logger: RPCLogger + private extensionManager: ExtensionManager + + constructor( + private protocol: PersistentProtocol, + extensionManager: ExtensionManager, + ) { + this.logger = new RPCLogger() + this.rpcProtocol = new RPCProtocol(this.protocol, this.logger) + this.extensionManager = extensionManager + this.setupDefaultProtocols() + this.setupExtensionRequiredProtocols() + this.setupRooCodeRequiredProtocols() + } + + public startInitialize(): void { + // ExtHostConfiguration + const extHostConfiguration = this.rpcProtocol.getProxy(ExtHostContext.ExtHostConfiguration) + + // Send initialization configuration message + extHostConfiguration.$initializeConfiguration({ + defaults: ConfigurationModel.createEmptyModel(new NullLogService()), + policy: ConfigurationModel.createEmptyModel(new NullLogService()), + application: ConfigurationModel.createEmptyModel(new NullLogService()), + userLocal: ConfigurationModel.createEmptyModel(new NullLogService()), + userRemote: ConfigurationModel.createEmptyModel(new NullLogService()), + workspace: ConfigurationModel.createEmptyModel(new NullLogService()), + folders: [], + configurationScopes: [], + }) + + const extHostWorkspace = this.rpcProtocol.getProxy(ExtHostContext.ExtHostWorkspace) + + // Initialize workspace + extHostWorkspace.$initializeWorkspace(null, true) + } + + // Protocols needed for extHost process startup and initialization + public setupDefaultProtocols(): void { + if (!this.rpcProtocol) { + throw new Error("RPCProtocol not initialized") + } + + // MainThreadErrors + this.rpcProtocol.set(MainContext.MainThreadErrors, { + dispose(): void { + // Nothing to do + }, + $onUnexpectedError(err: any | SerializedError): void { + if (err && err.$isError) { + err = transformErrorFromSerialization(err) + } + console.error("Unexpected error:", err) + /* + if (err instanceof Error && err.stack) { + console.error('Stack trace:', err.stack); + } + */ + }, + }) + + // MainThreadConsole + this.rpcProtocol.set(MainContext.MainThreadConsole, { + dispose(): void { + // Nothing to do + }, + $logExtensionHostMessage(entry: IRemoteConsoleLog): void { + // Parse the entry + const args = this.parseRemoteConsoleLog(entry) + + // Log based on severity + switch (entry.severity) { + case "log": + case "info": + console.log("[Extension Host]", ...args) + break + case "warn": + console.warn("[Extension Host]", ...args) + break + case "error": + console.error("[Extension Host]", ...args) + break + case "debug": + console.debug("[Extension Host]", ...args) + break + default: + console.log("[Extension Host]", ...args) + } + }, + parseRemoteConsoleLog(entry: IRemoteConsoleLog): any[] { + const args: any[] = [] + + try { + // Parse the arguments string as JSON + const parsedArguments = JSON.parse(entry.arguments) + args.push(...parsedArguments) + } catch (error) { + // If parsing fails, just log the raw arguments string + args.push("Unable to log remote console arguments", entry.arguments) + } + + return args + }, + }) + + // MainThreadLogger + this.rpcProtocol.set(MainContext.MainThreadLogger, { + $log(file: UriComponents, messages: [LogLevel, string][]): void { + console.log("Logger message:", { file, messages }) + }, + $flush(file: UriComponents): void { + console.log("Flush logger:", file) + }, + $createLogger(file: UriComponents, options?: any): Promise { + console.log("Create logger:", { file, options }) + return Promise.resolve() + }, + $registerLogger(logger: UriDto): Promise { + console.log("Register logger (id: ", logger.id, ", name: ", logger.name, ")") + return Promise.resolve() + }, + $deregisterLogger(resource: UriComponents): Promise { + console.log("Deregister logger:", resource) + return Promise.resolve() + }, + $setVisibility(resource: UriComponents, visible: boolean): Promise { + console.log("Set logger visibility:", { resource, visible }) + return Promise.resolve() + }, + }) + + // MainThreadCommands + this.rpcProtocol.set(MainContext.MainThreadCommands, { + $registerCommand(id: string): void { + console.log("Register command:", id) + }, + $unregisterCommand(id: string): void { + console.log("Unregister command:", id) + }, + $executeCommand(id: string, ...args: any[]): Promise { + console.log("Execute command:", id, args) + return Promise.resolve(null as T) + }, + $fireCommandActivationEvent(id: string): void { + console.log("Fire command activation event:", id) + }, + $getCommands(): Promise { + return Promise.resolve([]) + }, + dispose(): void { + console.log("Dispose MainThreadCommands") + }, + }) + + // MainThreadTerminalService + this.rpcProtocol.set(MainContext.MainThreadTerminalService, { + $registerProcessSupport(isSupported: boolean): void { + console.log("Register process support:", isSupported) + }, + $createTerminal(extHostTerminalId: string, config: TerminalLaunchConfig): Promise { + console.log("Create terminal:", { extHostTerminalId, config }) + return Promise.resolve() + }, + $dispose(id: string): void { + console.log("Dispose terminal:", id) + }, + $hide(id: string): void { + console.log("Hide terminal:", id) + }, + $sendText(id: string, text: string, shouldExecute: boolean): void { + console.log("Send text to terminal:", { id, text, shouldExecute }) + }, + $show(id: string, preserveFocus: boolean): void { + console.log("Show terminal:", { id, preserveFocus }) + }, + $registerProfileProvider(id: string, extensionIdentifier: string): void { + console.log("Register profile provider:", { id, extensionIdentifier }) + }, + $unregisterProfileProvider(id: string): void { + console.log("Unregister profile provider:", id) + }, + $registerCompletionProvider(id: string, extensionIdentifier: string, ...triggerCharacters: string[]): void { + console.log("Register completion provider:", { id, extensionIdentifier, triggerCharacters }) + }, + $unregisterCompletionProvider(id: string): void { + console.log("Unregister completion provider:", id) + }, + $registerQuickFixProvider(id: string, extensionIdentifier: string): void { + console.log("Register quick fix provider:", { id, extensionIdentifier }) + }, + $unregisterQuickFixProvider(id: string): void { + console.log("Unregister quick fix provider:", id) + }, + $setEnvironmentVariableCollection( + extensionIdentifier: string, + persistent: boolean, + collection: any, + descriptionMap: any, + ): void { + console.log("Set environment variable collection:", { + extensionIdentifier, + persistent, + collection, + descriptionMap, + }) + }, + $startSendingDataEvents(): void { + console.log("Start sending data events") + }, + $stopSendingDataEvents(): void { + console.log("Stop sending data events") + }, + $startSendingCommandEvents(): void { + console.log("Start sending command events") + }, + $stopSendingCommandEvents(): void { + console.log("Stop sending command events") + }, + $startLinkProvider(): void { + console.log("Start link provider") + }, + $stopLinkProvider(): void { + console.log("Stop link provider") + }, + $sendProcessData(terminalId: number, data: string): void { + console.log("Send process data:", { terminalId, data }) + }, + $sendProcessReady(terminalId: number, pid: number, cwd: string, windowsPty: any): void { + console.log("Send process ready:", { terminalId, pid, cwd, windowsPty }) + }, + $sendProcessProperty(terminalId: number, property: any): void { + console.log("Send process property:", { terminalId, property }) + }, + $sendProcessExit(terminalId: number, exitCode: number | undefined): void { + console.log("Send process exit:", { terminalId, exitCode }) + }, + dispose(): void { + console.log("Dispose MainThreadTerminalService") + }, + }) + + // MainThreadWindow + this.rpcProtocol.set(MainContext.MainThreadWindow, { + $getInitialState(): Promise<{ isFocused: boolean; isActive: boolean }> { + console.log("Get initial state") + return Promise.resolve({ isFocused: false, isActive: false }) + }, + $openUri(uri: UriComponents, uriString: string | undefined, options: any): Promise { + console.log("Open URI:", { uri, uriString, options }) + return Promise.resolve(true) + }, + $asExternalUri(uri: UriComponents, options: any): Promise { + console.log("As external URI:", { uri, options }) + return Promise.resolve(uri) + }, + dispose(): void { + console.log("Dispose MainThreadWindow") + }, + }) + + // MainThreadSearch + this.rpcProtocol.set(MainContext.MainThreadSearch, { + $registerFileSearchProvider(handle: number, scheme: string): void { + console.log("Register file search provider:", { handle, scheme }) + }, + $registerAITextSearchProvider(handle: number, scheme: string): void { + console.log("Register AI text search provider:", { handle, scheme }) + }, + $registerTextSearchProvider(handle: number, scheme: string): void { + console.log("Register text search provider:", { handle, scheme }) + }, + $unregisterProvider(handle: number): void { + console.log("Unregister provider:", handle) + }, + $handleFileMatch(handle: number, session: number, data: UriComponents[]): void { + console.log("Handle file match:", { handle, session, data }) + }, + $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void { + console.log("Handle text match:", { handle, session, data }) + }, + $handleTelemetry(eventName: string, data: any): void { + console.log("Handle telemetry:", { eventName, data }) + }, + dispose(): void { + console.log("Dispose MainThreadSearch") + }, + }) + + // MainThreadTask + this.rpcProtocol.set(MainContext.MainThreadTask, { + $createTaskId(task: any): Promise { + console.log("Create task ID:", task) + return Promise.resolve("task-id") + }, + $registerTaskProvider(handle: number, type: string): Promise { + console.log("Register task provider:", { handle, type }) + return Promise.resolve() + }, + $unregisterTaskProvider(handle: number): Promise { + console.log("Unregister task provider:", handle) + return Promise.resolve() + }, + $fetchTasks(filter?: any): Promise { + console.log("Fetch tasks:", filter) + return Promise.resolve([]) + }, + $getTaskExecution(value: any): Promise { + console.log("Get task execution:", value) + return Promise.resolve(null) + }, + $executeTask(task: any): Promise { + console.log("Execute task:", task) + return Promise.resolve(null) + }, + $terminateTask(id: string): Promise { + console.log("Terminate task:", id) + return Promise.resolve() + }, + $registerTaskSystem(scheme: string, info: any): void { + console.log("Register task system:", { scheme, info }) + }, + $customExecutionComplete(id: string, result?: number): Promise { + console.log("Custom execution complete:", { id, result }) + return Promise.resolve() + }, + $registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise { + console.log("Register supported executions:", { custom, shell, process }) + return Promise.resolve() + }, + dispose(): void { + console.log("Dispose MainThreadTask") + }, + }) + + // MainThreadConfiguration + this.rpcProtocol.set(MainContext.MainThreadConfiguration, { + $updateConfigurationOption( + target: any, + key: string, + value: any, + overrides: any, + scopeToLanguage: boolean | undefined, + ): Promise { + console.log("Update configuration option:", { target, key, value, overrides, scopeToLanguage }) + return Promise.resolve() + }, + $removeConfigurationOption( + target: any, + key: string, + overrides: any, + scopeToLanguage: boolean | undefined, + ): Promise { + console.log("Remove configuration option:", { target, key, overrides, scopeToLanguage }) + return Promise.resolve() + }, + dispose(): void { + console.log("Dispose MainThreadConfiguration") + }, + }) + + // MainThreadFileSystem + this.rpcProtocol.set(MainContext.MainThreadFileSystem, { + async $registerFileSystemProvider( + handle: number, + scheme: string, + capabilities: any, + readonlyMessage?: any, + ): Promise { + console.log("Register file system provider:", { handle, scheme, capabilities, readonlyMessage }) + }, + $unregisterProvider(handle: number): void { + console.log("Unregister provider:", handle) + }, + $onFileSystemChange(handle: number, resource: any[]): void { + console.log("File system change:", { handle, resource }) + }, + async $stat(resource: UriComponents): Promise { + console.log("Stat:", resource) + try { + const filePath = this.uriToPath(resource) + const stats = await fsStat(filePath) + + return { + type: this.getFileType(stats), + ctime: stats.birthtimeMs, + mtime: stats.mtimeMs, + size: stats.size, + permissions: stats.mode & 0o444 ? FilePermission.Readonly : undefined, + } + } catch (error) { + console.error("Error in $stat:", error) + throw this.handleFileSystemError(error) + } + }, + async $readdir(resource: UriComponents): Promise<[string, FileType][]> { + console.log("Read directory:", resource) + try { + const filePath = this.uriToPath(resource) + const entries = await fsReadDir(filePath, { withFileTypes: true }) + + return entries.map((entry) => { + let type = FileType.Unknown + if (entry.isFile()) { + type = FileType.File + } else if (entry.isDirectory()) { + type = FileType.Directory + } + + // Check if it's a symbolic link + if (entry.isSymbolicLink()) { + type |= FileType.SymbolicLink + } + + return [entry.name, type] as [string, FileType] + }) + } catch (error) { + console.error("Error in $readdir:", error) + throw this.handleFileSystemError(error) + } + }, + async $readFile(resource: UriComponents): Promise { + console.log("Read file:", resource) + try { + const filePath = this.uriToPath(resource) + const buffer = await fsReadFile(filePath) + return VSBuffer.wrap(buffer) + } catch (error) { + console.error("Error in $readFile:", error) + throw this.handleFileSystemError(error) + } + }, + async $writeFile(resource: UriComponents, content: any): Promise { + console.log("Write file:", { resource, content }) + try { + const filePath = this.uriToPath(resource) + const buffer = content instanceof VSBuffer ? content.buffer : content + await fsWriteFile(filePath, buffer) + } catch (error) { + console.error("Error in $writeFile:", error) + throw this.handleFileSystemError(error) + } + }, + async $rename(resource: UriComponents, target: UriComponents, opts: any): Promise { + console.log("Rename:", { resource, target, opts }) + try { + const sourcePath = this.uriToPath(resource) + const targetPath = this.uriToPath(target) + + // Check if target exists and handle overwrite option + if (opts.overwrite) { + try { + await fsUnlink(targetPath) + } catch (error) { + // Ignore error if file doesn't exist + } + } + + await fsRename(sourcePath, targetPath) + } catch (error) { + console.error("Error in $rename:", error) + throw this.handleFileSystemError(error) + } + }, + async $copy(resource: UriComponents, target: UriComponents, opts: any): Promise { + console.log("Copy:", { resource, target, opts }) + try { + const sourcePath = this.uriToPath(resource) + const targetPath = this.uriToPath(target) + + // Check if target exists and handle overwrite option + if (opts.overwrite) { + try { + await fsUnlink(targetPath) + } catch (error) { + // Ignore error if file doesn't exist + } + } + + await fsCopyFile(sourcePath, targetPath) + } catch (error) { + console.error("Error in $copy:", error) + throw this.handleFileSystemError(error) + } + }, + async $mkdir(resource: UriComponents): Promise { + console.log("Make directory:", resource) + try { + const dirPath = this.uriToPath(resource) + await fsMkdir(dirPath, { recursive: true }) + } catch (error) { + console.error("Error in $mkdir:", error) + throw this.handleFileSystemError(error) + } + }, + async $delete(resource: UriComponents, opts: any): Promise { + console.log("Delete:", { resource, opts }) + try { + const filePath = this.uriToPath(resource) + + // Check if it's a directory + const stats = await fsLstat(filePath) + if (stats.isDirectory()) { + // For directories, we need to implement recursive deletion + // This is a simplified version + await fs.promises.rm(filePath, { recursive: true }) + } else { + await fsUnlink(filePath) + } + } catch (error) { + console.error("Error in $delete:", error) + throw this.handleFileSystemError(error) + } + }, + async $ensureActivation(scheme: string): Promise { + console.log("Ensure activation:", scheme) + // No-op implementation + return Promise.resolve() + }, + dispose(): void { + console.log("Dispose MainThreadFileSystem") + }, + + // Helper methods + uriToPath(uri: UriComponents): string { + // Convert URI to file path + // This is a simplified implementation + if (uri.scheme !== "file") { + throw new Error(`Unsupported URI scheme: ${uri.scheme}`) + } + + // Handle Windows paths + let filePath = uri.path || "" + if (process.platform === "win32" && filePath.startsWith("/")) { + filePath = filePath.substring(1) + } + + return filePath + }, + + getFileType(stats: fs.Stats): FileType { + let type = FileType.Unknown + + if (stats.isFile()) { + type = FileType.File + } else if (stats.isDirectory()) { + type = FileType.Directory + } + + // Check if it's a symbolic link + if (stats.isSymbolicLink()) { + type |= FileType.SymbolicLink + } + + return type + }, + + handleFileSystemError(error: any): Error { + // Map Node.js errors to VSCode file system errors + if (error.code === "ENOENT") { + const err = new Error(error.message) + err.name = FileSystemProviderErrorCode.FileNotFound + return err + } else if (error.code === "EACCES" || error.code === "EPERM") { + const err = new Error(error.message) + err.name = FileSystemProviderErrorCode.NoPermissions + return err + } else if (error.code === "EEXIST") { + const err = new Error(error.message) + err.name = FileSystemProviderErrorCode.FileExists + return err + } else if (error.code === "EISDIR") { + const err = new Error(error.message) + err.name = FileSystemProviderErrorCode.FileIsADirectory + return err + } else if (error.code === "ENOTDIR") { + const err = new Error(error.message) + err.name = FileSystemProviderErrorCode.FileNotADirectory + return err + } + + // Default error + return error + }, + }) + + // MainThreadLanguageModelTools + this.rpcProtocol.set(MainContext.MainThreadLanguageModelTools, { + $getTools(): Promise { + console.log("Getting language model tools") + return Promise.resolve([]) + }, + $invokeTool(dto: any, token: any): Promise { + console.log("Invoking language model tool:", dto) + return Promise.resolve({}) + }, + $countTokensForInvocation(callId: string, input: string, token: any): Promise { + console.log("Counting tokens for invocation:", { callId, input }) + return Promise.resolve(0) + }, + $registerTool(id: string): void { + console.log("Registering language model tool:", id) + }, + $unregisterTool(name: string): void { + console.log("Unregistering language model tool:", name) + }, + dispose(): void { + console.log("Disposing MainThreadLanguageModelTools") + }, + }) + } + + // Protocols needed for general extension loading process + public setupExtensionRequiredProtocols(): void { + if (!this.rpcProtocol) { + return + } + + this.rpcProtocol.set(MainContext.MainThreadExtensionService, { + $getExtension: async (extensionId: string): Promise | undefined> => { + console.log(`Getting extension: ${extensionId}`) + return this.extensionManager.getExtensionDescription(extensionId) + }, + $activateExtension: async ( + extensionId: ExtensionIdentifier, + reason: ExtensionActivationReason, + ): Promise => { + console.log(`Activating extension ${extensionId.value} with reason:`, reason) + await this.extensionManager.activateExtension(extensionId.value, this.rpcProtocol) + }, + $onWillActivateExtension: async (extensionId: ExtensionIdentifier): Promise => { + console.log(`Extension ${extensionId.value} will be activated`) + }, + $onDidActivateExtension: ( + extensionId: ExtensionIdentifier, + codeLoadingTime: number, + activateCallTime: number, + activateResolvedTime: number, + activationReason: ExtensionActivationReason, + ): void => { + console.log(`Extension ${extensionId.value} was activated with reason:`, activationReason) + }, + $onExtensionActivationError: async ( + extensionId: ExtensionIdentifier, + error: any, + missingExtensionDependency: any | null, + ): Promise => { + console.error(`Extension ${extensionId.value} activation error:`, error) + }, + $onExtensionRuntimeError: (extensionId: ExtensionIdentifier, error: any): void => { + console.error(`Extension ${extensionId.value} runtime error:`, error) + }, + $setPerformanceMarks: async (marks: { name: string; startTime: number }[]): Promise => { + console.log("Setting performance marks:", marks) + }, + $asBrowserUri: async (uri: any): Promise => { + console.log("Converting to browser URI:", uri) + return uri + }, + dispose: () => { + console.log("Disposing MainThreadExtensionService") + }, + }) + + this.rpcProtocol.set(MainContext.MainThreadTelemetry, { + $publicLog(eventName: string, data?: any): void { + console.log(`[Telemetry] ${eventName}`, data) + }, + $publicLog2(eventName: string, data?: any): void { + console.log(`[Telemetry] ${eventName}`, data) + }, + dispose(): void { + console.log("Disposing MainThreadTelemetry") + }, + }) + + this.rpcProtocol.set(MainContext.MainThreadDebugService, { + $registerDebugTypes(debugTypes: string[]): void { + console.log("Register debug types:", debugTypes) + }, + $sessionCached(sessionID: string): void { + console.log("Session cached:", sessionID) + }, + $acceptDAMessage(handle: number, message: any): void { + console.log("Accept debug adapter message:", { handle, message }) + }, + $acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void { + console.error("Debug adapter error:", { handle, name, message, stack }) + }, + $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void { + console.log("Debug adapter exit:", { handle, code, signal }) + }, + async $registerDebugConfigurationProvider( + type: string, + triggerKind: any, + hasProvideMethod: boolean, + hasResolveMethod: boolean, + hasResolve2Method: boolean, + handle: number, + ): Promise { + console.log("Register debug configuration provider:", { + type, + triggerKind, + hasProvideMethod, + hasResolveMethod, + hasResolve2Method, + handle, + }) + }, + async $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise { + console.log("Register debug adapter descriptor factory:", { type, handle }) + }, + $unregisterDebugConfigurationProvider(handle: number): void { + console.log("Unregister debug configuration provider:", handle) + }, + $unregisterDebugAdapterDescriptorFactory(handle: number): void { + console.log("Unregister debug adapter descriptor factory:", handle) + }, + async $startDebugging(folder: any, nameOrConfig: string | any, options: any): Promise { + console.log("Start debugging:", { folder, nameOrConfig, options }) + return true + }, + async $stopDebugging(sessionId: string | undefined): Promise { + console.log("Stop debugging:", sessionId) + }, + $setDebugSessionName(id: string, name: string): void { + console.log("Set debug session name:", { id, name }) + }, + async $customDebugAdapterRequest(id: string, command: string, args: any): Promise { + console.log("Custom debug adapter request:", { id, command, args }) + return null + }, + async $getDebugProtocolBreakpoint(id: string, breakpoinId: string): Promise { + console.log("Get debug protocol breakpoint:", { id, breakpoinId }) + return undefined + }, + $appendDebugConsole(value: string): void { + console.log("Debug console:", value) + }, + async $registerBreakpoints(breakpoints: any[]): Promise { + console.log("Register breakpoints:", breakpoints) + }, + async $unregisterBreakpoints( + breakpointIds: string[], + functionBreakpointIds: string[], + dataBreakpointIds: string[], + ): Promise { + console.log("Unregister breakpoints:", { breakpointIds, functionBreakpointIds, dataBreakpointIds }) + }, + $registerDebugVisualizer(extensionId: string, id: string): void { + console.log("Register debug visualizer:", { extensionId, id }) + }, + $unregisterDebugVisualizer(extensionId: string, id: string): void { + console.log("Unregister debug visualizer:", { extensionId, id }) + }, + $registerDebugVisualizerTree(treeId: string, canEdit: boolean): void { + console.log("Register debug visualizer tree:", { treeId, canEdit }) + }, + $unregisterDebugVisualizerTree(treeId: string): void { + console.log("Unregister debug visualizer tree:", treeId) + }, + $registerCallHierarchyProvider(handle: number, supportsResolve: boolean): void { + console.log("Register call hierarchy provider:", { handle, supportsResolve }) + }, + dispose(): void { + console.log("Disposing MainThreadDebugService") + }, + }) + } + + public setupRooCodeRequiredProtocols(): void { + if (!this.rpcProtocol) { + return + } + + // MainThreadTextEditors + this.rpcProtocol.set(MainContext.MainThreadTextEditors, { + $tryShowTextDocument(resource: UriComponents, options: any): Promise { + console.log("Try show text document:", { resource, options }) + return Promise.resolve(undefined) + }, + $tryShowEditor(id: string, position?: any): Promise { + console.log("Try show editor:", { id, position }) + return Promise.resolve() + }, + $tryHideEditor(id: string): Promise { + console.log("Try hide editor:", id) + return Promise.resolve() + }, + $trySetSelections(id: string, selections: any[]): Promise { + console.log("Try set selections:", { id, selections }) + return Promise.resolve() + }, + $tryRevealRange(id: string, range: any, revealType: any): Promise { + console.log("Try reveal range:", { id, range, revealType }) + return Promise.resolve() + }, + $trySetOptions(id: string, options: any): Promise { + console.log("Try set options:", { id, options }) + return Promise.resolve() + }, + $tryApplyEdits(id: string, modelVersionId: number, edits: any[], opts: any): Promise { + console.log("Try apply edits:", { id, modelVersionId, edits, opts }) + return Promise.resolve(true) + }, + $registerTextEditorDecorationType(extensionId: ExtensionIdentifier, key: string, options: any): void { + console.log("Register text editor decoration type:", { extensionId, key, options }) + }, + $removeTextEditorDecorationType(key: string): void { + console.log("Remove text editor decoration type:", key) + }, + $trySetDecorations(id: string, key: string, ranges: any[]): Promise { + console.log("Try set decorations:", { id, key, ranges }) + return Promise.resolve() + }, + $trySetDecorationsFast(id: string, key: string, ranges: any[]): Promise { + console.log("Try set decorations fast:", { id, key, ranges }) + return Promise.resolve() + }, + $tryInsertSnippet(id: string, snippet: any, location: any, options: any): Promise { + console.log("Try insert snippet:", { id, snippet, location, options }) + return Promise.resolve(true) + }, + $getDiffInformation(id: string): Promise { + console.log("Get diff information:", id) + return Promise.resolve(null) + }, + dispose(): void { + console.log("Dispose MainThreadTextEditors") + }, + }) + + // MainThreadStorage + this.rpcProtocol.set(MainContext.MainThreadStorage, { + $initializeExtensionStorage(shared: boolean, extensionId: string): Promise { + console.log("Initialize extension storage:", { shared, extensionId }) + return Promise.resolve(undefined) + }, + $setValue(shared: boolean, extensionId: string, value: object): Promise { + console.log("Set value:", { shared, extensionId, value }) + return Promise.resolve() + }, + $registerExtensionStorageKeysToSync(extension: any, keys: string[]): void { + console.log("Register extension storage keys to sync:", { extension, keys }) + }, + dispose(): void { + console.log("Dispose MainThreadStorage") + }, + }) + + // MainThreadOutputService + this.rpcProtocol.set(MainContext.MainThreadOutputService, { + $register( + label: string, + file: UriComponents, + languageId: string | undefined, + extensionId: string, + ): Promise { + console.log("Register output channel:", { label, file, languageId, extensionId }) + return Promise.resolve(`output-${extensionId}-${label}`) + }, + $update(channelId: string, mode: any, till?: number): Promise { + console.log("Update output channel:", { channelId, mode, till }) + return Promise.resolve() + }, + $reveal(channelId: string, preserveFocus: boolean): Promise { + console.log("Reveal output channel:", { channelId, preserveFocus }) + return Promise.resolve() + }, + $close(channelId: string): Promise { + console.log("Close output channel:", channelId) + return Promise.resolve() + }, + $dispose(channelId: string): Promise { + console.log("Dispose output channel:", channelId) + return Promise.resolve() + }, + dispose(): void { + console.log("Dispose MainThreadOutputService") + }, + }) + + // Create a single WebViewManager instance + const webViewManager = new WebViewManager(this.rpcProtocol) + + // MainThreadWebviewViews + this.rpcProtocol.set(MainContext.MainThreadWebviewViews, webViewManager) + + // MainThreadDocumentContentProviders + this.rpcProtocol.set(MainContext.MainThreadDocumentContentProviders, { + $registerTextContentProvider(handle: number, scheme: string): void { + console.log("Register text content provider:", { handle, scheme }) + }, + $unregisterTextContentProvider(handle: number): void { + console.log("Unregister text content provider:", handle) + }, + $onVirtualDocumentChange(uri: UriComponents, value: string): Promise { + console.log("Virtual document change:", { uri, value }) + return Promise.resolve() + }, + dispose(): void { + console.log("Dispose MainThreadDocumentContentProviders") + }, + }) + + // MainThreadUrls + this.rpcProtocol.set(MainContext.MainThreadUrls, { + $registerUriHandler( + handle: number, + extensionId: ExtensionIdentifier, + extensionDisplayName: string, + ): Promise { + console.log("Register URI handler:", { handle, extensionId, extensionDisplayName }) + return Promise.resolve() + }, + $unregisterUriHandler(handle: number): Promise { + console.log("Unregister URI handler:", handle) + return Promise.resolve() + }, + $createAppUri(uri: UriComponents): Promise { + console.log("Create app URI:", uri) + return Promise.resolve(uri) + }, + dispose(): void { + console.log("Dispose MainThreadUrls") + }, + }) + + // MainThreadWebviews + this.rpcProtocol.set(MainContext.MainThreadWebviews, webViewManager) + } + + public getRPCProtocol(): IRPCProtocol | null { + return this.rpcProtocol + } +} diff --git a/jetbrains/host/src/webViewManager.ts b/jetbrains/host/src/webViewManager.ts new file mode 100644 index 0000000000..a10d22edf9 --- /dev/null +++ b/jetbrains/host/src/webViewManager.ts @@ -0,0 +1,159 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import { + ExtHostContext, + ExtHostWebviewViewsShape, + MainThreadWebviewViewsShape, + WebviewExtensionDescription as ExtHostWebviewExtensionDescription, + MainThreadWebviewsShape, + IWebviewContentOptions, +} from "../deps/vscode/vs/workbench/api/common/extHost.protocol.js" +import { IRPCProtocol } from "../deps/vscode/vs/workbench/services/extensions/common/proxyIdentifier.js" +import { WebviewContentOptions } from "../deps/vscode/vs/workbench/contrib/webview/browser/webview.js" +import { URI } from "../deps/vscode/vs/base/common/uri.js" +import { CancellationToken } from "../deps/vscode/vs/base/common/cancellation.js" +import { VSBuffer } from "../deps/vscode/vs/base/common/buffer.js" + +/** + * A simplified webview implementation that only includes methods used by WebViewManager + */ +class SimpleWebview { + contentOptions: WebviewContentOptions = {} + + setHtml(html: string): void { + console.log("[SimpleWebview] Set HTML:", html) + } + + setTitle(title: string): void { + console.log("[SimpleWebview] Set title:", title) + } + + postMessage(message: any, transfer?: readonly VSBuffer[]): Promise { + console.log("[SimpleWebview] Post message:", message) + return Promise.resolve(true) + } + + focus(): void { + console.log("[SimpleWebview] Focus") + } + + dispose(): void { + console.log("[SimpleWebview] Dispose") + } +} + +export class WebViewManager implements MainThreadWebviewViewsShape, MainThreadWebviewsShape { + private readonly _proxy: ExtHostWebviewViewsShape + private readonly _webviews = new Map() + + constructor(private readonly rpcProtocol: IRPCProtocol) { + this._proxy = this.rpcProtocol.getProxy(ExtHostContext.ExtHostWebviewViews) + } + + // MainThreadWebviewViewsShape implementation + $registerWebviewViewProvider( + extension: ExtHostWebviewExtensionDescription, + viewType: string, + options: { retainContextWhenHidden?: boolean; serializeBuffersForPostMessage: boolean }, + ): void { + console.log("Register webview view provider:", { extension, viewType, options }) + + // Create a new webview instance + const webview = new SimpleWebview() + + // Store the webview instance + this._webviews.set(viewType, webview) + + // Generate a unique handle for this webview + const webviewHandle = `webview-${viewType}-${Date.now()}` + + // Notify the extension host that the webview is ready + this._proxy.$resolveWebviewView( + webviewHandle, + viewType, + undefined, // title + undefined, // state + CancellationToken.None, // cancellation + ) + } + + $unregisterWebviewViewProvider(viewType: string): void { + console.log("Unregister webview view provider:", viewType) + + // Remove the webview instance + const webview = this._webviews.get(viewType) + if (webview) { + webview.dispose() + this._webviews.delete(viewType) + } + } + + $setWebviewViewTitle(handle: string, value: string | undefined): void { + console.log("Set webview view title:", { handle, value }) + const webview = this._webviews.get(handle) + if (webview) { + webview.setTitle(value || "") + } + } + + $setWebviewViewDescription(handle: string, value: string | undefined): void { + console.log("Set webview view description:", { handle, value }) + } + + $setWebviewViewBadge(handle: string, badge: any | undefined): void { + console.log("Set webview view badge:", { handle, badge }) + } + + $show(handle: string, preserveFocus: boolean): void { + console.log("Show webview view:", { handle, preserveFocus }) + const webview = this._webviews.get(handle) + if (webview) { + webview.focus() + } + } + + // MainThreadWebviewsShape implementation + $setHtml(handle: string, value: string): void { + console.log("Set webview HTML:", { handle, value }) + const webview = this._webviews.get(handle) + if (webview) { + webview.setHtml(value) + } + } + + $setOptions(handle: string, options: IWebviewContentOptions): void { + console.log("Set webview panel options:", { handle, options }) + const webview = this._webviews.get(handle) + if (webview) { + // Convert IWebviewContentOptions to WebviewContentOptions + const contentOptions: WebviewContentOptions = { + allowScripts: options.enableScripts, + allowForms: options.enableForms, + localResourceRoots: options.localResourceRoots?.map((uri) => URI.revive(uri)), + portMapping: options.portMapping, + } + webview.contentOptions = contentOptions + } + } + + $postMessage(handle: string, value: string, ...buffers: VSBuffer[]): Promise { + console.log("Post message to webview:", { handle, value, buffers }) + const webview = this._webviews.get(handle) + if (webview) { + return webview.postMessage(value, buffers) + } + return Promise.resolve(false) + } + + dispose(): void { + console.log("Dispose WebViewManager") + // Dispose all webviews + for (const webview of this._webviews.values()) { + webview.dispose() + } + this._webviews.clear() + } +} diff --git a/jetbrains/host/tsconfig.base.json b/jetbrains/host/tsconfig.base.json new file mode 100644 index 0000000000..f5eafdb661 --- /dev/null +++ b/jetbrains/host/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "moduleDetection": "legacy", + "experimentalDecorators": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUncheckedSideEffectImports": true, + "allowUnreachableCode": false, + "strict": true, + "exactOptionalPropertyTypes": false, + "useUnknownInCatchVariables": false, + "forceConsistentCasingInFileNames": true, + "target": "es2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker.ImportScripts"], + "allowSyntheticDefaultImports": true + } +} diff --git a/jetbrains/host/tsconfig.json b/jetbrains/host/tsconfig.json new file mode 100644 index 0000000000..1024743d1e --- /dev/null +++ b/jetbrains/host/tsconfig.json @@ -0,0 +1,51 @@ +{ + // This is the configuration for plugins/base independent project, compiled separately from the outer project + "extends": "./tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "esModuleInterop": true, + "removeComments": false, + "preserveConstEnums": true, + "sourceMap": true, + "inlineSources": true, + "allowJs": true, + "resolveJsonModule": true, + "isolatedModules": false, + "outDir": "./dist", + "skipLibCheck": true, // Skip library file checking to avoid type conflicts + "declaration": true, + "declarationMap": true, + "types": [ + "@types/mocha", + "@types/semver", + "@types/sinon", + "@types/trusted-types", + "@types/winreg", + "@types/wicg-file-system-access" + ] + }, + "include": [ + "src/**/*.ts", // Include all TS files under src + "src/**/*.js", + "electron.d.ts", + "deps/vscode/typings", + "deps/vscode/vs/workbench/api/node", + "deps/vscode/vs/workbench/contrib/debug/common/debugProtocol.d.ts", + "deps/vscode/vscode-dts/vscode.d.ts", + "deps/vscode/vscode-dts/vscode.proposed.*.d.ts", + "deps/vscode/vs/base/common/marked", + "deps/vscode/vs/base/common/semver" + ], + "exclude": [ + "node_modules", + ".vscode-test", + "webview-ui", + "deps/vscode/**/test/**/*.ts", + "deps/vscode/**/test/**/*.js", + "deps/vscode/**/fixtures/**/*.js", + "deps/vscode/**/fixtures/**/*.ts", + "node_modules/@types/vscode", + "node_modules/@types/electron", + "dist/**/*" + ] +} diff --git a/jetbrains/host/tsconfig.monaco.json b/jetbrains/host/tsconfig.monaco.json new file mode 100644 index 0000000000..fe4fdb1590 --- /dev/null +++ b/jetbrains/host/tsconfig.monaco.json @@ -0,0 +1,37 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": ["@webgpu/types", "trusted-types", "wicg-file-system-access"], + "paths": {}, + "module": "amd", + "moduleResolution": "node", + "removeComments": false, + "preserveConstEnums": true, + "target": "ES2022", + "sourceMap": false, + "declaration": true + }, + "include": [ + "typings/css.d.ts", + "typings/thenable.d.ts", + "typings/vscode-globals-product.d.ts", + "typings/vscode-globals-nls.d.ts", + "typings/editContext.d.ts", + "vs/monaco.d.ts", + "vs/editor/*", + "vs/base/common/*", + "vs/base/browser/*", + "vs/platform/*/common/*", + "vs/platform/*/browser/*" + ], + "exclude": [ + "node_modules/*", + "vs/platform/files/browser/htmlFileSystemProvider.ts", + "vs/platform/files/browser/webFileSystemAccess.ts", + "vs/platform/telemetry/*", + "vs/platform/assignment/*", + "vs/platform/terminal/*", + "vs/platform/externalTerminal/*" + ] +} diff --git a/jetbrains/host/tsconfig.tsec.json b/jetbrains/host/tsconfig.tsec.json new file mode 100644 index 0000000000..bce7743f7a --- /dev/null +++ b/jetbrains/host/tsconfig.tsec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "skipLibCheck": true, + "plugins": [ + { + "name": "tsec", + "exemptionConfig": "./tsec.exemptions.json" + } + ] + }, + "exclude": ["./vs/workbench/contrib/webview/browser/pre/service-worker.js", "*/test/*", "**/*.test.ts"] +} diff --git a/jetbrains/host/tsconfig.vscode-dts.json b/jetbrains/host/tsconfig.vscode-dts.json new file mode 100644 index 0000000000..97f27ddf78 --- /dev/null +++ b/jetbrains/host/tsconfig.vscode-dts.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "noEmit": true, + "module": "None", + "experimentalDecorators": false, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "allowUnreachableCode": false, + "strict": true, + "exactOptionalPropertyTypes": false, + "useUnknownInCatchVariables": false, + "forceConsistentCasingInFileNames": true, + "types": [], + "lib": ["ES2022"] + }, + "include": ["vscode-dts/vscode.d.ts"] +} diff --git a/jetbrains/host/tsconfig.vscode-proposed-dts.json b/jetbrains/host/tsconfig.vscode-proposed-dts.json new file mode 100644 index 0000000000..67458c8cb2 --- /dev/null +++ b/jetbrains/host/tsconfig.vscode-proposed-dts.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.vscode-dts.json", + "include": ["vscode-dts/vscode.d.ts", "vscode-dts/vscode.proposed.*.d.ts"] +} diff --git a/jetbrains/host/tsec.exemptions.json b/jetbrains/host/tsec.exemptions.json new file mode 100644 index 0000000000..55696acf15 --- /dev/null +++ b/jetbrains/host/tsec.exemptions.json @@ -0,0 +1,34 @@ +{ + "ban-document-execcommand": [ + "vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts", + "vs/editor/contrib/clipboard/browser/clipboard.ts" + ], + "ban-eval-calls": ["vs/workbench/api/worker/extHostExtensionService.ts"], + "ban-function-calls": [ + "vs/workbench/api/worker/extHostExtensionService.ts", + "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts", + "vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts" + ], + "ban-trustedtypes-createpolicy": [ + "bootstrap-window.ts", + "vs/amdX.ts", + "vs/base/browser/trustedTypes.ts", + "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts" + ], + "ban-worker-calls": [ + "vs/base/browser/webWorkerFactory.ts", + "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" + ], + "ban-worker-importscripts": [ + "vs/amdX.ts", + "vs/workbench/services/extensions/worker/polyfillNestedWorker.ts", + "vs/workbench/api/worker/extensionHostWorker.ts" + ], + "ban-domparser-parsefromstring": [ + "vs/base/browser/markdownRenderer.ts", + "vs/base/test/browser/markdownRenderer.test.ts" + ], + "ban-element-setattribute": ["**/*.ts"], + "ban-element-insertadjacenthtml": ["**/*.ts"], + "ban-script-content-assignments": ["bootstrap-window.ts"] +} diff --git a/jetbrains/host/tsup.config.ts b/jetbrains/host/tsup.config.ts new file mode 100644 index 0000000000..c580c60ebe --- /dev/null +++ b/jetbrains/host/tsup.config.ts @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +// tsup.config.ts +import { defineConfig } from "tsup" + +import { dependencies } from "./package.json" + +export default defineConfig({ + entry: ["src/extension.ts"], // Your entry file + format: ["esm"], // Output format, e.g., ES Module and CommonJS + minify: true, // Minify code + clean: true, // Clean output directory + splitting: false, + platform: "node", // Target platform, e.g., Node.js + target: "node18", // Target environment, e.g., latest ECMAScript standard + skipNodeModulesBundle: false, // Don't bundle dependencies in node_modules + // noExternal: Object.keys(dependencies), + // external:[/^@vscode\/.*$/], // Don't bundle vscode-related dependencies + dts: false, // Don't generate type declaration files, as we usually handle type declarations separately +}) diff --git a/jetbrains/host/turbo.json b/jetbrains/host/turbo.json new file mode 100644 index 0000000000..b0d048b696 --- /dev/null +++ b/jetbrains/host/turbo.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "deps:check": { + "cache": false, + "env": ["DEVENV"] + }, + "deps:clean": { + "cache": false + }, + "deps:patch": { + "cache": false, + "dependsOn": ["deps:check", "deps:clean"], + "env": ["DEVENV"] + }, + "deps:copy": { + "cache": false, + "dependsOn": ["deps:check", "deps:patch"], + "env": ["DEVENV"] + }, + "prop:deps": { + "cache": false + }, + "bundle:build": { + "outputs": ["dist/**"], + "inputs": ["package.json", "tsconfig.json", "src/**", "deps/vscode/**"], + "dependsOn": ["clean", "deps:clean", "deps:patch", "deps:copy"] + }, + "bundle:package": { + "cache": false, + "dependsOn": ["bundle:build"] + }, + "bundle": { + "cache": false, + "dependsOn": ["bundle:package"] + }, + "build": { + "outputs": ["dist/**"], + "inputs": ["package.json", "tsconfig.json", "src/**", "deps/vscode/**"], + "dependsOn": ["clean", "deps:clean", "deps:patch", "deps:copy"] + } + } +} diff --git a/jetbrains/host/typings/base-common.d.ts b/jetbrains/host/typings/base-common.d.ts new file mode 100644 index 0000000000..7689aa0a96 --- /dev/null +++ b/jetbrains/host/typings/base-common.d.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Declare types that we probe for to implement util and/or polyfill functions + +declare global { + interface IdleDeadline { + readonly didTimeout: boolean + timeRemaining(): number + } + + function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number + function cancelIdleCallback(handle: number): void +} + +export {} diff --git a/jetbrains/host/typings/crypto.d.ts b/jetbrains/host/typings/crypto.d.ts new file mode 100644 index 0000000000..9521e3cec7 --- /dev/null +++ b/jetbrains/host/typings/crypto.d.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE that this is a partial copy from lib.dom.d.ts which is NEEDED because these utils are used in the /common/ +// layer which has no dependency on the DOM/browser-context. However, `crypto` is available as global in all browsers and +// in nodejs. Therefore it's OK to spell out its typings here + +declare global { + /** + * This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto). + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) + */ + interface SubtleCrypto { + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) */ + // decrypt(algorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */ + // deriveBits(algorithm: AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length?: number | null): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */ + // deriveKey(algorithm: AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, derivedKeyType: AlgorithmIdentifier | AesDerivedKeyParams | HmacImportParams | HkdfParams | Pbkdf2Params, extractable: boolean, keyUsages: KeyUsage[]): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */ + digest(algorithm: { name: string } | string, data: ArrayBufferView | ArrayBuffer): Promise + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */ + // encrypt(algorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */ + // exportKey(format: "jwk", key: CryptoKey): Promise; + // exportKey(format: Exclude, key: CryptoKey): Promise; + // exportKey(format: KeyFormat, key: CryptoKey): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ + // generateKey(algorithm: "Ed25519", extractable: boolean, keyUsages: ReadonlyArray<"sign" | "verify">): Promise; + // generateKey(algorithm: RsaHashedKeyGenParams | EcKeyGenParams, extractable: boolean, keyUsages: ReadonlyArray): Promise; + // generateKey(algorithm: AesKeyGenParams | HmacKeyGenParams | Pbkdf2Params, extractable: boolean, keyUsages: ReadonlyArray): Promise; + // generateKey(algorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) */ + // importKey(format: "jwk", keyData: JsonWebKey, algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: ReadonlyArray): Promise; + // importKey(format: Exclude, keyData: BufferSource, algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) */ + // sign(algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey, data: BufferSource): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */ + // unwrapKey(format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, unwrapAlgorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, unwrappedKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[]): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */ + // verify(algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey, signature: BufferSource, data: BufferSource): Promise; + // /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ + // wrapKey(format: KeyFormat, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams): Promise; + } + + /** + * Basic cryptography features available in the current context. It allows access to a cryptographically strong random number generator and to cryptographic primitives. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto) + */ + interface Crypto { + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) + */ + readonly subtle: SubtleCrypto + /** + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) + */ + getRandomValues(array: T): T + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) + */ + randomUUID(): `${string}-${string}-${string}-${string}-${string}` + } + + var Crypto: { + prototype: Crypto + new (): Crypto + } + + var crypto: Crypto +} +export {} diff --git a/jetbrains/host/typings/css.d.ts b/jetbrains/host/typings/css.d.ts new file mode 100644 index 0000000000..fd4822aef0 --- /dev/null +++ b/jetbrains/host/typings/css.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Recognize all CSS files as valid module imports +declare module "vs/css!*" {} +declare module "*.css" {} diff --git a/jetbrains/host/typings/editContext.d.ts b/jetbrains/host/typings/editContext.d.ts new file mode 100644 index 0000000000..a99a892184 --- /dev/null +++ b/jetbrains/host/typings/editContext.d.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +type DOMString = string + +interface EditContext extends EventTarget { + updateText(rangeStart: number, rangeEnd: number, text: DOMString): void + updateSelection(start: number, end: number): void + updateControlBounds(controlBounds: DOMRect): void + updateSelectionBounds(selectionBounds: DOMRect): void + updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void + + attachedElements(): HTMLElement[] + + get text(): DOMString + get selectionStart(): number + get selectionEnd(): number + get characterBoundsRangeStart(): number + characterBounds(): DOMRect[] + + get ontextupdate(): EventHandler | null + set ontextupdate(value: EventHandler | null) + + get ontextformatupdate(): EventHandler | null + set ontextformatupdate(value: EventHandler | null) + + get oncharacterboundsupdate(): EventHandler | null + set oncharacterboundsupdate(value: EventHandler | null) + + get oncompositionstart(): EventHandler | null + set oncompositionstart(value: EventHandler | null) + + get oncompositionend(): EventHandler | null + set oncompositionend(value: EventHandler | null) + + addEventListener( + type: K, + listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, + options?: boolean | AddEventListenerOptions, + ): void + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ): void + removeEventListener( + type: K, + listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, + options?: boolean | EventListenerOptions, + ): void + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions, + ): void +} + +interface EditContextInit { + text: DOMString + selectionStart: number + selectionEnd: number +} + +interface EditContextEventHandlersEventMap { + textupdate: TextUpdateEvent + textformatupdate: TextFormatUpdateEvent + characterboundsupdate: CharacterBoundsUpdateEvent + compositionstart: Event + compositionend: Event +} + +type EventHandler = (event: TEvent) => void + +declare class TextUpdateEvent extends Event { + constructor(type: DOMString, options?: TextUpdateEventInit) + + readonly updateRangeStart: number + readonly updateRangeEnd: number + readonly text: DOMString + readonly selectionStart: number + readonly selectionEnd: number +} + +interface TextUpdateEventInit extends EventInit { + updateRangeStart: number + updateRangeEnd: number + text: DOMString + selectionStart: number + selectionEnd: number + compositionStart: number + compositionEnd: number +} + +interface TextFormat { + new (options?: TextFormatInit): TextFormat + + readonly rangeStart: number + readonly rangeEnd: number + readonly underlineStyle: UnderlineStyle + readonly underlineThickness: UnderlineThickness +} + +interface TextFormatInit { + rangeStart: number + rangeEnd: number + underlineStyle: UnderlineStyle + underlineThickness: UnderlineThickness +} + +type UnderlineStyle = "none" | "solid" | "dotted" | "dashed" | "wavy" +type UnderlineThickness = "none" | "thin" | "thick" + +interface TextFormatUpdateEvent extends Event { + new (type: DOMString, options?: TextFormatUpdateEventInit): TextFormatUpdateEvent + getTextFormats(): TextFormat[] +} + +interface TextFormatUpdateEventInit extends EventInit { + textFormats: TextFormat[] +} + +interface CharacterBoundsUpdateEvent extends Event { + new (type: DOMString, options?: CharacterBoundsUpdateEventInit): CharacterBoundsUpdateEvent + + readonly rangeStart: number + readonly rangeEnd: number +} + +interface CharacterBoundsUpdateEventInit extends EventInit { + rangeStart: number + rangeEnd: number +} + +interface HTMLElement { + editContext?: EditContext +} diff --git a/jetbrains/host/typings/thenable.d.ts b/jetbrains/host/typings/thenable.d.ts new file mode 100644 index 0000000000..f043812455 --- /dev/null +++ b/jetbrains/host/typings/thenable.d.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, + * and others. This API makes no assumption about what promise library is being used which + * enables reusing existing code without migrating to a specific promise implementation. Still, + * we recommend the use of native promises which are available in VS Code. + */ +interface Thenable extends PromiseLike {} diff --git a/jetbrains/host/typings/vscode-globals-nls.d.ts b/jetbrains/host/typings/vscode-globals-nls.d.ts new file mode 100644 index 0000000000..211bf9d22b --- /dev/null +++ b/jetbrains/host/typings/vscode-globals-nls.d.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM migration relevant + +/** + * NLS Globals: these need to be defined in all contexts that make + * use of our `nls.localize` and `nls.localize2` functions. This includes: + * - Electron main process + * - Electron window (renderer) process + * - Utility Process + * - Node.js + * - Browser + * - Web worker + * + * That is because during build time we strip out all english strings from + * the resulting JS code and replace it with a that is then looked + * up from the `_VSCODE_NLS_MESSAGES` array. + */ +declare global { + /** + * All NLS messages produced by `localize` and `localize2` calls + * under `src/vs` translated to the language as indicated by + * `_VSCODE_NLS_LANGUAGE`. + * + * Instead of accessing this global variable directly, use function getNLSMessages. + */ + var _VSCODE_NLS_MESSAGES: string[] + /** + * The actual language of the NLS messages (e.g. 'en', de' or 'pt-br'). + * + * Instead of accessing this global variable directly, use function getNLSLanguage. + */ + var _VSCODE_NLS_LANGUAGE: string | undefined +} + +// fake export to make global work +export {} diff --git a/jetbrains/host/typings/vscode-globals-product.d.ts b/jetbrains/host/typings/vscode-globals-product.d.ts new file mode 100644 index 0000000000..999c9a9945 --- /dev/null +++ b/jetbrains/host/typings/vscode-globals-product.d.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM migration relevant + +declare global { + /** + * Holds the file root for resources. + */ + var _VSCODE_FILE_ROOT: string + + /** + * CSS loader that's available during development time. + * DO NOT call directly, instead just import css modules, like `import 'some.css'` + */ + var _VSCODE_CSS_LOAD: (module: string) => void + + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PRODUCT_JSON: Record + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PACKAGE_JSON: Record +} + +// fake export to make global work +export {} diff --git a/jetbrains/host/typings/vscode-globals-ttp.d.ts b/jetbrains/host/typings/vscode-globals-ttp.d.ts new file mode 100644 index 0000000000..399ff268b5 --- /dev/null +++ b/jetbrains/host/typings/vscode-globals-ttp.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM migration relevant + +declare global { + var _VSCODE_WEB_PACKAGE_TTP: + | Pick< + TrustedTypePolicy<{ + createScriptURL(value: string): string + }>, + "name" | "createScriptURL" + > + | undefined +} + +// fake export to make global work +export {} diff --git a/jetbrains/plugin/.gitignore b/jetbrains/plugin/.gitignore new file mode 100644 index 0000000000..08536c965d --- /dev/null +++ b/jetbrains/plugin/.gitignore @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 Weibo, Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store +src/main/resources/ai/kilocode/jetbrains/plugin/config/plugin.properties + +### Deps ### +prodDep.txt +plugins \ No newline at end of file diff --git a/jetbrains/plugin/.run/Run Plugin.run.xml b/jetbrains/plugin/.run/Run Plugin.run.xml new file mode 100644 index 0000000000..da43cc5efc --- /dev/null +++ b/jetbrains/plugin/.run/Run Plugin.run.xml @@ -0,0 +1,31 @@ + + + + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/jetbrains/plugin/build.gradle.kts b/jetbrains/plugin/build.gradle.kts new file mode 100644 index 0000000000..0ec87170f1 --- /dev/null +++ b/jetbrains/plugin/build.gradle.kts @@ -0,0 +1,255 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: APACHE2.0 +// SPDX-License-Identifier: Apache-2.0 + +// Convenient for reading variables from gradle.properties +fun properties(key: String) = providers.gradleProperty(key) + +plugins { + id("java") + id("org.jetbrains.kotlin.jvm") version "1.8.10" + id("org.jetbrains.intellij") version "1.17.4" + id("org.jlleitschuh.gradle.ktlint") version "11.6.1" + id("io.gitlab.arturbosch.detekt") version "1.23.4" +} + +apply("genPlatform.gradle") + +// ------------------------------------------------------------ +// The 'debugMode' setting controls how plugin resources are prepared during the build process. +// It supports the following three modes: +// +// 1. "idea" — Local development mode (used for debugging VSCode plugin integration) +// - Copies theme resources from src/main/resources/themes to: +// ../resources//src/integrations/theme/default-themes/ +// - Automatically creates a .env file, which the Extension Host (Node.js side) reads at runtime. +// - Enables the VSCode plugin to load resources from this directory for integration testing. +// - Typically used when running IntelliJ with an Extension Host for live debugging and hot-reloading. +// +// 2. "release" — Production build mode (used to generate deployment artifacts) +// - Requires platform.zip to exist, which can be retrieved via git-lfs or generated with genPlatform.gradle. +// - This file includes the full runtime environment for VSCode plugins (e.g., node_modules, platform.txt). +// - The zip is extracted to build/platform/, and its node_modules take precedence over other dependencies. +// - Copies compiled host outputs (dist, package.json, node_modules) and plugin resources. +// - The result is a fully self-contained package ready for deployment across platforms. +// +// 3. "none" (default) — Lightweight mode (used for testing and CI) +// - Does not rely on platform.zip or prepare VSCode runtime resources. +// - Only copies the plugin's core assets such as themes. +// - Useful for early-stage development, static analysis, unit tests, and continuous integration pipelines. +// +// How to configure: +// - Set via gradle argument: -PdebugMode=idea / release / none +// Example: ./gradlew prepareSandbox -PdebugMode=idea +// - Defaults to "none" if not explicitly set. +// ------------------------------------------------------------ +ext { + set("debugMode", project.findProperty("debugMode") ?: "none") + set("debugResource", project.projectDir.resolve("../resources").absolutePath) + set("vscodePlugin", project.findProperty("vscodePlugin") ?: "kilocode") +} + +project.afterEvaluate { + tasks.findByName(":prepareSandbox")?.inputs?.properties?.put("build_mode", ext.get("debugMode")) +} + +fun Sync.prepareSandbox() { + // Set duplicate strategy to include files, with later sources taking precedence + duplicatesStrategy = DuplicatesStrategy.INCLUDE + + if (ext.get("debugMode") == "idea") { + from("${project.projectDir.absolutePath}/src/main/resources/themes/") { + into("${ext.get("debugResource")}/${ext.get("vscodePlugin")}/integrations/theme/default-themes/") + } + doLast { + val vscodePluginDir = File("${ext.get("debugResource")}/${ext.get("vscodePlugin")}") + vscodePluginDir.mkdirs() + File(vscodePluginDir, ".env").createNewFile() + } + } else { + val vscodePluginDir = File("./plugins/${ext.get("vscodePlugin")}") + if (!vscodePluginDir.exists()) { + throw IllegalStateException("missing plugin dir") + } + val list = mutableListOf() + val depfile = File("prodDep.txt") + if (!depfile.exists()) { + throw IllegalStateException("missing prodDep.txt") + } + depfile.readLines().let { + it.forEach { line -> + list.add(line.substringAfterLast("node_modules/") + "/**") + } + } + + from("../host/dist") { into("${intellij.pluginName.get()}/runtime/") } + from("../host/package.json") { into("${intellij.pluginName.get()}/runtime/") } + + // First copy host node_modules + from("../resources/node_modules") { + into("${intellij.pluginName.get()}/node_modules/") + list.forEach { + include(it) + } + } + + from("${vscodePluginDir.path}/extension") { into("${intellij.pluginName.get()}/${ext.get("vscodePlugin")}") } + from("src/main/resources/themes/") { into("${intellij.pluginName.get()}/${ext.get("vscodePlugin")}/integrations/theme/default-themes/") } + + // The platform.zip file required for release mode is associated with the code in ../base/vscode, currently using version 1.100.0. If upgrading this code later + // Need to modify the vscodeVersion value in gradle.properties, then execute the task named genPlatform, which will generate a new platform.zip file for submission + // To support new architectures, modify according to the logic in genPlatform.gradle script + if (ext.get("debugMode") == "release") { + // Check if platform.zip file exists and is larger than 1MB, otherwise throw exception + val platformZip = File("platform.zip") + if (platformZip.exists() && platformZip.length() >= 1024 * 1024) { + // Extract platform.zip to the platform subdirectory under the project build directory + val platformDir = File("${layout.buildDirectory.get().asFile}/platform") + platformDir.mkdirs() + copy { + from(zipTree(platformZip)) + into(platformDir) + } + } else { + throw IllegalStateException("platform.zip file does not exist or is smaller than 1MB. This file is supported through git lfs and needs to be obtained through git lfs") + } + + from(File(layout.buildDirectory.get().asFile, "platform/platform.txt")) { into("${intellij.pluginName.get()}/") } + // Copy platform node_modules last to ensure it takes precedence over host node_modules + from(File(layout.buildDirectory.get().asFile, "platform/node_modules")) { into("${intellij.pluginName.get()}/node_modules") } + } + + doLast { + File("${destinationDir}/${intellij.pluginName.get()}/${ext.get("vscodePlugin")}/.env").createNewFile() + } + } +} + +group = properties("pluginGroup").get() +version = properties("pluginVersion").get() + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.10.0") + implementation("com.google.code.gson:gson:2.10.1") + testImplementation("junit:junit:4.13.2") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4") +} + +// Configure Java toolchain to force Java 17 +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +// Configure Gradle IntelliJ Plugin +// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html +intellij { + version = properties("platformVersion") + type = properties("platformType") + + plugins.set( + listOf( + "com.intellij.java", + // Add JCEF support + "org.jetbrains.plugins.terminal" + ) + ) +} + +tasks { + + // Create task for generating configuration files + register("generateConfigProperties") { + description = "Generate properties file containing plugin configuration" + doLast { + val configDir = File("$projectDir/src/main/resources/ai/kilocode/jetbrains/plugin/config") + configDir.mkdirs() + + val configFile = File(configDir, "plugin.properties") + configFile.writeText("debug.mode=${ext.get("debugMode")}") + configFile.appendText("\n") + configFile.appendText("debug.resource=${ext.get("debugResource")}") + println("Configuration file generated: ${configFile.absolutePath}") + } + } + + prepareSandbox { + prepareSandbox() + } + + // Generate configuration file before compilation + withType { + dependsOn("generateConfigProperties") + } + + // Set the JVM compatibility versions + withType { + dependsOn("generateConfigProperties") + kotlinOptions { + jvmTarget = "17" + } + } + + withType { + sourceCompatibility = "17" + targetCompatibility = "17" + } + + patchPluginXml { + version.set(properties("pluginVersion")) + sinceBuild.set(properties("pluginSinceBuild")) + untilBuild.set("") + } + + signPlugin { + certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) + privateKey.set(System.getenv("PRIVATE_KEY")) + password.set(System.getenv("PRIVATE_KEY_PASSWORD")) + } + + publishPlugin { + token.set(System.getenv("PUBLISH_TOKEN")) + } + +} + +// Configure ktlint +ktlint { + version.set("0.50.0") + debug.set(false) + verbose.set(true) + android.set(false) + outputToConsole.set(true) + outputColorName.set("RED") + ignoreFailures.set(true) + enableExperimentalRules.set(false) + filter { + exclude("**/generated/**") + include("**/kotlin/**") + } +} + +// Configure detekt +detekt { + toolVersion = "1.23.4" + config.setFrom(file("detekt.yml")) + buildUponDefaultConfig = true + allRules = false + + reports { + html.required.set(true) + xml.required.set(true) + txt.required.set(true) + sarif.required.set(true) + md.required.set(true) + } +} diff --git a/jetbrains/plugin/detekt.yml b/jetbrains/plugin/detekt.yml new file mode 100644 index 0000000000..6ba49e6ef6 --- /dev/null +++ b/jetbrains/plugin/detekt.yml @@ -0,0 +1,729 @@ +# SPDX-FileCopyrightText: 2025 Weibo, Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# Detekt configuration file for Kilo Code JetBrains Plugin +# https://detekt.github.io/detekt/configurations.html + +build: + maxIssues: 5000 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + +complexity: + active: true + CognitiveComplexMethod: + active: false + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + CyclomaticComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'NullPointerException' + - 'IndexOutOfBoundsException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + ignoreAnnotated: ['Composable'] + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - '*.CheckResult' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.Flow' + - 'java.util.stream.Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: ['**/*.kts'] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + comments: + - value: 'FIXME:' + reason: 'Forbidden FIXME todo marker' + - value: 'STOPSHIP:' + reason: 'Forbidden STOPSHIP todo marker' + - value: 'TODO:' + reason: 'Forbidden TODO todo marker' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - 'kotlin.io.print' + - 'kotlin.io.println' + ForbiddenPublicDataClass: + active: false + excludes: ['**'] + ignorePackages: + - '*.internal' + - '*.internal.*' + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LibraryCodeMustSpecifyReturnType: + active: false + excludes: ['**'] + LibraryEntitiesShouldNotBePublic: + active: false + excludes: ['**'] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + BracesOnIfStatements: + active: false + singleLine: 'always' + multiLine: 'always' + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludeImports: + - 'java.util.*' \ No newline at end of file diff --git a/jetbrains/plugin/genPlatform.gradle b/jetbrains/plugin/genPlatform.gradle new file mode 100644 index 0000000000..32a6342829 --- /dev/null +++ b/jetbrains/plugin/genPlatform.gradle @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +import java.nio.file.* +import java.security.MessageDigest + + +tasks.register('genPlatform', Zip) { + download(project) + from(new File(project.buildDir, "genPlatform/gen")) + into("") + destinationDirectory = project.projectDir + archiveFileName = "platform.zip" +} +def download(Project project){ + + def version = project.findProperty("vscodeVersion") + String windows_x64 = "https://update.code.visualstudio.com/${version}/win32-x64-archive/stable" + String mac_x64 = "https://update.code.visualstudio.com/${version}/darwin/stable" + String mac_arm64 = "https://update.code.visualstudio.com/${version}/darwin-arm64/stable" + String linux_x64 = "https://update.code.visualstudio.com/${version}/linux-x64/stable" + // To support other platforms, need to synchronously modify the initPlatfromFiles method in WecoderPlugin + + def list = [] // node_module directories for multiple platforms + + def projectBuild = new File(project.buildDir,"genPlatform") + projectBuild.mkdirs(); + println "Downloading Windows platform files" + def windowsZipFile = new File(projectBuild,"windows-x64.zip") + if (!windowsZipFile.exists()) { + windowsZipFile << new URL(windows_x64).openStream() + } else { + println "Windows platform file already exists, skipping download" + } + + def windowsDir = new File(projectBuild, "windows-x64") + copy { + from(zipTree(new File(projectBuild, "windows-x64.zip"))) + into(windowsDir) + } + new File(windowsDir, "resources/app/node_modules").renameTo( new File(windowsDir, "resources/app/windows-x64")) + list << new File(windowsDir, "resources/app/windows-x64") + + println "Downloading Mac x64 platform files" + def macX64ZipFile = new File(projectBuild,"darwin-x64.zip") + if (!macX64ZipFile.exists()) { + macX64ZipFile << new URL(mac_x64).openStream() + } else { + println "Mac x64 platform file already exists, skipping download" + } + + def macX64Dir = new File(projectBuild, "darwin-x64") + copy { + from(zipTree(new File(projectBuild, "darwin-x64.zip"))) + into(macX64Dir) + } + new File(macX64Dir, "Visual Studio Code.app/Contents/Resources/app/node_modules").renameTo(new File(macX64Dir, "Visual Studio Code.app/Contents/Resources/app/darwin-x64")) + list << new File(macX64Dir, "Visual Studio Code.app/Contents/Resources/app/darwin-x64") + + println "Downloading Mac arm64 platform files" + def macArm64ZipFile = new File(projectBuild,"darwin-arm64.zip") + if (!macArm64ZipFile.exists()) { + macArm64ZipFile << new URL(mac_arm64).openStream() + } else { + println "Mac arm64 platform file already exists, skipping download" + } + + def macArm64Dir = new File(projectBuild, "darwin-arm64") + copy { + from(zipTree(new File(projectBuild, "darwin-arm64.zip"))) + into(macArm64Dir) + } + new File(macArm64Dir, "Visual Studio Code.app/Contents/Resources/app/node_modules").renameTo(new File(macArm64Dir, "Visual Studio Code.app/Contents/Resources/app/darwin-arm64")) + list << new File(macArm64Dir, "Visual Studio Code.app/Contents/Resources/app/darwin-arm64") + + println "Downloading Linux x64 platform files" + def linuxZipFile = new File(projectBuild,"linux-x64.zip") + if (!linuxZipFile.exists()) { + linuxZipFile << new URL(linux_x64).openStream() + } else { + println "Linux x64 platform file already exists, skipping download" + } + + def linuxDir = new File(projectBuild, "linux-x64") + copy { + from(tarTree(resources.gzip(projectBuild.toPath().resolve("linux-x64.zip")))) + into(linuxDir) + } + new File(linuxDir, "VSCode-linux-x64/resources/app/node_modules").renameTo(new File(linuxDir, "VSCode-linux-x64/resources/app/linux-x64")) + list << new File(linuxDir, "VSCode-linux-x64/resources/app/linux-x64") + + def targetDir = new File(projectBuild, "gen/node_modules") + def txtFile = new File(projectBuild, "gen/platform.txt") + mergeDirectories(list, targetDir,txtFile) + + def zipFile = new File(project.projectDir,"platform.zip") + if(zipFile.exists()) { + zipFile.delete() + } +} + + +def mergeDirectories(List dirs, File targetDir, File outputFile = null) { + def outputContent = new StringBuilder() + if (!targetDir.exists()) { + targetDir.mkdirs() + } + + // Collect all file paths (relative paths), ignore .DS_Store + def allFiles = [] + dirs.each { dir -> + if (dir.exists()) { + dir.eachFileRecurse { file -> + if (file.isFile() && file.name != ".DS_Store") { // Ignore .DS_Store + def relativePath = dir.toPath().relativize(file.toPath()).toString() + allFiles << [dir: dir, file: file, relativePath: relativePath] + } + + } + } + } + + // Group by relative path + def groupedFiles = allFiles.groupBy { it.relativePath } + + groupedFiles.each { relativePath, entries -> + def targetFile = new File(targetDir, relativePath) + targetFile.parentFile.mkdirs() + + if (entries.size() == 1) { + // Unique file, copy directly + Files.copy(entries[0].file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } else { + // Check if all file contents are the same + def uniqueHashes = entries.collect { entry -> + def file = entry.file + def digest = MessageDigest.getInstance("SHA-256") + file.withInputStream { is -> + byte[] buffer = new byte[8192] + int read + while ((read = is.read(buffer)) != -1) { + digest.update(buffer, 0, read) + } + } + def hash = digest.digest().encodeHex().toString() + [hash: hash, file: file, dir: entry.dir] + }.groupBy { it.hash } + + if (uniqueHashes.size() == 1) { + // Same content, keep one copy + Files.copy(entries[0].file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } else { + // Different content, add directory name as suffix + if (outputFile) { + outputContent.append("$relativePath\n") + } else { + println "$relativePath" + } + uniqueHashes.each { hash, files -> + def sourceFile = files[0].file + def dirName = files[0].dir.name + def newName = targetFile.name + dirName + def conflictFile = new File(targetFile.parentFile, newName) + Files.copy(sourceFile.toPath(), conflictFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + } + } + if (outputFile && outputContent.length() > 0) { + outputFile.parentFile.mkdirs() + outputFile.text = outputContent.toString() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/gradle.properties b/jetbrains/plugin/gradle.properties new file mode 100644 index 0000000000..995010ed77 --- /dev/null +++ b/jetbrains/plugin/gradle.properties @@ -0,0 +1,11 @@ +# Plugin basic information +pluginGroup=ai.kilocode.jetbrains +pluginVersion=4.88.0 + +# Platform basic information +platformVersion=2024.1.4 +platformType=IC +pluginSinceBuild=241 + +# Other project configurations can be added here +vscodeVersion=1.100.0 diff --git a/jetbrains/plugin/gradle/wrapper/gradle-wrapper.jar b/jetbrains/plugin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..249e5832f0 Binary files /dev/null and b/jetbrains/plugin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/jetbrains/plugin/gradle/wrapper/gradle-wrapper.properties b/jetbrains/plugin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..2e8fde5a71 --- /dev/null +++ b/jetbrains/plugin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Aug 20 11:09:31 ART 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/jetbrains/plugin/gradlew b/jetbrains/plugin/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/jetbrains/plugin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/jetbrains/plugin/gradlew.bat b/jetbrains/plugin/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/jetbrains/plugin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jetbrains/plugin/package.json b/jetbrains/plugin/package.json new file mode 100644 index 0000000000..aeaadf4243 --- /dev/null +++ b/jetbrains/plugin/package.json @@ -0,0 +1,31 @@ +{ + "name": "@kilo-code/jetbrains-plugin", + "license": "Apache-2.0", + "type": "module", + "devDependencies": { + "del-cli": "^5.1.0", + "cpy-cli": "^5.0.0", + "mkdirp": "^3.0.1" + }, + "scripts": { + "clean": "./gradlew clean", + "build": "./gradlew buildPlugin -PdebugMode=idea", + "run": "./gradlew runIde -PdebugMode=idea", + "run:bundle": "./gradlew runIde -PdebugMode=release", + "bundle": "./gradlew buildPlugin -PdebugMode=release", + "bundle:name": "node scripts/get_bundle_name.js", + "clean:kilocode": "npx del-cli ./plugins/kilocode --force && npx mkdirp ./plugins/kilocode", + "copy:kilocode": "npx cpy '../../bin-unpacked/extension/**' './plugins/kilocode/extension' --parents", + "clean:resource-kilocode": "npx del-cli ../resources/kilocode --force", + "copy:resource-kilocode": "npx cpy '../../bin-unpacked/extension/**' '../resources/kilocode' --parents", + "clean:resource-host": "npx del-cli ../resources/runtime --force", + "copy:resource-host": "npx cpy '../host/dist/**' '../resources/runtime' --parents", + "clean:resource-logs": "npx del-cli ../resources/logs --force", + "copy:resource-logs": "npx mkdirp ../resources/logs", + "clean:resource-nodemodules": "npx del-cli ../resources/node_modules --force && npx del-cli ../resources/package.json --force", + "copy:resource-nodemodules": "cp ../host/package.json ../resources/package.json && npm install --prefix ../resources", + "propDep": "npx del-cli ./propDep.txt --force && npm ls --omit=dev --all --parseable --prefix ../resources > ./prodDep.txt", + "sync:version": "node scripts/sync_version.js", + "sync:changelog": "node scripts/update_change_notes.js" + } +} diff --git a/jetbrains/plugin/scripts/get_bundle_name.js b/jetbrains/plugin/scripts/get_bundle_name.js new file mode 100644 index 0000000000..c8119c705d --- /dev/null +++ b/jetbrains/plugin/scripts/get_bundle_name.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +import { readFileSync } from "fs" +import { join, dirname } from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +/** + * Get the bundle zip file name based on version from gradle.properties + */ +function getBundleName() { + try { + // Read version from gradle.properties + const gradlePropertiesPath = join(__dirname, "../gradle.properties") + const gradlePropertiesContent = readFileSync(gradlePropertiesPath, "utf8") + + const gradleVersionMatch = gradlePropertiesContent.match(/^pluginVersion=(.+)$/m) + if (!gradleVersionMatch) { + throw new Error("pluginVersion not found in gradle.properties") + } + + const version = gradleVersionMatch[1].trim() + + // Generate the bundle name following the pattern: Kilo Code-{version}.zip + const bundleName = `Kilo Code-${version}.zip` + + // Output just the filename for CI usage + process.stdout.write(bundleName) + + return bundleName + } catch (error) { + console.error("❌ Error getting bundle name:", error.message) + process.exit(1) + } +} + +// Run the function if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + getBundleName() +} + +export default getBundleName diff --git a/jetbrains/plugin/scripts/sync_version.js b/jetbrains/plugin/scripts/sync_version.js new file mode 100644 index 0000000000..825dfc7e3e --- /dev/null +++ b/jetbrains/plugin/scripts/sync_version.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from "fs" +import { join, dirname } from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +/** + * Sync version from src/package.json to jetbrains/plugin/gradle.properties + */ +function syncVersion() { + try { + // Read version from src/package.json + const srcPackageJsonPath = join(__dirname, "../../../src/package.json") + const srcPackageJson = JSON.parse(readFileSync(srcPackageJsonPath, "utf8")) + const version = srcPackageJson.version + + if (!version) { + throw new Error("Version not found in src/package.json") + } + + console.log(`Found version: ${version}`) + + // Read gradle.properties + const gradlePropertiesPath = join(__dirname, "../gradle.properties") + const gradlePropertiesContent = readFileSync(gradlePropertiesPath, "utf8") + + // Update pluginVersion in gradle.properties + const updatedContent = gradlePropertiesContent.replace(/^pluginVersion=.*$/m, `pluginVersion=${version}`) + + // Check if the replacement was successful + if (updatedContent === gradlePropertiesContent) { + console.warn("Warning: pluginVersion property not found or already up to date") + return + } + + // Write updated gradle.properties + writeFileSync(gradlePropertiesPath, updatedContent, "utf8") + + console.log(`✅ Successfully updated pluginVersion to ${version} in gradle.properties`) + } catch (error) { + console.error("❌ Error syncing version:", error.message) + process.exit(1) + } +} + +// Run the sync if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + syncVersion() +} + +export default syncVersion diff --git a/jetbrains/plugin/scripts/update_change_notes.js b/jetbrains/plugin/scripts/update_change_notes.js new file mode 100644 index 0000000000..c1009d9881 --- /dev/null +++ b/jetbrains/plugin/scripts/update_change_notes.js @@ -0,0 +1,158 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from "fs" +import { join, dirname } from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +/** + * Update change-notes in plugin.xml based on version from gradle.properties and CHANGELOG.md + */ +function updateChangeNotes() { + try { + // Read version from gradle.properties + const gradlePropertiesPath = join(__dirname, "../gradle.properties") + const gradlePropertiesContent = readFileSync(gradlePropertiesPath, "utf8") + + const gradleVersionMatch = gradlePropertiesContent.match(/^pluginVersion=(.+)$/m) + if (!gradleVersionMatch) { + throw new Error("pluginVersion not found in gradle.properties") + } + + const version = gradleVersionMatch[1].trim() + console.log(`Found plugin version: ${version}`) + + // Read CHANGELOG.md + const changelogPath = join(__dirname, "../../../CHANGELOG.md") + const changelogContent = readFileSync(changelogPath, "utf8") + + // Find the version section in changelog + const versionPattern = new RegExp(`## \\[v${version.replace(/\./g, "\\.")}\\]([\\s\\S]*?)(?=## \\[v|$)`) + const changelogVersionMatch = changelogContent.match(versionPattern) + + if (!changelogVersionMatch) { + throw new Error(`Version ${version} not found in CHANGELOG.md`) + } + + const changelogSection = changelogVersionMatch[1].trim() + console.log(`Found changelog section for version ${version}`) + + // Convert markdown to HTML format suitable for plugin.xml + const changeNotesHtml = convertMarkdownToHtml(changelogSection, version) + + // Read plugin.xml + const pluginXmlPath = join(__dirname, "../src/main/resources/META-INF/plugin.xml") + const pluginXmlContent = readFileSync(pluginXmlPath, "utf8") + + // Replace change-notes section + const changeNotesPattern = /(<\/change-notes>)/ + const updatedPluginXml = pluginXmlContent.replace(changeNotesPattern, `$1\n${changeNotesHtml}\n $3`) + + // Check if the replacement was successful + if (updatedPluginXml === pluginXmlContent) { + console.warn("Warning: change-notes section not found or already up to date") + return + } + + // Write updated plugin.xml + writeFileSync(pluginXmlPath, updatedPluginXml, "utf8") + + console.log(`✅ Successfully updated change-notes for version ${version} in plugin.xml`) + } catch (error) { + console.error("❌ Error updating change-notes:", error.message) + process.exit(1) + } +} + +/** + * Convert markdown changelog to HTML format suitable for plugin.xml + */ +function convertMarkdownToHtml(markdown, version) { + let html = `

Version ${version}

\n
    ` + + // Split into lines and process + const lines = markdown.split("\n").filter((line) => line.trim()) + + for (const line of lines) { + const trimmedLine = line.trim() + + // Skip empty lines and section headers + if (!trimmedLine || trimmedLine.startsWith("##") || trimmedLine.startsWith("###")) { + continue + } + + // Handle main bullet points (features/changes) + if (trimmedLine.startsWith("- ")) { + const content = trimmedLine.substring(2).trim() + const cleanContent = cleanMarkdownContent(content) + + if (cleanContent) { + html += `\n
  • ${escapeHtml(cleanContent)}
  • ` + } + } + + // Handle sub-bullet points (patch changes, etc.) + else if (trimmedLine.match(/^\s*-\s/)) { + const content = trimmedLine.replace(/^\s*-\s/, "").trim() + const cleanContent = cleanMarkdownContent(content) + + if (cleanContent) { + html += `\n
  • ${escapeHtml(cleanContent)}
  • ` + } + } + } + + html += "\n
" + return html +} + +/** + * Clean markdown content by removing links, PR references, and contributor mentions + */ +function cleanMarkdownContent(content) { + return ( + content + // Remove PR links like [#2012](https://github.com/...) + .replace(/\[#\d+\]\([^)]+\)\s*/g, "") + // Remove commit hash links like [`1fd698a`](https://github.com/...) + .replace(/\[`[^`]+`\]\([^)]+\)\s*/g, "") + // Remove GitHub user links like [@catrielmuller](https://github.com/catrielmuller) + .replace(/\[@[^\]]+\]\([^)]+\)\s*/g, "") + // Remove "Thanks @username!" mentions at the beginning + .replace(/^Thanks\s+@[^!]+!\s*-?\s*/g, "") + // Remove "Thanks @username!" mentions anywhere + .replace(/Thanks\s+@[^!]+!\s*-?\s*/g, "") + // Remove "Thanks @username" mentions (without exclamation) + .replace(/Thanks\s+@[^,)]+[,)]\s*/g, "") + // Remove standalone contributor mentions like "(thanks @username!)" + .replace(/\(thanks\s+@[^)]+\)\s*/g, "") + // Remove leftover "Thanks !" patterns + .replace(/Thanks\s*!\s*-?\s*/g, "") + // Remove leading dashes and spaces + .replace(/^[-\s]+/g, "") + // Clean up multiple spaces + .replace(/\s+/g, " ") + .trim() + ) +} + +/** + * Escape HTML special characters + */ +function escapeHtml(text) { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") +} + +// Run the update if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + updateChangeNotes() +} + +export default updateChangeNotes diff --git a/jetbrains/plugin/settings.gradle.kts b/jetbrains/plugin/settings.gradle.kts new file mode 100644 index 0000000000..d05bf38bc1 --- /dev/null +++ b/jetbrains/plugin/settings.gradle.kts @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +rootProject.name = "Kilo Code" \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/ActionConstants.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/ActionConstants.kt new file mode 100644 index 0000000000..4a967d5020 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/ActionConstants.kt @@ -0,0 +1,285 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actions + +/** + * Constants for action names displayed in the UI. + * These represent the text shown to users in menus and context options. + */ +object ActionNames { + /** Action to explain selected code */ + const val EXPLAIN = "Explain Code" + /** Action to fix issues in selected code */ + const val FIX = "Fix Code" + /** Action to fix logical issues in selected code */ + const val FIX_LOGIC = "Fix Logic" + /** Action to improve selected code */ + const val IMPROVE = "Improve Code" + /** Action to add selected code to context */ + const val ADD_TO_CONTEXT = "Add to Context" + /** Action to create a new task */ + const val NEW_TASK = "New Task" +} + +/** + * Command identifiers used for internal command registration and execution. + * These IDs are used to register commands with the IDE. + */ +object CommandIds { + /** Command ID for explaining code */ + const val EXPLAIN = "kilo-code.explainCode" + /** Command ID for fixing code */ + const val FIX = "kilo-code.fixCode" + /** Command ID for improving code */ + const val IMPROVE = "kilo-code.improveCode" + /** Command ID for adding to context */ + const val ADD_TO_CONTEXT = "kilo-code.addToContext" + /** Command ID for creating a new task */ + const val NEW_TASK = "kilo-code.newTask" +} + +/** Type alias for prompt type identifiers */ +typealias SupportPromptType = String +/** Type alias for prompt parameters map */ +typealias PromptParams = Map + +/** + * Data class representing a prompt configuration with a template string. + * Templates contain placeholders that will be replaced with actual values. + */ +data class SupportPromptConfig(val template: String) + +/** + * Collection of predefined prompt configurations for different use cases. + * Each configuration contains a template with placeholders for dynamic content. + */ +object SupportPromptConfigs { + /** + * Template for enhancing user prompts. + * Instructs the AI to generate an improved version of the user's input. + */ + val ENHANCE = SupportPromptConfig( + """Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes): + +${'$'}{userInput}""" + ) + + /** + * Template for explaining code. + * Provides structure for code explanation requests with file path and line information. + */ + val EXPLAIN = SupportPromptConfig( + """Explain the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please provide a clear and concise explanation of what this code does, including: +1. The purpose and functionality +2. Key components and their interactions +3. Important patterns or techniques used""" + ) + + /** + * Template for fixing code issues. + * Includes diagnostic information and structured format for issue resolution. + */ + val FIX = SupportPromptConfig( + """Fix any issues in the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{diagnosticText} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please: +1. Address all detected problems listed above (if any) +2. Identify any other potential bugs or issues +3. Provide corrected code +4. Explain what was fixed and why""" + ) + + /** + * Template for improving code quality. + * Focuses on readability, performance, best practices, and error handling. + */ + val IMPROVE = SupportPromptConfig( + """Improve the following code from file path ${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +${'$'}{userInput} + +``` +${'$'}{selectedText} +``` + +Please suggest improvements for: +1. Code readability and maintainability +2. Performance optimization +3. Best practices and patterns +4. Error handling and edge cases + +Provide the improved code along with explanations for each enhancement.""" + ) + + /** + * Template for adding code to context. + * Simple format that includes file path, line range, and selected code. + */ + val ADD_TO_CONTEXT = SupportPromptConfig( + """${'$'}{filePath}:${'$'}{startLine}-${'$'}{endLine} +``` +${'$'}{selectedText} +```""" + ) + + /** + * Template for adding terminal output to context. + * Includes user input and terminal content. + */ + val TERMINAL_ADD_TO_CONTEXT = SupportPromptConfig( + """${'$'}{userInput} +Terminal output: +``` +${'$'}{terminalContent} +```""" + ) + + /** + * Template for fixing terminal commands. + * Structured format for identifying and resolving command issues. + */ + val TERMINAL_FIX = SupportPromptConfig( + """${'$'}{userInput} +Fix this terminal command: +``` +${'$'}{terminalContent} +``` + +Please: +1. Identify any issues in the command +2. Provide the corrected command +3. Explain what was fixed and why""" + ) + + /** + * Template for explaining terminal commands. + * Provides structure for command explanation with focus on functionality and behavior. + */ + val TERMINAL_EXPLAIN = SupportPromptConfig( + """${'$'}{userInput} +Explain this terminal command: +``` +${'$'}{terminalContent} +``` + +Please provide: +1. What the command does +2. Explanation of each part/flag +3. Expected output and behavior""" + ) + + /** + * Template for creating a new task. + * Simple format that passes through user input directly. + */ + val NEW_TASK = SupportPromptConfig( + """${'$'}{userInput}""" + ) + + /** + * Map of all available prompt configurations indexed by their type identifiers. + * Used for lookup when creating prompts. + */ + val configs = mapOf( + "ENHANCE" to ENHANCE, + "EXPLAIN" to EXPLAIN, + "FIX" to FIX, + "IMPROVE" to IMPROVE, + "ADD_TO_CONTEXT" to ADD_TO_CONTEXT, + "TERMINAL_ADD_TO_CONTEXT" to TERMINAL_ADD_TO_CONTEXT, + "TERMINAL_FIX" to TERMINAL_FIX, + "TERMINAL_EXPLAIN" to TERMINAL_EXPLAIN, + "NEW_TASK" to NEW_TASK + ) +} + +/** + * Utility object for working with support prompts. + * Provides methods for creating and customizing prompts based on templates. + */ +object SupportPrompt { + /** + * Generates formatted diagnostic text from a list of diagnostic items. + * + * @param diagnostics List of diagnostic items containing source, message, and code + * @return Formatted string of diagnostic messages or empty string if no diagnostics + */ + private fun generateDiagnosticText(diagnostics: List>?): String { + if (diagnostics.isNullOrEmpty()) return "" + return "\nCurrent problems detected:\n" + diagnostics.joinToString("\n") { d -> + val source = d["source"] as? String ?: "Error" + val message = d["message"] as? String ?: "" + val code = d["code"] as? String + "- [$source] $message${code?.let { " ($it)" } ?: ""}" + } + } + + /** + * Creates a prompt by replacing placeholders in a template with actual values. + * + * @param template The prompt template with placeholders + * @param params Map of parameter values to replace placeholders + * @return The processed prompt with placeholders replaced by actual values + */ + private fun createPrompt(template: String, params: PromptParams): String { + val pattern = Regex("""\$\{(.*?)}""") + return pattern.replace(template) { matchResult -> + val key = matchResult.groupValues[1] + if (key == "diagnosticText") { + generateDiagnosticText(params["diagnostics"] as? List>) + } else if (params.containsKey(key)) { + // Ensure the value is treated as a string for replacement + val value = params[key] + when (value) { + is String -> value + else -> { + // Convert non-string values to string for replacement + value?.toString() ?: "" + } + } + } else { + // If the placeholder key is not in params, replace with empty string + "" + } + } + } + + /** + * Gets the template for a specific prompt type, with optional custom overrides. + * + * @param customSupportPrompts Optional map of custom prompt templates + * @param type The type of prompt to retrieve + * @return The template string for the specified prompt type + */ + fun get(customSupportPrompts: Map?, type: SupportPromptType): String { + return customSupportPrompts?.get(type) ?: SupportPromptConfigs.configs[type]?.template ?: "" + } + + /** + * Creates a complete prompt by getting the template and replacing placeholders. + * + * @param type The type of prompt to create + * @param params Parameters to substitute into the template + * @param customSupportPrompts Optional custom prompt templates + * @return The final prompt with all placeholders replaced + */ + fun create(type: SupportPromptType, params: PromptParams, customSupportPrompts: Map? = null): String { + val template = get(customSupportPrompts, type) + return createPrompt(template, params) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RegisterCodeActions.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RegisterCodeActions.kt new file mode 100644 index 0000000000..38715b1ef6 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RegisterCodeActions.kt @@ -0,0 +1,299 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import ai.kilocode.jetbrains.webview.WebViewManager + +/** + * Code action provider, similar to VSCode's CodeActionProvider. + * Provides functionality for creating and managing code-related actions. + */ +class CodeActionProvider { + + /** + * Creates a single code action with the specified title and command. + * + * @param title The display title for the action + * @param command The command identifier to execute when action is triggered + * @return An AnAction instance that can be registered with the IDE + */ + private fun createAction( + title: String, + command: String + ): AnAction { + return object : AnAction(title) { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + val file = e.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + + // Get current parameters when the action is clicked + val effectiveRange = getEffectiveRange(editor) + if (effectiveRange == null) return + + val args = mutableMapOf() + args["filePath"] = file.path + args["selectedText"] = effectiveRange.text + args["startLine"] = effectiveRange.startLine + 1 + args["endLine"] = effectiveRange.endLine + 1 + + handleCodeAction(command, title, args, project) + } + } + } + + /** + * Creates a pair of actions (new task version and current task version). + * + * @param baseTitle The base title for the actions + * @param baseCommand The base command identifier + * @return List of AnAction instances + */ + private fun createActionPair( + baseTitle: String, + baseCommand: String + ): List { + return listOf( + createAction("$baseTitle in Current Task", "${baseCommand}InCurrentTask") + ) + } + + /** + * Gets the effective range and text from the current editor selection. + * + * @param editor The current editor instance + * @return EffectiveRange object containing selected text and line numbers, or null if no selection + */ + private fun getEffectiveRange(editor: Editor): EffectiveRange? { + val document = editor.document + val selectionModel = editor.selectionModel + + return if (selectionModel.hasSelection()) { + val selectedText = selectionModel.selectedText ?: "" + val startLine = document.getLineNumber(selectionModel.selectionStart) + val endLine = document.getLineNumber(selectionModel.selectionEnd) + EffectiveRange(selectedText, startLine, endLine) + } else { + null + } + } + + /** + * Provides a list of code actions for the given action event. + * + * @param e The action event containing context information + * @return List of available code actions + */ + fun provideCodeActions(e: AnActionEvent): List { + val actions = mutableListOf() + + // Add to context action + actions.add( + createAction( + ActionNames.ADD_TO_CONTEXT, + CommandIds.ADD_TO_CONTEXT + ) + ) + + // Explain code action pair + actions.addAll( + createActionPair( + ActionNames.EXPLAIN, + CommandIds.EXPLAIN + ) + ) + + // Fix code action pair (logic fix) + actions.addAll( + createActionPair( + ActionNames.FIX_LOGIC, + CommandIds.FIX + ) + ) + + // Improve code action pair + actions.addAll( + createActionPair( + ActionNames.IMPROVE, + CommandIds.IMPROVE + ) + ) + + return actions + } +} + +/** + * Data class representing an effective range of selected text. + * Contains the selected text and its start/end line numbers. + * + * @property text The selected text content + * @property startLine The starting line number (0-based) + * @property endLine The ending line number (0-based) + */ +data class EffectiveRange( + val text: String, + val startLine: Int, + val endLine: Int +) + +/** + * Registers a code action with the specified parameters. + * + * @param command The command identifier + * @param promptType The type of prompt to use + * @param inputPrompt Optional prompt text for user input dialog + * @param inputPlaceholder Optional placeholder text for input field + * @return An AnAction instance that can be registered with the IDE + */ +fun registerCodeAction( + command: String, + promptType: String, + inputPrompt: String? = null, + inputPlaceholder: String? = null +) : AnAction { + return object : AnAction(command) { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val editor = e.getData(CommonDataKeys.EDITOR) ?: return + + var userInput: String? = null + if (inputPrompt != null) { + userInput = Messages.showInputDialog( + project, + inputPrompt, + "Kilo Code", + null, + inputPlaceholder, + null + ) + if (userInput == null) return // Cancelled + } + + // Get selected content, line numbers, etc. + val document = editor.document + val selectionModel = editor.selectionModel + val selectedText = selectionModel.selectedText ?: "" + val startLine = if (selectionModel.hasSelection()) document.getLineNumber(selectionModel.selectionStart) else null + val endLine = if (selectionModel.hasSelection()) document.getLineNumber(selectionModel.selectionEnd) else null + val file = e.getData(CommonDataKeys.VIRTUAL_FILE) + val filePath = file?.path ?: "" + + val params = mutableMapOf( + "filePath" to filePath, + "selectedText" to selectedText + ) + if (startLine != null) params["startLine"] = (startLine + 1).toString() + if (endLine != null) params["endLine"] = (endLine + 1).toString() + if (!userInput.isNullOrEmpty()) params["userInput"] = userInput + + handleCodeAction(command, promptType, params, e.project) + } + } +} +/** + * Registers a pair of code actions with the specified parameters. + * + * @param baseCommand The base command identifier + * @param inputPrompt Optional prompt text for user input dialog + * @param inputPlaceholder Optional placeholder text for input field + * @return An AnAction instance for the new task version + */ +fun registerCodeActionPair( + baseCommand: String, + inputPrompt: String? = null, + inputPlaceholder: String? = null +) : AnAction { + // New task version + return registerCodeAction(baseCommand, baseCommand, inputPrompt, inputPlaceholder) +} + +/** + * Core logic for handling code actions. + * Processes different types of commands and sends appropriate messages to the webview. + * + * @param command The command identifier + * @param promptType The type of prompt to use + * @param params Parameters for the action (can be Map or List) + * @param project The current project + */ +fun handleCodeAction(command: String, promptType: String, params: Any, project: Project?) { + val latestWebView = project?.getService(WebViewManager::class.java)?.getLatestWebView() + if (latestWebView == null) { + return + } + + // Create message content based on command type + val messageContent = when { + // Add to context command + command.contains("addToContext") -> { + val promptParams = if (params is Map<*, *>) params as Map else emptyMap() + mapOf( + "type" to "invoke", + "invoke" to "setChatBoxMessage", + "text" to SupportPrompt.create("ADD_TO_CONTEXT", promptParams) + ) + } + // Command executed in current task + command.endsWith("InCurrentTask") -> { + val promptParams = if (params is Map<*, *>) params as Map else emptyMap() + val basePromptType = when { + command.contains("explain") -> "EXPLAIN" + command.contains("fix") -> "FIX" + command.contains("improve") -> "IMPROVE" + else -> promptType + } + mapOf( + "type" to "invoke", + "invoke" to "sendMessage", + "text" to SupportPrompt.create(basePromptType, promptParams) + ) + } + // Command executed in new task + else -> { + val promptParams = if (params is List<*>) { + // Process parameter list from createAction + val argsList = params as List + if (argsList.size >= 4) { + mapOf( + "filePath" to argsList[0], + "selectedText" to argsList[1], + "startLine" to argsList[2], + "endLine" to argsList[3] + ) + } else { + emptyMap() + } + } else if (params is Map<*, *>) { + params as Map + } else { + emptyMap() + } + + val basePromptType = when { + command.contains("explain") -> "EXPLAIN" + command.contains("fix") -> "FIX" + command.contains("improve") -> "IMPROVE" + else -> promptType + } + + mapOf( + "type" to "invoke", + "invoke" to "initClineWithTask", + "text" to SupportPrompt.create(basePromptType, promptParams) + ) + } + } + + // Convert to JSON and send + val messageJson = com.google.gson.Gson().toJson(messageContent) + latestWebView.postMessageToWebView(messageJson) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RightClickChatActionGroup.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RightClickChatActionGroup.kt new file mode 100644 index 0000000000..c381a58483 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/RightClickChatActionGroup.kt @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actions + +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.project.DumbAware + +/** + * Right-click menu code action group, similar to VSCode's code action provider. + * This class manages the dynamic actions that appear in the context menu when text is selected. + * Implements DumbAware to ensure the action works during indexing, and ActionUpdateThreadAware + * to specify which thread should handle action updates. + */ +class RightClickChatActionGroup : DefaultActionGroup(), DumbAware, ActionUpdateThreadAware { + + /** + * Provider that supplies the actual code actions to be displayed in the menu. + */ + private val codeActionProvider = CodeActionProvider() + + /** + * Updates the action group based on the current context. + * This method is called each time the menu needs to be displayed. + * + * @param e The action event containing context information + */ + override fun update(e: AnActionEvent) { + removeAll() + + // Check if there is an editor and selected text + val editor = e.getData(CommonDataKeys.EDITOR) + val hasSelection = editor?.selectionModel?.hasSelection() == true + + if (hasSelection) { + loadDynamicActions(e) + } + + // Set the visibility of the action group + e.presentation.isVisible = hasSelection + } + + /** + * Loads dynamic actions into this action group based on the current context. + * + * @param e The action event containing context information + */ + private fun loadDynamicActions(e: AnActionEvent) { + // Use actions provided by CodeActionProvider + val actions = codeActionProvider.provideCodeActions(e) + actions.forEach { action -> + add(action) + } + } + + /** + * Specifies which thread should be used for updating this action. + * EDT (Event Dispatch Thread) is used for UI-related operations. + * + * @return The thread to use for action updates + */ + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.EDT + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/VSCodeCommandActions.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/VSCodeCommandActions.kt new file mode 100644 index 0000000000..c94089512a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actions/VSCodeCommandActions.kt @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actions + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +/** + * Executes a VSCode command with the given command ID. + * This function uses the RPC protocol to communicate with the extension host. + * + * @param commandId The identifier of the command to execute + * @param project The current project context + */ +fun executeCommand(commandId: String, project: Project?) { + val proxy = + project?.getService(PluginContext::class.java)?.getRPCProtocol()?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostCommands) + proxy?.executeContributedCommand(commandId, emptyList()) +} + +/** + * Action that handles clicks on the Plus button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class PlusButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(PlusButtonClickAction::class.java) + private val commandId: String = "kilo-code.plusButtonClicked" + + /** + * Performs the action when the Plus button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("Plus button clicked") + executeCommand(commandId,e.project) + } +} + +/** + * Action that handles clicks on the Prompts button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class PromptsButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(PromptsButtonClickAction::class.java) + private val commandId: String = "kilo-code.promptsButtonClicked" + + /** + * Performs the action when the Prompts button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("Prompts button clicked") + executeCommand(commandId, e.project) + } +} + +/** + * Action that handles clicks on the MCP button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class MCPButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(MCPButtonClickAction::class.java) + private val commandId: String = "kilo-code.mcpButtonClicked" + + /** + * Performs the action when the MCP button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("MCP button clicked") + executeCommand(commandId, e.project) + } +} + +/** + * Action that handles clicks on the History button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class HistoryButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(HistoryButtonClickAction::class.java) + private val commandId: String = "kilo-code.historyButtonClicked" + + /** + * Performs the action when the History button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("History button clicked") + executeCommand(commandId, e.project) + } +} + +class ProfileButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(ProfileButtonClickAction::class.java) + private val commandId: String = "kilo-code.profileButtonClicked" + /** + * Performs the action when the Profile button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("Profile button clicked") + executeCommand(commandId, e.project) + } +} + +/** + * Action that handles clicks on the Settings button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class SettingsButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(SettingsButtonClickAction::class.java) + private val commandId: String = "kilo-code.settingsButtonClicked" + + /** + * Performs the action when the Settings button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("Settings button clicked") + executeCommand(commandId, e.project) + } +} + +/** + * Action that handles clicks on the Marketplace button in the UI. + * Executes the corresponding VSCode command when triggered. + */ +class MarketplaceButtonClickAction : AnAction() { + private val logger: Logger = Logger.getInstance(MarketplaceButtonClickAction::class.java) + private val commandId: String = "kilo-code.marketplaceButtonClicked" + + /** + * Performs the action when the Marketplace button is clicked. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + logger.info("Marketplace button clicked") + executeCommand(commandId, e.project) + } +} + +/** + * Action that opens developer tools for the WebView. + * Takes a function that provides the current WebView instance. + * + * @property getWebViewInstance Function that returns the current WebView instance or null if not available + */ +class OpenDevToolsAction(private val getWebViewInstance: () -> ai.kilocode.jetbrains.webview.WebViewInstance?) : AnAction("Open Developer Tools") { + private val logger: Logger = Logger.getInstance(OpenDevToolsAction::class.java) + + /** + * Performs the action to open developer tools for the WebView. + * + * @param e The action event containing context information + */ + override fun actionPerformed(e: AnActionEvent) { + val webView = getWebViewInstance() + if (webView != null) { + webView.openDevTools() + } else { + logger.warn("No WebView instance available, cannot open developer tools") + } + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadBulkEditsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadBulkEditsShape.kt new file mode 100644 index 0000000000..3c53c47d16 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadBulkEditsShape.kt @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import ai.kilocode.jetbrains.editor.EditorAndDocManager +import ai.kilocode.jetbrains.editor.EditorHolder +import ai.kilocode.jetbrains.editor.WorkspaceEdit +import ai.kilocode.jetbrains.ipc.proxy.SerializableObjectWithBuffers +import kotlinx.coroutines.delay +import java.io.File +import java.nio.file.Files +/** + * Interface for handling bulk edits in the main thread. + * Provides functionality to apply workspace edits that may include multiple file and text changes. + */ +interface MainThreadBulkEditsShape { + /** + * Attempts to apply a workspace edit. + * + * @param workspaceEditDto The workspace edit data transfer object + * @param undoRedoGroupId Optional ID for grouping undo/redo operations + * @param respectAutoSaveConfig Whether to respect auto-save configuration + * @return True if all edits were applied successfully, false otherwise + */ + suspend fun tryApplyWorkspaceEdit(workspaceEditDto: SerializableObjectWithBuffers, undoRedoGroupId: Int?, respectAutoSaveConfig: Boolean?): Boolean +} + +/** + * Implementation of MainThreadBulkEditsShape that handles bulk edits in the main thread. + * Processes workspace edits including file operations (create, delete, rename) and text edits. + * + * @property project The current project context + */ +class MainThreadBulkEdits(val project: Project) : MainThreadBulkEditsShape { + val logger = Logger.getInstance(MainThreadBulkEditsShape::class.java) + + /** + * Attempts to apply a workspace edit by processing file operations and text edits. + * + * @param workspaceEditDto The workspace edit data transfer object + * @param undoRedoGroupId Optional ID for grouping undo/redo operations + * @param respectAutoSaveConfig Whether to respect auto-save configuration + * @return True if all edits were applied successfully, false otherwise + */ + override suspend fun tryApplyWorkspaceEdit(workspaceEditDto: SerializableObjectWithBuffers, undoRedoGroupId: Int?, respectAutoSaveConfig: Boolean?): Boolean { + val json = workspaceEditDto.value as String + logger.info("[Bulk Edit] Starting process: $json") + val cto = WorkspaceEdit.from(json) + var allSuccess = true + + // Process file edits - using background thread to avoid EDT violations + cto.files.forEach { fileEdit -> + if (fileEdit.oldResource != null && fileEdit.newResource != null) { + val oldResource = File(fileEdit.oldResource.path) + val newResource = File(fileEdit.newResource.path) + try { + Files.move(oldResource.toPath(), newResource.toPath()) + // Move VFS refresh operations to background thread + ApplicationManager.getApplication().executeOnPooledThread { + val vfs = LocalFileSystem.getInstance() + vfs.refreshIoFiles(listOf(oldResource, newResource)) + } + logger.info("[Bulk Edit] Renamed file: ${oldResource.path} -> ${newResource.path}") + } catch (e: Exception) { + logger.error("[Bulk Edit] Failed to rename file: ${oldResource.path} -> ${newResource.path}", e) + allSuccess = false + } + } else if (fileEdit.oldResource != null) { + val oldResource = File(fileEdit.oldResource.path) + try { + oldResource.delete() + // Move VFS refresh operations to background thread + ApplicationManager.getApplication().executeOnPooledThread { + val vfs = LocalFileSystem.getInstance() + vfs.refreshIoFiles(listOf(oldResource.parentFile)) + } + logger.info("[Bulk Edit] Deleted file: ${oldResource.path}") + } catch (e: Exception) { + logger.error("[Bulk Edit] Failed to delete file: ${oldResource.path}", e) + allSuccess = false + } + } else if (fileEdit.newResource != null) { + val newResource = File(fileEdit.newResource.path) + try { + val parentDir = newResource.parentFile + if (!parentDir.exists()) { + parentDir.mkdirs() + } + if (fileEdit.options?.contents != null) { + Files.write(newResource.toPath(), fileEdit.options!!.contents!!.toByteArray(Charsets.UTF_8)) + } else { + newResource.createNewFile() + } + // Move VFS refresh operations to background thread + ApplicationManager.getApplication().executeOnPooledThread { + val vfs = LocalFileSystem.getInstance() + vfs.refreshIoFiles(listOf(newResource)) + } + logger.info("[Bulk Edit] Created file: ${newResource.path}") + } catch (e: Exception) { + logger.error("[Bulk Edit] Failed to create file: ${newResource.path}", e) + allSuccess = false + } + } + } + // Process text edits + cto.texts.forEach { textEdit -> + logger.info("[Bulk Edit] Processing text edit: ${textEdit.resource.path}") + if (textEdit.resource.scheme != "file") { + logger.error("[Bulk Edit] Non-file resources not supported: ${textEdit.resource.path}") + allSuccess = false + return@forEach + } + + var handle:EditorHolder? = null; + try { + handle = project.getService(EditorAndDocManager::class.java).getEditorHandleByUri(textEdit.resource,true) + if (handle == null) { + handle = project.getService(EditorAndDocManager::class.java).sync2ExtHost(textEdit.resource,true) + } + } catch (e: Exception) { + logger.info("[Bulk Edit] Failed to get editor handle: ${textEdit.resource.path}", e) + } + + if (handle == null) { + logger.info("[Bulk Edit] Editor handle not found: ${textEdit.resource.path}") + allSuccess = false + return@forEach + } + + try { + val result = handle.applyEdit(textEdit) + if (!result) { + logger.info("[Bulk Edit] Failed to apply edit: ${textEdit.resource.path}") + allSuccess = false + } else { + logger.info("[Bulk Edit] Successfully updated file: ${textEdit.resource.path}") + } + } catch (e: Exception) { + logger.error("[Bulk Edit] Exception applying edit: ${textEdit.resource.path}", e) + allSuccess = false + } + } + return allSuccess + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadClipboardShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadClipboardShape.kt new file mode 100644 index 0000000000..858e5a5c9e --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadClipboardShape.kt @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.awt.Toolkit +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection + +/** + * Main thread clipboard interface. + * Corresponds to the MainThreadClipboardShape interface in VSCode. + */ +interface MainThreadClipboardShape : Disposable { + /** + * Reads text from the clipboard. + * @return The string from the clipboard, or null if no text is available + */ + fun readText(): String? + + /** + * Writes text to the clipboard. + * @param value The string to write to the clipboard + */ + fun writeText(value: String?) +} + +/** + * Implementation of the MainThreadClipboardShape interface. + * Provides functionality to read from and write to the system clipboard. + */ +class MainThreadClipboard : MainThreadClipboardShape { + private val logger = Logger.getInstance(MainThreadClipboardShape::class.java) + + /** + * Reads text from the system clipboard. + * + * @return The string from the clipboard, or null if no text is available or an error occurs + */ + override fun readText(): String? { + logger.info("Reading clipboard text") + return try { + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + val data = clipboard.getContents(null) + if (data != null && data.isDataFlavorSupported(DataFlavor.stringFlavor)) { + data.getTransferData(DataFlavor.stringFlavor) as? String + } else { + null + } + } catch (e: Exception) { + logger.error("Failed to read clipboard", e) + null + } + } + + /** + * Writes text to the system clipboard. + * + * @param value The string to write to the clipboard + */ + override fun writeText(value: String?) { + value?.let { + logger.info("Writing clipboard text: $value") + try { + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + val selection = StringSelection(value) + clipboard.setContents(selection, selection) + } catch (e: Exception) { + logger.error("Failed to write to clipboard", e) + } + } + } + + /** + * Releases resources used by this clipboard handler. + */ + override fun dispose() { + logger.info("Releasing resources: MainThreadClipboard") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadCommandsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadCommandsShape.kt new file mode 100644 index 0000000000..ce0fe4a665 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadCommandsShape.kt @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.commands.CommandRegistry +import ai.kilocode.jetbrains.commands.ICommand +import ai.kilocode.jetbrains.editor.registerOpenEditorAPICommands +import ai.kilocode.jetbrains.terminal.registerTerminalAPICommands +import ai.kilocode.jetbrains.util.doInvokeMethod +import kotlin.reflect.full.functions + +/** + * Main thread commands interface. + * Corresponds to the MainThreadCommandsShape interface in VSCode. + */ +interface MainThreadCommandsShape : Disposable { + /** + * Registers a command. + * @param id The command identifier + */ + fun registerCommand(id: String) + + /** + * Unregisters a command. + * @param id The command identifier + */ + fun unregisterCommand(id: String) + + /** + * Fires a command activation event. + * @param id The command identifier + */ + fun fireCommandActivationEvent(id: String) + + /** + * Executes a command. + * @param id The command identifier + * @param args List of arguments for the command + * @return The execution result + */ + suspend fun executeCommand(id: String, args: List): Any? + + /** + * Gets all registered commands. + * @return List of command identifiers + */ + fun getCommands(): List +} + +/** + * Implementation of MainThreadCommandsShape that handles command registration and execution. + * Manages a registry of commands and provides methods to interact with them. + * + * @property project The current project context + */ +class MainThreadCommands(val project: Project) : MainThreadCommandsShape { + private val registry = CommandRegistry(project) + private val logger = Logger.getInstance(MainThreadCommandsShape::class.java) + + /** + * Initializes the command registry with default commands. + */ + init { + registerOpenEditorAPICommands(project,registry); + registerTerminalAPICommands(project,registry); + //TODO other commands + } + /** + * Registers a command with the given identifier. + * + * @param id The command identifier + */ + override fun registerCommand(id: String) { + logger.info("Registering command: $id") + } + + /** + * Unregisters a command with the given identifier. + * + * @param id The command identifier + */ + override fun unregisterCommand(id: String) { + logger.info("Unregistering command: $id") + } + + /** + * Fires an activation event for the specified command. + * + * @param id The command identifier + */ + override fun fireCommandActivationEvent(id: String) { + logger.info("Firing command activation event: $id") + } + + /** + * Executes a command with the given identifier and arguments. + * + * @param id The command identifier + * @param args List of arguments for the command + * @return The execution result + */ + override suspend fun executeCommand(id: String, args: List): Any? { + logger.info("Executing command: $id ") + registry.getCommand(id)?.let { cmd-> + runCmd(cmd,args) + }?: run { + logger.warn("Command not found: $id") + } + return Unit + } + + /** + * Gets all registered command identifiers. + * + * @return List of command identifiers + */ + override fun getCommands(): List { + logger.info("Getting all commands") + return registry.getCommands().keys.toList() + } + + /** + * Releases resources used by this command handler. + */ + override fun dispose() { + logger.info("Releasing resources: MainThreadCommands") + } + + /** + * Runs a command with the given arguments. + * Finds the appropriate method on the command handler and invokes it. + * + * @param cmd The command to run + * @param args List of arguments for the command + */ + private suspend fun runCmd(cmd: ICommand, args: List) { + val handler = cmd.handler(); + val method = try { +// handler.javaClass.methods.first { it.name == cmd.getMethod()} + handler::class.functions.first{ it.name == cmd.getMethod()} + }catch (e: Exception){ + logger.error("Command method not found: ${cmd.getMethod()}") + return + } + doInvokeMethod(method,args,handler) + } + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConfigurationShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConfigurationShape.kt new file mode 100644 index 0000000000..20f556127d --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConfigurationShape.kt @@ -0,0 +1,366 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.util.URI +import ai.kilocode.jetbrains.util.URIComponents + +/** + * Enum for configuration targets. + * Corresponds to the ConfigurationTarget enum in VSCode. + * Defines the different scopes where configuration can be applied. + */ +enum class ConfigurationTarget(val value: Int) { + /** Application-level configuration, applies globally to the entire IDE */ + APPLICATION(1), + /** User-level configuration, applies to the current user across all projects */ + USER(2), + /** Local user configuration, specific to the local machine */ + USER_LOCAL(3), + /** Remote user configuration, for remote development scenarios */ + USER_REMOTE(4), + /** Workspace-level configuration, applies to the current project workspace */ + WORKSPACE(5), + /** Workspace folder-level configuration, applies to specific folders within a workspace */ + WORKSPACE_FOLDER(6), + /** Default configuration target when no specific target is provided */ + DEFAULT(7), + /** Memory-only configuration, temporary and not persisted */ + MEMORY(8); + + companion object { + /** + * Creates a ConfigurationTarget from its integer value. + * @param value The integer value representing the configuration target + * @return The corresponding ConfigurationTarget enum value, or null if not found + */ + fun fromValue(value: Int?): ConfigurationTarget? { + return values().find { it.value == value } + } + + /** + * Converts a ConfigurationTarget enum to its string representation. + * @param target The configuration target to convert + * @return The string name of the configuration target + */ + fun toString(target: ConfigurationTarget): String { + return when(target) { + APPLICATION -> "APPLICATION" + USER -> "USER" + USER_LOCAL -> "USER_LOCAL" + USER_REMOTE -> "USER_REMOTE" + WORKSPACE -> "WORKSPACE" + WORKSPACE_FOLDER -> "WORKSPACE_FOLDER" + DEFAULT -> "DEFAULT" + MEMORY -> "MEMORY" + } + } + } +} + +/** + * Interface for configuration overrides. + * Corresponds to the IConfigurationOverrides interface in VSCode. + * Used to provide context-specific configuration overrides. + */ +data class ConfigurationOverrides( + /** Optional identifier for overriding configuration values, typically used for language-specific settings */ + val overrideIdentifier: String? = null, + /** Optional URI specifying the resource context for the configuration override */ + val resource: URI? = null +) + +/** + * Main thread configuration interface. + * Corresponds to the MainThreadConfigurationShape interface in VSCode. + * Defines the contract for configuration management operations that can be performed + * from the main thread of the IDE. + */ +interface MainThreadConfigurationShape : Disposable { + /** + * Updates a configuration option with the specified parameters. + * @param target Configuration target scope (application, user, workspace, etc.) + * @param key Configuration key path (e.g., "editor.fontSize") + * @param value Configuration value to set, can be null to unset + * @param overrides Optional configuration overrides for specific contexts + * @param scopeToLanguage Whether to scope this configuration to a specific language + */ + fun updateConfigurationOption( + target: Int, + key: String, + value: Any?, + overrides: Map?, + scopeToLanguage: Boolean? + ) + + /** + * Removes a configuration option from the specified target scope. + * @param target Configuration target scope from which to remove the setting + * @param key Configuration key path to remove + * @param overrides Optional configuration overrides to consider during removal + * @param scopeToLanguage Whether the configuration was scoped to a specific language + */ + fun removeConfigurationOption( + target: Int, + key: String, + overrides: Map?, + scopeToLanguage: Boolean? + ) +} + +/** + * Implementation of the main thread configuration interface. + * Provides concrete implementation for managing IDE configuration settings + * across different scopes and contexts. + */ +class MainThreadConfiguration : MainThreadConfigurationShape { + private val logger = Logger.getInstance(MainThreadConfiguration::class.java) + + /** + * Updates a configuration option in the specified target scope. + * Handles the conversion of parameters and delegates to the appropriate + * storage mechanism based on the configuration target. + */ + override fun updateConfigurationOption( + target: Int, + key: String, + value: Any?, + overrides: Map?, + scopeToLanguage: Boolean? + ) { + // Convert parameter types from raw values to type-safe objects + val configTarget = ConfigurationTarget.fromValue(target) + val configOverrides = convertToConfigurationOverrides(overrides) + + // Log the configuration update for debugging purposes + logger.info("Update configuration option: target=${configTarget?.let { ConfigurationTarget.toString(it) }}, key=$key, value=$value, " + + "overrideIdentifier=${configOverrides?.overrideIdentifier}, resource=${configOverrides?.resource}, " + + "scopeToLanguage=$scopeToLanguage") + + // Build the complete configuration key including overrides and language scoping + val fullKey = buildConfigurationKey(key, configOverrides, scopeToLanguage) + + // Store the configuration value based on the target scope + when (configTarget) { + ConfigurationTarget.APPLICATION -> { + // Application-level configuration applies to all projects and users + val properties = PropertiesComponent.getInstance() + storeValue(properties, fullKey, value) + } + ConfigurationTarget.WORKSPACE, ConfigurationTarget.WORKSPACE_FOLDER -> { + // Project-level configuration applies to the current project + val activeProject = getActiveProject() + if (activeProject != null) { + val properties = PropertiesComponent.getInstance(activeProject) + storeValue(properties, fullKey, value) + } else { + logger.warn("Failed to save project-level configuration, no active project found") + } + } + ConfigurationTarget.USER, ConfigurationTarget.USER_LOCAL -> { + // User-level configuration applies to the current user across projects + val properties = PropertiesComponent.getInstance() + val userPrefixedKey = "user.$fullKey" + storeValue(properties, userPrefixedKey, value) + } + else -> { + // Memory-level configuration is temporary and not persisted + val properties = PropertiesComponent.getInstance() + val memoryPrefixedKey = "memory.$fullKey" + storeValue(properties, memoryPrefixedKey, value) + } + } + } + + /** + * Removes a configuration option from the specified target scope. + * Handles the conversion of parameters and delegates to the appropriate + * removal mechanism based on the configuration target. + */ + override fun removeConfigurationOption( + target: Int, + key: String, + overrides: Map?, + scopeToLanguage: Boolean? + ) { + // Convert parameter types from raw values to type-safe objects + val configTarget = ConfigurationTarget.fromValue(target) + val configOverrides = convertToConfigurationOverrides(overrides) + + // Log the configuration removal for debugging purposes + logger.info("Remove configuration option: target=${configTarget?.let { ConfigurationTarget.toString(it) }}, key=$key, " + + "overrideIdentifier=${configOverrides?.overrideIdentifier}, resource=${configOverrides?.resource}, " + + "scopeToLanguage=$scopeToLanguage") + + // Build the complete configuration key including overrides and language scoping + val fullKey = buildConfigurationKey(key, configOverrides, scopeToLanguage) + + // Remove the configuration value based on the target scope + when (configTarget) { + ConfigurationTarget.APPLICATION -> { + // Remove application-level configuration + val properties = PropertiesComponent.getInstance() + properties.unsetValue(fullKey) + } + ConfigurationTarget.WORKSPACE, ConfigurationTarget.WORKSPACE_FOLDER -> { + // Remove project-level configuration + val activeProject = getActiveProject() + if (activeProject != null) { + val properties = PropertiesComponent.getInstance(activeProject) + properties.unsetValue(fullKey) + } else { + logger.warn("Failed to remove project-level configuration, no active project found") + } + } + ConfigurationTarget.USER, ConfigurationTarget.USER_LOCAL -> { + // Remove user-level configuration + val properties = PropertiesComponent.getInstance() + val userPrefixedKey = "user.$fullKey" + properties.unsetValue(userPrefixedKey) + } + else -> { + // Remove memory-level configuration + val properties = PropertiesComponent.getInstance() + val memoryPrefixedKey = "memory.$fullKey" + properties.unsetValue(memoryPrefixedKey) + } + } + } + + /** + * Converts a Map to a ConfigurationOverrides object. + * Handles the parsing of URI strings and map structures into proper URI objects. + * @param overridesMap The overrides map containing configuration override data + * @return The configuration overrides object, or null if conversion fails + */ + @Suppress("UNCHECKED_CAST") + private fun convertToConfigurationOverrides(overridesMap: Map?): ConfigurationOverrides? { + if (overridesMap.isNullOrEmpty()) { + return null + } + + try { + val overrideIdentifier = overridesMap["overrideIdentifier"] as? String + val resourceUri = when (val uriObj = overridesMap["resource"]) { + is Map<*, *> -> { + // Extract URI components from the map structure + val scheme = uriObj["scheme"] as? String ?: "" + val path = uriObj["path"] as? String ?: "" + val authority = uriObj["authority"] as? String ?: "" + val query = uriObj["query"] as? String ?: "" + val fragment = uriObj["fragment"] as? String ?: "" + + if (path.isNotEmpty()) { + // Create URI instance using URI.from static method + val uriComponents = object : URIComponents { + override val scheme: String = scheme + override val authority: String = authority + override val path: String = path + override val query: String = query + override val fragment: String = fragment + } + URI.from(uriComponents) + } else { + null + } + } + is String -> { + try { + // Parse URI string using URI.parse static method + URI.parse(uriObj) + } catch (e: Exception) { + logger.warn("Failed to parse URI string: $uriObj", e) + null + } + } + else -> null + } + + return ConfigurationOverrides(overrideIdentifier, resourceUri) + } catch (e: Exception) { + logger.error("Failed to convert configuration overrides: $overridesMap", e) + return null + } + } + + /** + * Builds a complete configuration key based on base key, overrides, and language scoping. + * Constructs a unique key that incorporates override identifiers and resource contexts. + * @param baseKey The base configuration key + * @param overrides Optional configuration overrides to include in the key + * @param scopeToLanguage Whether to scope the configuration to a specific language + * @return The complete configuration key string + */ + private fun buildConfigurationKey(baseKey: String, overrides: ConfigurationOverrides?, scopeToLanguage: Boolean?): String { + val keyBuilder = StringBuilder(baseKey) + + // Add override identifier if language scoping is enabled + overrides?.let { + it.overrideIdentifier?.let { identifier -> + if (scopeToLanguage == true) { + keyBuilder.append(".").append(identifier) + } + } + + // Add resource identifier if URI is provided + it.resource?.let { uri -> + keyBuilder.append("@").append(uri.toString().hashCode()) + } + } + + return keyBuilder.toString() + } + + /** + * Retrieves the currently active project in the IDE. + * @return The active project instance, or null if no project is currently open + */ + private fun getActiveProject(): Project? { + val openProjects = ProjectManager.getInstance().openProjects + return openProjects.firstOrNull { it.isInitialized && !it.isDisposed } + } + + /** + * Stores a configuration value in the properties component based on its type. + * Handles type-specific storage for common data types and falls back to string + * representation for complex objects. + * @param properties The properties component to store the value in + * @param key The configuration key + * @param value The configuration value to store + */ + private fun storeValue(properties: PropertiesComponent, key: String, value: Any?) { + when (value) { + null -> properties.unsetValue(key) + is String -> properties.setValue(key, value) + is Boolean -> properties.setValue(key, value) + is Int -> properties.setValue(key, value, 0) + is Float -> properties.setValue(key, value.toString()) + is Double -> properties.setValue(key, value.toString()) + is Long -> properties.setValue(key, value.toString()) + else -> { + // Convert complex objects to JSON string for storage + try { + properties.setValue(key, value.toString()) + } catch (e: Exception) { + logger.error("Failed to serialize configuration value, type: ${value.javaClass.name}", e) + } + } + } + } + + /** + * Disposes of resources when the configuration manager is no longer needed. + * Called when the plugin or component is being unloaded. + */ + override fun dispose() { + logger.info("Releasing resources: MainThreadConfiguration") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConsoleShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConsoleShape.kt new file mode 100644 index 0000000000..753b6f9534 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadConsoleShape.kt @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger + +/** + * Remote console log. + * Corresponds to the IRemoteConsoleLog interface in TypeScript. + */ +data class RemoteConsoleLog( + val type: String, // Log type: "log", "warn", "error", "info", "debug" + val severity: Int, // Severity level + val args: List, // Log arguments + val source: String? = null, // Log source + val line: Int? = null, // Source line number + val columnNumber: Int? = null, // Column number + val timestamp: Long = System.currentTimeMillis() // Timestamp +) + +/** + * Main thread console service interface. + * Corresponds to the MainThreadConsoleShape interface in VSCode. + */ +interface MainThreadConsoleShape : Disposable { + /** + * Logs extension host message. + * @param msg Log message object + */ + fun logExtensionHostMessage(msg: Map) + + /** + * Releases resources. + */ + override fun dispose() +} + +class MainThreadConsole : MainThreadConsoleShape { + private val logger = Logger.getInstance(MainThreadConsole::class.java) + + /** + * Logs extension host message. + * @param msg Log message object + */ + override fun logExtensionHostMessage(msg: Map) { + val type = msg["type"] + val severity = msg["severity"] + val arguments = msg["arguments"]?.let { args -> + if (args is List<*>) { + args.joinToString(", ") { it.toString() } + } else { + args.toString() + } + } ?: return + + try { + when (severity) { +// "log", "info" -> logger.info("[Extension Host] $arguments") + "warn" -> logger.warn("[Extension Host] $arguments") + "error" -> logger.warn("[Extension Host] ERROR: $arguments") +// "debug" -> logger.debug("[Extension Host] $arguments") +// else -> logger.info("[Extension Host] $arguments") + } + } catch (e: Exception) { + logger.error("Failed to process extension host log message", e) + } + } + + /** + * Releases resources. + */ + override fun dispose() { + logger.info("Disposing MainThreadConsole") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDebugServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDebugServiceShape.kt new file mode 100644 index 0000000000..8d4dc7a49d --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDebugServiceShape.kt @@ -0,0 +1,305 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.net.URI + +/** + * Main thread debug service interface. + * This interface defines the contract for debug services that operate on the main thread, + * providing methods for managing debug sessions, breakpoints, and debug adapter communication. + */ +interface MainThreadDebugServiceShape : Disposable { + /** + * Registers debug types that this service can handle. + * @param debugTypes List of debug type identifiers (e.g., "java", "python", "node") + */ + fun registerDebugTypes(debugTypes: List) + + /** + * Notifies that a debug session has been cached/stored for later use. + * @param sessionID Unique identifier for the debug session + */ + fun sessionCached(sessionID: String) + + /** + * Accepts and processes a message from the debug adapter. + * @param handle Unique handle identifying the debug adapter connection + * @param message The protocol message received from the debug adapter + */ + fun acceptDAMessage(handle: Int, message: Any) + + /** + * Accepts and processes an error reported by the debug adapter. + * @param handle Unique handle identifying the debug adapter connection + * @param name The error name/type + * @param message Human-readable error message + * @param stack Optional stack trace for the error + */ + fun acceptDAError(handle: Int, name: String, message: String, stack: String?) + + /** + * Accepts notification that the debug adapter has exited. + * @param handle Unique handle identifying the debug adapter connection + * @param code Optional exit code (null if terminated by signal) + * @param signal Optional signal name that caused termination (null if exited normally) + */ + fun acceptDAExit(handle: Int, code: Int?, signal: String?) + + /** + * Registers a debug configuration provider for a specific debug type. + * @param type The debug type this provider handles + * @param triggerKind When this provider should be triggered (1=initial, 2=dynamic) + * @param hasProvideMethod Whether this provider has a provideDebugConfigurations method + * @param hasResolveMethod Whether this provider has a resolveDebugConfiguration method + * @param hasResolve2Method Whether this provider has a resolveDebugConfigurationWithSubstitutedVariables method + * @param handle Unique handle for this provider registration + * @return Registration result (typically Unit or success indicator) + */ + fun registerDebugConfigurationProvider( + type: String, + triggerKind: Int, + hasProvideMethod: Boolean, + hasResolveMethod: Boolean, + hasResolve2Method: Boolean, + handle: Int + ): Any + + /** + * Registers a debug adapter descriptor factory for a specific debug type. + * @param type The debug type this factory creates adapters for + * @param handle Unique handle for this factory registration + * @return Registration result (typically Unit or success indicator) + */ + fun registerDebugAdapterDescriptorFactory(type: String, handle: Int): Any + + /** + * Unregisters a debug configuration provider. + * @param handle The handle of the provider to unregister + */ + fun unregisterDebugConfigurationProvider(handle: Int) + + /** + * Unregisters a debug adapter descriptor factory. + * @param handle The handle of the factory to unregister + */ + fun unregisterDebugAdapterDescriptorFactory(handle: Int) + + /** + * Starts a new debugging session. + * @param folder Optional workspace folder URI for the debug session + * @param nameOrConfig Either the name of a predefined configuration or the configuration object itself + * @param options Launch options for the debug session + * @return Success indicator (true if debugging started successfully) + */ + fun startDebugging(folder: URI?, nameOrConfig: Any, options: Any): Any + + /** + * Stops an active debugging session. + * @param sessionId Optional session ID to stop (null stops all sessions) + * @return Operation result (typically Unit) + */ + fun stopDebugging(sessionId: String?): Any + + /** + * Sets a custom name for a debug session. + * @param id The session ID to name + * @param name The display name for the session + */ + fun setDebugSessionName(id: String, name: String) + + /** + * Sends a custom request to the debug adapter. + * @param id The session ID to send the request to + * @param command The debug adapter protocol command + * @param args Arguments for the command + * @return The response from the debug adapter + */ + fun customDebugAdapterRequest(id: String, command: String, args: Any): Any + + /** + * Retrieves information about a specific breakpoint from the debug protocol. + * @param id The session ID + * @param breakpoinId The breakpoint ID to query + * @return Breakpoint information or null if not found + */ + fun getDebugProtocolBreakpoint(id: String, breakpoinId: String): Any? + + /** + * Appends text to the debug console output. + * @param value The text to append to the console + */ + fun appendDebugConsole(value: String) + + /** + * Registers new breakpoints with the debug service. + * @param breakpoints List of breakpoint objects to register + * @return Registration result (typically Unit or success indicator) + */ + fun registerBreakpoints(breakpoints: List): Any + + /** + * Unregisters existing breakpoints. + * @param breakpointIds List of regular breakpoint IDs to remove + * @param functionBreakpointIds List of function breakpoint IDs to remove + * @param dataBreakpointIds List of data breakpoint IDs to remove + * @return Unregistration result (typically Unit) + */ + fun unregisterBreakpoints( + breakpointIds: List, + functionBreakpointIds: List, + dataBreakpointIds: List + ): Any + + /** + * Registers a debug visualizer extension. + * @param extensionId The ID of the extension providing the visualizer + * @param id The unique ID of the visualizer within the extension + */ + fun registerDebugVisualizer(extensionId: String, id: String) + + /** + * Unregisters a debug visualizer extension. + * @param extensionId The ID of the extension providing the visualizer + * @param id The unique ID of the visualizer within the extension + */ + fun unregisterDebugVisualizer(extensionId: String, id: String) + + /** + * Registers a debug visualizer tree structure. + * @param treeId Unique identifier for the tree + * @param canEdit Whether the tree structure can be edited by users + */ + fun registerDebugVisualizerTree(treeId: String, canEdit: Boolean) + + /** + * Unregisters a debug visualizer tree structure. + * @param treeId Unique identifier for the tree to unregister + */ + fun unregisterDebugVisualizerTree(treeId: String) +} + +/** + * Main thread debug service implementation. + * This class provides the concrete implementation of the MainThreadDebugServiceShape interface, + * handling debug session management, breakpoint operations, and debug adapter communication. + * All operations are logged for debugging purposes. + */ +class MainThreadDebugService : MainThreadDebugServiceShape { + private val logger = Logger.getInstance(MainThreadDebugService::class.java) + + override fun registerDebugTypes(debugTypes: List) { + logger.info("Registering debug types: $debugTypes") + } + + override fun sessionCached(sessionID: String) { + logger.info("Session cached: $sessionID") + } + + override fun acceptDAMessage(handle: Int, message: Any) { + logger.info("Received debug adapter message: handle=$handle, message=$message") + } + + override fun acceptDAError(handle: Int, name: String, message: String, stack: String?) { + logger.info("Received debug adapter error: handle=$handle, name=$name, message=$message, stack=$stack") + } + + override fun acceptDAExit(handle: Int, code: Int?, signal: String?) { + logger.info("Received debug adapter exit: handle=$handle, code=$code, signal=$signal") + } + + override fun registerDebugConfigurationProvider( + type: String, + triggerKind: Int, + hasProvideMethod: Boolean, + hasResolveMethod: Boolean, + hasResolve2Method: Boolean, + handle: Int + ): Any { + logger.info("Registering debug configuration provider: type=$type, triggerKind=$triggerKind, " + + "hasProvideMethod=$hasProvideMethod, hasResolveMethod=$hasResolveMethod, " + + "hasResolve2Method=$hasResolve2Method, handle=$handle") + return Unit + } + + override fun registerDebugAdapterDescriptorFactory(type: String, handle: Int): Any { + logger.info("Registering debug adapter descriptor factory: type=$type, handle=$handle") + return Unit + } + + override fun unregisterDebugConfigurationProvider(handle: Int) { + logger.info("Unregistering debug configuration provider: handle=$handle") + } + + override fun unregisterDebugAdapterDescriptorFactory(handle: Int) { + logger.info("Unregistering debug adapter descriptor factory: handle=$handle") + } + + override fun startDebugging(folder: URI?, nameOrConfig: Any, options: Any): Any { + logger.info("Starting debugging: folder=$folder, nameOrConfig=$nameOrConfig, options=$options") + return true + } + + override fun stopDebugging(sessionId: String?): Any { + logger.info("Stopping debugging: sessionId=$sessionId") + return Unit + } + + override fun setDebugSessionName(id: String, name: String) { + logger.info("Setting debug session name: id=$id, name=$name") + } + + override fun customDebugAdapterRequest(id: String, command: String, args: Any): Any { + logger.info("Custom debug adapter request: id=$id, command=$command, args=$args") + return Unit + } + + override fun getDebugProtocolBreakpoint(id: String, breakpoinId: String): Any? { + logger.info("Getting debug protocol breakpoint: id=$id, breakpoinId=$breakpoinId") + return Unit + } + + override fun appendDebugConsole(value: String) { + logger.info("Appending to debug console: $value") + } + + override fun registerBreakpoints(breakpoints: List): Any { + logger.info("Registering breakpoints: ${breakpoints.size} total") + return Unit + } + + override fun unregisterBreakpoints( + breakpointIds: List, + functionBreakpointIds: List, + dataBreakpointIds: List + ): Any { + logger.info("Unregistering breakpoints: ${breakpointIds.size} regular, " + + "${functionBreakpointIds.size} function, " + + "${dataBreakpointIds.size} data breakpoints") + return Unit + } + + override fun registerDebugVisualizer(extensionId: String, id: String) { + logger.info("Registering debug visualizer: extensionId=$extensionId, id=$id") + } + + override fun unregisterDebugVisualizer(extensionId: String, id: String) { + logger.info("Unregistering debug visualizer: extensionId=$extensionId, id=$id") + } + + override fun registerDebugVisualizerTree(treeId: String, canEdit: Boolean) { + logger.info("Registering debug visualizer tree: treeId=$treeId, canEdit=$canEdit") + } + + override fun unregisterDebugVisualizerTree(treeId: String) { + logger.info("Unregistering debug visualizer tree: treeId=$treeId") + } + + override fun dispose() { + logger.info("Disposing MainThreadDebugService") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDiaglogsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDiaglogsShape.kt new file mode 100644 index 0000000000..4514602e4c --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDiaglogsShape.kt @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import ai.kilocode.jetbrains.util.URI +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.fileChooser.FileChooserFactory +import com.intellij.openapi.fileChooser.FileSaverDescriptor +import kotlinx.coroutines.suspendCancellableCoroutine +import java.io.File +import java.nio.file.Path +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * Configuration options for the open file dialog. + * This data class encapsulates all the parameters needed to customize the file chooser dialog. + * + * @property defaultUri The default URI/path to start browsing from + * @property openLabel Custom label text for the dialog's open button + * @property canSelectFiles Whether files can be selected in the dialog + * @property canSelectFolders Whether folders can be selected in the dialog + * @property canSelectMany Whether multiple items can be selected simultaneously + * @property filters File extension filters for filtering displayed files (format: {"Description": ["ext1", "ext2"]}) + * @property title Custom title for the dialog window + * @property allowUIResources Whether to allow UI resources to be selected + */ +data class MainThreadDialogOpenOptions( + val defaultUri: Map?, + val openLabel: String?, + val canSelectFiles: Boolean?, + val canSelectFolders: Boolean?, + val canSelectMany: Boolean?, + val filters: MutableMap>?, + val title: String?, + val allowUIResources: Boolean?, +) + +/** + * Interface defining the contract for main thread dialog operations. + * This interface provides methods for showing file open and save dialogs that must be executed on the main UI thread. + */ +interface MainThreadDiaglogsShape : Disposable { + /** + * Shows an open file dialog and returns the selected file URIs. + * + * @param options Configuration options for customizing the dialog behavior + * @return List of selected file URIs, or null if the dialog was cancelled + */ + suspend fun showOpenDialog(options: Map?): MutableList? + + /** + * Shows a save file dialog and returns the selected file URI. + * + * @param options Configuration options for customizing the dialog behavior + * @return The selected file URI for saving, or null if the dialog was cancelled + */ + suspend fun showSaveDialog(options: Map?): URI? +} + +/** + * Implementation of MainThreadDiaglogsShape that provides file dialog functionality + * executed on the IntelliJ platform's main UI thread. + * + * This class handles both file open and save dialogs using IntelliJ's file chooser APIs, + * ensuring all UI operations are performed on the main thread as required by the platform. + */ +class MainThreadDiaglogs : MainThreadDiaglogsShape { + private val logger = Logger.getInstance(MainThreadDiaglogs::class.java) + + /** + * Shows an open file dialog with the specified options. + * + * This method creates a file chooser dialog that allows users to select one or more files + * based on the provided configuration. The operation is performed on the main UI thread + * using IntelliJ's invokeLater mechanism. + * + * @param map Configuration map containing dialog options + * @return Mutable list of selected file URIs, or null if cancelled + */ + override suspend fun showOpenDialog(map: Map?): MutableList? { + // Convert the configuration map to typed options + val options = create(map) + + // Create file chooser descriptor with default values for unspecified options + val descriptor = FileChooserDescriptor( + /* chooseFiles = */ true, + /* chooseFolders = */ options?.canSelectFolders ?: true, + /* chooseJars = */ false, + /* chooseJarsAsFiles = */ false, + /* chooseMultipleJars = */ false, + /* chooseMultiple = */ options?.canSelectMany ?: true + ) + .withTitle(options?.title ?: "Open") + .withDescription(options?.openLabel ?: "Select files") + + // Apply file extension filters if provided + options?.filters?.forEach { (name, extensions) -> + descriptor.withFileFilter { file -> + extensions.any { file.extension?.equals(it, true) ?: false } + } + } + + // Use coroutine to handle the asynchronous file chooser operation + return suspendCancellableCoroutine { continuation -> + ApplicationManager.getApplication().invokeLater({ + try { + // Show the file chooser dialog and get selected files + val files = FileChooser.chooseFiles(descriptor, null, null) + + // Convert IntelliJ VirtualFile objects to URI objects + val result = files.map { file -> + URI.file(file.path) + }.toMutableList() + + // Resume coroutine with the result + continuation.resume(result) + } catch (e: Exception) { + // Resume coroutine with exception if an error occurs + continuation.resumeWithException(e) + } + }, ModalityState.defaultModalityState()) + } + } + + /** + * Shows a save file dialog with the specified options. + * + * This method creates a file saver dialog that allows users to select a location + * and filename for saving a file. The operation is performed on the main UI thread. + * + * @param map Configuration map containing dialog options + * @return URI of the selected save location, or null if cancelled + */ + override suspend fun showSaveDialog(map: Map?): URI? { + // Convert the configuration map to typed options + val options = create(map) + + // Create file saver descriptor with custom title and description + val descriptor = FileSaverDescriptor("Save", options?.openLabel ?: "Select save location") + + // Apply file extension filters if provided + options?.filters?.forEach { (name, extensions) -> + descriptor.withFileFilter { file -> + extensions.any { file.extension?.equals(it, true) ?: false } + } + } + + // Extract default path and filename from options + val path = options?.defaultUri?.get("path") + var fileName: String? = null + + // Convert the path string to a Path object and extract filename + val virtualFile = path?.let { filePath -> + val file = File(filePath) + fileName = file.name + Path.of(file.parentFile.absolutePath) + } + + // Use coroutine to handle the asynchronous save dialog operation + return suspendCancellableCoroutine { continuation -> + ApplicationManager.getApplication().invokeLater({ + try { + // Show the save file dialog and get the selected file + val file = FileChooserFactory.getInstance() + .createSaveFileDialog(descriptor, null) + .save(virtualFile, fileName) + + // Convert the result to URI format + val result = file?.let { URI.file(it.file.absolutePath) } + + // Resume coroutine with the result + continuation.resume(result) + } catch (e: Exception) { + // Resume coroutine with exception if an error occurs + continuation.resumeWithException(e) + } + }, ModalityState.defaultModalityState()) + } + } + + /** + * Creates a MainThreadDialogOpenOptions instance from a configuration map. + * + * This helper method safely extracts typed values from a generic map structure + * and constructs a properly typed configuration object. + * + * @param map Configuration map containing dialog options as key-value pairs + * @return MainThreadDialogOpenOptions instance, or null if map is null + */ + private fun create(map: Map?): MainThreadDialogOpenOptions? { + map?.let { + return MainThreadDialogOpenOptions( + defaultUri = it["defaultUri"] as? Map, + openLabel = it["openLabel"] as? String, + canSelectFiles = it["canSelectFiles"] as? Boolean, + canSelectFolders = it["canSelectFolders"] as? Boolean, + canSelectMany = it["canSelectMany"] as? Boolean, + filters = it["filters"] as? MutableMap>, + title = it["title"] as? String, + allowUIResources = it["allowUIResources"] as? Boolean + ) + } ?: return null + } + + /** + * Disposes of any resources held by this dialog handler. + * + * This method is called when the plugin or component is being shut down, + * allowing for proper cleanup of resources. + */ + override fun dispose() { + logger.info("Disposing MainThreadDiaglogs") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentContentProvidersShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentContentProvidersShape.kt new file mode 100644 index 0000000000..efa66338b5 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentContentProvidersShape.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.net.URI +import kotlinx.coroutines.CompletableDeferred + +/** + * Interfaces related to document content providers. + */ +interface MainThreadDocumentContentProvidersShape : Disposable { + /** + * Registers a text content provider. + * @param handle Provider handle + * @param scheme URI scheme + */ + fun registerTextContentProvider(handle: Int, scheme: String) + + /** + * Unregisters a text content provider. + * @param handle Provider handle + */ + fun unregisterTextContentProvider(handle: Int) + + /** + * Virtual document content change. + * @param uri Document URI + * @param value New content + * @return Execution result + */ + suspend fun onVirtualDocumentChange(uri: Map, value: String): Any +} + +class MainThreadDocumentContentProviders : MainThreadDocumentContentProvidersShape { + private val logger = Logger.getInstance(MainThreadDocumentContentProviders::class.java) + + override fun registerTextContentProvider(handle: Int, scheme: String) { + logger.info("Register text content provider: handle=$handle, scheme=$scheme") + } + + override fun unregisterTextContentProvider(handle: Int) { + logger.info("Unregister text content provider: handle=$handle") + } + + override suspend fun onVirtualDocumentChange(uri: Map, value: String): Any { + logger.info("Virtual document content changed: uri=$uri") + return CompletableDeferred().also { it.complete(Unit) }.await() + } + + override fun dispose() { + logger.info("Disposing MainThreadDocumentContentProviders resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentsShape.kt new file mode 100644 index 0000000000..1d79160eeb --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadDocumentsShape.kt @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.google.common.collect.Maps +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.testFramework.utils.vfs.createFile +import ai.kilocode.jetbrains.editor.EditorAndDocManager +import ai.kilocode.jetbrains.editor.createURI +import ai.kilocode.jetbrains.service.DocumentSyncService +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileDocumentManagerListener +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Document +import com.intellij.util.messages.MessageBusConnection +import kotlinx.coroutines.delay +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import com.intellij.openapi.progress.ProcessCanceledException +import kotlinx.coroutines.cancel +import java.io.File + +interface MainThreadDocumentsShape { + suspend fun tryCreateDocument(options: Map?): Map + suspend fun tryOpenDocument(uri: Map, options: Map?): Map + suspend fun trySaveDocument(uri: Map): Boolean + suspend fun tryOpenDocument(map: Map, options: String?): Map +} + +class MainThreadDocuments(var project: Project) : MainThreadDocumentsShape { + val logger = Logger.getInstance(MainThreadDocuments::class.java) + private var messageBusConnection: MessageBusConnection? = null + private val documentSyncService = DocumentSyncService(project) + + /** Coroutine scope tied to this instance, cancelled in [dispose]. */ + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + init { + setupDocumentSaveListener() + } + + private fun setupDocumentSaveListener() { + try { + // Connect to the message bus + messageBusConnection = ApplicationManager.getApplication().messageBus.connect() + + // Listen for document save events + messageBusConnection?.subscribe( + FileDocumentManagerListener.TOPIC, + object : FileDocumentManagerListener { + override fun beforeDocumentSaving(document: Document) { + handleDocumentSaving(document) + } + } + ) + + logger.info("Document save listener registered successfully") + } catch (e: Exception) { + logger.error("Failed to setup document save listener", e) + } + } + + private fun handleDocumentSaving(document: Document) { + // Get the virtual file associated with the document + val virtualFile = FileDocumentManager.getInstance().getFile(document) + logger.info("Handle document save event: ${virtualFile?.path}") + + if (virtualFile != null && documentSyncService.shouldHandleFileEvent(virtualFile)) { + // Handle in the coroutine scope dedicated to this instance to avoid issues with older IDEs lacking project.coroutineScope + coroutineScope.launch { + try { + // Wait a short time to ensure the save operation is complete + delay(50) + if (!project.isDisposed) { + documentSyncService.syncDocumentStateOnSave(virtualFile, document) + } + } catch (e: ProcessCanceledException) { + // Normal control flow exception, can be ignored + logger.debug("Document save cancelled because project is disposed") + } catch (e: Exception) { + logger.error("Error handling document save event", e) + } + } + } + } + + override suspend fun tryCreateDocument(options: Map?): Map { + logger.info("tryCreateDocument$options") + return mapOf() + } + + override suspend fun tryOpenDocument(map: Map, options: Map?): Map { + val uri = createURI(map) + logger.info("tryOpenDocument : ${uri.path}") + + val file = File(uri.path) + val vfs = LocalFileSystem.getInstance() + if (!file.exists()) { + file.parentFile.mkdirs() + val vf = vfs.findFileByIoFile(file.parentFile) + vf?.createFile(file.name) + } + + project.getService(EditorAndDocManager::class.java).openDocument(uri) + + logger.info("tryOpenDocument : ${uri.path} execution completed") + return map + } + + // This function is designed to work around a VS Code type system issue where a string argument may be incorrectly treated as an options: {} object. To prevent this, multiple function overloads are declared. + override suspend fun tryOpenDocument(map: Map, options: String?): Map { + return tryOpenDocument(map, HashMap()) + } + + override suspend fun trySaveDocument(map: Map): Boolean { + val uri = createURI(map) + + logger.info("trySaveDocument: ${uri.path}") + + project.getService(EditorAndDocManager::class.java).getEditorHandleByUri(uri,true)?.updateDocumentDirty(false) ?: run { + logger.info("trySaveDocument: ${uri.path} not found") + return false + } + logger.info("trySaveDocument: ${uri.path} execution completed") + return true + } + + + fun dispose() { + try { + messageBusConnection?.disconnect() + messageBusConnection = null + documentSyncService.dispose() + coroutineScope.cancel() + logger.info("Document save listener disposed") + } catch (e: Exception) { + logger.error("Error disposing document save listener", e) + } + } + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadEditorTabsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadEditorTabsShape.kt new file mode 100644 index 0000000000..461b88b745 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadEditorTabsShape.kt @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.editor.EditorAndDocManager + + +interface MainThreadEditorTabsShape { + fun moveTab(tabId: String, index: Int, viewColumn: Int, preserveFocus: Boolean?) + suspend fun closeTab(tabIds: List, preserveFocus: Boolean?): Boolean + suspend fun closeGroup(groupIds: List, preservceFocus: Boolean?): Boolean +} + +class MainThreadEditorTabs(val project : Project) : MainThreadEditorTabsShape { + private val logger = Logger.getInstance(MainThreadEditorTabs::class.java) + override fun moveTab(tabId: String, index: Int, viewColumn: Int, preserveFocus: Boolean?) { + logger.info("moveTab $tabId") + } + + override suspend fun closeTab(tabIds: List, preserveFocus: Boolean?): Boolean { + logger.info("closeTab $tabIds") + + // Iterate all tab IDs and trigger close event + var closedAny = true + for (tabId in tabIds){ + val tab = project.getService(EditorAndDocManager::class.java).closeTab(tabId) +// closedAny = tab?.triggerClose()?:false +// if (closedAny){ +// project.getService(TabStateManager::class.java).removeTab(tabId) +// } + } + + return closedAny + } + + override suspend fun closeGroup(groupIds: List, preservceFocus: Boolean?): Boolean { + logger.info("closeGroup $groupIds") + + // Iterate all tab group IDs and trigger close event + var closedAny = false + for (groupId in groupIds){ + val group = project.getService(EditorAndDocManager::class.java).closeGroup(groupId) +// closedAny = group?.triggerClose()?:false + } + return closedAny + } + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadErrorsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadErrorsShape.kt new file mode 100644 index 0000000000..e0cc9849ba --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadErrorsShape.kt @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger + +/** + * Main thread error handling interface. + * Corresponds to the MainThreadErrorsShape interface in VSCode. + */ +interface MainThreadErrorsShape : Disposable { + /** + * Handles unexpected errors. + * @param err Error information + */ + fun onUnexpectedError(err: Any?) + + /** + * Releases resources. + */ + override fun dispose() +} + +class MainThreadErrors : MainThreadErrorsShape { + private val logger = Logger.getInstance(MainThreadErrors::class.java) + + /** + * Handles unexpected errors. + * @param err Error information + */ + override fun onUnexpectedError(err: Any?) { + logger.warn("Unexpected error occurred in plugin: $err") + } + + /** + * Releases resources. + */ + override fun dispose() { + logger.info("Dispose MainThreadErrors") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadExtensionServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadExtensionServiceShape.kt new file mode 100644 index 0000000000..5c76bac1e8 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadExtensionServiceShape.kt @@ -0,0 +1,281 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.core.ExtensionManager +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol +import java.net.URI + +/** + * Main thread extension service interface. + * Defines the contract for managing extensions in the main thread context. + * This interface provides methods for extension lifecycle management, + * activation, error handling, and utility operations. + */ +interface MainThreadExtensionServiceShape : Disposable { + /** + * Retrieves extension information by extension ID. + * @param extensionId Extension identifier, typically provided as a Map with "value" key + * @return Extension description object containing metadata about the extension, + * or null if the extension is not found + */ + fun getExtension(extensionId: Any): Any? + + /** + * Activates an extension with the specified ID and reason. + * This method triggers the extension activation process and waits for completion. + * @param extensionId Extension identifier to activate + * @param reason Optional activation reason or context information + * @return Boolean indicating whether the activation was successful (true) or failed (false) + */ + fun activateExtension(extensionId: Any, reason: Any?): Any + + /** + * Called immediately before an extension is about to be activated. + * This provides a hook for pre-activation setup or logging. + * @param extensionId Extension identifier that will be activated + */ + fun onWillActivateExtension(extensionId: Any) + + /** + * Called after an extension has been successfully activated. + * Provides detailed timing information about the activation process. + * @param extensionId Extension identifier that was activated + * @param codeLoadingTime Time taken to load extension code (in milliseconds) + * @param activateCallTime Time taken for the activation call (in milliseconds) + * @param activateResolvedTime Time taken to resolve activation (in milliseconds) + * @param activationReason Reason or context for the activation + */ + fun onDidActivateExtension( + extensionId: Any, + codeLoadingTime: Double, + activateCallTime: Double, + activateResolvedTime: Double, + activationReason: Any? + ) + + /** + * Handles extension activation errors. + * Called when an extension fails to activate due to errors or missing dependencies. + * @param extensionId Extension identifier that failed to activate + * @param error Error information or exception details + * @param missingExtensionDependency Information about missing dependencies, if applicable + * @return Unit (void) - the method handles the error internally + */ + fun onExtensionActivationError( + extensionId: Any, + error: Any?, + missingExtensionDependency: Any? + ): Any + + /** + * Handles runtime errors that occur during extension execution. + * Called when an extension encounters errors after successful activation. + * @param extensionId Extension identifier that encountered the runtime error + * @param error Error information or exception details + */ + fun onExtensionRuntimeError(extensionId: Any, error: Any?) + + /** + * Sets performance marks for extension profiling and monitoring. + * Used to track performance metrics across extension lifecycle events. + * @param marks List of performance mark objects containing timing information + * @return Unit (void) - the method processes the marks internally + */ + fun setPerformanceMarks(marks: List) + + /** + * Converts a standard URI to a browser-compatible URI format. + * This method ensures URIs are properly formatted for web browser contexts. + * @param uri The original URI to convert + * @return Browser-compatible URI object + */ + fun asBrowserUri(uri: URI): URI +} + +/** + * Main thread extension service implementation. + * Provides concrete implementation for extension management in the main thread, + * handling extension lifecycle events, activation, and error management. + * + * @param extensionManager Core extension manager responsible for extension operations + * @param rpcProtocol RPC protocol for inter-process communication with extensions + */ +class MainThreadExtensionService( + private val extensionManager: ExtensionManager, + private val rpcProtocol: IRPCProtocol +) : MainThreadExtensionServiceShape { + private val logger = Logger.getInstance(MainThreadExtensionService::class.java) + + /** + * Retrieves extension information by extension ID. + * Safely extracts the extension ID from various input formats and queries the extension manager. + * + * @param extensionId Extension identifier, expected as Map with "value" key or any other type + * @return Extension description object containing metadata, or null if not found + */ + override fun getExtension(extensionId: Any): Any? { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.info("Retrieving extension: $extensionIdStr") + return extensionManager.getExtensionDescription(extensionIdStr.toString()) + } + + /** + * Activates an extension with the specified ID and reason. + * Uses asynchronous activation via Future and waits for completion. + * + * @param extensionId Extension identifier to activate + * @param reason Optional activation reason or context information + * @return Boolean indicating activation success (true) or failure (false) + */ + override fun activateExtension(extensionId: Any, reason: Any?): Any { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.info("Activating extension: $extensionIdStr, reason: $reason") + + // Use Future to get asynchronous activation result + val future = extensionManager.activateExtension(extensionIdStr.toString(), rpcProtocol) + + return try { + // Wait for Future completion and return result + val result = future.get() + logger.info("Extension $extensionIdStr activation ${if (result) "successful" else "failed"}") + true + } catch (e: Exception) { + logger.error("Extension $extensionIdStr activation exception", e) + false + } + } + + /** + * Called immediately before extension activation begins. + * Provides logging for pre-activation state tracking. + * + * @param extensionId Extension identifier about to be activated + */ + override fun onWillActivateExtension(extensionId: Any) { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.info("Extension $extensionIdStr is about to be activated") + } + + /** + * Called after extension activation has completed successfully. + * Logs activation completion with detailed timing information. + * + * @param extensionId Extension identifier that was activated + * @param codeLoadingTime Time taken to load extension code (milliseconds) + * @param activateCallTime Time taken for activation call (milliseconds) + * @param activateResolvedTime Time taken to resolve activation (milliseconds) + * @param activationReason Reason or context for activation + */ + override fun onDidActivateExtension( + extensionId: Any, + codeLoadingTime: Double, + activateCallTime: Double, + activateResolvedTime: Double, + activationReason: Any? + ) { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.info("Extension $extensionIdStr activated, reason: $activationReason") + } + + /** + * Handles extension activation errors with detailed logging. + * Called when extension activation fails due to errors or missing dependencies. + * + * @param extensionId Extension identifier that failed activation + * @param error Error information or exception details + * @param missingExtensionDependency Information about missing dependencies + * @return Unit (void) - error is handled through logging + */ + override fun onExtensionActivationError( + extensionId: Any, + error: Any?, + missingExtensionDependency: Any? + ): Any { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.error("Extension $extensionIdStr activation error: $error, missing dependency: $missingExtensionDependency") + return Unit + } + + /** + * Handles runtime errors that occur during extension execution. + * Called when an activated extension encounters runtime errors. + * + * @param extensionId Extension identifier that encountered the error + * @param error Error information or exception details + */ + override fun onExtensionRuntimeError(extensionId: Any, error: Any?) { + // Safely extract extension ID string from input parameter + val extensionIdStr = try { + (extensionId as? Map<*, *>)?.get("value") as? String + } catch (e: Exception) { + // Fallback to string representation if extraction fails + "$extensionId" + } + logger.warn("Extension $extensionIdStr runtime error: $error") + } + + /** + * Sets performance marks for extension profiling and monitoring. + * Used to track performance metrics across extension operations. + * + * @param marks List of performance mark objects containing timing information + */ + override fun setPerformanceMarks(marks: List) { + logger.info("Setting performance marks: $marks") + } + + /** + * Converts a standard URI to browser-compatible format. + * Ensures URIs are properly formatted for web browser contexts. + * + * @param uri The original URI to convert + * @return Browser-compatible URI object (currently returns the original URI) + */ + override fun asBrowserUri(uri: URI): URI { + logger.info("Converting to browser URI: $uri") + return uri + } + + /** + * Disposes of resources when the service is no longer needed. + * Called during application shutdown or when the extension service is being replaced. + */ + override fun dispose() { + logger.info("Disposing MainThreadExtensionService") + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemEventServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemEventServiceShape.kt new file mode 100644 index 0000000000..7b7f27807b --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemEventServiceShape.kt @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger + +/** + * File system event service interface. + * Provides functionality for watching file system changes. + * Corresponds to the MainThreadFileSystemEventServiceShape interface in VSCode. + */ +interface MainThreadFileSystemEventServiceShape : Disposable { + /** + * Watches for file system changes. + * + * @param extensionId The extension identifier + * @param session The session identifier + * @param resource The resource URI as a map + * @param opts Watch options + * @param correlate Whether to correlate events + */ + fun watch( + extensionId: String, + session: Int, + resource: Map, + opts: Map, + correlate: Boolean + ) + + /** + * Stops watching for file system changes. + * + * @param session The session identifier to stop watching + */ + fun unwatch(session: Int) +} + +/** + * Implementation of the file system event service interface. + * Handles watching and unwatching file system changes. + */ +class MainThreadFileSystemEventService : MainThreadFileSystemEventServiceShape { + private val logger = Logger.getInstance(MainThreadFileSystemEventService::class.java) + + /** + * Starts watching for file system changes. + * + * @param extensionId The extension identifier + * @param session The session identifier + * @param resource The resource URI as a map + * @param opts Watch options + * @param correlate Whether to correlate events + */ + override fun watch( + extensionId: String, + session: Int, + resource: Map, + opts: Map, + correlate: Boolean + ) { + logger.info("Starting to watch file system changes: extensionId=$extensionId, session=$session, resource=$resource, opts=$opts, correlate=$correlate") + } + + /** + * Stops watching for file system changes. + * + * @param session The session identifier to stop watching + */ + override fun unwatch(session: Int) { + logger.info("Stopping file system watch: session=$session") + } + + /** + * Releases resources used by this service. + */ + override fun dispose() { + logger.info("Releasing MainThreadFileSystemEventService resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemShape.kt new file mode 100644 index 0000000000..7d36c3ee6b --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadFileSystemShape.kt @@ -0,0 +1,616 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.vfs.VirtualFile +import java.io.File +import java.net.URI +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.concurrent.ConcurrentHashMap + +/** + * File type enumeration. + * Defines the possible types of files in the file system. + */ +enum class FileType { + UNKNOWN, + FILE, + DIRECTORY, + SYMBOLIC_LINK +} + +/** + * File statistics information. + * Contains metadata about a file including its type, creation time, modification time, and size. + * + * @property type The type of the file (file, directory, symbolic link, etc.) + * @property ctime The creation time of the file in milliseconds since epoch + * @property mtime The last modification time of the file in milliseconds since epoch + * @property size The size of the file in bytes + */ +data class FileStat( + val type: FileType, + val ctime: Long, + val mtime: Long, + val size: Long +) + +/** + * File system provider capabilities. + * Defines the capabilities and features supported by a file system provider. + * + * @property isCaseSensitive Whether the file system is case-sensitive + * @property isReadonly Whether the file system is read-only + * @property isPathCaseSensitive Whether file paths are case-sensitive + * @property canHandleFileUri Whether the provider can handle file URIs + * @property hasFileCopy Whether the provider supports file copying + * @property hasFolderCopy Whether the provider supports folder copying + * @property hasOpenReadWriteCloseCapability Whether the provider supports open/read/write/close operations + * @property hasLegacyWatchCapability Whether the provider supports legacy file watching + * @property hasDiffCapability Whether the provider supports diff operations + * @property hasFileChangeCapability Whether the provider supports file change notifications + */ +data class FileSystemProviderCapabilities( + val isCaseSensitive: Boolean, + val isReadonly: Boolean, + val isPathCaseSensitive: Boolean, + val canHandleFileUri: Boolean, + val hasFileCopy: Boolean, + val hasFolderCopy: Boolean, + val hasOpenReadWriteCloseCapability: Boolean, + val hasLegacyWatchCapability: Boolean, + val hasDiffCapability: Boolean, + val hasFileChangeCapability: Boolean +) + +/** + * File overwrite options. + * Options for controlling file overwrite behavior during write operations. + * + * @property overwrite Whether to overwrite existing files + */ +data class FileOverwriteOptions( + val overwrite: Boolean +) + +/** + * File delete options. + * Options for controlling file deletion behavior. + * + * @property recursive Whether to delete directories recursively + * @property useTrash Whether to move files to trash instead of permanent deletion + */ +data class FileDeleteOptions( + val recursive: Boolean, + val useTrash: Boolean +) + +/** + * File change data. + * Represents a change event in the file system. + * + * @property type The type of change: 1 for ADDED, 2 for UPDATED, 3 for DELETED + * @property resource The resource that was changed, represented as a map of properties + */ +data class FileChangeDto( + val type: Int, // 1: ADDED, 2: UPDATED, 3: DELETED + val resource: Map +) + +/** + * Markdown string interface. + * Represents a string that can contain markdown formatting. + */ +interface MarkdownString { + val value: String + val isTrusted: Boolean +} + +/** + * Main thread file system service interface. + * Corresponds to the MainThreadFileSystemShape interface in VSCode. + * Provides an abstraction layer for file system operations that can be executed from the main thread. + */ +interface MainThreadFileSystemShape : Disposable { + /** + * Registers a file system provider with the given handle and scheme. + * + * @param handle A unique identifier for the provider + * @param scheme The URI scheme this provider handles (e.g., "file", "ftp", etc.) + */ + fun registerFileSystemProvider(handle: Int, scheme: String) + + /** + * Unregisters a file system provider. + * + * @param handle The handle of the provider to unregister + */ + fun unregisterProvider(handle: Int) + + /** + * Gets file status information for the specified resource. + * + * @param resource The URI of the file or directory to get information about + * @return FileStat object containing file metadata + */ + fun stat(resource: URI): FileStat + + /** + * Reads directory contents. + * Returns a list of entries in the specified directory. + * + * @param resource The URI of the directory to read + * @return List of pairs, where each pair contains (filename, fileType) + */ + fun readdir(resource: URI): List> + + /** + * Reads file content. + * Returns the raw bytes of the specified file. + * + * @param uri The URI of the file to read + * @return Byte array containing the file content + */ + fun readFile(uri: URI): ByteArray + + /** + * Writes file content. + * Writes the provided content to the specified file. + * + * @param uri The URI of the file to write + * @param content The content to write as a byte array + * @param overwrite Whether to overwrite if the file already exists + * @return The written content as a byte array + */ + fun writeFile(uri: URI, content: ByteArray, overwrite: Boolean): ByteArray + + /** + * Renames a file or directory. + * Moves a file or directory from source to target location. + * + * @param source The URI of the source file/directory + * @param target The URI of the target location + * @param options Additional options for the rename operation + */ + fun rename(source: URI, target: URI, options: Map) + + /** + * Copies a file or directory. + * Creates a copy of the source at the target location. + * + * @param source The URI of the source file/directory + * @param target The URI of the target location + * @param options Additional options for the copy operation + */ + fun copy(source: URI, target: URI, options: Map) + + /** + * Creates a directory. + * Creates the specified directory and any necessary parent directories. + * + * @param uri The URI of the directory to create + */ + fun mkdir(uri: URI) + + /** + * Deletes a file or directory. + * Removes the specified file or directory from the file system. + * + * @param uri The URI of the file/directory to delete + * @param options Additional options for the delete operation + */ + fun delete(uri: URI, options: Map) + + /** + * Ensures activation. + * Ensures that the file system provider for the given scheme is activated. + * + * @param scheme The URI scheme to ensure is activated + */ + fun ensureActivation(scheme: String) + + /** + * Listens for file system changes. + * Processes file system change notifications from providers. + * + * @param handle The handle of the provider sending the change notification + * @param resources List of file changes to process + */ + fun onFileSystemChange(handle: Int, resources: List) +} + +/** + * Main thread file system service implementation. + * Provides implementation of file system related functionality for the IDEA platform. + * This class implements the MainThreadFileSystemShape interface and provides + * concrete implementations for all file system operations. + */ +class MainThreadFileSystem : MainThreadFileSystemShape { + private val logger = Logger.getInstance(MainThreadFileSystem::class.java) + + // Registered file system providers mapped by their handles + private val providers = ConcurrentHashMap() + + /** + * Registers a file system provider with the given handle and scheme. + * This method stores the provider information for later use. + * + * @param handle A unique identifier for the provider + * @param scheme The URI scheme this provider handles + */ + override fun registerFileSystemProvider(handle: Int, scheme: String) { + logger.info("Registering file system provider: handle=$handle, scheme=$scheme") + + try { + // Store provider information + providers[handle] = scheme + + // Actual implementation would need to integrate with IDEA's VFS + // based on the scheme + } catch (e: Exception) { + logger.error("Failed to register file system provider: $e") + throw e + } + } + + /** + * Unregisters a file system provider. + * Removes the provider associated with the given handle. + * + * @param handle The handle of the provider to unregister + */ + override fun unregisterProvider(handle: Int) { + logger.info("Unregistering file system provider: handle=$handle") + + try { + // Remove provider information + providers.remove(handle) + + // Actual implementation would need to unregister the corresponding file system provider + } catch (e: Exception) { + logger.error("Failed to unregister file system provider: $e") + throw e + } + } + + /** + * Gets file status information for the specified resource. + * Retrieves metadata about a file or directory including type, timestamps, and size. + * + * @param resource The URI of the file or directory to get information about + * @return FileStat object containing file metadata + */ + override fun stat(resource: URI): FileStat { + logger.info("Getting file status information: $resource") + + try { + val path = getPathFromUriComponents(resource) + val file = File(path) + + if (!file.exists()) { + throw Exception("File does not exist: $path") + } + + val type = when { + file.isDirectory -> FileType.DIRECTORY + Files.isSymbolicLink(Paths.get(file.toURI())) -> FileType.SYMBOLIC_LINK + else -> FileType.FILE + } + + val ctime = file.lastModified() + val mtime = file.lastModified() + val size = file.length() + + return FileStat(type, ctime, mtime, size) + } catch (e: Exception) { + logger.error("Failed to get file status information: $e") + throw e + } + } + + /** + * Reads directory contents. + * Returns a list of all entries in the specified directory. + * + * @param resource The URI of the directory to read + * @return List of pairs, where each pair contains (filename, fileType) + */ + override fun readdir(resource: URI): List> { + logger.info("Reading directory contents: $resource") + + try { + val path = getPathFromUriComponents(resource) + val file = File(path) + + if (!file.exists() || !file.isDirectory) { + throw Exception("Directory does not exist or is not a directory: $path") + } + + // Read directory contents + return file.listFiles()?.map { + Pair(it.name, if (it.isDirectory) FileType.DIRECTORY.ordinal.toString() else FileType.FILE.ordinal.toString()) + } ?: emptyList() + } catch (e: Exception) { + logger.error("Failed to read directory contents: $e") + throw e + } + } + + /** + * Reads file content. + * Returns the raw bytes of the specified file. + * + * @param uri The URI of the file to read + * @return Byte array containing the file content + */ + override fun readFile(uri: URI): ByteArray { + logger.info("Reading file content: $uri") + + try { + val path = getPathFromUriComponents(uri) + val file = File(path) + + if (!file.exists() || file.isDirectory) { + throw Exception("File does not exist or is a directory: $path") + } + + // Read file content + return file.readBytes() + } catch (e: Exception) { + logger.error("Failed to read file content: $e") + throw e + } + } + + /** + * Writes file content. + * Writes the provided content to the specified file. + * + * @param uri The URI of the file to write + * @param content The content to write as a byte array + * @param overwrite Whether to overwrite if the file already exists + * @return The written content as a byte array + */ + override fun writeFile(uri: URI, content: ByteArray, overwrite: Boolean): ByteArray { + logger.info("Writing file content: $uri, content size: ${content.size} bytes") + + try { + val path = getPathFromUriComponents(uri) + val file = File(path) + + // Ensure parent directory exists + file.parentFile?.mkdirs() + + // Write file content + file.writeBytes(content) + return content + } catch (e: Exception) { + logger.error("Failed to write file content: $e") + throw e + } + } + + /** + * Renames a file or directory. + * Moves a file or directory from source to target location. + * + * @param source The URI of the source file/directory + * @param target The URI of the target location + * @param options Additional options for the rename operation + */ + override fun rename(source: URI, target: URI, options: Map) { + logger.info("Renaming: $source -> $target") + + try { + val sourcePath = getPathFromUriComponents(source) + val targetPath = getPathFromUriComponents(target) + val overwrite = options["overwrite"] as? Boolean ?: false + + val sourceFile = File(sourcePath) + val targetFile = File(targetPath) + + if (!sourceFile.exists()) { + throw Exception("Source file does not exist: $sourcePath") + } + + if (targetFile.exists() && !overwrite) { + throw Exception("Target file already exists and overwrite is not allowed: $targetPath") + } + + // Ensure parent directory exists + targetFile.parentFile?.mkdirs() + + // Perform rename operation + if (!sourceFile.renameTo(targetFile)) { + // If simple rename fails, try copy then delete + Files.move( + Paths.get(sourcePath), + Paths.get(targetPath), + if (overwrite) StandardCopyOption.REPLACE_EXISTING else StandardCopyOption.ATOMIC_MOVE + ) + } + } catch (e: Exception) { + logger.error("Rename operation failed: $e") + throw e + } + } + + /** + * Copies a file or directory. + * Creates a copy of the source at the target location. + * + * @param source The URI of the source file/directory + * @param target The URI of the target location + * @param options Additional options for the copy operation + */ + override fun copy(source: URI, target: URI, options: Map) { + logger.info("Copying: $source -> $target") + + try { + val sourcePath = getPathFromUriComponents(source) + val targetPath = getPathFromUriComponents(target) + val overwrite = options["overwrite"] as? Boolean ?: false + + val sourceFile = File(sourcePath) + val targetFile = File(targetPath) + + if (!sourceFile.exists()) { + throw Exception("Source file does not exist: $sourcePath") + } + + if (targetFile.exists() && !overwrite) { + throw Exception("Target file already exists and overwrite is not allowed: $targetPath") + } + + // Ensure parent directory exists + targetFile.parentFile?.mkdirs() + + if (sourceFile.isDirectory) { + // Copy directory recursively + sourceFile.copyRecursively(targetFile, overwrite) + } else { + // Copy file + Files.copy( + Paths.get(sourcePath), + Paths.get(targetPath), + if (overwrite) StandardCopyOption.REPLACE_EXISTING else StandardCopyOption.COPY_ATTRIBUTES + ) + } + } catch (e: Exception) { + logger.error("Copy operation failed: $e") + throw e + } + } + + /** + * Creates a directory. + * Creates the specified directory and any necessary parent directories. + * + * @param uri The URI of the directory to create + */ + override fun mkdir(uri: URI) { + logger.info("Creating directory: $uri") + + try { + val path = getPathFromUriComponents(uri) + val file = File(path) + + if (file.exists()) { + throw Exception("File or directory already exists: $path") + } + + // Create directory + if (!file.mkdirs()) { + throw Exception("Failed to create directory: $path") + } + } catch (e: Exception) { + logger.error("Failed to create directory: $e") + throw e + } + } + + /** + * Deletes a file or directory. + * Removes the specified file or directory from the file system. + * + * @param uri The URI of the file/directory to delete + * @param options Additional options for the delete operation + */ + override fun delete(uri: URI, options: Map) { + logger.info("Deleting: $uri, options: $options") + + try { + val path = getPathFromUriComponents(uri) + val file = File(path) + val recursive = options["recursive"] as? Boolean ?: false + val useTrash = options["useTrash"] as? Boolean ?: false + + if (!file.exists()) { + // If file doesn't exist, consider deletion successful + return + } + + if (useTrash) { + // TODO: Implement trash deletion based on platform + // Currently performs direct deletion, should move to trash in actual implementation + logger.warn("Trash deletion not implemented, performing direct deletion") + } + + if (file.isDirectory && recursive) { + // Recursively delete directory + file.deleteRecursively() + } else if (file.isDirectory && !recursive) { + throw Exception("Cannot delete non-empty directory unless recursive=true: $path") + } else { + // Delete file + file.delete() + } + } catch (e: Exception) { + logger.error("Delete operation failed: $e") + throw e + } + } + + /** + * Ensures activation. + * Ensures that the file system provider for the given scheme is activated. + * + * @param scheme The URI scheme to ensure is activated + */ + override fun ensureActivation(scheme: String) { + logger.info("Ensuring activation: $scheme") + + try { + // This should handle file system activation + // Actual implementation may need to notify IDEA's VFS to refresh + } catch (e: Exception) { + logger.error("Failed to ensure activation: $e") + throw e + } + } + + /** + * Listens for file system changes. + * Processes file system change notifications from providers. + * + * @param handle The handle of the provider sending the change notification + * @param resources List of file changes to process + */ + override fun onFileSystemChange(handle: Int, resources: List) { + logger.info("File system change notification: handle=$handle, resources=${resources.joinToString { it.resource.toString() }}") + + try { + // This should handle file system change notifications + // Actual implementation may need to notify IDEA's VFS to refresh + } catch (e: Exception) { + logger.error("Failed to process file system change notification: $e") + throw e + } + } + + /** + * Gets file system path from URI components. + * Converts a URI to a local file system path. + * + * @param uri The URI to convert + * @return The corresponding file system path + */ + private fun getPathFromUriComponents(uri: URI): String { + return File(uri).path + } + + /** + * Disposes of resources. + * Cleans up resources when this service is no longer needed. + */ + override fun dispose() { + logger.info("Disposing MainThreadFileSystem resources") + providers.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageFeaturesShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageFeaturesShape.kt new file mode 100644 index 0000000000..2a5382993b --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageFeaturesShape.kt @@ -0,0 +1,713 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.core.ExtensionIdentifier + +/** + * Language features related interface. + * Corresponds to the MainThreadLanguageFeaturesShape interface in VSCode. + * This interface defines the contract for language feature providers that run on the main thread. + * It provides methods to register various language intelligence features like code completion, + * hover information, symbol navigation, and more. + */ +interface MainThreadLanguageFeaturesShape : Disposable { + /** + * Unregisters service. + * @param handle Provider handle + */ + fun unregister(handle: Int) + + /** + * Registers document symbol provider. + * @param handle Provider handle + * @param selector Document selector + * @param label Label + */ + fun registerDocumentSymbolProvider(handle: Int, selector: List>, label: String) + + /** + * Registers code lens support. + * @param handle Provider handle + * @param selector Document selector + * @param eventHandle Event handle + */ + fun registerCodeLensSupport(handle: Int, selector: List>, eventHandle: Int?) + + /** + * Emits code lens event. + * @param eventHandle Event handle + * @param event Event content + */ + fun emitCodeLensEvent(eventHandle: Int, event: Any?) + + /** + * Registers definition support. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerDefinitionSupport(handle: Int, selector: List>) + + /** + * Registers declaration support. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerDeclarationSupport(handle: Int, selector: List>) + + /** + * Registers implementation support. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerImplementationSupport(handle: Int, selector: List>) + + /** + * Registers type definition support. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerTypeDefinitionSupport(handle: Int, selector: List>) + + /** + * Registers hover provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerHoverProvider(handle: Int, selector: List>) + + /** + * Registers evaluatable expression provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerEvaluatableExpressionProvider(handle: Int, selector: List>) + + /** + * Registers inline values provider. + * @param handle Provider handle + * @param selector Document selector + * @param eventHandle Event handle + */ + fun registerInlineValuesProvider(handle: Int, selector: List>, eventHandle: Int?) + + /** + * Emits inline values event. + * @param eventHandle Event handle + * @param event Event content + */ + fun emitInlineValuesEvent(eventHandle: Int, event: Any?) + + /** + * Registers document highlight provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerDocumentHighlightProvider(handle: Int, selector: List>) + + /** + * Registers multi-document highlight provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerMultiDocumentHighlightProvider(handle: Int, selector: List>) + + /** + * Registers linked editing range provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerLinkedEditingRangeProvider(handle: Int, selector: List>) + + /** + * Registers reference support. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerReferenceSupport(handle: Int, selector: List>) + + /** + * Registers code action support. + * @param handle Provider handle + * @param selector Document selector + * @param metadata Metadata + * @param displayName Display name + * @param extensionID Extension ID + * @param supportsResolve Whether to support resolve + */ + fun registerCodeActionSupport( + handle: Int, + selector: List>, + metadata: Map, + displayName: String, + extensionID: String, + supportsResolve: Boolean + ) + + /** + * Registers paste edit provider. + * @param handle Provider handle + * @param selector Document selector + * @param metadata Metadata + */ + fun registerPasteEditProvider( + handle: Int, + selector: List>, + metadata: Map + ) + + /** + * Registers document formatting support. + * @param handle Provider handle + * @param selector Document selector + * @param extensionId Extension ID + * @param displayName Display name + */ + fun registerDocumentFormattingSupport( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String + ) + + /** + * Registers range formatting support. + * @param handle Provider handle + * @param selector Document selector + * @param extensionId Extension ID + * @param displayName Display name + * @param supportRanges Whether to support ranges + */ + fun registerRangeFormattingSupport( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String, + supportRanges: Boolean + ) + + /** + * Registers on-type formatting support. + * @param handle Provider handle + * @param selector Document selector + * @param autoFormatTriggerCharacters Auto-format trigger characters + * @param extensionId Extension ID + */ + fun registerOnTypeFormattingSupport( + handle: Int, + selector: List>, + autoFormatTriggerCharacters: List, + extensionId: ExtensionIdentifier + ) + + /** + * Registers navigate type support. + * @param handle Provider handle + * @param supportsResolve Whether to support resolve + */ + fun registerNavigateTypeSupport(handle: Int, supportsResolve: Boolean) + + /** + * Registers rename support. + * @param handle Provider handle + * @param selector Document selector + * @param supportsResolveInitialValues Whether to support resolve initial values + */ + fun registerRenameSupport(handle: Int, selector: List>, supportsResolveInitialValues: Boolean) + + /** + * Registers new symbol names provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerNewSymbolNamesProvider(handle: Int, selector: List>) + + /** + * Registers document semantic tokens provider. + * @param handle Provider handle + * @param selector Document selector + * @param legend Legend + * @param eventHandle Event handle + */ + fun registerDocumentSemanticTokensProvider( + handle: Int, + selector: List>, + legend: Map, + eventHandle: Int? + ) + + /** + * Emits document semantic tokens event. + * @param eventHandle Event handle + */ + fun emitDocumentSemanticTokensEvent(eventHandle: Int) + + /** + * Registers document range semantic tokens provider. + * @param handle Provider handle + * @param selector Document selector + * @param legend Legend + */ + fun registerDocumentRangeSemanticTokensProvider( + handle: Int, + selector: List>, + legend: Map + ) + + /** + * Registers completions provider. + * @param handle Provider handle + * @param selector Document selector + * @param triggerCharacters Trigger characters + * @param supportsResolveDetails Whether to support resolve details + * @param extensionId Extension ID + */ + fun registerCompletionsProvider( + handle: Int, + selector: List>, + triggerCharacters: List, + supportsResolveDetails: Boolean, + extensionId: ExtensionIdentifier + ) + + /** + * Registers inline completions support. + * @param handle Provider handle + * @param selector Document selector + * @param supportsHandleDidShowCompletionItem Whether to support handle did show completion item + * @param extensionId Extension ID + * @param yieldsToExtensionIds Yields to extension IDs + * @param displayName Display name + * @param debounceDelayMs Debounce delay in milliseconds + */ + fun registerInlineCompletionsSupport( + handle: Int, + selector: List>, + supportsHandleDidShowCompletionItem: Boolean, + extensionId: String, + yieldsToExtensionIds: List, + displayName: String?, + debounceDelayMs: Int? + ) + + /** + * Registers inline edit provider. + * @param handle Provider handle + * @param selector Document selector + * @param extensionId Extension ID + * @param displayName Display name + */ + fun registerInlineEditProvider( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String + ) + + /** + * Registers signature help provider. + * @param handle Provider handle + * @param selector Document selector + * @param metadata Metadata + */ + fun registerSignatureHelpProvider( + handle: Int, + selector: List>, + metadata: Map + ) + + /** + * Registers inlay hints provider. + * @param handle Provider handle + * @param selector Document selector + * @param supportsResolve Whether to support resolve + * @param eventHandle Event handle + * @param displayName Display name + */ + fun registerInlayHintsProvider( + handle: Int, + selector: List>, + supportsResolve: Boolean, + eventHandle: Int?, + displayName: String? + ) + + /** + * Emits inlay hints event. + * @param eventHandle Event handle + */ + fun emitInlayHintsEvent(eventHandle: Int) + + /** + * Registers document link provider. + * @param handle Provider handle + * @param selector Document selector + * @param supportsResolve Whether to support resolve + */ + fun registerDocumentLinkProvider( + handle: Int, + selector: List>, + supportsResolve: Boolean + ) + + /** + * Registers document color provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerDocumentColorProvider(handle: Int, selector: List>) + + /** + * Registers folding range provider. + * @param handle Provider handle + * @param selector Document selector + * @param extensionId Extension ID + * @param eventHandle Event handle + */ + fun registerFoldingRangeProvider( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + eventHandle: Int? + ) + + /** + * Emits folding range event. + * @param eventHandle Event handle + * @param event Event content + */ + fun emitFoldingRangeEvent(eventHandle: Int, event: Any?) + + /** + * Registers selection range provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerSelectionRangeProvider(handle: Int, selector: List>) + + /** + * Registers call hierarchy provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerCallHierarchyProvider(handle: Int, selector: List>) + + /** + * Registers type hierarchy provider. + * @param handle Provider handle + * @param selector Document selector + */ + fun registerTypeHierarchyProvider(handle: Int, selector: List>) + + /** + * Registers document on drop edit provider. + * @param handle Provider handle + * @param selector Document selector + * @param metadata Metadata + */ + fun registerDocumentOnDropEditProvider( + handle: Int, + selector: List>, + metadata: Map? + ) + + /** + * Resolves paste file data. + * @param handle Provider handle + * @param requestId Request ID + * @param dataId Data ID + * @return File data + */ + fun resolvePasteFileData(handle: Int, requestId: Int, dataId: String): ByteArray + + /** + * Resolves document on drop file data. + * @param handle Provider handle + * @param requestId Request ID + * @param dataId Data ID + * @return File data + */ + fun resolveDocumentOnDropFileData(handle: Int, requestId: Int, dataId: String): ByteArray + + /** + * Sets language configuration. + * @param handle Provider handle + * @param languageId Language ID + * @param configuration Configuration + */ + fun setLanguageConfiguration(handle: Int, languageId: String, configuration: Map) +} + +/** + * Language features related implementation class. + * This class implements the MainThreadLanguageFeaturesShape interface and provides + * concrete implementations for all language feature registration methods. + * It acts as a bridge between the extension host and the IDE's language services. + */ +class MainThreadLanguageFeatures : MainThreadLanguageFeaturesShape { + private val logger = Logger.getInstance(MainThreadLanguageFeatures::class.java) + + override fun unregister(handle: Int) { + logger.info("Unregistering service: handle=$handle") + } + + override fun registerDocumentSymbolProvider(handle: Int, selector: List>, label: String) { + logger.info("Registering document symbol provider: handle=$handle, selector=$selector, label=$label") + } + + override fun registerCodeLensSupport(handle: Int, selector: List>, eventHandle: Int?) { + logger.info("Registering code lens support: handle=$handle, selector=$selector, eventHandle=$eventHandle") + } + + override fun emitCodeLensEvent(eventHandle: Int, event: Any?) { + logger.info("Emitting code lens event: eventHandle=$eventHandle, event=$event") + } + + override fun registerDefinitionSupport(handle: Int, selector: List>) { + logger.info("Registering definition support: handle=$handle, selector=$selector") + } + + override fun registerDeclarationSupport(handle: Int, selector: List>) { + logger.info("Registering declaration support: handle=$handle, selector=$selector") + } + + override fun registerImplementationSupport(handle: Int, selector: List>) { + logger.info("Registering implementation support: handle=$handle, selector=$selector") + } + + override fun registerTypeDefinitionSupport(handle: Int, selector: List>) { + logger.info("Registering type definition support: handle=$handle, selector=$selector") + } + + override fun registerHoverProvider(handle: Int, selector: List>) { + logger.info("Registering hover provider: handle=$handle, selector=$selector") + } + + override fun registerEvaluatableExpressionProvider(handle: Int, selector: List>) { + logger.info("Registering evaluatable expression provider: handle=$handle, selector=$selector") + } + + override fun registerInlineValuesProvider(handle: Int, selector: List>, eventHandle: Int?) { + logger.info("Registering inline values provider: handle=$handle, selector=$selector, eventHandle=$eventHandle") + } + + override fun emitInlineValuesEvent(eventHandle: Int, event: Any?) { + logger.info("Emitting inline values event: eventHandle=$eventHandle, event=$event") + } + + override fun registerDocumentHighlightProvider(handle: Int, selector: List>) { + logger.info("Registering document highlight provider: handle=$handle, selector=$selector") + } + + override fun registerMultiDocumentHighlightProvider(handle: Int, selector: List>) { + logger.info("Registering multi-document highlight provider: handle=$handle, selector=$selector") + } + + override fun registerLinkedEditingRangeProvider(handle: Int, selector: List>) { + logger.info("Registering linked editing range provider: handle=$handle, selector=$selector") + } + + override fun registerReferenceSupport(handle: Int, selector: List>) { + logger.info("Registering reference support: handle=$handle, selector=$selector") + } + + override fun registerCodeActionSupport( + handle: Int, + selector: List>, + metadata: Map, + displayName: String, + extensionID: String, + supportsResolve: Boolean + ) { + logger.info("Registering code action support: handle=$handle, selector=$selector, metadata=$metadata, displayName=$displayName, extensionID=$extensionID, supportsResolve=$supportsResolve") + } + + override fun registerPasteEditProvider( + handle: Int, + selector: List>, + metadata: Map + ) { + logger.info("Registering paste edit provider: handle=$handle, selector=$selector, metadata=$metadata") + } + + override fun registerDocumentFormattingSupport( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String + ) { + logger.info("Registering document formatting support: handle=$handle, selector=$selector, extensionId=${extensionId.value}, displayName=$displayName") + } + + override fun registerRangeFormattingSupport( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String, + supportRanges: Boolean + ) { + logger.info("Registering range formatting support: handle=$handle, selector=$selector, extensionId=${extensionId.value}, displayName=$displayName, supportRanges=$supportRanges") + } + + override fun registerOnTypeFormattingSupport( + handle: Int, + selector: List>, + autoFormatTriggerCharacters: List, + extensionId: ExtensionIdentifier + ) { + logger.info("Registering on-type formatting support: handle=$handle, selector=$selector, autoFormatTriggerCharacters=$autoFormatTriggerCharacters, extensionId=${extensionId.value}") + } + + override fun registerNavigateTypeSupport(handle: Int, supportsResolve: Boolean) { + logger.info("Registering navigate type support: handle=$handle, supportsResolve=$supportsResolve") + } + + override fun registerRenameSupport(handle: Int, selector: List>, supportsResolveInitialValues: Boolean) { + logger.info("Registering rename support: handle=$handle, selector=$selector, supportsResolveInitialValues=$supportsResolveInitialValues") + } + + override fun registerNewSymbolNamesProvider(handle: Int, selector: List>) { + logger.info("Registering new symbol names provider: handle=$handle, selector=$selector") + } + + override fun registerDocumentSemanticTokensProvider( + handle: Int, + selector: List>, + legend: Map, + eventHandle: Int? + ) { + logger.info("Registering document semantic tokens provider: handle=$handle, selector=$selector, legend=$legend, eventHandle=$eventHandle") + } + + override fun emitDocumentSemanticTokensEvent(eventHandle: Int) { + logger.info("Emitting document semantic tokens event: eventHandle=$eventHandle") + } + + override fun registerDocumentRangeSemanticTokensProvider( + handle: Int, + selector: List>, + legend: Map + ) { + logger.info("Registering document range semantic tokens provider: handle=$handle, selector=$selector, legend=$legend") + } + + override fun registerCompletionsProvider( + handle: Int, + selector: List>, + triggerCharacters: List, + supportsResolveDetails: Boolean, + extensionId: ExtensionIdentifier + ) { + logger.info("Registering completions provider: handle=$handle, selector=$selector, triggerCharacters=$triggerCharacters, supportsResolveDetails=$supportsResolveDetails, extensionId=${extensionId.value}") + } + + override fun registerInlineCompletionsSupport( + handle: Int, + selector: List>, + supportsHandleDidShowCompletionItem: Boolean, + extensionId: String, + yieldsToExtensionIds: List, + displayName: String?, + debounceDelayMs: Int? + ) { + logger.info("Registering inline completions support: handle=$handle, selector=$selector, supportsHandleDidShowCompletionItem=$supportsHandleDidShowCompletionItem, extensionId=$extensionId, yieldsToExtensionIds=$yieldsToExtensionIds, displayName=$displayName, debounceDelayMs=$debounceDelayMs") + } + + override fun registerInlineEditProvider( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + displayName: String + ) { + logger.info("Registering inline edit provider: handle=$handle, selector=$selector, extensionId=${extensionId.value}, displayName=$displayName") + } + + override fun registerSignatureHelpProvider( + handle: Int, + selector: List>, + metadata: Map + ) { + logger.info("Registering signature help provider: handle=$handle, selector=$selector, metadata=$metadata") + } + + override fun registerInlayHintsProvider( + handle: Int, + selector: List>, + supportsResolve: Boolean, + eventHandle: Int?, + displayName: String? + ) { + logger.info("Registering inlay hints provider: handle=$handle, selector=$selector, supportsResolve=$supportsResolve, eventHandle=$eventHandle, displayName=$displayName") + } + + override fun emitInlayHintsEvent(eventHandle: Int) { + logger.info("Emitting inlay hints event: eventHandle=$eventHandle") + } + + override fun registerDocumentLinkProvider( + handle: Int, + selector: List>, + supportsResolve: Boolean + ) { + logger.info("Registering document link provider: handle=$handle, selector=$selector, supportsResolve=$supportsResolve") + } + + override fun registerDocumentColorProvider(handle: Int, selector: List>) { + logger.info("Registering document color provider: handle=$handle, selector=$selector") + } + + override fun registerFoldingRangeProvider( + handle: Int, + selector: List>, + extensionId: ExtensionIdentifier, + eventHandle: Int? + ) { + logger.info("Registering folding range provider: handle=$handle, selector=$selector, extensionId=${extensionId.value}, eventHandle=$eventHandle") + } + + override fun emitFoldingRangeEvent(eventHandle: Int, event: Any?) { + logger.info("Emitting folding range event: eventHandle=$eventHandle, event=$event") + } + + override fun registerSelectionRangeProvider(handle: Int, selector: List>) { + logger.info("Registering selection range provider: handle=$handle, selector=$selector") + } + + override fun registerCallHierarchyProvider(handle: Int, selector: List>) { + logger.info("Registering call hierarchy provider: handle=$handle, selector=$selector") + } + + override fun registerTypeHierarchyProvider(handle: Int, selector: List>) { + logger.info("Registering type hierarchy provider: handle=$handle, selector=$selector") + } + + override fun registerDocumentOnDropEditProvider( + handle: Int, + selector: List>, + metadata: Map? + ) { + logger.info("Registering document on drop edit provider: handle=$handle, selector=$selector, metadata=$metadata") + } + + override fun resolvePasteFileData(handle: Int, requestId: Int, dataId: String): ByteArray { + logger.info("Resolving paste file data: handle=$handle, requestId=$requestId, dataId=$dataId") + return ByteArray(0) // Return empty array, actual implementation needs to handle real file data + } + + override fun resolveDocumentOnDropFileData(handle: Int, requestId: Int, dataId: String): ByteArray { + logger.info("Resolving document on drop file data: handle=$handle, requestId=$requestId, dataId=$dataId") + return ByteArray(0) // Return empty array, actual implementation needs to handle real file data + } + + override fun setLanguageConfiguration(handle: Int, languageId: String, configuration: Map) { + logger.info("Setting language configuration: handle=$handle, languageId=$languageId, configuration=$configuration") + } + + override fun dispose() { + logger.info("Disposing MainThreadLanguageFeatures resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageModelToolsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageModelToolsShape.kt new file mode 100644 index 0000000000..e21bda1273 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLanguageModelToolsShape.kt @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.plugin.SystemObjectProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import com.intellij.openapi.diagnostic.logger +import java.util.concurrent.ConcurrentHashMap + +/** + * Language model tools service interface. + * Corresponds to the MainThreadLanguageModelTools interface in VSCode. + */ +interface MainThreadLanguageModelToolsShape : Disposable { + /** + * Gets all available tool list. + */ + fun getTools(): List> + + /** + * Invokes the specified tool. + * @param dto Tool invocation parameters + * @param token Cancellation token + */ + fun invokeTool(dto: Map, token: Any? = null): Map + + /** + * Calculates the number of tokens for the given input. + * @param callId Call ID + * @param input Input content + * @param token Cancellation token + */ + fun countTokensForInvocation(callId: String, input: String, token: Any?): Int + + /** + * Registers a tool. + * @param id Tool ID + */ + fun registerTool(id: String) + + /** + * Unregisters a tool. + * @param name Tool name + */ + fun unregisterTool(name: String) +} + +/** + * Implementation of the language model tools service. + */ +class MainThreadLanguageModelTools : MainThreadLanguageModelToolsShape { + + private val logger = logger() + private val tools = ConcurrentHashMap() + + /** + * Tool information + */ + private data class ToolInfo( + val id: String, + val registered: Boolean = true + ) + + override fun getTools(): List> { + logger.info("Get available language model tool list") + // Return the list of registered tools + return tools.values.filter { it.registered }.map { + mapOf("id" to it.id) + } + } + + override fun invokeTool(dto: Map, token: Any?): Map { + val toolId = dto["id"] as? String ?: throw IllegalArgumentException("Tool ID cannot be empty") + val params = dto["params"] ?: emptyMap() + + logger.info("Invoke language model tool: $toolId") + val toolInfo = tools[toolId] ?: throw IllegalArgumentException("Tool with ID $toolId not found") + + if (!toolInfo.registered) { + throw IllegalStateException("Tool $toolId is not registered") + } + + // The actual tool should be invoked here. Currently returns a mock result. + // In the actual implementation, it may need to call the real tool in the extension process via RPC. + return mapOf( + "result" to "Tool $toolId invoked successfully", + "id" to toolId + ) + } + + override fun countTokensForInvocation(callId: String, input: String, token: Any?): Int { + logger.info("Calculate token count for tool invocation $callId") + + // The actual token count should be calculated here. Currently returns a mock result. + // In the actual implementation, it may need to use a specific algorithm or service to calculate the token count. + return input.length / 4 + 1 // Simple mock token calculation + } + + override fun registerTool(id: String) { + logger.info("Register language model tool: $id") + + tools[id] = ToolInfo(id, true) + } + + override fun unregisterTool(name: String) { + logger.info("Unregister language model tool: $name") + + if (tools.containsKey(name)) { + tools[name] = tools[name]!!.copy(registered = false) + } else { + logger.warn("Attempting to unregister non-existent tool: $name") + } + } + + override fun dispose() { + logger.info("Dispose MainThreadLanguageModelTools resources") + tools.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLoggerShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLoggerShape.kt new file mode 100644 index 0000000000..92191a7ef9 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadLoggerShape.kt @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.util.URI + +/** + * Main thread logger interface. + */ +interface MainThreadLoggerShape : Disposable { + /** + * Logs messages. + * @param file Log file URI + * @param messages List of log messages + */ + fun log(file: URI, messages: List) + + /** + * Flushes log. + * @param file Log file URI + */ + fun flush(file: URI) + + /** + * Creates logger. + * @param file Log file URI + * @param options Log options + * @return Creation result + */ + fun createLogger(file: URI, options: Map): Any + + /** + * Registers logger. + * @param logger Logger information + * @return Registration result + */ + fun registerLogger(logger: Map): Any + + /** + * Deregisters logger. + * @param resource Resource URI + * @return Deregistration result + */ + fun deregisterLogger(resource: String): Any + + /** + * Sets logger visibility. + * @param resource Resource URI + * @param visible Whether visible + * @return Setting result + */ + fun setVisibility(resource: String, visible: Boolean): Any + +} + +class MainThreadLogger : MainThreadLoggerShape { + private val logger = Logger.getInstance(MainThreadLogger::class.java) + + override fun log(file: URI, messages: List) { + logger.info("Logging to file: $file") + } + + override fun flush(file: URI) { + logger.info("Flushing log file: $file") + } + + override fun createLogger(file: URI, options: Map): Any { + logger.info("Creating logger for file: $file with options: $options") + return Unit // Placeholder for actual logger object + } + + override fun registerLogger(log: Map): Any { + logger.info("Registering logger: $log") + return Unit // Placeholder for actual registration result + } + + override fun deregisterLogger(resource: String): Any { + logger.info("Deregistering logger for resource: $resource") + return Unit // Placeholder for actual deregistration result + } + + override fun setVisibility(resource: String, visible: Boolean): Any { + logger.info("Setting visibility for resource: $resource to $visible") + return Unit // Placeholder for actual visibility result + } + + override fun dispose() { + logger.info("Disposing MainThreadLogger") + } + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadMessageServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadMessageServiceShape.kt new file mode 100644 index 0000000000..160f7161a9 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadMessageServiceShape.kt @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.application.ApplicationManager +import java.util.concurrent.atomic.AtomicReference + +interface MainThreadMessageServiceShape : Disposable { + // $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number }[]): Promise; + fun showMessage(severity: Int, message: String, options: Map, commands: List>): Int? +} + +class MainThreadMessageService : MainThreadMessageServiceShape { + private val logger = Logger.getInstance(MainThreadMessageService::class.java) + + override fun showMessage( + severity: Int, + message: String, + options: Map, + commands: List> + ): Int? { + logger.info("showMessage - severity: $severity, message: $message, options: $options, commands: $commands") + + val project = ProjectManager.getInstance().defaultProject + val isModal = options["modal"] as? Boolean ?: false + val detail = options["detail"] as? String + return if (isModal) { + showModalMessage(project, severity, message, detail, options, commands) + } else { + showNotificationMessage(project, severity, message) + null + } + } + + private fun showModalMessage( + project: com.intellij.openapi.project.Project, + severity: Int, + message: String, + detail: String?, + options: Map, + commands: List> + ): Int? { + // Find if there's a button with isCloseAffordance=true as cancel button + var cancelIdx = commands.indexOfFirst { it["isCloseAffordance"] == true } + // If no cancel button, automatically add a "Cancel" button at the end + val commandsWithCancel = if (cancelIdx < 0) { + val cancelHandle = commands.size + commands + mapOf("title" to "Cancel", "handle" to cancelHandle, "isCloseAffordance" to true) + } else { + commands + } + // Button title array for dialog buttons + val buttonTitles = commandsWithCancel.map { it["title"].toString() } + // Establish mapping from button index to handle for returning handle later + val handleMap = commandsWithCancel.mapIndexed { idx, cmd -> idx to (cmd["handle"] as? Number)?.toInt() }.toMap() + // Re-find the index of cancel button + val cancelIdxFinal = commandsWithCancel.indexOfFirst { it["isCloseAffordance"] == true } + // Assemble dialog main message and subtitle + val dialogMessage = if (detail.isNullOrBlank()) message else "$message\n\n$detail" + // For thread-safe retrieval of user-selected button index + val selectedIdxRef = AtomicReference() + // Ensure UI operations execute on EDT thread, show modal dialog + ApplicationManager.getApplication().invokeAndWait { + val selectedIdx = Messages.showDialog( + project, + dialogMessage, + options["source"]?.let { (it as? Map<*, *>)?.get("label")?.toString() } ?: "kilocode", + buttonTitles.toTypedArray(), + if (cancelIdxFinal >= 0) cancelIdxFinal else 0, + // Choose different icons based on severity + when (severity) { + 1 -> Messages.getInformationIcon() + 2 -> Messages.getWarningIcon() + 3 -> Messages.getErrorIcon() + else -> Messages.getInformationIcon() + } + ) + selectedIdxRef.set(selectedIdx) + } + // Get user-clicked button index and return corresponding handle + val selectedIdx = selectedIdxRef.get() + return if (selectedIdx != null && selectedIdx >= 0) handleMap[selectedIdx] else null + } + + private fun showNotificationMessage( + project: com.intellij.openapi.project.Project, + severity: Int, + message: String + ) { + val notificationType = when (severity) { + 1 -> NotificationType.INFORMATION + 2 -> NotificationType.WARNING + 3 -> NotificationType.ERROR + else -> NotificationType.INFORMATION + } + val notification = NotificationGroupManager.getInstance().getNotificationGroup("kilocode").createNotification( + message, + notificationType + ) + notification.notify(project) + } + + override fun dispose() { + logger.info("dispose") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadOutputServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadOutputServiceShape.kt new file mode 100644 index 0000000000..22f696b71b --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadOutputServiceShape.kt @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.util.URI + +/** + * Main thread output service interface. + * Corresponds to the MainThreadOutputServiceShape interface in VSCode. + */ +interface MainThreadOutputServiceShape : Disposable { + /** + * Registers output channel. + * @param label Label + * @param file File URI components + * @param languageId Language ID + * @param extensionId Extension ID + * @return Channel ID + */ + suspend fun register(label: String, file: Map, languageId: String?, extensionId: String): String + + /** + * Updates output channel. + * @param channelId Channel ID + * @param mode Update mode + * @param till Update to specified position + */ + suspend fun update(channelId: String, mode: Int, till: Int? = null) + + /** + * Reveals output channel. + * @param channelId Channel ID + * @param preserveFocus Whether to preserve focus + */ + suspend fun reveal(channelId: String, preserveFocus: Boolean) + + /** + * Closes output channel. + * @param channelId Channel ID + */ + suspend fun close(channelId: String) + + /** + * Disposes output channel. + * @param channelId Channel ID + */ + suspend fun dispose(channelId: String) +} + +/** + * Implementation of the main thread output service. + */ +class MainThreadOutputService : MainThreadOutputServiceShape { + private val logger = Logger.getInstance(MainThreadOutputService::class.java) + + /** + * Registers output channel. + * @param label Label + * @param file File URI components + * @param languageId Language ID + * @param extensionId Extension ID + * @return Channel ID + */ + override suspend fun register(label: String, file: Map, languageId: String?, extensionId: String): String { + logger.info("Register output channel: label=$label, file=$file, extensionId=$extensionId") + return label // Use label as channel ID + } + + /** + * Updates output channel. + * @param channelId Channel ID + * @param mode Update mode + * @param till Update to specified position + */ + override suspend fun update(channelId: String, mode: Int, till: Int?) { + logger.info("Update output channel: channelId=$channelId, mode=$mode, till=$till") + } + + /** + * Reveals output channel. + * @param channelId Channel ID + * @param preserveFocus Whether to preserve focus + */ + override suspend fun reveal(channelId: String, preserveFocus: Boolean) { + logger.info("Reveal output channel: channelId=$channelId, preserveFocus=$preserveFocus") + } + + /** + * Closes output channel. + * @param channelId Channel ID + */ + override suspend fun close(channelId: String) { + logger.info("Close output channel: channelId=$channelId") + } + + /** + * Disposes output channel. + * @param channelId Channel ID + */ + override suspend fun dispose(channelId: String) { + logger.info("Disposing output channel: channelId=$channelId") + } + + /** + * Dispose all resources + */ + override fun dispose() { + logger.info("Disposing all output channel resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSearchShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSearchShape.kt new file mode 100644 index 0000000000..9ef5c7eb68 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSearchShape.kt @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.net.URI + +/** + * Main thread search service interface. + * Corresponds to the MainThreadSearchShape interface in VSCode. + */ +interface MainThreadSearchShape : Disposable { + /** + * Registers file search provider. + * @param handle Provider ID + * @param scheme Scheme + */ + fun registerFileSearchProvider(handle: Int, scheme: String) + + /** + * Registers AI text search provider. + * @param handle Provider ID + * @param scheme Scheme + */ + fun registerAITextSearchProvider(handle: Int, scheme: String) + + /** + * Registers text search provider. + * @param handle Provider ID + * @param scheme Scheme + */ + fun registerTextSearchProvider(handle: Int, scheme: String) + + /** + * Unregisters provider. + * @param handle Provider ID + */ + fun unregisterProvider(handle: Int) + + /** + * Handles file match. + * @param handle Provider ID + * @param session Session ID + * @param data List of URI components + */ + fun handleFileMatch(handle: Int, session: Int, data: List>) + + /** + * Handles text match. + * @param handle Provider ID + * @param session Session ID + * @param data Raw file match data + */ + fun handleTextMatch(handle: Int, session: Int, data: List>) + + /** + * Handles telemetry data. + * @param eventName Event name + * @param data Telemetry data + */ + fun handleTelemetry(eventName: String, data: Any?) +} + +/** + * Implementation of the main thread search service. + * Provides search-related functionality for the IDEA platform. + */ +class MainThreadSearch : MainThreadSearchShape { + private val logger = Logger.getInstance(MainThreadSearch::class.java) + private val searchProviders = mutableMapOf() + private val fileSessions = mutableMapOf>() + private val textSessions = mutableMapOf>>() + + override fun registerFileSearchProvider(handle: Int, scheme: String) { + try { + logger.info("Registering file search provider: handle=$handle, scheme=$scheme") + searchProviders[handle] = "file:$scheme" + } catch (e: Exception) { + logger.error("Failed to register file search provider", e) + } + } + + override fun registerAITextSearchProvider(handle: Int, scheme: String) { + try { + logger.info("Registering AI text search provider: handle=$handle, scheme=$scheme") + searchProviders[handle] = "aitext:$scheme" + } catch (e: Exception) { + logger.error("Failed to register AI text search provider", e) + } + } + + override fun registerTextSearchProvider(handle: Int, scheme: String) { + try { + logger.info("Registering text search provider: handle=$handle, scheme=$scheme") + searchProviders[handle] = "text:$scheme" + } catch (e: Exception) { + logger.error("Failed to register text search provider", e) + } + } + + override fun unregisterProvider(handle: Int) { + try { + logger.info("Unregistering provider: handle=$handle") + searchProviders.remove(handle) + } catch (e: Exception) { + logger.error("Failed to unregister search provider", e) + } + } + + override fun handleFileMatch(handle: Int, session: Int, data: List>) { + try { + logger.info("Handling file match: handle=$handle, session=$session, matches=${data.size}") + + // Convert URI components to URI + val uris = data.mapNotNull { uriComponents -> + try { + val scheme = uriComponents["scheme"] as? String ?: return@mapNotNull null + val authority = uriComponents["authority"] as? String ?: "" + val path = uriComponents["path"] as? String ?: return@mapNotNull null + val query = uriComponents["query"] as? String ?: "" + val fragment = uriComponents["fragment"] as? String ?: "" + + URI(scheme, authority, path, query, fragment) + } catch (e: Exception) { + logger.warn("Failed to convert URI components: $uriComponents", e) + null + } + } + + // Store match results + fileSessions.getOrPut(session) { mutableListOf() }.addAll(uris) + + // TODO: Actual implementation should display these results in IDEA's search results panel + } catch (e: Exception) { + logger.error("Failed to handle file match", e) + } + } + + override fun handleTextMatch(handle: Int, session: Int, data: List>) { + try { + logger.info("Handling text match: handle=$handle, session=$session, matches=${data.size}") + + // Store match results + textSessions.getOrPut(session) { mutableListOf() }.addAll(data) + + // TODO: Actual implementation should display these results in IDEA's search results panel, including highlighting matched text + } catch (e: Exception) { + logger.error("Failed to handle text match", e) + } + } + + override fun handleTelemetry(eventName: String, data: Any?) { + try { + logger.info("Handling telemetry: event=$eventName, data=$data") + } catch (e: Exception) { + logger.error("Failed to handle telemetry data", e) + } + } + + override fun dispose() { + logger.info("Disposing MainThreadSearch") + searchProviders.clear() + fileSessions.clear() + textSessions.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSecretStateShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSecretStateShape.kt new file mode 100644 index 0000000000..096f0ecb50 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadSecretStateShape.kt @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardOpenOption + +/** + * Secret state management service interface. + */ +interface MainThreadSecretStateShape : Disposable { + /** + * Gets the secret. + * @param extensionId Extension ID + * @param key Secret key identifier + * @return Secret value, returns null if not exists + */ + suspend fun getPassword(extensionId: String, key: String): String? + + /** + * Sets the secret. + * @param extensionId Extension ID + * @param key Secret key identifier + * @param value Secret value + */ + suspend fun setPassword(extensionId: String, key: String, value: String) + + /** + * Deletes the secret. + * @param extensionId Extension ID + * @param key Secret key identifier + */ + suspend fun deletePassword(extensionId: String, key: String) +} + +/** + * Implementation of the secret state management service. + * Stores secrets in ~/.roo-cline/secrets.json file. + */ +class MainThreadSecretState : MainThreadSecretStateShape { + private val logger = Logger.getInstance(MainThreadSecretState::class.java) + private val gson = GsonBuilder().setPrettyPrinting().create() + private val mutex = Mutex() + + // Configuration file path + private val secretsDir = File(System.getProperty("user.home"), ".roo-cline") + private val secretsFile = File(secretsDir, "secrets.json") + + init { + // Ensure the directory exists + if (!secretsDir.exists()) { + secretsDir.mkdirs() + logger.info("Create secret storage directory: ${secretsDir.absolutePath}") + } + } + + override suspend fun getPassword(extensionId: String, key: String): String? = mutex.withLock { + try { + if (!secretsFile.exists()) { + return null + } + + val jsonContent = secretsFile.readText() + if (jsonContent.isBlank()) { + return null + } + + val jsonObject = JsonParser.parseString(jsonContent).asJsonObject + val extensionObject = jsonObject.getAsJsonObject(extensionId) ?: return null + val passwordElement = extensionObject.get(key) ?: return null + + return passwordElement.asString + } catch (e: Exception) { + logger.warn("Failed to get secret: extensionId=$extensionId, key=$key", e) + return null + } + } + + override suspend fun setPassword(extensionId: String, key: String, value: String) = mutex.withLock { + try { + val jsonObject = if (secretsFile.exists() && secretsFile.readText().isNotBlank()) { + JsonParser.parseString(secretsFile.readText()).asJsonObject + } else { + JsonObject() + } + + val extensionObject = jsonObject.getAsJsonObject(extensionId) ?: JsonObject().also { + jsonObject.add(extensionId, it) + } + + extensionObject.addProperty(key, value) + + val jsonString = gson.toJson(jsonObject) + secretsFile.writeText(jsonString) + + logger.info("Successfully set secret: extensionId=$extensionId, key=$key") + } catch (e: Exception) { + logger.error("Failed to set secret: extensionId=$extensionId, key=$key", e) + throw e + } + } + + override suspend fun deletePassword(extensionId: String, key: String) = mutex.withLock { + try { + if (!secretsFile.exists()) { + return + } + + val jsonContent = secretsFile.readText() + if (jsonContent.isBlank()) { + return + } + + val jsonObject = JsonParser.parseString(jsonContent).asJsonObject + val extensionObject = jsonObject.getAsJsonObject(extensionId) ?: return + + extensionObject.remove(key) + + // If extension object is empty, delete the entire extension + if (extensionObject.size() == 0) { + jsonObject.remove(extensionId) + } + + val jsonString = gson.toJson(jsonObject) + secretsFile.writeText(jsonString) + + logger.info("Successfully deleted secret: extensionId=$extensionId, key=$key") + } catch (e: Exception) { + logger.error("Failed to delete secret: extensionId=$extensionId, key=$key", e) + throw e + } + } + + override fun dispose() { + logger.info("Disposing MainThreadSecretState resources") + // JSON file storage doesn't require special resource disposal + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadStorageShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadStorageShape.kt new file mode 100644 index 0000000000..1daedaf6d9 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadStorageShape.kt @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.service.ExtensionStorageService + +/** + * Main thread storage service interface. + */ +interface MainThreadStorageShape : Disposable { + /** + * Initializes extension storage. + * @param shared Whether shared + * @param extensionId Extension ID + * @return Initialization result + */ + fun initializeExtensionStorage(shared: Boolean, extensionId: String): Any? + + /** + * Sets value. + * @param shared Whether shared + * @param extensionId Extension ID + * @param value Value object + * @return Set result + */ + fun setValue(shared: Boolean, extensionId: String, value: Any) + + /** + * Registers extension storage keys for synchronization. + * @param extension Extension ID and version + * @param keys List of keys + */ + fun registerExtensionStorageKeysToSync(extension: Any, keys: List) +} + +/** + * Implementation of the main thread storage service. + */ +class MainThreadStorage : MainThreadStorageShape { + private val logger = Logger.getInstance(MainThreadStorage::class.java) + + override fun initializeExtensionStorage(shared: Boolean, extensionId: String): Any? { + logger.info("Initializing extension storage: shared=$shared, extensionId=$extensionId") + val storage = service() + return storage.getValue(extensionId) + } + + override fun setValue(shared: Boolean, extensionId: String, value: Any) { +// logger.info("Setting value: shared=$shared, extensionId=$extensionId, value=$value") + val storage = service() + storage.setValue(extensionId, value) + } + + override fun registerExtensionStorageKeysToSync(extension: Any, keys: List) { + val extensionId = if (extension is Map<*, *>) { + "${extension["id"]}_${extension["version"]}" + } else { + "$extension" + } + logger.info("Registering extension storage keys for sync: extension=$extensionId, keys=$keys") + } + + override fun dispose() { + logger.info("Dispose MainThreadStorage") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTaskShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTaskShape.kt new file mode 100644 index 0000000000..aec26f1582 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTaskShape.kt @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.plugin.SystemObjectProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.future.future +import java.util.concurrent.CompletableFuture + +/** + * Main thread task service interface. + * Corresponds to the MainThreadTaskShape interface in VSCode. + */ +interface MainThreadTaskShape : Disposable { + /** + * Creates task ID. + * @param task Task DTO + * @return Task ID + */ + fun createTaskId(task: Map): String + + /** + * Registers task provider. + * @param handle Provider ID + * @param type Task type + */ + fun registerTaskProvider(handle: Int, type: String) + + /** + * Unregisters task provider. + * @param handle Provider ID + */ + fun unregisterTaskProvider(handle: Int) + + /** + * Fetches task list. + * @param filter Task filter + * @return Task list + */ + fun fetchTasks(filter: Map?): List> + + /** + * Gets task execution instance. + * @param value Task handle or task DTO + * @return Task execution DTO + */ + fun getTaskExecution(value: Map): Map + + /** + * Executes task. + * @param task Task handle or task DTO + * @return Task execution DTO + */ + fun executeTask(task: Map): Map + + /** + * Terminates task. + * @param id Task ID + */ + fun terminateTask(id: String) + + /** + * Registers task system. + * @param scheme Scheme + * @param info Task system information + */ + fun registerTaskSystem(scheme: String, info: Map) + + /** + * Custom execution complete. + * @param id Task ID + * @param result Execution result + */ + fun customExecutionComplete(id: String, result: Int?) + + /** + * Registers supported execution types. + * @param custom Whether supports custom execution + * @param shell Whether supports shell execution + * @param process Whether supports process execution + */ + fun registerSupportedExecutions(custom: Boolean?, shell: Boolean?, process: Boolean?) +} + +/** + * Implementation of the main thread task service. + * Provides task-related functionality for the IDEA platform. + */ +class MainThreadTask : MainThreadTaskShape { + private val logger = Logger.getInstance(MainThreadTask::class.java) + private val taskProviders = mutableMapOf() + private val taskExecutions = mutableMapOf>() + + override fun createTaskId(task: Map):String { + try { + logger.info("Creating task ID for task: $task") + val id = "task-${System.currentTimeMillis()}-${task.hashCode()}" + logger.debug("Generated task ID: $id") + return id + } catch (e: Exception) { + logger.error("Failed to create task ID", e) + throw e + } + } + + override fun registerTaskProvider(handle: Int, type: String) { + try { + logger.info("Registering task provider: handle=$handle, type=$type") + taskProviders[handle] = type + } catch (e: Exception) { + logger.error("Failed to register task provider", e) + } + } + + override fun unregisterTaskProvider(handle: Int) { + try { + logger.info("Unregistering task provider: handle=$handle") + taskProviders.remove(handle) + } catch (e: Exception) { + logger.error("Failed to unregister task provider", e) + } + } + + override fun fetchTasks(filter: Map?): List> { + try { + logger.info("Fetching tasks with filter: $filter") + // TODO: Actual implementation should query IDEA's task system + return emptyList() + } catch (e: Exception) { + logger.error("Failed to get tasks", e) + throw e + } + } + + override fun getTaskExecution(value: Map): Map { + try { + val taskId = value["id"] as? String ?: value["taskId"] as? String + logger.info("Getting task execution for task: $taskId") + + // Create a simple task execution DTO + return mapOf( + "id" to (taskId ?: "unknown-task"), + "task" to value, + "active" to false + ) + } catch (e: Exception) { + logger.error("Failed to get task execution", e) + throw e + } + } + + override fun executeTask(task: Map):Map { + try { + val taskId = task["id"] as? String ?: task["taskId"] as? String ?: "unknown-task" + logger.info("Executing task: $taskId") + + // Create an executing task execution DTO + val execution = mapOf( + "id" to taskId, + "task" to task, + "active" to true + ) + + // Store task execution information + taskExecutions[taskId] = execution + return execution + } catch (e: Exception) { + logger.error("Failed to execute task", e) + throw e + } + } + + override fun terminateTask(id: String) { + try { + logger.info("Terminating task: $id") + taskExecutions.remove(id) + } catch (e: Exception) { + logger.error("Failed to terminate task", e) + } + } + + override fun registerTaskSystem(scheme: String, info: Map) { + try { + logger.info("Registering task system: scheme=$scheme, info=$info") + // Register task system + } catch (e: Exception) { + logger.error("Failed to register task system", e) + } + } + + override fun customExecutionComplete(id: String, result: Int?) { + try { + logger.info("Custom execution complete for task: $id with result: $result") + // Update task execution status + taskExecutions[id]?.let { execution -> + taskExecutions[id] = execution + ("active" to false) + } + } catch (e: Exception) { + logger.error("Failed to update custom execution completion status", e) + } + } + + override fun registerSupportedExecutions(custom: Boolean?, shell: Boolean?, process: Boolean?) { + try { + logger.info("Registering supported executions: custom=$custom, shell=$shell, process=$process") + } catch (e: Exception) { + logger.error("Failed to register supported execution types", e) + } + } + + override fun dispose() { + logger.info("Disposing MainThreadTask") + taskProviders.clear() + taskExecutions.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTelemetryShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTelemetryShape.kt new file mode 100644 index 0000000000..4c3e7d4ee2 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTelemetryShape.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger + +/** + * Main thread telemetry service interface. + */ +interface MainThreadTelemetryShape : Disposable { + /** + * Logs public event. + * @param eventName Event name + * @param data Event data + */ + fun publicLog(eventName: String, data: Any?) + + /** + * Logs public event (supports categorized events). + * @param eventName Event name + * @param data Event data + */ + fun publicLog2(eventName: String, data: Any?) +} + +class MainThreadTelemetry : MainThreadTelemetryShape { + private val logger = Logger.getInstance(MainThreadTelemetry::class.java) + + override fun publicLog(eventName: String, data: Any?) { + logger.info("[Telemetry] $eventName: $data") + } + + override fun publicLog2(eventName: String, data: Any?) { + logger.info("[Telemetry] $eventName: $data") + } + + override fun dispose() { + logger.info("Dispose MainThreadTelemetry") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalServiceShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalServiceShape.kt new file mode 100644 index 0000000000..c2acebff76 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalServiceShape.kt @@ -0,0 +1,442 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.terminal.TerminalInstance +import ai.kilocode.jetbrains.terminal.TerminalInstanceManager +import ai.kilocode.jetbrains.terminal.TerminalConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel + + +/** + * Main thread terminal service interface. + * Corresponds to the MainThreadTerminalServiceShape interface in VSCode. + */ +interface MainThreadTerminalServiceShape : Disposable { + /** + * Creates terminal. + * @param extHostTerminalId Extension host terminal ID + * @param config Terminal launch configuration + */ + suspend fun createTerminal(extHostTerminalId: String, config: Map) + + /** + * Disposes terminal resources. + * @param id Terminal identifier (can be String or Number) + */ + fun dispose(id: Any) + + /** + * Hides terminal. + * @param id Terminal identifier (can be String or Number) + */ + fun hide(id: Any) + + /** + * Sends text to terminal. + * @param id Terminal identifier (can be String or Number) + * @param text Text to send + * @param shouldExecute Whether to execute + */ + fun sendText(id: Any, text: String, shouldExecute: Boolean?) + + /** + * Shows terminal. + * @param id Terminal identifier (can be String or Number) + * @param preserveFocus Whether to preserve focus + */ + fun show(id: Any, preserveFocus: Boolean?) + + /** + * Registers process support. + * @param isSupported Whether supported + */ + fun registerProcessSupport(isSupported: Boolean) + + /** + * Registers profile provider. + * @param id Profile provider ID + * @param extensionIdentifier Extension identifier + */ + fun registerProfileProvider(id: String, extensionIdentifier: String) + + /** + * Unregisters profile provider. + * @param id Profile provider ID + */ + fun unregisterProfileProvider(id: String) + + /** + * Registers completion provider. + * @param id Completion provider ID + * @param extensionIdentifier Extension identifier + * @param triggerCharacters List of trigger characters + */ + fun registerCompletionProvider(id: String, extensionIdentifier: String, vararg triggerCharacters: String) + + /** + * Unregisters completion provider. + * @param id Completion provider ID + */ + fun unregisterCompletionProvider(id: String) + + /** + * Registers quick fix provider. + * @param id Quick fix provider ID + * @param extensionIdentifier Extension identifier + */ + fun registerQuickFixProvider(id: String, extensionIdentifier: String) + + /** + * Unregisters quick fix provider. + * @param id Quick fix provider ID + */ + fun unregisterQuickFixProvider(id: String) + + /** + * Set environment variable collection + * @param extensionIdentifier Extension identifier + * @param persistent Whether to persist + * @param collection Serializable environment variable collection + * @param descriptionMap Serializable environment description mapping + */ + fun setEnvironmentVariableCollection( + extensionIdentifier: String, + persistent: Boolean, + collection: Map?, + descriptionMap: Map + ) + + /** + * Start sending data events + */ + fun startSendingDataEvents() + + /** + * Stop sending data events + */ + fun stopSendingDataEvents() + + /** + * Start sending command events + */ + fun startSendingCommandEvents() + + /** + * Stop sending command events + */ + fun stopSendingCommandEvents() + + /** + * Start link provider + */ + fun startLinkProvider() + + /** + * Stop link provider + */ + fun stopLinkProvider() + + /** + * Send process data + * @param terminalId Terminal ID + * @param data Data + */ + fun sendProcessData(terminalId: Int, data: String) + + /** + * Send process ready + * @param terminalId Terminal ID + * @param pid Process ID + * @param cwd Current working directory + * @param windowsPty Windows PTY information + */ + fun sendProcessReady( + terminalId: Int, + pid: Int, + cwd: String, + windowsPty: Map? + ) + + /** + * Send process property + * @param terminalId Terminal ID + * @param property Process property + */ + fun sendProcessProperty(terminalId: Int, property: Map) + + /** + * Send process exit + * @param terminalId Terminal ID + * @param exitCode Exit code + */ + fun sendProcessExit(terminalId: Int, exitCode: Int?) +} + +/** + * Main thread terminal service implementation class + * Provides implementation of IDEA platform terminal-related functionality + */ +class MainThreadTerminalService(private val project: Project) : MainThreadTerminalServiceShape { + private val logger = Logger.getInstance(MainThreadTerminalService::class.java) + + // Use terminal instance manager + private val terminalManager = project.service() + + // Coroutine scope - use IO dispatcher to avoid Main Dispatcher issues + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + override suspend fun createTerminal(extHostTerminalId: String, config: Map) { + logger.info("🚀 Creating terminal: $extHostTerminalId, config: $config") + + try { + // Check if terminal already exists + if (terminalManager.containsTerminal(extHostTerminalId)) { + logger.warn("Terminal already exists: $extHostTerminalId") + return + } + + // Get RPC protocol instance + val pluginContext = PluginContext.getInstance(project) + val rpcProtocol = pluginContext.getRPCProtocol() + if (rpcProtocol == null) { + logger.error("❌ Unable to get RPC protocol instance, terminal creation failed: $extHostTerminalId") + throw IllegalStateException("RPC protocol not initialized") + } + logger.info("✅ Got RPC protocol instance: ${rpcProtocol.javaClass.simpleName}") + + // Allocate numeric ID + val numericId = terminalManager.allocateNumericId() + logger.info("🔢 Allocated terminal numeric ID: $numericId") + + // Create terminal instance + val terminalConfig = TerminalConfig.fromMap(config) + val terminalInstance = TerminalInstance(extHostTerminalId, numericId, project, terminalConfig, rpcProtocol) + + // Initialize terminal + terminalInstance.initialize() + + // Register to manager + terminalManager.registerTerminal(extHostTerminalId, terminalInstance) + + logger.info("✅ Terminal created successfully: $extHostTerminalId (numericId: $numericId)") + + } catch (e: Exception) { + logger.error("❌ Failed to create terminal: $extHostTerminalId", e) + // Clean up possibly created resources + terminalManager.unregisterTerminal(extHostTerminalId) + throw e + } + } + + override fun dispose(id: Any) { + try { + logger.info("🧹 Destroying terminal: $id") + + val terminalInstance = terminalManager.unregisterTerminal(id.toString()) + if (terminalInstance != null) { + terminalInstance.dispose() + logger.info("✅ Terminal destroyed: $id") + } else { + logger.warn("Terminal does not exist: $id") + } + + } catch (e: Exception) { + logger.error("❌ Failed to destroy terminal: $id", e) + } + } + + override fun hide(id: Any) { + try { + logger.info("🙈 Hiding terminal: $id") + + val terminalInstance = getTerminalInstance(id) + if (terminalInstance != null) { + terminalInstance.hide() + logger.info("✅ Terminal hidden: $id") + } else { + logger.warn("Terminal does not exist: $id") + } + + } catch (e: Exception) { + logger.error("❌ Failed to hide terminal: $id", e) + } + } + + override fun sendText(id: Any, text: String, shouldExecute: Boolean?) { + try { + logger.debug("📤 Sending text to terminal $id: $text (execute: $shouldExecute)") + + val terminalInstance = getTerminalInstance(id) + if (terminalInstance != null) { + terminalInstance.sendText(text, shouldExecute ?: false) + logger.debug("✅ Text sent to terminal: $id") + } else { + logger.warn("Terminal does not exist: $id") + } + + } catch (e: Exception) { + logger.error("❌ Failed to send text to terminal: $id", e) + } + } + + override fun show(id: Any, preserveFocus: Boolean?) { + try { + logger.info("👁️ Showing terminal: $id (preserve focus: $preserveFocus)") + + val terminalInstance = getTerminalInstance(id) + if (terminalInstance != null) { + terminalInstance.show(preserveFocus ?: true) + logger.info("✅ Terminal shown: $id") + } else { + logger.warn("Terminal does not exist: $id") + } + + } catch (e: Exception) { + logger.error("❌ Failed to show terminal: $id", e) + } + } + + override fun registerProcessSupport(isSupported: Boolean) { + logger.info("📋 Registering process support: $isSupported") + // In IDEA, process support is built-in, mainly used for logging state here + } + + override fun registerProfileProvider(id: String, extensionIdentifier: String) { + logger.info("📋 Registering profile provider: $id (extension: $extensionIdentifier)") + // TODO: Implement profile provider registration logic + } + + override fun unregisterProfileProvider(id: String) { + logger.info("📋 Unregistering profile provider: $id") + // TODO: Implement profile provider unregistration logic + } + + override fun registerCompletionProvider(id: String, extensionIdentifier: String, vararg triggerCharacters: String) { + logger.info("📋 Registering completion provider: $id (extension: $extensionIdentifier, trigger characters: ${triggerCharacters.joinToString()})") + // TODO: Implement completion provider registration logic + } + + override fun unregisterCompletionProvider(id: String) { + logger.info("📋 Unregistering completion provider: $id") + // TODO: Implement completion provider unregistration logic + } + + override fun registerQuickFixProvider(id: String, extensionIdentifier: String) { + logger.info("📋 Registering quick fix provider: $id (extension: $extensionIdentifier)") + // TODO: Implement quick fix provider registration logic + } + + override fun unregisterQuickFixProvider(id: String) { + logger.info("📋 Unregistering quick fix provider: $id") + // TODO: Implement quick fix provider unregistration logic + } + + override fun setEnvironmentVariableCollection( + extensionIdentifier: String, + persistent: Boolean, + collection: Map?, + descriptionMap: Map + ) { + logger.info("📋 Setting environment variable collection: $extensionIdentifier (persistent: $persistent)") + // TODO: Implement environment variable collection setting logic + } + + override fun startSendingDataEvents() { + logger.info("📋 Starting to send data events") + // TODO: Implement data event sending logic + } + + override fun stopSendingDataEvents() { + logger.info("📋 Stopping data event sending") + // TODO: Implement stopping data event sending logic + } + + override fun startSendingCommandEvents() { + logger.info("📋 Starting to send command events") + // TODO: Implement command event sending logic + } + + override fun stopSendingCommandEvents() { + logger.info("📋 Stopping command event sending") + // TODO: Implement stopping command event sending logic + } + + override fun startLinkProvider() { + logger.info("📋 Starting link provider") + // TODO: Implement link provider startup logic + } + + override fun stopLinkProvider() { + logger.info("📋 Stopping link provider") + // TODO: Implement link provider stopping logic + } + + override fun sendProcessData(terminalId: Int, data: String) { + logger.debug("Send process data to terminal $terminalId") + // Send process data to terminal + } + + override fun sendProcessReady(terminalId: Int, pid: Int, cwd: String, windowsPty: Map?) { + logger.info("Send process ready: terminal=$terminalId, pid=$pid, cwd=$cwd") + // Send process ready information + } + + override fun sendProcessProperty(terminalId: Int, property: Map) { + logger.debug("📋 Sending process property: terminal=$terminalId") + // TODO: Notify extension host of process property changes + } + + override fun sendProcessExit(terminalId: Int, exitCode: Int?) { + logger.info("📋 Sending process exit: terminal=$terminalId, exit code=$exitCode") + // TODO: Notify extension host of process exit + } + + /** + * Get terminal instance (by string ID or numeric ID) + */ + fun getTerminalInstance(id: Any): TerminalInstance? { + return when (id) { + is String -> terminalManager.getTerminalInstance(id) + is Number -> terminalManager.getTerminalInstance(id.toInt()) + else -> { + logger.warn("Unsupported ID type: ${id.javaClass.name}, attempting to convert to string") + terminalManager.getTerminalInstance(id.toString()) + } + } + } + + /** + * Get all terminal instances + */ + fun getAllTerminals(): Collection { + return terminalManager.getAllTerminals() + } + + override fun dispose() { + logger.info("🧹 Disposing main thread terminal service") + + try { + // Cancel coroutine scope + scope.cancel() + + // Terminal instance manager will automatically handle cleanup of all terminals + // No manual cleanup needed here as TerminalInstanceManager is project-level service + + logger.info("✅ Main thread terminal service disposed") + + } catch (e: Exception) { + logger.error("❌ Failed to dispose main thread terminal service", e) + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalShellIntegrationShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalShellIntegrationShape.kt new file mode 100644 index 0000000000..e33d855fcc --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTerminalShellIntegrationShape.kt @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.terminal.TerminalInstanceManager + +interface MainThreadTerminalShellIntegrationShape : Disposable { + fun executeCommand(terminalId: Int, commandLine: String) +} + +class MainThreadTerminalShellIntegration( + private val project: Project +) : MainThreadTerminalShellIntegrationShape { + private val logger = Logger.getInstance(MainThreadTerminalShellIntegration::class.java) + + private val terminalManager = project.service() + + override fun executeCommand(terminalId: Int, commandLine: String) { + logger.info("🚀 Executing Shell Integration command: terminalId=$terminalId, commandLine='$commandLine'") + + try { + // Get terminal instance by numeric ID + val terminalInstance = terminalManager.getTerminalInstance(terminalId) + + if (terminalInstance == null) { + logger.warn("❌ Terminal instance not found: terminalId=$terminalId") + return + } + + logger.info("✅ Found terminal instance: ${terminalInstance.extHostTerminalId}") + + // Execute command in terminal + terminalInstance.sendText(commandLine, shouldExecute = true) + + logger.info("✅ Command sent to terminal: terminalId=$terminalId, command='$commandLine'") + + } catch (e: Exception) { + logger.error("❌ Failed to execute Shell Integration command: terminalId=$terminalId, command='$commandLine'", e) + } + } + + override fun dispose() { + logger.info("🧹 Destroying MainThreadTerminalShellIntegration") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTextEditorsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTextEditorsShape.kt new file mode 100644 index 0000000000..7fe8fc7aa7 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadTextEditorsShape.kt @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import ai.kilocode.jetbrains.editor.EditorAndDocManager +import ai.kilocode.jetbrains.editor.Range +import ai.kilocode.jetbrains.editor.createURI +import kotlinx.coroutines.delay +import java.io.File + +/** + * Main thread text editor interface + */ +interface MainThreadTextEditorsShape : Disposable { + /** + * Try to show text document + * @param resource Resource URI + * @param options Display options + * @return Editor ID or null + */ + suspend fun tryShowTextDocument(resource: Map, options: Any?): Any? + + /** + * Register text editor decoration type + * @param extensionId Extension ID + * @param key Decoration type key + * @param options Decoration rendering options + */ + fun registerTextEditorDecorationType(extensionId: Map, key: String, options: Any) + + /** + * Remove text editor decoration type + * @param key Decoration type key + */ + fun removeTextEditorDecorationType(key: String) + + /** + * Try to show editor + * @param id Editor ID + * @param position Position + * @return Operation result + */ + fun tryShowEditor(id: String, position: Any?): Any + + /** + * Try to hide editor + * @param id Editor ID + * @return Operation result + */ + fun tryHideEditor(id: String): Any + + /** + * Try to set options + * @param id Editor ID + * @param options Configuration updates + * @return Operation result + */ + fun trySetOptions(id: String, options: Any): Any + + /** + * Try to set decorations + * @param id Editor ID + * @param key Decoration type key + * @param ranges Decoration ranges + * @return Operation result + */ + fun trySetDecorations(id: String, key: String, ranges: List): Any + + /** + * Try to quickly set decorations + * @param id Editor ID + * @param key Decoration type key + * @param ranges Decoration ranges array + * @return Operation result + */ + fun trySetDecorationsFast(id: String, key: String, ranges: List): Any + + /** + * Try to reveal range + * @param id Editor ID + * @param range Display range + * @param revealType Display type + * @return Operation result + */ + fun tryRevealRange(id: String, range: Map, revealType: Int): Any + + /** + * Try to set selections + * @param id Editor ID + * @param selections Selections array + * @return Operation result + */ + fun trySetSelections(id: String, selections: List): Any + + /** + * Try to apply edits + * @param id Editor ID + * @param modelVersionId Model version ID + * @param edits Edit operations + * @param opts Apply options + * @return Whether successful + */ + fun tryApplyEdits(id: String, modelVersionId: Int, edits: List, opts: Any?): Boolean + + /** + * Try to insert snippet + * @param id Editor ID + * @param modelVersionId Model version ID + * @param template Code snippet template + * @param selections Selection ranges + * @param opts Undo options + * @return Whether successful + */ + fun tryInsertSnippet(id: String, modelVersionId: Int, template: String, selections: List, opts: Any?): Boolean + + /** + * Get diff information + * @param id Editor ID + * @return Diff information + */ + fun getDiffInformation(id: String): Any? +} + +/** + * Main thread text editor implementation + */ +class MainThreadTextEditors(var project: Project) : MainThreadTextEditorsShape { + private val logger = Logger.getInstance(MainThreadTextEditors::class.java) + + override suspend fun tryShowTextDocument(resource: Map, options: Any?): Any? { + logger.info("Trying to show text document: resource=$resource, options=$options") + val path = resource["path"] as String? ?: "" + + val vfs = LocalFileSystem.getInstance() + vfs.refreshIoFiles(listOf(File(path))) + val resourceURI = createURI(resource) + val editorHandle = project.getService(EditorAndDocManager::class.java).openEditor(resourceURI) + logger.info("Trying to show text document: resource=$resource execution completed" ) + return editorHandle.id + } + + override fun registerTextEditorDecorationType(extensionId: Map, key: String, options: Any) { + logger.info("Registering text editor decoration type: extensionId=$extensionId, key=$key, options=$options") + } + + override fun removeTextEditorDecorationType(key: String) { + logger.info("Removing text editor decoration type: $key") + } + + override fun tryShowEditor(id: String, position: Any?): Any { + logger.info("Trying to show editor: id=$id, position=$position") + return Unit + } + + override fun tryHideEditor(id: String): Any { + logger.info("Trying to hide editor: $id") + return Unit + } + + override fun trySetOptions(id: String, options: Any): Any { + logger.info("Try to set options: id=$id, options=$options") + return Unit + } + + override fun trySetDecorations(id: String, key: String, ranges: List): Any { + logger.info("Try to set decorations: id=$id, key=$key, ranges=${ranges.size}") + return Unit + } + + override fun trySetDecorationsFast(id: String, key: String, ranges: List): Any { + logger.info("Try to quickly set decorations: id=$id, key=$key, ranges=${ranges.size}") + return Unit + } + + override fun tryRevealRange(id: String, range: Map, revealType: Int): Any { + logger.info("Try to reveal range: id=$id, range=$range, revealType=$revealType") + val handle = project.getService(EditorAndDocManager::class.java).getEditorHandleById(id) + handle?.let { + val rang = createRanges(range) + handle.revealRange(rang) + } + return Unit + } + + private fun createRanges(range: Map): Range { + val startLineNumber = (range["startLineNumber"] as? Number)?.toInt() ?: 0 + val startColumn = (range["startColumn"] as? Number)?.toInt() ?: 0 + val endLineNumber = (range["endLineNumber"] as? Number)?.toInt() ?: startLineNumber + val endColumn = (range["endColumn"] as? Number)?.toInt() ?: startColumn + return Range(startLineNumber, startColumn, endLineNumber, endColumn) + } + + override fun trySetSelections(id: String, selections: List): Any { + logger.info("Try to set selections: id=$id, selections=$selections") + return Unit + } + + override fun tryApplyEdits(id: String, modelVersionId: Int, edits: List, opts: Any?): Boolean { + logger.info("Try to apply edits: id=$id, modelVersionId=$modelVersionId, edits=$edits, opts=$opts") + return true + } + + override fun tryInsertSnippet(id: String, modelVersionId: Int, template: String, selections: List, opts: Any?): Boolean { + logger.info("Try to insert snippet: id=$id, modelVersionId=$modelVersionId, template=$template, selections=$selections, opts=$opts") + return true + } + + override fun getDiffInformation(id: String): Any? { + logger.info("Get diff information: $id") + return null + } + + override fun dispose() { + logger.info("Dispose MainThreadTextEditors") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadUrlsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadUrlsShape.kt new file mode 100644 index 0000000000..4c5877f8ba --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadUrlsShape.kt @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.CompletableDeferred + +/** + * URL handling related interface + */ +interface MainThreadUrlsShape : Disposable { + /** + * Register URI handler + * @param handle Handler identifier + * @param extensionId Extension ID + * @param extensionDisplayName Extension display name + * @return Execution result + */ + suspend fun registerUriHandler(handle: Int, extensionId: Map, extensionDisplayName: String): Any + + /** + * Unregister URI handler + * @param handle Handler identifier + * @return Execution result + */ + suspend fun unregisterUriHandler(handle: Int): Any + + /** + * Create application URI + * @param uri URI components + * @return Created URI components + */ + suspend fun createAppUri(uri: Map): Map +} + +class MainThreadUrls : MainThreadUrlsShape { + private val logger = Logger.getInstance(MainThreadUrls::class.java) + + override suspend fun registerUriHandler(handle: Int, extensionId: Map, extensionDisplayName: String): Any { + logger.info("Registering URI handler: handle=$handle, extensionId=$extensionId, displayName=$extensionDisplayName") + return CompletableDeferred().also { it.complete(Unit) }.await() + } + + override suspend fun unregisterUriHandler(handle: Int): Any { + logger.info("Unregistering URI handler: handle=$handle") + return CompletableDeferred().also { it.complete(Unit) }.await() + } + + override suspend fun createAppUri(uri: Map): Map { + logger.info("Creating application URI: uri=$uri") + // Simply return original URI + return uri + } + + override fun dispose() { + logger.info("Disposing MainThreadUrls resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewViewsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewViewsShape.kt new file mode 100644 index 0000000000..53d8542a25 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewViewsShape.kt @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.events.WebviewViewProviderData +import ai.kilocode.jetbrains.webview.WebViewManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers + +/** + * Webview view related interface + */ +interface MainThreadWebviewViewsShape : Disposable { + /** + * Register Webview view provider + * @param extension Webview extension description + * @param viewType View type + * @param options Option configuration + */ + fun registerWebviewViewProvider( + extension: Map, + viewType: String, + options: Map + ) + + /** + * Unregister Webview view provider + * @param viewType View type + */ + fun unregisterWebviewViewProvider(viewType: String) + + /** + * Set Webview view title + * @param handle Webview handle + * @param value Title value + */ + fun setWebviewViewTitle(handle: String, value: String?) + + /** + * Set Webview view description + * @param handle Webview handle + * @param value Description content + */ + fun setWebviewViewDescription(handle: String, value: String?) + + /** + * Set Webview view badge + * @param handle Webview handle + * @param badge Badge information + */ + fun setWebviewViewBadge(handle: String, badge: Map?) + + /** + * Show Webview view + * @param handle Webview handle + * @param preserveFocus Whether to preserve focus + */ + fun show(handle: String, preserveFocus: Boolean) +} + +class MainThreadWebviewViews(val project: Project) : MainThreadWebviewViewsShape { + private val logger = Logger.getInstance(MainThreadWebviewViews::class.java) + private val coroutineScope = CoroutineScope(Dispatchers.Default) + + override fun registerWebviewViewProvider( + extension: Map, + viewType: String, + options: Map + ) { + logger.info("Registering Webview view provider: viewType=$viewType, options=$options") + +// Use EventBus to send WebView view provider registration event, using IntelliJ platform compatible method +// project.getService(ProjectEventBus::class.java).emitInApplication( +// WebviewViewProviderRegisterEvent, +// WebviewViewProviderData(extension, viewType, options) +// ) + project.getService(WebViewManager::class.java).registerProvider(WebviewViewProviderData(extension, viewType, options)) + } + + override fun unregisterWebviewViewProvider(viewType: String) { + logger.info("Unregistering Webview view provider: viewType=$viewType") + } + + override fun setWebviewViewTitle(handle: String, value: String?) { + logger.info("Setting Webview view title: handle=$handle, title=$value") + } + + override fun setWebviewViewDescription(handle: String, value: String?) { + logger.info("Setting Webview view description: handle=$handle, description=$value") + } + + override fun setWebviewViewBadge(handle: String, badge: Map?) { + logger.info("Setting Webview view badge: handle=$handle, badge=$badge") + } + + override fun show(handle: String, preserveFocus: Boolean) { + logger.info("Showing Webview view: handle=$handle, preserveFocus=$preserveFocus") + } + + override fun dispose() { + logger.info("Disposing MainThreadWebviewViews resources") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewsShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewsShape.kt new file mode 100644 index 0000000000..d0b4c33f36 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWebviewsShape.kt @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import ai.kilocode.jetbrains.events.EventBus +import ai.kilocode.jetbrains.events.ProjectEventBus +import ai.kilocode.jetbrains.events.WebviewHtmlUpdateData +import ai.kilocode.jetbrains.events.WebviewHtmlUpdateEvent +import ai.kilocode.jetbrains.webview.WebViewManager +import java.util.concurrent.ConcurrentHashMap + +/** + * Webview handle type + * Corresponds to WebviewHandle type in TypeScript + */ +typealias WebviewHandle = String + +/** + * Main thread Webviews service interface + * Corresponds to MainThreadWebviewsShape interface in VSCode + */ +interface MainThreadWebviewsShape : Disposable { + /** + * Set HTML content + * Corresponds to $setHtml method in TypeScript interface + * @param handle Webview handle + * @param value HTML content + */ + fun setHtml(handle: WebviewHandle, value: String) + + /** + * Set Webview options + * Corresponds to $setOptions method in TypeScript interface + * @param handle Webview handle + * @param options Webview content options + */ + fun setOptions(handle: WebviewHandle, options: Map) + + /** + * Send message to Webview + * Corresponds to $postMessage method in TypeScript interface + * @param handle Webview handle + * @param value Message content + * @param buffers Binary buffer array + * @return Whether operation succeeded + */ + fun postMessage(handle: WebviewHandle, value: String): Boolean +} + +/** + * Main thread Webviews service implementation class + */ +class MainThreadWebviews(val project: Project) : MainThreadWebviewsShape { + private val logger = Logger.getInstance(MainThreadWebviews::class.java) + + // Store registered Webviews + private val webviews = ConcurrentHashMap() + private var webviewHandle : WebviewHandle = "" + + override fun setHtml(handle: WebviewHandle, value: String) { + logger.info("Setting Webview HTML: handle=$handle, length=${value.length}") + webviewHandle = handle + try { + // Replace vscode-file protocol format, using regex to match from vscode-file:/ to /kilocode/ part + val modifiedHtml = value.replace(Regex("vscode-file:/.*?/kilocode/"), "/") + logger.info("Replaced vscode-file protocol path format") + + // Send HTML content update event through EventBus + val data = WebviewHtmlUpdateData(handle, modifiedHtml) +// project.getService(ProjectEventBus::class.java).emitInApplication(WebviewHtmlUpdateEvent, data) + project.getService(WebViewManager::class.java).updateWebViewHtml(data) + logger.info("Sent HTML content update event: handle=$handle") + } catch (e: Exception) { + logger.error("Failed to set Webview HTML", e) + } + } + + override fun setOptions(handle: WebviewHandle, options: Map) { + logger.info("Setting Webview options: handle=$handle, options=$options") + webviewHandle = handle + try { + // Actual implementation should set options for Webview component on IDEA platform + // Here we just log + } catch (e: Exception) { + logger.error("Failed to set Webview options: $e") + } + } + + override fun postMessage(handle: WebviewHandle, value: String): Boolean { +// logger.info("Sending message to Webview: handle=$handle") + if(value.contains("theme")) { + logger.info("Sending theme message to Webview") + } + + return try { + val mangler = project.getService(WebViewManager::class.java) + +// mangler.getWebView(handle)?.postMessageToWebView(value) + mangler.getLatestWebView()?.postMessageToWebView(value) + true + } catch (e: Exception) { + logger.error("Failed to send message to Webview: $e") + false + } + } + + override fun dispose() { + logger.info("Disposing MainThreadWebviews resources") + webviews.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWindowShape.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWindowShape.kt new file mode 100644 index 0000000000..527f0a76e0 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/actors/MainThreadWindowShape.kt @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.actors + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.WindowManager +import ai.kilocode.jetbrains.plugin.SystemObjectProvider +import ai.kilocode.jetbrains.plugin.WecoderPluginService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.future.future +import java.awt.Desktop +import java.net.URI +import java.util.concurrent.CompletableFuture + +/** + * Main thread window service interface + * Corresponds to MainThreadWindowShape interface in VSCode + */ +interface MainThreadWindowShape : Disposable { + /** + * Get initial state + * @return Initial window state including focus and active status + */ + fun getInitialState(): Map + + /** + * Open URI + * @param uri URI component + * @param uriString URI string + * @param options Open options + * @return Whether successfully opened + */ + fun openUri(uri: Map, uriString: String?, options: Map): Boolean + + /** + * Convert to external URI + * @param uri URI component + * @param options Open options + * @return External URI component + */ + fun asExternalUri(uri: Map, options: Map): Map +} + +/** + * Main thread window service implementation + * Provides IDEA platform window related functionality + */ +class MainThreadWindow(val project: Project) : MainThreadWindowShape { + private val logger = Logger.getInstance(MainThreadWindow::class.java) + + override fun getInitialState(): Map { + try { + logger.info("Getting window initial state") + + if (project != null) { + // Get current project window state + val frame = WindowManager.getInstance().getFrame(project) + val isFocused = frame?.isFocused ?: false + val isActive = frame?.isActive ?: false + + return mapOf( + "isFocused" to isFocused, + "isActive" to isActive + ) + } else { + logger.warn("Cannot get current project, returning default window state") + return mapOf( + "isFocused" to false, + "isActive" to false + ) + } + } catch (e: Exception) { + logger.error("Failed to get window initial state", e) + return mapOf( + "isFocused" to false, + "isActive" to false + ) + } + } + + override fun openUri(uri: Map, uriString: String?, options: Map): Boolean { + try { + logger.info("Opening URI: $uriString") + + // Try to get URI + val actualUri = if (uriString != null) { + try { + URI(uriString) + } catch (e: Exception) { + // If URI string is invalid, try to build from URI components + createUriFromComponents(uri) + } + } else { + createUriFromComponents(uri) + } + + return if (actualUri != null) { + // Check if Desktop operation is supported + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(actualUri) + true + } else { + logger.warn("System does not support opening URI") + false + } + } else { + logger.warn("Cannot create valid URI") + false + } + } catch (e: Exception) { + logger.error("Failed to open URI", e) + return false + } + } + + override fun asExternalUri(uri: Map, options: Map): Map { + return try { + logger.info("Converting to external URI: $uri") + + // For most cases, we directly return the same URI components + // Actual implementation may need to handle specific protocol conversion + uri + } catch (e: Exception) { + logger.error("Failed to convert to external URI", e) + uri // Return original URI on error + } + } + + /** + * Create URI from URI components + * @param components URI components + * @return Created URI or null + */ + private fun createUriFromComponents(components: Map): URI? { + return try { + val scheme = components["scheme"] as? String ?: return null + val authority = components["authority"] as? String ?: "" + val path = components["path"] as? String ?: "" + val query = components["query"] as? String ?: "" + val fragment = components["fragment"] as? String ?: "" + + URI(scheme, authority, path, query, fragment) + } catch (e: Exception) { + logger.warn("Failed to create URI from components: $components", e) + null + } + } + + override fun dispose() { + logger.info("Disposing MainThreadWindow") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/Commands.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/Commands.kt new file mode 100644 index 0000000000..d8e3875608 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/Commands.kt @@ -0,0 +1,143 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.commands + +import com.intellij.openapi.project.Project +/** + * Interface representing a command in the system. + * Commands are used to define executable actions that can be registered and invoked. + */ +interface ICommand { + /** + * Gets the unique identifier for this command. + * @return The command ID as a string + */ + fun getId(): String + + /** + * Gets the method name that should be invoked when this command is executed. + * @return The method name as a string + */ + fun getMethod(): String + + /** + * Gets the handler object that contains the method to be invoked. + * @return The handler object + */ + fun handler(): Any + + /** + * Gets the return type of the command, if any. + * @return The return type as a string, or null if the command doesn't return a value + */ + fun returns(): String? +} + +/** + * Interface for a registry that manages commands. + * Provides functionality to register, retrieve, and manage commands in the system. + */ +interface ICommandRegistry { + /** + * Called when a command is registered. + * @param id The ID of the registered command + */ + fun onDidRegisterCommand(id: String) + + /** + * Registers a command in the registry. + * @param command The command to register + */ + fun registerCommand(command: ICommand) + + /** + * Registers an alias for an existing command. + * @param oldId The ID of the existing command + * @param newId The new alias ID for the command + */ + fun registerCommandAlias(oldId: String, newId: String) + + /** + * Gets a command by its ID. + * @param id The ID of the command to retrieve + * @return The command, or null if not found + */ + fun getCommand(id: String): ICommand? + + /** + * Gets all registered commands. + * @return A map of command IDs to commands + */ + fun getCommands(): Map +} + +/** + * Implementation of the ICommandRegistry interface. + * Manages commands for a specific project. + * + * @property project The project context for this command registry + */ +class CommandRegistry(val project: Project) : ICommandRegistry { + + /** + * Map of command IDs to lists of commands. + * Using a list allows for potential command overloading in the future. + */ + private val commands = mutableMapOf>() + + /** + * Called when a command is registered. + * Currently not implemented. + * + * @param id The ID of the registered command + */ + override fun onDidRegisterCommand(id: String) { + TODO("Not yet implemented") + } + + /** + * Registers a command in the registry. + * + * @param command The command to register + */ + override fun registerCommand(command: ICommand) { + commands.put(command.getId(), mutableListOf(command)) + } + + /** + * Registers an alias for an existing command. + * If the original command exists, creates a new entry with the new ID pointing to the same command. + * + * @param oldId The ID of the existing command + * @param newId The new alias ID for the command + */ + override fun registerCommandAlias(oldId: String, newId: String) { + getCommand(oldId)?.let { + commands.put(newId, mutableListOf(it)) + } + } + + /** + * Gets a command by its ID. + * Returns the first command registered with the given ID, or null if not found. + * + * @param id The ID of the command to retrieve + * @return The command, or null if not found + */ + override fun getCommand(id: String): ICommand? { + return commands[id]?.firstOrNull() + } + + /** + * Gets all registered commands. + * Returns a map of command IDs to the first command registered with each ID. + * + * @return A map of command IDs to commands + */ + override fun getCommands(): Map { + return commands.mapValues { it.value.first() } + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/KiloCodeAuthProtocolCommand.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/KiloCodeAuthProtocolCommand.kt new file mode 100644 index 0000000000..3bd48ac893 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/commands/KiloCodeAuthProtocolCommand.kt @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.commands + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.JBProtocolCommand +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import java.net.URI +import java.net.URLDecoder +import java.nio.charset.StandardCharsets + +/** + * JetBrains Protocol Command for handling Kilo Code authentication URLs + * + * Handles URLs in the format: jetbrains://idea/ai.kilocode.jetbrains.auth?token=HERE + * and forwards them to the VSCode extension via RPC protocol + */ +class KiloCodeAuthProtocolCommand : JBProtocolCommand("ai.kilocode.jetbrains.auth") { + private val logger = Logger.getInstance(KiloCodeAuthProtocolCommand::class.java) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + companion object { + const val COMMAND_ID = "ai.kilocode.jetbrains.auth" + const val TOKEN_PARAM = "token" + } + + /** + * Public method for testing the protocol command execution + * @param target The target parameter from the URL + * @param parameters Map of URL parameters + * @param fragment The URL fragment + * @return null on success, error message on failure + */ + suspend fun executeForTesting(target: String?, parameters: Map, fragment: String?): String? { + return execute(target, parameters, fragment) + } + + /** + * Handle the protocol command + * @param target The target parameter from the URL + * @param parameters Map of URL parameters + * @param fragment The URL fragment + * @return null on success, error message on failure + */ + override suspend fun execute(target: String?, parameters: Map, fragment: String?): String? { + logger.info("Handling Kilo Code auth protocol command: target=$target, parameters=$parameters") + + return try { + // Extract token from parameters + val token = parameters[TOKEN_PARAM] + if (token.isNullOrBlank()) { + val errorMsg = "No token found in parameters: $parameters" + logger.warn(errorMsg) + return errorMsg + } + + logger.info("Extracted token from parameters, forwarding to VSCode extension") + + // Forward to VSCode extension via RPC + forwardTokenToVSCodeExtension(token) + + null // Success + } catch (e: Exception) { + val errorMsg = "Error handling Kilo Code auth protocol command: ${e.message}" + logger.error(errorMsg, e) + errorMsg + } + } + + + /** + * Forward the token to the VSCode extension by simulating a VSCode URL handler call + */ + private fun forwardTokenToVSCodeExtension(token: String) { + coroutineScope.launch { + try { + // Get the current project (or default project if none is open) + val project = getCurrentProject() + + if (project == null) { + logger.warn("No project available to forward token") + return@launch + } + + // Get RPC protocol instance + val protocol = project.getService(PluginContext::class.java)?.getRPCProtocol() + + if (protocol == null) { + logger.error("Cannot get RPC protocol instance, cannot forward token") + return@launch + } + + logger.info("Forwarding token to VSCode extension via RPC") + + // Use ExtHostCommands to execute a command that handles the URL + val extHostCommands = protocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostCommands) + + // Create the VSCode URI string that would normally be handled by handleUri + val vscodeUriString = "vscode://kilocode.kilo-code/kilocode?token=${token}" + + // Execute a command to handle the URI - this simulates what happens when VSCode receives a URL + // We'll use a special command that the VSCode extension can handle + extHostCommands.executeContributedCommand( + "kilo-code.handleExternalUri", + listOf(vscodeUriString) + ) + + logger.info("Successfully forwarded token to VSCode extension via command execution") + + } catch (e: Exception) { + logger.error("Error forwarding token to VSCode extension", e) + } + } + } + + /** + * Get the current project, preferring the focused project + */ + private fun getCurrentProject(): Project? { + return try { + val projectManager = ProjectManager.getInstance() + + // Try to get the default project first + val openProjects = projectManager.openProjects + + if (openProjects.isNotEmpty()) { + // Return the first open project + openProjects[0] + } else { + // Fallback to default project + projectManager.defaultProject + } + } catch (e: Exception) { + logger.warn("Error getting current project", e) + null + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostManager.kt new file mode 100644 index 0000000000..8b5e1b0759 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostManager.kt @@ -0,0 +1,354 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.google.gson.Gson +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.application.ApplicationInfo +import ai.kilocode.jetbrains.editor.EditorAndDocManager +import ai.kilocode.jetbrains.ipc.NodeSocket +import ai.kilocode.jetbrains.ipc.PersistentProtocol +import ai.kilocode.jetbrains.ipc.proxy.ResponsiveState +import ai.kilocode.jetbrains.util.PluginConstants +import ai.kilocode.jetbrains.util.PluginResourceUtil +import ai.kilocode.jetbrains.util.URI +import ai.kilocode.jetbrains.workspace.WorkspaceFileChangeManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import java.net.Socket +import java.nio.channels.SocketChannel +import java.nio.file.Paths +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId + +/** + * Extension host manager, responsible for communication with extension processes. + * Handles Ready and Initialized messages from extension processes. + */ +class ExtensionHostManager : Disposable { + companion object { + val LOG = Logger.getInstance(ExtensionHostManager::class.java) + } + + private val project: Project + private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // Communication protocol + private var nodeSocket: NodeSocket + private var protocol: PersistentProtocol? = null + + // RPC manager + private var rpcManager: RPCManager? = null + + // Extension manager + private var extensionManager: ExtensionManager? = null + + // Plugin identifier + private var rooCodeIdentifier: String? = null + + // JSON serialization + private val gson = Gson() + + // Last diagnostic log time + private var lastDiagnosticLogTime = 0L + + private var projectPath: String? = null + + // Support Socket constructor + constructor(clientSocket: Socket, projectPath: String,project: Project) { + clientSocket.tcpNoDelay = true + this.nodeSocket = NodeSocket(clientSocket, "extension-host") + this.projectPath = projectPath + this.project = project + } + // Support SocketChannel constructor + constructor(clientChannel: SocketChannel, projectPath: String , project: Project) { + this.nodeSocket = NodeSocket(clientChannel, "extension-host") + this.projectPath = projectPath + this.project = project + } + + /** + * Start communication with the extension process. + */ + fun start() { + try { + // Initialize extension manager + extensionManager = ExtensionManager() + val extensionPath = PluginResourceUtil.getResourcePath(PluginConstants.PLUGIN_ID, PluginConstants.PLUGIN_CODE_DIR) + rooCodeIdentifier = extensionPath?.let { extensionManager!!.registerExtension(it).identifier.value } + // Create protocol + protocol = PersistentProtocol( + PersistentProtocol.PersistentProtocolOptions( + socket = nodeSocket, + initialChunk = null, + loadEstimator = null, + sendKeepAlive = true + ), + this::handleMessage + ) + + LOG.info("ExtensionHostManager started successfully") + } catch (e: Exception) { + LOG.error("Failed to start ExtensionHostManager", e) + dispose() + } + } + + /** + * Get RPC responsive state. + * @return Responsive state, or null if RPC manager is not initialized. + */ + fun getResponsiveState(): ResponsiveState? { + val currentTime = System.currentTimeMillis() + // Limit diagnostic log frequency, at most once every 60 seconds + val shouldLogDiagnostics = currentTime - lastDiagnosticLogTime > 60000 + if (rpcManager == null) { + if (shouldLogDiagnostics) { + LOG.debug("Unable to get responsive state: RPC manager is not initialized") + lastDiagnosticLogTime = currentTime + } + return null + } + // Log connection diagnostic information + if (shouldLogDiagnostics) { + val socketInfo = buildString { + append("NodeSocket: ") + append(if (nodeSocket.isClosed()) "closed" else "active") + append(", input stream: ") + append(if (nodeSocket.isInputClosed()) "closed" else "normal") + append(", output stream: ") + append(if (nodeSocket.isOutputClosed()) "closed" else "normal") + append(", disposed=") + append(nodeSocket.isDisposed()) + } + + val protocolInfo = protocol?.let { proto -> + "Protocol: ${if (proto.isDisposed()) "disposed" else "active"}" + } ?: "Protocol is null" + LOG.debug("Connection diagnostics: $socketInfo, $protocolInfo") + lastDiagnosticLogTime = currentTime + } + return rpcManager?.getRPCProtocol()?.responsiveState + } + + /** + * Handle messages from the extension process. + */ + private fun handleMessage(data: ByteArray) { + // Check if data is a single-byte message (extension host protocol message) + if (data.size == 1) { + // Try to parse as extension host message type + + when (ExtensionHostMessageType.fromData(data)) { + ExtensionHostMessageType.Ready -> handleReadyMessage() + ExtensionHostMessageType.Initialized -> handleInitializedMessage() + ExtensionHostMessageType.Terminate -> LOG.info("Received Terminate message") + null -> LOG.debug("Received unknown message type: ${data.contentToString()}") + } + } else { + LOG.debug("Received message with length ${data.size}, not handling as extension host message") + } + } + + /** + * Handle Ready message, send initialization data. + */ + private fun handleReadyMessage() { + LOG.info("Received Ready message from extension host") + + try { + // Build initialization data + val initData = createInitData() + + // Send initialization data + val jsonData = gson.toJson(initData).toByteArray() + + protocol?.send(jsonData) + LOG.info("Sent initialization data to extension host") + } catch (e: Exception) { + LOG.error("Failed to handle Ready message", e) + } + } + + /** + * Handle Initialized message, create RPC manager and activate plugin. + */ + private fun handleInitializedMessage() { + LOG.info("Received Initialized message from extension host") + + try { + // Get protocol + val protocol = this.protocol ?: throw IllegalStateException("Protocol is not initialized") + val extensionManager = this.extensionManager ?: throw IllegalStateException("ExtensionManager is not initialized") + + // Create RPC manager + rpcManager = RPCManager(protocol, extensionManager,null, project) + + // Start initialization process + rpcManager?.startInitialize() + + // Start file monitoring + project.getService(WorkspaceFileChangeManager::class.java) +// WorkspaceFileChangeManager.getInstance() + project.getService(EditorAndDocManager::class.java).initCurrentIdeaEditor() + // Activate RooCode plugin + val rooCodeId = rooCodeIdentifier ?: throw IllegalStateException("RooCode identifier is not initialized") + extensionManager.activateExtension(rooCodeId, rpcManager!!.getRPCProtocol()) + .whenComplete { _, error -> + if (error != null) { + LOG.error("Failed to activate RooCode plugin", error) + } else { + LOG.info("RooCode plugin activated successfully") + } + } + + LOG.info("Initialized extension host") + } catch (e: Exception) { + LOG.error("Failed to handle Initialized message", e) + } + } + + /** + * Create initialization data. + * Corresponds to the initData object in main.js. + */ + private fun createInitData(): Map { + val pluginDir = getPluginDir() + val basePath = projectPath + + return mapOf( + "commit" to "development", + "version" to getIDEVersion(), + "quality" to null, + "parentPid" to ProcessHandle.current().pid(), + "environment" to mapOf( + "isExtensionDevelopmentDebug" to false, + "appName" to getCurrentIDEName(), + "appHost" to "node", + "appLanguage" to "en", + "appUriScheme" to "vscode", + "appRoot" to uriFromPath(pluginDir), + "globalStorageHome" to uriFromPath(Paths.get(System.getProperty("user.home"),".roo-cline", "globalStorage").toString()), + "workspaceStorageHome" to uriFromPath(Paths.get(System.getProperty("user.home"),".roo-cline", "workspaceStorage").toString()), + "extensionDevelopmentLocationURI" to null, + "extensionTestsLocationURI" to null, + "useHostProxy" to false, + "skipWorkspaceStorageLock" to false, + "isExtensionTelemetryLoggingOnly" to false, + ), + "workspace" to mapOf( + "id" to "intellij-workspace", + "name" to "IntelliJ Workspace", + "transient" to false, + "configuration" to null, + "isUntitled" to false + ), + "remote" to mapOf( + "authority" to null, + "connectionData" to null, + "isRemote" to false + ), + "extensions" to mapOf( + "versionId" to 1, + "allExtensions" to (extensionManager?.getAllExtensionDescriptions() ?: emptyList()), + "myExtensions" to (extensionManager?.getAllExtensionDescriptions()?.map { it.identifier } ?: emptyList()), + "activationEvents" to (extensionManager?.getAllExtensionDescriptions()?.associate { ext -> + ext.identifier.value to (ext.activationEvents ?: emptyList()) + } ?: emptyMap()) + ), + "telemetryInfo" to mapOf( + "sessionId" to "intellij-session", + "machineId" to "intellij-machine", + "sqmId" to "", + "devDeviceId" to "", + "firstSessionDate" to java.time.Instant.now().toString(), + "msftInternal" to false + ), + "logLevel" to 0, // Info level + "loggers" to emptyList(), + "logsLocation" to uriFromPath(Paths.get(pluginDir, "logs").toString()), + "autoStart" to true, + "consoleForward" to mapOf( + "includeStack" to false, + "logNative" to false + ), + "uiKind" to 1 // Desktop + ) + } + + /** + * Get current IDE name. + */ + private fun getCurrentIDEName(): String { + val applicationInfo = ApplicationInfo.getInstance() + val productCode = applicationInfo.build.productCode + val version = applicationInfo.shortVersion ?: "1.0.0" + + // Return in the format: wrapper|jetbrains|productCode + val result = "wrapper|jetbrains|$productCode|$version" + return result + } + + /** + * Get current IDE version. + */ + private fun getIDEVersion(): String { + val applicationInfo = ApplicationInfo.getInstance() + val version = applicationInfo.shortVersion ?: "1.0.0" + LOG.info("Get IDE version: $version") + + val pluginVersion = PluginManagerCore.getPlugin(PluginId.getId(PluginConstants.PLUGIN_ID))?.version + if (pluginVersion != null) { + val fullVersion = "$version, $pluginVersion" + LOG.info("Get IDE version and plugin version: $fullVersion") + return fullVersion + } + + return version + } + + /** + * Get plugin directory. + */ + private fun getPluginDir(): String { + return PluginResourceUtil.getResourcePath(PluginConstants.PLUGIN_ID, "") + ?: throw IllegalStateException("Unable to get plugin directory") + } + + /** + * Create URI object. + */ + private fun uriFromPath(path: String): URI { + return URI.file(path) + } + + /** + * Resource disposal. + */ + override fun dispose() { + LOG.info("Disposing ExtensionHostManager") + + // Cancel coroutines + coroutineScope.cancel() + + // Release RPC manager + rpcManager = null + + // Release protocol + protocol?.dispose() + protocol = null + + // Release socket + nodeSocket.dispose() + + LOG.info("ExtensionHostManager disposed") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostMessageType.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostMessageType.kt new file mode 100644 index 0000000000..1de87a1c7b --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionHostMessageType.kt @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +/** + * Extension host message type + * Corresponds to extensionHostProtocol.MessageType in VSCode + */ +enum class ExtensionHostMessageType { + /** + * Initialized + */ + Initialized, + + /** + * Ready + */ + Ready, + + /** + * Terminated + */ + Terminate; + + companion object { + /** + * Get message type from numeric value + * @param value Numeric value + * @return Corresponding message type, or null if not matched + */ + fun fromValue(value: Int): ExtensionHostMessageType? { + return when (value) { + 0 -> Initialized + 1 -> Ready + 2 -> Terminate + else -> null + } + } + + /** + * Get message type from protocol message data + * @param data Message data + * @return Corresponding message type, or null if not matched + */ + fun fromData(data: ByteArray): ExtensionHostMessageType? { + if (data.size != 1) { + return null + } + + return when (data[0].toInt()) { + 1 -> Initialized + 2 -> Ready + 3 -> Terminate + else -> null + } + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionIdentifier.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionIdentifier.kt new file mode 100644 index 0000000000..0f0763cf65 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionIdentifier.kt @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +/** + * Extension identifier class + * Corresponds to ExtensionIdentifier in VSCode + */ +class ExtensionIdentifier(val value: String) { + /** + * Stores lowercase value for comparison and indexing + */ + val _lower: String = value.toLowerCase() + + companion object { + /** + * Compare whether two extension identifiers are equal + * @param a First extension identifier or string + * @param b Second extension identifier or string + * @return true if equal, false otherwise + */ + fun equals( + a: ExtensionIdentifier?, + b: ExtensionIdentifier? + ): Boolean { + if (a == null) { + return b == null + } + if (b == null) { + return false + } + return a._lower == b._lower + } + + /** + * Compare extension identifier and string for equality + * @param a Extension identifier + * @param b String + * @return true if values are equal (case-insensitive), false otherwise + */ + fun equals( + a: ExtensionIdentifier?, + b: String? + ): Boolean { + if (a == null) { + return b == null + } + if (b == null) { + return false + } + return a._lower == b.toLowerCase() + } + + /** + * Compare string and extension identifier for equality + * @param a String + * @param b Extension identifier + * @return true if values are equal (case-insensitive), false otherwise + */ + fun equals( + a: String?, + b: ExtensionIdentifier? + ): Boolean { + if (a == null) { + return b == null + } + if (b == null) { + return false + } + return a.toLowerCase() == b._lower + } + + /** + * Get key for indexing + * @param id Extension identifier or string + * @return Key for indexing + */ + fun toKey(id: ExtensionIdentifier): String { + return id._lower + } + + /** + * Get key for indexing + * @param id String + * @return Key for indexing + */ + fun toKey(id: String): String { + return id.toLowerCase() + } + } + + override fun equals(other: Any?): Boolean { + if (other === this) return true + if (other !is ExtensionIdentifier) return false + return _lower == other._lower + } + + override fun hashCode(): Int { + return _lower.hashCode() + } + + override fun toString(): String { + return value + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionManager.kt new file mode 100644 index 0000000000..8217c29323 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionManager.kt @@ -0,0 +1,250 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.google.gson.Gson +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol +import ai.kilocode.jetbrains.ipc.proxy.ProxyIdentifier +import ai.kilocode.jetbrains.ipc.proxy.createProxyIdentifier +import ai.kilocode.jetbrains.util.URI +import ai.kilocode.jetbrains.util.toCompletableFuture +import java.io.File +import java.nio.file.Paths +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap + +/** + * Extension manager + * Responsible for managing extension registration and activation + */ +class ExtensionManager : Disposable { + companion object { + val LOG = Logger.getInstance(ExtensionManager::class.java) + } + + // Registered extensions + private val extensions = ConcurrentHashMap() + + // Gson instance + private val gson = Gson() + + /** + * Parse extension description information + * @param extensionPath Extension path + * @return Extension description object + */ + private fun parseExtensionDescription(extensionPath: String): ExtensionDescription { + LOG.info("Parsing extension: $extensionPath") + + // Read package.json file + val packageJsonPath = Paths.get(extensionPath, "package.json").toString() + val packageJsonContent = File(packageJsonPath).readText() + val packageJson = gson.fromJson(packageJsonContent, PackageJson::class.java) + + // Create extension identifier + val name = packageJson.name + val publisher = "Kilo Code" + val extensionIdentifier = ExtensionIdentifier("$publisher.$name") + + // Create extension description + return ExtensionDescription( + id = "${publisher}.${name}", + identifier = extensionIdentifier, + name = "${publisher}.${name}", + displayName = packageJson.displayName, + description = packageJson.description, + version = packageJson.version ?: "1.0.0", + publisher = "Kilo Code", + main = packageJson.main ?: "./dist/extension.js", + activationEvents = packageJson.activationEvents ?: listOf("onStartupFinished"), + extensionLocation = URI.file(extensionPath), + targetPlatform = "universal", // TargetPlatform.UNIVERSAL + isBuiltin = false, + isUserBuiltin = false, + isUnderDevelopment = false, + engines = packageJson.engines?.let { + mapOf("vscode" to (it.vscode ?: "^1.0.0")) + } ?: mapOf("vscode" to "^1.0.0"), + preRelease = false, + capabilities = mapOf(), + extensionDependencies = packageJson.extensionDependencies ?: emptyList(), + ) + } + + /** + * Get all parsed extension descriptions + * @return Extension description array + */ + fun getAllExtensionDescriptions(): List { + return extensions.values.toList() + } + + /** + * Get description information for the specified extension + * @param extensionId Extension ID + * @return Extension description object, or null if not found + */ + fun getExtensionDescription(extensionId: String): ExtensionDescription? { + return extensions[extensionId] + } + + /** + * Register extension + * @param extensionPath Extension path + * @return Extension description object + */ + fun registerExtension(extensionPath: String): ExtensionDescription { + val extensionDescription = parseExtensionDescription(extensionPath) + extensions[extensionDescription.name] = extensionDescription + LOG.info("Extension registered: ${extensionDescription.name}") + return extensionDescription + } + + /** + * Activate extension + * @param extensionId Extension ID + * @param rpcProtocol RPC protocol + * @return Completion Future + */ + fun activateExtension(extensionId: String, rpcProtocol: IRPCProtocol): CompletableFuture { + LOG.info("Activating extension: $extensionId") + + try { + // Get extension description + val extension = extensions[extensionId] + if (extension == null) { + LOG.error("Extension not found: $extensionId") + val future = CompletableFuture() + future.completeExceptionally(IllegalArgumentException("Extension not found: $extensionId")) + return future + } + + // Create activation parameters + val activationParams = mapOf( + "startup" to true, + "extensionId" to extension.identifier, + "activationEvent" to "api" + ) + + // Get proxy of ExtHostExtensionServiceShape type + val extHostService = rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostExtensionService) + + try { + // Get LazyPromise instance and convert it to CompletableFuture + val lazyPromise = extHostService.activate(extension.identifier.value, activationParams) + + return lazyPromise.toCompletableFuture().thenApply { result -> + val boolResult = when (result) { + is Boolean -> result + else -> false + } + LOG.info("Extension activation ${if (boolResult) "successful" else "failed"}: $extensionId") + boolResult + }.exceptionally { throwable -> + LOG.error("Failed to activate extension: $extensionId", throwable) + false + } + } catch (e: Exception) { + LOG.error("Failed to call activate method: $extensionId", e) + val future = CompletableFuture() + future.completeExceptionally(e) + return future + } + + } catch (e: Exception) { + LOG.error("Failed to activate extension: $extensionId", e) + val future = CompletableFuture() + future.completeExceptionally(e) + return future + } + } + + /** + * Release resources + */ + override fun dispose() { + LOG.info("Releasing ExtensionManager resources") + extensions.clear() + } +} + +/** + * package.json data class + * Used for Gson parsing of extension's package.json file + */ +data class PackageJson( + val name: String, + val displayName: String? = null, + val description: String? = null, + val publisher: String? = null, + val version: String? = null, + val engines: Engines? = null, + val activationEvents: List? = null, + val main: String? = null, + val extensionDependencies: List? = null +) + +/** + * Engines data class + * Used for parsing engines field + */ +data class Engines( + val vscode: String? = null, + val node: String? = null +) + +/** + * Extension description + * Corresponds to IExtensionDescription in VSCode + */ +data class ExtensionDescription( + val id: String? = null, + val identifier: ExtensionIdentifier, + val name: String, + val displayName: String? = null, + val description: String? = null, + val version: String, + val publisher: String, + val main: String? = null, + val activationEvents: List? = null, + val extensionLocation: URI, + val targetPlatform: String = "universal", + val isBuiltin: Boolean = false, + val isUserBuiltin: Boolean = false, + val isUnderDevelopment: Boolean = false, + val engines: Map, + val preRelease: Boolean = false, + val capabilities: Map = emptyMap(), + val extensionDependencies: List = emptyList(), +) + +/** + * Convert ExtensionDescription to Map + * @return Map containing all properties of ExtensionDescription, where identifier is converted to sid string + */ +fun ExtensionDescription.toMap(): Map { + return mapOf( + "identifier" to this.identifier.value, + "name" to this.name, + "displayName" to this.displayName, + "description" to this.description, + "version" to this.version, + "publisher" to this.publisher, + "main" to this.main, + "activationEvents" to this.activationEvents, + "extensionLocation" to this.extensionLocation, + "targetPlatform" to this.targetPlatform, + "isBuiltin" to this.isBuiltin, + "isUserBuiltin" to this.isUserBuiltin, + "isUnderDevelopment" to this.isUnderDevelopment, + "engines" to this.engines, + "preRelease" to this.preRelease, + "capabilities" to this.capabilities, + "extensionDependencies" to this.extensionDependencies + ) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionProcessManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionProcessManager.kt new file mode 100644 index 0000000000..48283b0ab1 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionProcessManager.kt @@ -0,0 +1,445 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.execution.configurations.PathEnvironmentVariableUtil +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.SystemInfo +import ai.kilocode.jetbrains.plugin.DEBUG_MODE +import ai.kilocode.jetbrains.plugin.WecoderPluginService +import ai.kilocode.jetbrains.util.PluginResourceUtil +import ai.kilocode.jetbrains.util.ProxyConfigUtil +import java.io.File +import java.util.concurrent.TimeUnit +import ai.kilocode.jetbrains.util.ExtensionUtils +import ai.kilocode.jetbrains.util.PluginConstants +import ai.kilocode.jetbrains.util.NotificationUtil +import ai.kilocode.jetbrains.util.NodeVersionUtil +import ai.kilocode.jetbrains.util.NodeVersion + +/** + * Extension process manager + * Responsible for starting and managing extension processes + */ +class ExtensionProcessManager : Disposable { + companion object { + // Node modules path + private const val NODE_MODULES_PATH = PluginConstants.NODE_MODULES_PATH + + // Extension process entry file + private const val EXTENSION_ENTRY_FILE = PluginConstants.EXTENSION_ENTRY_FILE + + // Plugin code directory + private const val PLUGIN_CODE_DIR = PluginConstants.PLUGIN_CODE_DIR + + // Runtime directory + private const val RUNTIME_DIR = PluginConstants.RUNTIME_DIR + + // Plugin ID + private const val PLUGIN_ID = PluginConstants.PLUGIN_ID + + // Minimum required Node.js version + private val MIN_REQUIRED_NODE_VERSION = NodeVersion(20, 6, 0, "20.6.0") + } + + private val LOG = Logger.getInstance(ExtensionProcessManager::class.java) + + // Extension process + private var process: Process? = null + + // Process monitor thread + private var monitorThread: Thread? = null + + // Whether running + @Volatile + private var isRunning = false + + /** + * Start extension process + * @param portOrPath Socket server port (Int) or UDS path (String) + * @return Whether started successfully + */ + fun start(portOrPath: Any?): Boolean { + if (isRunning) { + LOG.info("Extension process is already running") + return true + } + val isUds = portOrPath is String + if (!ExtensionUtils.isValidPortOrPath(portOrPath)) { + LOG.error("Invalid socket info: $portOrPath") + return false + } + + try { + // Prepare Node.js executable path + val nodePath = findNodeExecutable() + if (nodePath == null) { + LOG.error("Failed to find Node.js executable") + + // Show notification to prompt user to install Node.js + NotificationUtil.showError( + "Node.js environment missing", + "Node.js environment not detected, please install Node.js and try again. Recommended version: $MIN_REQUIRED_NODE_VERSION or higher." + ) + + return false + } + + // Check Node.js version + val nodeVersion = NodeVersionUtil.getNodeVersion(nodePath) + if (!NodeVersionUtil.isVersionSupported(nodeVersion, MIN_REQUIRED_NODE_VERSION)) { + LOG.error("Node.js version is not supported: $nodeVersion, required: $MIN_REQUIRED_NODE_VERSION") + + NotificationUtil.showError( + "Node.js version too low", + "Current Node.js($nodePath) version is $nodeVersion, please upgrade to $MIN_REQUIRED_NODE_VERSION or higher for better compatibility." + ) + + return false + } + + // Prepare extension process entry file path + val extensionPath = findExtensionEntryFile() + if (extensionPath == null) { + LOG.error("Failed to find extension entry file") + return false + } + + LOG.info("Starting extension process with node: $nodePath, entry: $extensionPath") + + val envVars = HashMap(System.getenv()) + + // Build complete PATH + envVars["PATH"] = buildEnhancedPath(envVars, nodePath) + LOG.info("Enhanced PATH for ${SystemInfo.getOsNameAndVersion()}: ${envVars["PATH"]}") + + // Add key environment variables + if (isUds) { + envVars["VSCODE_EXTHOST_IPC_HOOK"] = portOrPath.toString() + }else{ + envVars["VSCODE_EXTHOST_WILL_SEND_SOCKET"] = "1" + envVars["VSCODE_EXTHOST_SOCKET_HOST"] = "127.0.0.1" + envVars["VSCODE_EXTHOST_SOCKET_PORT"] = portOrPath.toString() + } + + // Build command line arguments + val commandArgs = mutableListOf( + nodePath, + "--experimental-global-webcrypto", + "--no-deprecation", +// "--trace-uncaught", + extensionPath, + "--vscode-socket-port=${envVars["VSCODE_EXTHOST_SOCKET_PORT"]}", + "--vscode-socket-host=${envVars["VSCODE_EXTHOST_SOCKET_HOST"]}", + "--vscode-will-send-socket=${envVars["VSCODE_EXTHOST_WILL_SEND_SOCKET"]}" + ) + + // Get and set proxy configuration + try { + val proxyEnvVars = ProxyConfigUtil.getProxyEnvVarsForProcessStart() + + // Add proxy environment variables + envVars.putAll(proxyEnvVars) + + // Log proxy configuration if used + if (proxyEnvVars.isNotEmpty()) { + LOG.info("Applied proxy configuration for process startup") + } + } catch (e: Exception) { + LOG.warn("Failed to configure proxy settings", e) + } + + // Create process builder + val builder = ProcessBuilder(commandArgs) + + // Print environment variables + LOG.info("Environment variables:") + envVars.forEach { (key, value) -> + LOG.info(" $key = $value") + } + builder.environment().putAll(envVars) + + // Redirect error stream to standard output + builder.redirectErrorStream(true) + + // Start process + process = builder.start() + + // Start monitor thread + monitorThread = Thread { + monitorProcess() + }.apply { + name = "ExtensionProcessMonitor" + isDaemon = true + start() + } + + isRunning = true + LOG.info("Extension process started") + return true + } catch (e: Exception) { + LOG.error("Failed to start extension process", e) + stopInternal() + return false + } + } + + /** + * Monitor extension process + */ + private fun monitorProcess() { + val proc = process ?: return + + try { + // Start log reading thread + val logThread = Thread { + proc.inputStream.bufferedReader().use { reader -> + var line: String? + while (reader.readLine().also { line = it } != null) { + LOG.info("Extension process: $line") + } + } + } + logThread.name = "ExtensionProcessLogger" + logThread.isDaemon = true + logThread.start() + + // Wait for process to end + try { + val exitCode = proc.waitFor() + LOG.info("Extension process exited with code: $exitCode") + } catch (e: InterruptedException) { + LOG.info("Process monitor interrupted") + } + + // Ensure log thread ends + logThread.interrupt() + try { + logThread.join(1000) + } catch (e: InterruptedException) { + // Ignore + } + } catch (e: Exception) { + LOG.error("Error monitoring extension process", e) + } finally { + synchronized(this) { + if (process === proc) { + isRunning = false + process = null + } + } + } + } + + /** + * Stop extension process + */ + fun stop() { + if (!isRunning) { + return + } + + stopInternal() + } + + /** + * Internal stop logic + */ + private fun stopInternal() { + LOG.info("Stopping extension process") + + val proc = process + if (proc != null) { + try { + // Try to close normally + if (proc.isAlive) { + proc.destroy() + + // Wait for process to end + if (!proc.waitFor(5, TimeUnit.SECONDS)) { + // Force terminate + proc.destroyForcibly() + proc.waitFor(2, TimeUnit.SECONDS) + } + } + } catch (e: Exception) { + LOG.error("Error stopping extension process", e) + } + } + + // Interrupt monitor thread + monitorThread?.interrupt() + try { + monitorThread?.join(1000) + } catch (e: InterruptedException) { + // Ignore + } + + process = null + monitorThread = null + isRunning = false + + LOG.info("Extension process stopped") + } + + /** + * Find Node.js executable + */ + private fun findNodeExecutable(): String? { + // First check built-in Node.js + val resourcesPath = PluginResourceUtil.getResourcePath(PLUGIN_ID, NODE_MODULES_PATH) + if (resourcesPath != null) { + val resourceDir = File(resourcesPath) + if (resourceDir.exists() && resourceDir.isDirectory) { + val nodeBin = if (SystemInfo.isWindows) { + File(resourceDir, "node.exe") + } else { + File(resourceDir, ".bin/node") + } + + if (nodeBin.exists() && nodeBin.canExecute()) { + return nodeBin.absolutePath + } + } + } + + // Then check system path + return findExecutableInPath("node") + } + + /** + * Find executable in system path + */ + private fun findExecutableInPath(name: String): String? { + val nodePath = PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("node")?.absolutePath + LOG.info("System Node path: $nodePath") + return nodePath + } + + /** + * Find extension process entry file + * @param projectBasePath Current project root path + */ + fun findExtensionEntryFile(): String? { + // In debug mode, directly return resources path + if (WecoderPluginService.getDebugMode() != DEBUG_MODE.NONE) { + val debugEntry = java.nio.file.Paths.get(WecoderPluginService.getDebugResource(), RUNTIME_DIR, "src", EXTENSION_ENTRY_FILE).normalize().toFile() + if (debugEntry.exists() && debugEntry.isFile) { + LOG.info("[DebugMode] Using debug entry file: ${debugEntry.absolutePath}") + return debugEntry.absolutePath + } else { + LOG.warn("[DebugMode] Debug entry file not found: ${debugEntry.absolutePath}") + } + } + // Normal mode + val resourcesPath = ai.kilocode.jetbrains.util.PluginResourceUtil.getResourcePath(PLUGIN_ID, "$RUNTIME_DIR/$EXTENSION_ENTRY_FILE") + if (resourcesPath != null) { + val resource = java.io.File(resourcesPath) + if (resource.exists() && resource.isFile) { + return resourcesPath + } + } + return null + } + + /** + * Find plugin code directory + */ + private fun findPluginCodeDir(): String? { + val pluginDirPath = PluginResourceUtil.getResourcePath(PLUGIN_ID, PLUGIN_CODE_DIR) + if (pluginDirPath != null) { + val pluginCodeDir = File(pluginDirPath) + if (pluginCodeDir.exists() && pluginCodeDir.isDirectory) { + return pluginCodeDir.absolutePath + } + } + + LOG.warn("Plugin code directory not found") + return null + } + + /** + * Find node_modules path + */ + private fun findNodeModulesPath(): String? { + val nodePath = PluginResourceUtil.getResourcePath(PLUGIN_ID, NODE_MODULES_PATH) + if (nodePath != null) { + val nodeDir = File(nodePath) + if (nodeDir.exists() && nodeDir.isDirectory) { + return nodeDir.absolutePath + } + } + return null + } + + /** + * Build enhanced PATH environment variable + * @param envVars Environment variable map + * @param nodePath Node.js executable path + * @return Enhanced PATH + */ + private fun buildEnhancedPath(envVars: MutableMap, nodePath: String): String { + // Find current PATH value (Path on Windows) + val currentPath = envVars.filterKeys { it.equals("PATH", ignoreCase = true) } + .values.firstOrNull() ?: "" + + val pathBuilder = mutableListOf() + + // Simplify: add Node directory to PATH head (npx usually in same dir as node) + val nodeDir = File(nodePath).parentFile?.absolutePath + if (nodeDir != null && !currentPath.contains(nodeDir)) { + pathBuilder.add(nodeDir) + } + + // Add common paths according to OS + val commonDevPaths = when { + SystemInfo.isMac -> listOf( + "/opt/homebrew/bin", + "/opt/homebrew/sbin", + "/usr/local/bin", + "/usr/local/sbin", + "${System.getProperty("user.home")}/.local/bin" + ) + SystemInfo.isWindows -> listOf( + "C:\\Windows\\System32", + "C:\\Windows\\SysWOW64", + "C:\\Windows", + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0", + "C:\\Program Files\\PowerShell\\7", + "C:\\Program Files (x86)\\PowerShell\\7" + ) + else -> emptyList() + } + + // Add existing paths + commonDevPaths.forEach { path -> + if (File(path).exists() && !currentPath.contains(path)) { + pathBuilder.add(path) + LOG.info("Add path to PATH: $path") + } else if (!File(path).exists()) { + LOG.warn("Path does not exist, skip: $path") + } + } + + // Keep original PATH + if (currentPath.isNotEmpty()) { + pathBuilder.add(currentPath) + } + + return pathBuilder.joinToString(File.pathSeparator) + } + + /** + * Whether running + */ + fun isRunning(): Boolean { + return isRunning && process?.isAlive == true + } + + override fun dispose() { + stop() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionSocketServer.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionSocketServer.kt new file mode 100644 index 0000000000..fad9e6272a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionSocketServer.kt @@ -0,0 +1,313 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import java.net.ServerSocket +import java.net.Socket +import java.io.IOException +import java.util.concurrent.ConcurrentHashMap +import kotlin.concurrent.thread + +/** + * Extension process Socket server + * Used to establish communication with extension process + */ +interface ISocketServer : Disposable { + fun start(projectPath: String = ""): Any? + fun stop() + fun isRunning(): Boolean +} + +class ExtensionSocketServer() : ISocketServer { + private val logger = Logger.getInstance(ExtensionSocketServer::class.java) + + // Server socket + private var serverSocket: ServerSocket? = null + + // Connected client managers + private val clientManagers = ConcurrentHashMap() + + // Server thread + private var serverThread: Thread? = null + + // Current project path + private var projectPath: String = "" + + // Whether running + @Volatile + private var isRunning = false + + lateinit var project: Project + + /** + * Start Socket server + * @param projectPath Current project path + * @return Server port, -1 if failed + */ + override fun start(projectPath: String): Int { + if (isRunning) { + logger.info("Socket server is already running") + return serverSocket?.localPort ?: -1 + } + + this.projectPath = projectPath + + try { + // Use 0 to indicate random port assignment + serverSocket = ServerSocket(0) + val port = serverSocket?.localPort ?: -1 + + if (port <= 0) { + logger.error("Failed to get valid port for socket server") + return -1 + } + + isRunning = true + logger.info("Starting socket server on port: $port") + + // Start the thread to accept connections + serverThread = thread(start = true, name = "ExtensionSocketServer") { + acceptConnections() + } + + return port + } catch (e: Exception) { + logger.error("Failed to start socket server", e) + stop() + return -1 + } + } + + /** + * Stop Socket server + */ + override fun stop() { + if (!isRunning) { + return + } + + isRunning = false + logger.info("Stopping socket server") + + // Close all client managers + clientManagers.forEach { (_, manager) -> + try { + manager.dispose() + } catch (e: Exception) { + logger.warn("Failed to dispose client manager", e) + } + } + clientManagers.clear() + + // Close the server + try { + serverSocket?.close() + } catch (e: IOException) { + logger.warn("Failed to close server socket", e) + } + + // Interrupt the server thread + serverThread?.interrupt() + serverThread = null + serverSocket = null + + logger.info("Socket server stopped") + } + + /** + * Thread function for accepting connections + */ + private fun acceptConnections() { + val server = serverSocket ?: return + + logger.info("Socket server started, waiting for connections..., tid: ${Thread.currentThread().id}") + + while (isRunning && !Thread.currentThread().isInterrupted) { + try { + val clientSocket = server.accept() + logger.info("New client connected from: ${clientSocket.inetAddress.hostAddress}") + + clientSocket.tcpNoDelay = true // Set no delay + + // Create extension host manager + val manager = ExtensionHostManager(clientSocket, projectPath,project) + clientManagers[clientSocket] = manager + + handleClient(clientSocket, manager) + } catch (e: IOException) { + if (isRunning) { + logger.error("Error accepting client connection", e) + } else { + // IOException is thrown when ServerSocket is closed, this is normal + logger.info("Socket server closed") + break + } + } catch (e: InterruptedException) { + // Thread interrupted, this is normal + logger.info("Socket server thread interrupted") + break + } catch (e: Exception) { + logger.error("Unexpected error in accept loop", e) + if (isRunning) { + try { + // Retry after short delay + Thread.sleep(1000) + } catch (ie: InterruptedException) { + // Thread interrupted, server is shutting down + logger.info("Socket server thread interrupted during sleep") + break + } + } + } + } + + logger.info("Socket accept loop terminated") + } + + /** + * Handle client connection + */ + private fun handleClient(clientSocket: Socket, manager: ExtensionHostManager) { + try { + // Start extension host manager + manager.start() + + // Periodically check socket health + var lastCheckTime = System.currentTimeMillis() + val CHECK_INTERVAL = 15000 // Check every 15 seconds + + // Wait for socket to close + while (clientSocket.isConnected && !clientSocket.isClosed && isRunning) { + try { + // Periodically check connection health + val currentTime = System.currentTimeMillis() + if (currentTime - lastCheckTime > CHECK_INTERVAL) { + lastCheckTime = currentTime + + if (!isSocketHealthy(clientSocket)) { + logger.error("Detected unhealthy Socket connection, closing connection") + break + } + + // Check RPC response state + val responsiveState = manager.getResponsiveState() + if (responsiveState != null) { + logger.debug("Current RPC response state: $responsiveState") + } + } + + Thread.sleep(500) + } catch (ie: InterruptedException) { + // Thread interrupted, server is shutting down, exit loop + logger.info("Client handler thread interrupted, exiting loop") + break + } + } + } catch (e: Exception) { + // Filter out InterruptedException, it means normal interruption + if (e !is InterruptedException) { + logger.error("Error handling client socket: ${e.message}", e) + } else { + logger.info("Client handler thread interrupted during processing") + } + } finally { + // Clean up resources + manager.dispose() + clientManagers.remove(clientSocket) + + if (!clientSocket.isClosed) { + try { + clientSocket.close() + } catch (e: IOException) { + logger.warn("Failed to close client socket", e) + } + } + + logger.info("Client socket closed and removed") + } + } + + /** + * Check socket connection health + */ + private fun isSocketHealthy(socket: Socket): Boolean { + val isHealthy = socket.isConnected && + !socket.isClosed && + !socket.isInputShutdown && + !socket.isOutputShutdown + + if (!isHealthy) { + logger.warn("Socket health check failed: isConnected=${socket.isConnected}, " + + "isClosed=${socket.isClosed}, " + + "isInputShutdown=${socket.isInputShutdown}, " + + "isOutputShutdown=${socket.isOutputShutdown}") + } + + return isHealthy + } + + /** + * Get current port + */ + fun getPort(): Int { + return serverSocket?.localPort ?: -1 + } + + /** + * Whether running + */ + override fun isRunning(): Boolean { + return isRunning + } + + /** + * Resource cleanup + */ + override fun dispose() { + stop() + } + + /** + * Connect to debug host + * @param host Debug host address + * @param port Debug host port + * @return Whether connection is successful + */ + fun connectToDebugHost(host: String, port: Int): Boolean { + if (isRunning) { + logger.info("Socket server is already running, stopping first") + stop() + } + + try { + logger.info("Connecting to debug host at $host:$port") + + // Directly connect to the specified address and port + val clientSocket = Socket(host, port) + clientSocket.tcpNoDelay = true // Set no delay + + isRunning = true + + // Create extension host manager + val manager = ExtensionHostManager(clientSocket, projectPath,project) + clientManagers[clientSocket] = manager + + // Start connection handling in background thread + thread(start = true, name = "DebugHostHandler") { + handleClient(clientSocket, manager) + } + + logger.info("Successfully connected to debug host at $host:$port") + return true + } catch (e: Exception) { + logger.error("Failed to connect to debug host at $host:$port", e) + stop() + return false + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionUnixDomainSocketServer.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionUnixDomainSocketServer.kt new file mode 100644 index 0000000000..e0e3d0909a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ExtensionUnixDomainSocketServer.kt @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import java.io.IOException +import java.net.StandardProtocolFamily +import java.net.UnixDomainSocketAddress +import java.nio.channels.ServerSocketChannel +import java.nio.channels.SocketChannel +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import kotlin.concurrent.thread + + // ExtensionUnixDomainSocketServer is responsible for communication between extension process and IDEA plugin process via Unix Domain Socket +class ExtensionUnixDomainSocketServer : ISocketServer { + // Logger + private val logger = Logger.getInstance(ExtensionUnixDomainSocketServer::class.java) + // UDS server channel + private var udsServerChannel: ServerSocketChannel? = null + // UDS socket file path + private var udsSocketPath: Path? = null + // Mapping of client connections and managers + private val clientManagers = ConcurrentHashMap() + // Server listening thread + private var serverThread: Thread? = null + // Current project path + private var projectPath: String = "" + + lateinit var project: Project + + @Volatile private var isRunning = false // Server running state + + // Start UDS server, return socket file path + override fun start(projectPath: String): String? { + if (isRunning) { + logger.info("UDS server is already running") + return udsSocketPath?.toString() + } + this.projectPath = projectPath + return startUds() + } + + // Actual logic to start UDS server + private fun startUds(): String? { + try { + val sockPath = createSocketFile() // Create socket file + val udsAddr = UnixDomainSocketAddress.of(sockPath) + udsServerChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX) + udsServerChannel!!.bind(udsAddr) + udsSocketPath = sockPath + isRunning = true + logger.info("[UDS] Listening on: $sockPath") + // Start listening thread, asynchronously accept client connections + serverThread = + thread(start = true, name = "ExtensionUDSSocketServer") { + acceptUdsConnections() + } + return sockPath.toString() + } catch (e: Exception) { + logger.error("[UDS] Failed to start server", e) + stop() + return null + } + } + + // Stop UDS server, release resources + override fun stop() { + if (!isRunning) return + isRunning = false + logger.info("Stopping UDS socket server") + // Close all client connections + clientManagers.forEach { (_, manager) -> + try { + manager.dispose() + } catch (e: Exception) { + logger.warn("Failed to dispose client manager", e) + } + } + clientManagers.clear() + try { + udsServerChannel?.close() + } catch (e: Exception) { + logger.warn("Failed to close UDS server channel", e) + } + try { + udsSocketPath?.let { Files.deleteIfExists(it) } + } catch (e: Exception) { + logger.warn("Failed to delete UDS socket file", e) + } + // Thread and channel cleanup + serverThread?.interrupt() + serverThread = null + udsServerChannel = null + udsSocketPath = null + logger.info("UDS socket server stopped") + } + + override fun isRunning(): Boolean = isRunning + override fun dispose() { + stop() + } + + // Listen and accept UDS client connections + private fun acceptUdsConnections() { + val server = udsServerChannel ?: return + logger.info("[UDS] Waiting for connections..., tid: ${Thread.currentThread().id}") + while (isRunning && !Thread.currentThread().isInterrupted) { + try { + val clientChannel = server.accept() // Block and wait for new connection + logger.info("[UDS] New client connected") + val manager = ExtensionHostManager(clientChannel, projectPath,project) + clientManagers[clientChannel] = manager + handleClient(clientChannel, manager) // Start client handler thread + } catch (e: Exception) { + if (isRunning) { + logger.error("[UDS] Accept failed, will retry in 1s", e) + Thread.sleep(1000) + } else { + logger.info("[UDS] Accept loop exiting (server stopped)") + break + } + } + } + logger.info("[UDS] Accept loop terminated.") + } + + // Handle single client connection, responsible for heartbeat check and resource release + private fun handleClient(clientChannel: SocketChannel, manager: ExtensionHostManager) { + try { + manager.start() // Start extension host manager + + var lastCheckTime = System.currentTimeMillis() + val CHECK_INTERVAL = 15000 // Heartbeat check interval + + while (clientChannel.isConnected && clientChannel.isOpen && isRunning) { + try { + val currentTime = System.currentTimeMillis() + if (currentTime - lastCheckTime > CHECK_INTERVAL) { + lastCheckTime = currentTime + + // UDS has no input/output shutdown flag, can only use isOpen + if (!clientChannel.isOpen) { + logger.error("[UDS] Client channel unhealthy, closing.") + break + } + + val responsiveState = manager.getResponsiveState() + if (responsiveState != null) { + logger.debug("[UDS] Client RPC state: $responsiveState") + } + } + + Thread.sleep(500) + } catch (ie: InterruptedException) { + logger.info("[UDS] Client handler interrupted, exiting loop") + break + } + } + } catch (e: Exception) { + if (e !is InterruptedException) { + logger.error("[UDS] Error in client handler: ${e.message}", e) + } else { + logger.info("[UDS] Client handler interrupted during processing") + } + } finally { + // Connection close and resource release + manager.dispose() + clientManagers.remove(clientChannel) + try { + clientChannel.close() + } catch (e: IOException) { + logger.warn("[UDS] Close client channel error", e) + } + logger.info("[UDS] Client channel closed and removed.") + } + } + + // Create temporary socket file, ensure uniqueness + private fun createSocketFile(): Path { + val tmpDir = java.nio.file.Paths.get("/tmp") + val sockPath = Files.createTempFile(tmpDir, "roo-cline-idea-extension-ipc-", ".sock") + Files.deleteIfExists(sockPath) // Ensure it does not exist + return sockPath + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/PluginContext.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/PluginContext.kt new file mode 100644 index 0000000000..37a4e6e926 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/PluginContext.kt @@ -0,0 +1,63 @@ +// Copyright 2009-2025 Weibo, Inc. +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol + +/** + * Plugin global context + * Used for managing globally accessible resources and objects + */ +@Service(Service.Level.PROJECT) +class PluginContext{ + private val logger = Logger.getInstance(PluginContext::class.java) + + // RPC protocol instance + @Volatile + private var rpcProtocol: IRPCProtocol? = null + + /** + * Set RPC protocol instance + * @param protocol RPC protocol instance + */ + fun setRPCProtocol(protocol: IRPCProtocol) { + logger.info("Setting RPC protocol instance") + rpcProtocol = protocol + } + + /** + * Get RPC protocol instance + * @return RPC protocol instance, or null if not set + */ + fun getRPCProtocol(): IRPCProtocol? { + return rpcProtocol + } + + /** + * Clear all resources + */ + fun clear() { + logger.info("Clearing resources in PluginContext") + rpcProtocol = null + } + + companion object { + // Singleton instance +// @Volatile +// private var instance: PluginContext? = null + + /** + * Get PluginContext singleton instance + * @return PluginContext instance + */ + fun getInstance(project : Project): PluginContext { + return project.getService(PluginContext::class.java) + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/RPCManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/RPCManager.kt new file mode 100644 index 0000000000..c1387241b2 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/RPCManager.kt @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.actors.* +import ai.kilocode.jetbrains.ipc.IMessagePassingProtocol +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol +import ai.kilocode.jetbrains.ipc.proxy.RPCProtocol +import ai.kilocode.jetbrains.ipc.proxy.logger.FileRPCProtocolLogger +import ai.kilocode.jetbrains.ipc.proxy.uri.IURITransformer +import ai.kilocode.jetbrains.theme.ThemeManager +import ai.kilocode.jetbrains.util.ProxyConfigUtil +import kotlinx.coroutines.runBlocking + +/** + * Responsible for managing RPC protocols, service registration and implementation, plugin lifecycle management + * This class is based on VSCode's rpcManager.js implementation + */ +class RPCManager( + private val protocol: IMessagePassingProtocol, + private val extensionManager: ExtensionManager, + private val uriTransformer: IURITransformer? = null, + private val project: Project +) { + private val logger = Logger.getInstance(RPCManager::class.java) + private val rpcProtocol: IRPCProtocol = RPCProtocol(protocol, FileRPCProtocolLogger(), uriTransformer) + + init { + setupDefaultProtocols() + setupExtensionRequiredProtocols() + setupWeCodeRequiredProtocols() + setupRooCodeFuncitonProtocols() + setupWebviewProtocols() + } + + /** + * Start initializing plugin environment + * Send configuration and workspace information to extension process + */ + fun startInitialize() { + try { + logger.info("Starting to initialize plugin environment") + runBlocking { + // Get ExtHostConfiguration proxy + val extHostConfiguration = + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostConfiguration) + + // Send empty configuration model + logger.info("Sending configuration information to extension process") + val themeName = + if (ThemeManager.getInstance().isDarkThemeForce()) "Default Dark Modern" else "Default Light Modern" + + // Create empty configuration model + val emptyMap = mapOf( + "contents" to emptyMap(), + "keys" to emptyList(), + "overrides" to emptyList() + ) + // Get proxy configuration + val httpProxyConfig = ProxyConfigUtil.getHttpProxyConfigForInitialization() + + // Build configuration contents + val contentsBuilder = mutableMapOf( + "workbench" to mapOf("colorTheme" to themeName) + ) + + // Add proxy configuration if available + httpProxyConfig?.let { + contentsBuilder["http"] = it + logger.info("Using proxy configuration for initialization: $it") + } + + val emptyConfigModel = mapOf( + "defaults" to mapOf( + "contents" to contentsBuilder, + "keys" to emptyList(), + "overrides" to emptyList() + ), + "policy" to emptyMap, + "application" to emptyMap, + "userLocal" to emptyMap, + "userRemote" to emptyMap, + "workspace" to emptyMap, + "folders" to emptyList(), + "configurationScopes" to emptyList() + ) + + // Directly call the interface method + extHostConfiguration.initializeConfiguration(emptyConfigModel) + + // Get ExtHostWorkspace proxy + val extHostWorkspace = rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostWorkspace) + + // Get current workspace data + logger.info("Getting current workspace data") + val workspaceData = project.getService(WorkspaceManager::class.java).getCurrentWorkspaceData() + + // If workspace data is obtained, send it to extension process, otherwise send null + if (workspaceData != null) { + logger.info("Sending workspace data to extension process: ${workspaceData.name}, folders: ${workspaceData.folders.size}") + extHostWorkspace.initializeWorkspace(workspaceData, true) + } else { + logger.info("No available workspace data, sending null to extension process") + extHostWorkspace.initializeWorkspace(null, true) + } + + // Initialize workspace + logger.info("Workspace initialization completed") + } + } catch (e: Exception) { + logger.error("Failed to initialize plugin environment: ${e.message}", e) + } + } + + /** + * Set up default protocol handlers + * These protocols are required for extHost process startup and initialization + */ + private fun setupDefaultProtocols() { + logger.info("Setting up default protocol handlers") + PluginContext.getInstance(project).setRPCProtocol(rpcProtocol) + + // MainThreadErrors + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadErrors, MainThreadErrors()) + + // MainThreadConsole + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadConsole, MainThreadConsole()) + + // MainThreadLogger + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadLogger, MainThreadLogger()) + + // MainThreadCommands + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadCommands, MainThreadCommands(project)) + + // MainThreadDebugService + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadDebugService, MainThreadDebugService()) + + // MainThreadConfiguration + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadConfiguration, MainThreadConfiguration()) + } + + /** + * Set up protocol handlers required for plugin package general loading process + */ + private fun setupExtensionRequiredProtocols() { + logger.info("Setting up required protocol handlers for plugins") + + // MainThreadExtensionService + val mainThreadExtensionService = MainThreadExtensionService(extensionManager, rpcProtocol) + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadExtensionService, mainThreadExtensionService) + + // MainThreadTelemetry + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadTelemetry, MainThreadTelemetry()) + + // MainThreadTerminalShellIntegration - use new architecture, pass project parameter + rpcProtocol.set( + ServiceProxyRegistry.MainContext.MainThreadTerminalShellIntegration, + MainThreadTerminalShellIntegration(project) + ) + + // MainThreadTerminalService - use new architecture, pass project parameter + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadTerminalService, MainThreadTerminalService(project)) + + // MainThreadTask + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadTask, MainThreadTask()) + + // MainThreadSearch + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadSearch, MainThreadSearch()) + + // MainThreadWindow + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadWindow, MainThreadWindow(project)) + + // MainThreadDiaglogs + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadDialogs, MainThreadDiaglogs()) + + // MainThreadLanguageModelTools + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadLanguageModelTools, MainThreadLanguageModelTools()) + + // MainThreadClipboard + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadClipboard, MainThreadClipboard()) + + //MainThreadBulkEdits + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadBulkEdits, MainThreadBulkEdits(project)) + + //MainThreadEditorTabs + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadEditorTabs, MainThreadEditorTabs(project)) + + //MainThreadDocuments + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadDocuments, MainThreadDocuments(project)) + } + + /** + * Set up protocol handlers required for WeCode plugin + */ + private fun setupWeCodeRequiredProtocols() { + logger.info("Setting up required protocol handlers for WeCode") + + // MainThreadTextEditors + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadTextEditors, MainThreadTextEditors(project)) + + // MainThreadStorage + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadStorage, MainThreadStorage()) + + // MainThreadOutputService + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadOutputService, MainThreadOutputService()) + + // MainThreadWebviewViews + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadWebviewViews, MainThreadWebviewViews(project)) + + // MainThreadDocumentContentProviders + rpcProtocol.set( + ServiceProxyRegistry.MainContext.MainThreadDocumentContentProviders, + MainThreadDocumentContentProviders() + ) + + // MainThreadUrls + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadUrls, MainThreadUrls()) + + // MainThreadLanguageFeatures + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadLanguageFeatures, MainThreadLanguageFeatures()) + + // MainThreadFileSystem + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadFileSystem, MainThreadFileSystem()) + + //MainThreadMessageServiceShape + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadMessageService, MainThreadMessageService()) + } + + private fun setupRooCodeFuncitonProtocols() { + logger.info("Setting up protocol handlers required for RooCode specific functionality") + + // MainThreadFileSystemEventService + rpcProtocol.set( + ServiceProxyRegistry.MainContext.MainThreadFileSystemEventService, + MainThreadFileSystemEventService() + ) + + // MainThreadSecretState + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadSecretState, MainThreadSecretState()) + } + + private fun setupWebviewProtocols() { + logger.info("Setting up protocol handlers required for Webview") + // MainThreadWebviews + rpcProtocol.set(ServiceProxyRegistry.MainContext.MainThreadWebviews, MainThreadWebviews(project)) + } + + /** + * Get RPC protocol instance + */ + fun getRPCProtocol(): IRPCProtocol { + return rpcProtocol + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ServiceProxyRegistry.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ServiceProxyRegistry.kt new file mode 100644 index 0000000000..98d555212d --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/ServiceProxyRegistry.kt @@ -0,0 +1,346 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.actors.* +import ai.kilocode.jetbrains.ipc.proxy.createProxyIdentifier +import ai.kilocode.jetbrains.ipc.proxy.interfaces.* + +/** + * Service proxy registry class, centrally manages registration of all service proxies + */ +@Service(Service.Level.PROJECT) +class ServiceProxyRegistry private constructor() { + private val logger = Logger.getInstance(this::class.java) + +// companion object { +// private val instance = ServiceProxyRegistry() +// +// fun getInstance(): ServiceProxyRegistry { +// return instance +// } +// } +// + /** + * Initialize and register all service proxies + */ + fun initialize() { + // Initialize all proxy identifiers + initializeAllProxies() + } + + /** + * Initialize all proxy identifiers + * Ensure all services are initialized when created + */ + private fun initializeAllProxies() { + logger.info("Initialize all proxy identifiers") + + // Main thread service proxies + val mainThreadProxies = listOf( + MainContext.MainThreadAuthentication, + MainContext.MainThreadBulkEdits, + MainContext.MainThreadLanguageModels, + MainContext.MainThreadEmbeddings, + MainContext.MainThreadChatAgents2, + MainContext.MainThreadCodeMapper, + MainContext.MainThreadLanguageModelTools, + MainContext.MainThreadClipboard, + MainContext.MainThreadCommands, + MainContext.MainThreadComments, + MainContext.MainThreadConfiguration, + MainContext.MainThreadConsole, + MainContext.MainThreadDebugService, + MainContext.MainThreadDecorations, + MainContext.MainThreadDiagnostics, + MainContext.MainThreadDialogs, + MainContext.MainThreadDocuments, + MainContext.MainThreadDocumentContentProviders, + MainContext.MainThreadTextEditors, + MainContext.MainThreadEditorInsets, + MainContext.MainThreadEditorTabs, + MainContext.MainThreadErrors, + MainContext.MainThreadTreeViews, + MainContext.MainThreadDownloadService, + MainContext.MainThreadLanguageFeatures, + MainContext.MainThreadLanguages, + MainContext.MainThreadLogger, + MainContext.MainThreadMessageService, + MainContext.MainThreadOutputService, + MainContext.MainThreadProgress, + MainContext.MainThreadQuickDiff, + MainContext.MainThreadQuickOpen, + MainContext.MainThreadStatusBar, + MainContext.MainThreadSecretState, + MainContext.MainThreadStorage, + MainContext.MainThreadSpeech, + MainContext.MainThreadTelemetry, + MainContext.MainThreadTerminalService, + MainContext.MainThreadTerminalShellIntegration, + MainContext.MainThreadWebviews, + MainContext.MainThreadWebviewPanels, + MainContext.MainThreadWebviewViews, + MainContext.MainThreadCustomEditors, + MainContext.MainThreadUrls, + MainContext.MainThreadUriOpeners, + MainContext.MainThreadProfileContentHandlers, + MainContext.MainThreadWorkspace, + MainContext.MainThreadFileSystem, + MainContext.MainThreadFileSystemEventService, + MainContext.MainThreadExtensionService, + MainContext.MainThreadSCM, + MainContext.MainThreadSearch, + MainContext.MainThreadShare, + MainContext.MainThreadTask, + MainContext.MainThreadWindow, + MainContext.MainThreadLabelService, + MainContext.MainThreadNotebook, + MainContext.MainThreadNotebookDocuments, + MainContext.MainThreadNotebookEditors, + MainContext.MainThreadNotebookKernels, + MainContext.MainThreadNotebookRenderers, + MainContext.MainThreadInteractive, + MainContext.MainThreadTheming, + MainContext.MainThreadTunnelService, + MainContext.MainThreadManagedSockets, + MainContext.MainThreadTimeline, + MainContext.MainThreadTesting, + MainContext.MainThreadLocalization, + MainContext.MainThreadMcp, + MainContext.MainThreadAiRelatedInformation, + MainContext.MainThreadAiEmbeddingVector, + MainContext.MainThreadChatStatus + ) + + // Extension host service proxies + val extHostProxies = listOf( + ExtHostContext.ExtHostCodeMapper, + ExtHostContext.ExtHostCommands, + ExtHostContext.ExtHostConfiguration, + ExtHostContext.ExtHostDiagnostics, + ExtHostContext.ExtHostDebugService, + ExtHostContext.ExtHostDecorations, + ExtHostContext.ExtHostDocumentsAndEditors, + ExtHostContext.ExtHostDocuments, + ExtHostContext.ExtHostDocumentContentProviders, + ExtHostContext.ExtHostDocumentSaveParticipant, + ExtHostContext.ExtHostEditors, + ExtHostContext.ExtHostTreeViews, + ExtHostContext.ExtHostFileSystem, + ExtHostContext.ExtHostFileSystemInfo, + ExtHostContext.ExtHostFileSystemEventService, + ExtHostContext.ExtHostLanguages, + ExtHostContext.ExtHostLanguageFeatures, + ExtHostContext.ExtHostQuickOpen, + ExtHostContext.ExtHostQuickDiff, + ExtHostContext.ExtHostStatusBar, + ExtHostContext.ExtHostShare, + ExtHostContext.ExtHostExtensionService, + ExtHostContext.ExtHostLogLevelServiceShape, + ExtHostContext.ExtHostTerminalService, + ExtHostContext.ExtHostTerminalShellIntegration, + ExtHostContext.ExtHostSCM, + ExtHostContext.ExtHostSearch, + ExtHostContext.ExtHostTask, + ExtHostContext.ExtHostWorkspace, + ExtHostContext.ExtHostWindow, + ExtHostContext.ExtHostWebviews, + ExtHostContext.ExtHostWebviewPanels, + ExtHostContext.ExtHostCustomEditors, + ExtHostContext.ExtHostWebviewViews, + ExtHostContext.ExtHostEditorInsets, + ExtHostContext.ExtHostEditorTabs, + ExtHostContext.ExtHostProgress, + ExtHostContext.ExtHostComments, + ExtHostContext.ExtHostSecretState, + ExtHostContext.ExtHostStorage, + ExtHostContext.ExtHostUrls, + ExtHostContext.ExtHostUriOpeners, + ExtHostContext.ExtHostProfileContentHandlers, + ExtHostContext.ExtHostOutputService, + ExtHostContext.ExtHostLabelService, + ExtHostContext.ExtHostNotebook, + ExtHostContext.ExtHostNotebookDocuments, + ExtHostContext.ExtHostNotebookEditors, + ExtHostContext.ExtHostNotebookKernels, + ExtHostContext.ExtHostNotebookRenderers, + ExtHostContext.ExtHostNotebookDocumentSaveParticipant, + ExtHostContext.ExtHostInteractive, + ExtHostContext.ExtHostChatAgents2, + ExtHostContext.ExtHostLanguageModelTools, + ExtHostContext.ExtHostChatProvider, + ExtHostContext.ExtHostSpeech, + ExtHostContext.ExtHostEmbeddings, + ExtHostContext.ExtHostAiRelatedInformation, + ExtHostContext.ExtHostAiEmbeddingVector, + ExtHostContext.ExtHostTheming, + ExtHostContext.ExtHostTunnelService, + ExtHostContext.ExtHostManagedSockets, + ExtHostContext.ExtHostAuthentication, + ExtHostContext.ExtHostTimeline, + ExtHostContext.ExtHostTesting, + ExtHostContext.ExtHostTelemetry, + ExtHostContext.ExtHostLocalization, + ExtHostContext.ExtHostMcp + ) + + logger.info("Initialized ${mainThreadProxies.size} main thread services and ${extHostProxies.size} extension host services") + } + + + /** + * Main thread context - Context ID enum values defined in VSCode + */ + object MainContext { + val MainThreadAuthentication = createProxyIdentifier("MainThreadAuthentication") + val MainThreadBulkEdits = createProxyIdentifier("MainThreadBulkEdits") + val MainThreadLanguageModels = createProxyIdentifier("MainThreadLanguageModels") + val MainThreadEmbeddings = createProxyIdentifier("MainThreadEmbeddings") + val MainThreadChatAgents2 = createProxyIdentifier("MainThreadChatAgents2") + val MainThreadCodeMapper = createProxyIdentifier("MainThreadCodeMapper") + val MainThreadLanguageModelTools = createProxyIdentifier("MainThreadLanguageModelTools") + val MainThreadClipboard = createProxyIdentifier("MainThreadClipboard") + val MainThreadCommands = createProxyIdentifier("MainThreadCommands") + val MainThreadComments = createProxyIdentifier("MainThreadComments") + val MainThreadConfiguration = createProxyIdentifier("MainThreadConfiguration") + val MainThreadConsole = createProxyIdentifier("MainThreadConsole") + val MainThreadDebugService = createProxyIdentifier("MainThreadDebugService") + val MainThreadDecorations = createProxyIdentifier("MainThreadDecorations") + val MainThreadDiagnostics = createProxyIdentifier("MainThreadDiagnostics") + val MainThreadDialogs = createProxyIdentifier("MainThreadDiaglogs") + val MainThreadDocuments = createProxyIdentifier("MainThreadDocuments") + val MainThreadDocumentContentProviders = createProxyIdentifier("MainThreadDocumentContentProviders") + val MainThreadTextEditors = createProxyIdentifier("MainThreadTextEditors") + val MainThreadEditorInsets = createProxyIdentifier("MainThreadEditorInsets") + val MainThreadEditorTabs = createProxyIdentifier("MainThreadEditorTabs") + val MainThreadErrors = createProxyIdentifier("MainThreadErrors") + val MainThreadTreeViews = createProxyIdentifier("MainThreadTreeViews") + val MainThreadDownloadService = createProxyIdentifier("MainThreadDownloadService") + val MainThreadLanguageFeatures = createProxyIdentifier("MainThreadLanguageFeatures") + val MainThreadLanguages = createProxyIdentifier("MainThreadLanguages") + val MainThreadLogger = createProxyIdentifier("MainThreadLogger") + val MainThreadMessageService = createProxyIdentifier("MainThreadMessageService") + val MainThreadOutputService = createProxyIdentifier("MainThreadOutputService") + val MainThreadProgress = createProxyIdentifier("MainThreadProgress") + val MainThreadQuickDiff = createProxyIdentifier("MainThreadQuickDiff") + val MainThreadQuickOpen = createProxyIdentifier("MainThreadQuickOpen") + val MainThreadStatusBar = createProxyIdentifier("MainThreadStatusBar") + val MainThreadSecretState = createProxyIdentifier("MainThreadSecretState") + val MainThreadStorage = createProxyIdentifier("MainThreadStorage") + val MainThreadSpeech = createProxyIdentifier("MainThreadSpeechProvider") + val MainThreadTelemetry = createProxyIdentifier("MainThreadTelemetry") + val MainThreadTerminalService = createProxyIdentifier("MainThreadTerminalService") + val MainThreadTerminalShellIntegration = createProxyIdentifier("MainThreadTerminalShellIntegration") + val MainThreadWebviews = createProxyIdentifier("MainThreadWebviews") + val MainThreadWebviewPanels = createProxyIdentifier("MainThreadWebviewPanels") + val MainThreadWebviewViews = createProxyIdentifier("MainThreadWebviewViews") + val MainThreadCustomEditors = createProxyIdentifier("MainThreadCustomEditors") + val MainThreadUrls = createProxyIdentifier("MainThreadUrls") + val MainThreadUriOpeners = createProxyIdentifier("MainThreadUriOpeners") + val MainThreadProfileContentHandlers = createProxyIdentifier("MainThreadProfileContentHandlers") + val MainThreadWorkspace = createProxyIdentifier("MainThreadWorkspace") + val MainThreadFileSystem = createProxyIdentifier("MainThreadFileSystem") + val MainThreadFileSystemEventService = createProxyIdentifier("MainThreadFileSystemEventService") + val MainThreadExtensionService = createProxyIdentifier("MainThreadExtensionService") + val MainThreadSCM = createProxyIdentifier("MainThreadSCM") + val MainThreadSearch = createProxyIdentifier("MainThreadSearch") + val MainThreadShare = createProxyIdentifier("MainThreadShare") + val MainThreadTask = createProxyIdentifier("MainThreadTask") + val MainThreadWindow = createProxyIdentifier("MainThreadWindow") + val MainThreadLabelService = createProxyIdentifier("MainThreadLabelService") + val MainThreadNotebook = createProxyIdentifier("MainThreadNotebook") + val MainThreadNotebookDocuments = createProxyIdentifier("MainThreadNotebookDocumentsShape") + val MainThreadNotebookEditors = createProxyIdentifier("MainThreadNotebookEditorsShape") + val MainThreadNotebookKernels = createProxyIdentifier("MainThreadNotebookKernels") + val MainThreadNotebookRenderers = createProxyIdentifier("MainThreadNotebookRenderers") + val MainThreadInteractive = createProxyIdentifier("MainThreadInteractive") + val MainThreadTheming = createProxyIdentifier("MainThreadTheming") + val MainThreadTunnelService = createProxyIdentifier("MainThreadTunnelService") + val MainThreadManagedSockets = createProxyIdentifier("MainThreadManagedSockets") + val MainThreadTimeline = createProxyIdentifier("MainThreadTimeline") + val MainThreadTesting = createProxyIdentifier("MainThreadTesting") + val MainThreadLocalization = createProxyIdentifier("MainThreadLocalizationShape") + val MainThreadMcp = createProxyIdentifier("MainThreadMcpShape") + val MainThreadAiRelatedInformation = createProxyIdentifier("MainThreadAiRelatedInformation") + val MainThreadAiEmbeddingVector = createProxyIdentifier("MainThreadAiEmbeddingVector") + val MainThreadChatStatus = createProxyIdentifier("MainThreadChatStatus") + } + + /** + * Extension host context - Extension host context ID enum values defined in VSCode + */ + object ExtHostContext { + val ExtHostCodeMapper = createProxyIdentifier("ExtHostCodeMapper") + val ExtHostCommands = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostCommandsProxy") + val ExtHostConfiguration = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostConfigurationProxy") + val ExtHostDiagnostics = createProxyIdentifier("ExtHostDiagnostics") + val ExtHostDebugService = createProxyIdentifier("ExtHostDebugService") + val ExtHostDecorations = createProxyIdentifier("ExtHostDecorations") + val ExtHostDocumentsAndEditors = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostDocumentsAndEditorsProxy") + val ExtHostDocuments = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostDocumentsProxy") + val ExtHostDocumentContentProviders = createProxyIdentifier("ExtHostDocumentContentProviders") + val ExtHostDocumentSaveParticipant = createProxyIdentifier("ExtHostDocumentSaveParticipant") + val ExtHostEditors = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostEditorsProxy") + val ExtHostTreeViews = createProxyIdentifier("ExtHostTreeViews") + val ExtHostFileSystem = createProxyIdentifier("ExtHostFileSystem") + val ExtHostFileSystemInfo = createProxyIdentifier("ExtHostFileSystemInfo") + val ExtHostFileSystemEventService = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostFileSystemEventServiceProxy") + val ExtHostLanguages = createProxyIdentifier("ExtHostLanguages") + val ExtHostLanguageFeatures = createProxyIdentifier("ExtHostLanguageFeatures") + val ExtHostQuickOpen = createProxyIdentifier("ExtHostQuickOpen") + val ExtHostQuickDiff = createProxyIdentifier("ExtHostQuickDiff") + val ExtHostStatusBar = createProxyIdentifier("ExtHostStatusBar") + val ExtHostShare = createProxyIdentifier("ExtHostShare") + val ExtHostExtensionService = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostExtensionServiceProxy") + val ExtHostLogLevelServiceShape = createProxyIdentifier("ExtHostLogLevelServiceShape") + val ExtHostTerminalService = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostTerminalServiceProxy") + val ExtHostTerminalShellIntegration = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostTerminalShellIntegrationProxy") + val ExtHostSCM = createProxyIdentifier("ExtHostSCM") + val ExtHostSearch = createProxyIdentifier("ExtHostSearch") + val ExtHostTask = createProxyIdentifier("ExtHostTask") + val ExtHostWorkspace = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostWorkspaceProxy") + val ExtHostWindow = createProxyIdentifier("ExtHostWindow") + val ExtHostWebviews = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostWebviewsProxy") + val ExtHostWebviewPanels = createProxyIdentifier("ExtHostWebviewPanels") + val ExtHostCustomEditors = createProxyIdentifier("ExtHostCustomEditors") + val ExtHostWebviewViews = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostWebviewViewsProxy") + val ExtHostEditorInsets = createProxyIdentifier("ExtHostEditorInsets") + val ExtHostEditorTabs = createProxyIdentifier("ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostEditorTabsProxy") + val ExtHostProgress = createProxyIdentifier("ExtHostProgress") + val ExtHostComments = createProxyIdentifier("ExtHostComments") + val ExtHostSecretState = createProxyIdentifier("ExtHostSecretState") + val ExtHostStorage = createProxyIdentifier("ExtHostStorage") + val ExtHostUrls = createProxyIdentifier("ExtHostUrls") + val ExtHostUriOpeners = createProxyIdentifier("ExtHostUriOpeners") + val ExtHostProfileContentHandlers = createProxyIdentifier("ExtHostProfileContentHandlers") + val ExtHostOutputService = createProxyIdentifier("ExtHostOutputService") + val ExtHostLabelService = createProxyIdentifier("ExtHostLabelService") + val ExtHostNotebook = createProxyIdentifier("ExtHostNotebook") + val ExtHostNotebookDocuments = createProxyIdentifier("ExtHostNotebookDocuments") + val ExtHostNotebookEditors = createProxyIdentifier("ExtHostNotebookEditors") + val ExtHostNotebookKernels = createProxyIdentifier("ExtHostNotebookKernels") + val ExtHostNotebookRenderers = createProxyIdentifier("ExtHostNotebookRenderers") + val ExtHostNotebookDocumentSaveParticipant = createProxyIdentifier("ExtHostNotebookDocumentSaveParticipant") + val ExtHostInteractive = createProxyIdentifier("ExtHostInteractive") + val ExtHostChatAgents2 = createProxyIdentifier("ExtHostChatAgents") + val ExtHostLanguageModelTools = createProxyIdentifier("ExtHostChatSkills") + val ExtHostChatProvider = createProxyIdentifier("ExtHostChatProvider") + val ExtHostSpeech = createProxyIdentifier("ExtHostSpeech") + val ExtHostEmbeddings = createProxyIdentifier("ExtHostEmbeddings") + val ExtHostAiRelatedInformation = createProxyIdentifier("ExtHostAiRelatedInformation") + val ExtHostAiEmbeddingVector = createProxyIdentifier("ExtHostAiEmbeddingVector") + val ExtHostTheming = createProxyIdentifier("ExtHostTheming") + val ExtHostTunnelService = createProxyIdentifier("ExtHostTunnelService") + val ExtHostManagedSockets = createProxyIdentifier("ExtHostManagedSockets") + val ExtHostAuthentication = createProxyIdentifier("ExtHostAuthentication") + val ExtHostTimeline = createProxyIdentifier("ExtHostTimeline") + val ExtHostTesting = createProxyIdentifier("ExtHostTesting") + val ExtHostTelemetry = createProxyIdentifier("ExtHostTelemetry") + val ExtHostLocalization = createProxyIdentifier("ExtHostLocalization") + val ExtHostMcp = createProxyIdentifier("ExtHostMcp") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/WorkspaceManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/WorkspaceManager.kt new file mode 100644 index 0000000000..3519026afd --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/core/WorkspaceManager.kt @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.core + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.util.io.FileUtil +import ai.kilocode.jetbrains.model.StaticWorkspaceData +import ai.kilocode.jetbrains.model.WorkspaceData +import ai.kilocode.jetbrains.model.WorkspaceFolder +import ai.kilocode.jetbrains.util.URI +import java.io.File +import java.util.* + +/** + * Workspace Manager + * Responsible for retrieving and managing IDEA workspace information. + * Provides functionality to access project workspace data and folders. + */ +@Service(Service.Level.PROJECT) +class WorkspaceManager(val project: Project) { + private val logger = Logger.getInstance(WorkspaceManager::class.java) + + /** + * Gets the current workspace data. + * @return Workspace data or null (if no project is open) + */ + fun getCurrentWorkspaceData(): WorkspaceData? { + return getProjectWorkspaceData(project) + } + + /** + * Gets workspace data for a specific project. + * + * @param project The project to get workspace data for + * @return Workspace data or null if the project is null + */ + fun getProjectWorkspaceData(project: Project): WorkspaceData? { + if (project == null) { + return null + } + + // Create workspace ID (using hash value of the project's base path) + val workspaceId = getWorkspaceId(project) + val workspaceName = project.name + + // Create static workspace data + val staticWorkspaceData = StaticWorkspaceData( + id = workspaceId, + name = workspaceName, + transient = false, + // Configuration can be the project's .idea directory or project configuration file + configuration = project.basePath?.let { URI.file("$it/.idea") }, + isUntitled = false + ) + + // Get workspace folders + val workspaceFolders = getWorkspaceFolders(project) + + return WorkspaceData(staticWorkspaceData, workspaceFolders) + } + + /** + * Gets the workspace ID for a project. + * + * @param project The project + * @return The workspace ID as a string + */ + private fun getWorkspaceId(project: Project): String { + // Use the hash value of the project path as ID + val basePath = project.basePath ?: return UUID.randomUUID().toString() + return basePath.hashCode().toString() + } + + /** + * Gets workspace folders for a project. + * + * @param project The project + * @return List of workspace folders + */ + private fun getWorkspaceFolders(project: Project): List { + val folders = mutableListOf() + val basePath = project.basePath ?: return folders + + // Add project root directory as the main workspace folder + folders.add(WorkspaceFolder( + uri = URI.file(basePath), + name = project.name, + index = 0 + )) + + // Get the virtual file for the project root directory - wrapped in ReadAction + val projectDir = ApplicationManager.getApplication().runReadAction { + LocalFileSystem.getInstance().findFileByPath(basePath) + } + if (projectDir == null || !projectDir.isDirectory) { + return folders + } + +// // Get subdirectories +// val contentRoots = projectDir.children +// +// // Filter files to get subfolders +// val subFolders = contentRoots.filter { file: VirtualFile -> +// file.isDirectory && +// !file.name.startsWith(".") && +// file.name !in listOf("out", "build", "target", "node_modules", ".idea", "dist", "bin", "obj") +// } +// +// // Add subfolders +// subFolders.forEachIndexed { index, folder -> +// if (folder.path != basePath) { +// folders.add(WorkspaceFolder( +// uri = URI.file(folder.path), +// name = folder.name, +// index = index + 1 // Start from 1, because 0 is the project root directory +// )) +// } +// } + + return folders + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorAndDocManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorAndDocManager.kt new file mode 100644 index 0000000000..749a635a83 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorAndDocManager.kt @@ -0,0 +1,644 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.diff.DiffContentFactory +import java.util.concurrent.ConcurrentHashMap +import com.intellij.diff.chains.DiffRequestChain +import com.intellij.diff.chains.SimpleDiffRequestChain +import com.intellij.diff.contents.DiffContent +import com.intellij.diff.contents.FileDocumentContentImpl +import com.intellij.diff.editor.ChainDiffVirtualFile +import com.intellij.diff.editor.DiffEditorTabFilesManager +import com.intellij.diff.editor.DiffRequestProcessorEditor +import com.intellij.diff.requests.SimpleDiffRequest +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.diff.DiffBundle +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.readText +import ai.kilocode.jetbrains.util.URI +import kotlinx.coroutines.* +import java.io.File +import java.io.FileInputStream +import java.lang.ref.WeakReference +import kotlin.math.max + +@Service(Service.Level.PROJECT) +class EditorAndDocManager(val project: Project) : Disposable { + + private val logger = Logger.getInstance(EditorAndDocManager::class.java) + private val ideaEditorListener : FileEditorManagerListener + + private val messageBusConnection = project.messageBus.connect() + + private var state = DocumentsAndEditorsState() + private var lastNotifiedState = DocumentsAndEditorsState() + private var editorHandles = ConcurrentHashMap() + private val ideaOpenedEditor = ConcurrentHashMap() + private var tabManager : TabStateManager = TabStateManager(project) + + private var job: Job? = null + private val editorStateService:EditorStateService = EditorStateService(project) + + init { + ideaEditorListener = object : FileEditorManagerListener { + // Update and synchronize editor state when file is opened + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + + source.getEditorList(file).forEach {editor-> + if(file == editor.file){ + // Record and synchronize + if (isSubClassof(editor, "com.intellij.diff.editor.DiffEditorBase") || isSubClassof(editor, "com.intellij.diff.editor.DiffFileEditorBase")){ + if(editor.filesToRefresh.size == 1){ + val reffile = editor.filesToRefresh[0] + val uri = URI.file(reffile.path) + val older = getEditorHandleByUri(uri,true) + if(older != null && older.ideaEditor == null){ + older.ideaEditor = editor + } + } + }else{ + val older = getEditorHandleByUri(URI.file(file.path),false) + if(older == null){ + val uri = URI.file(editor.file.path) + val isText = FileDocumentManager.getInstance().getDocument(file) != null + CoroutineScope(Dispatchers.IO).launch { + val handle = sync2ExtHost(uri, false,isText) + handle.ideaEditor = editor + val group = tabManager.createTabGroup(EditorGroupColumn.beside.value,true) + val options = TabOptions(isActive = true) + val tab = group.addTab(EditorTabInput(uri,uri.path, ""), options) + handle.tab = tab + handle.group = group + } + } + } + } + } + + } + + private fun isSubClassof(editor: FileEditor?, s: String): Boolean { + if (editor == null) return false + var clazz: Class<*>? = editor.javaClass + while (clazz != null) { + if (clazz.name == s) { + return true + } + clazz = clazz.superclass + } + return false + } + + override fun fileClosed(source: FileEditorManager, cFile: VirtualFile) { + logger.info("file closed $cFile") + var diff = false + var path = cFile.path + if(cFile is ChainDiffVirtualFile){ + (cFile.chain.requests[0] as? SimpleDiffRequest).let { + it?.contents?.forEach{ content-> + if( content is FileDocumentContentImpl){ + path = content.file.path + diff = true + } + } + } + } + getEditorHandleByUri(URI.file(path),diff)?.let { handle -> + handle.setActive(false) + logger.info("file closed handle $handle") + removeEditor(handle.id) + } + } + } + messageBusConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, ideaEditorListener) + } + + + fun initCurrentIdeaEditor(){ + CoroutineScope(Dispatchers.Default).launch { + FileEditorManager.getInstance(project).allEditors.forEach {editor-> + // Record and synchronize + if (editor is FileEditor) { + val uri = URI.file(editor.file.path) + val handle = sync2ExtHost(uri,false) + handle.ideaEditor = editor + val group = tabManager.createTabGroup(EditorGroupColumn.beside.value,true) + val options = TabOptions(isActive = true) + val tab = group.addTab(EditorTabInput(uri,uri.path, ""), options) + handle.tab = tab + handle.group = group + } + } + } + } + + suspend fun sync2ExtHost(documentUri: URI, diff : Boolean,isText : Boolean = true,options: ResolvedTextEditorConfiguration = ResolvedTextEditorConfiguration()):EditorHolder { + val eh = getEditorHandleByUri(documentUri,diff) + if (eh != null) + return eh + // Generate unique ID + val id = java.util.UUID.randomUUID().toString() + + val documentState = openDocument(documentUri,isText) + + // Create editor state + val editorState = TextEditorAddData( + id = id, + documentUri = documentUri, + options = options, + selections = emptyList(), + visibleRanges = emptyList(), + editorPosition = null + ) + // Create editor handle + val handle = EditorHolder(id, editorState, documentState, diff, this,) + // Update state + state.documents[documentUri] = documentState + state.editors[id] = editorState + editorHandles[id] = handle + handle.setActive(true) + processUpdates() + return handle + } + + fun createContent(uri: URI, project: Project,type: FileType?=null) : DiffContent?{ + val path = uri.path + val scheme = uri.scheme + val query = uri.query + if(scheme != null && scheme.isNotEmpty()){ + val contentFactory = DiffContentFactory.getInstance() + if(scheme == "file"){ + val vfs = LocalFileSystem.getInstance() + val fileIO = File(path) + if(!fileIO.exists()){ + fileIO.createNewFile() + vfs.refreshIoFiles(listOf(fileIO.parentFile)) + } + val file = vfs.refreshAndFindFileByPath(path) ?: run { + logger.warn("File not found: $path") + return null + } + return contentFactory.create(project, file) + }else if(scheme == "cline-diff"){ + val string = if(query != null){ + val bytes = java.util.Base64.getDecoder().decode(query) + String(bytes) + }else "" + val content = contentFactory.create(project, string,type) + return content + } + return null + }else{ + return null + } + } + + + suspend fun openEditor(documentUri: URI ,options: ResolvedTextEditorConfiguration = ResolvedTextEditorConfiguration()): EditorHolder { + val fileEditorManager = FileEditorManager.getInstance(project) + val path = documentUri.path + var ideaEditor : Array? = null + + val vfs = LocalFileSystem.getInstance() + val file = vfs.findFileByPath(path) + file?.let { + ApplicationManager.getApplication().invokeAndWait { + ideaEditor = fileEditorManager.openFile(it, true) + } + } + val eh = getEditorHandleByUri(documentUri,false) + if (eh != null) + return eh + val handle = sync2ExtHost(documentUri, false, true, options) + ideaEditor?.let { + if(it.isNotEmpty()){ + handle.ideaEditor = it[0] + } + } + val group = tabManager.createTabGroup(EditorGroupColumn.beside.value,true) + val options = TabOptions(isActive = true) + val tab = group.addTab(EditorTabInput(documentUri,documentUri.path, ""), options) + handle.tab = tab + handle.group = group + return handle + } + + suspend fun openDiffEditor(left: URI,documentUri: URI,title:String, options: ResolvedTextEditorConfiguration = ResolvedTextEditorConfiguration()): EditorHolder { + val content2 = createContent(documentUri, project) + val content1 = createContent(left, project,content2?.contentType) + if (content1 != null && content2 != null){ + val request = SimpleDiffRequest(title, content1, content2, left.path, documentUri.path) + var ideaEditor : Array? = null + ApplicationManager.getApplication().invokeAndWait{ + LocalFileSystem.getInstance().findFileByPath(documentUri.path) + ?.let { + ApplicationManager.getApplication().runReadAction { FileEditorManager.getInstance(project).closeFile(it) } + } + + val diffEditorTabFilesManager = DiffEditorTabFilesManager.getInstance(project) + val requestChain: DiffRequestChain = SimpleDiffRequestChain(request) + val diffFile = ChainDiffVirtualFile(requestChain, DiffBundle.message("label.default.diff.editor.tab.name", *arrayOfNulls(0))) + ideaEditor = diffEditorTabFilesManager.showDiffFile(diffFile, true) + } + ideaEditor?.let { + val handle = sync2ExtHost(documentUri, true, true, options) + if(it.isNotEmpty()){ + handle.ideaEditor = it[0] + } + handle.title = title + + val group = tabManager.createTabGroup(EditorGroupColumn.beside.value,true) + val options = TabOptions(isActive = true) + val tab = group.addTab(TextDiffTabInput(left,documentUri), options) + handle.tab = tab + handle.group = group + return handle + }?:run { + val handle = sync2ExtHost(documentUri, true, true,options) + return handle + } + }else{ + val handle = sync2ExtHost(documentUri, true, true, options) + return handle + } + } + + fun getEditorHandleByUri(resource: URI,diff: Boolean): EditorHolder? { + val values = editorHandles.values + for (handle in values){ + if (handle.document.uri.path == resource.path && handle.diff == diff) { + return handle + } + } + return null + } + + fun getEditorHandleByUri(resource: URI): List { + val list = mutableListOf() + val values = editorHandles.values + for (handle in values){ + if (handle.document.uri.path == resource.path ) { + list.add(handle) + } + } + return list + } + + + fun getEditorHandleById(id: String): EditorHolder? { + return editorHandles[id] + } + + suspend fun openDocument(uri: URI,isText: Boolean = true): ModelAddedData { + // Update document content - Use ReadAction to wrap file system operations + val text = if (isText){ + ApplicationManager.getApplication().runReadAction { + val vfs = LocalFileSystem.getInstance() + val file = vfs.findFileByPath(uri.path) + if(file != null){ + val len = file.length + if (len > 3 * 1024 * 1024) { + val buffer = ByteArray(3 * 1024 * 1024) + val inputStream = FileInputStream(File(file.path)) + val bytesRead = inputStream.read(buffer) + inputStream.close() + String(buffer, 0, bytesRead, Charsets.UTF_8) + } else { + file.readText() + } + }else{ + "" + } + } + }else{ + "bin" + } + if (state.documents[uri] == null) { + val document = ModelAddedData( + uri = uri, + versionId = 1, + lines = text.lines(), + EOL = "\n", + languageId = "", + isDirty = false, + encoding = "utf8" + ) + state.documents[uri] = document + processUpdates() + } + return state.documents[uri]!! + } + + fun removeEditor(id: String) { + state.editors.remove(id) + val handler = editorHandles.remove(id) + var needDeleteDoc = true + val values = editorHandles.values + values.forEach { value-> + if (value.document.uri == handler?.document?.uri) { + needDeleteDoc = false + } + } + if(needDeleteDoc){ + state.documents.remove(handler?.document?.uri) + } + if (state.activeEditorId == id) { + state.activeEditorId = null + } + scheduleUpdate() + + handler?.tab?.let { + tabManager.removeTab(it.id) + } + handler?.group?.let { + tabManager.removeGroup(it.groupId) + } + } + + //from exthost + fun closeTab(id:String){ + val tab = tabManager.removeTab(id) + tab?.let { tab-> + val handler = getEditorHandleByTabId(id) + handler?.let { + state.editors.remove(it.id) + val handler = editorHandles.remove(it.id) + this.state.documents.remove(it.document.uri) + if (state.activeEditorId == it.id) { + state.activeEditorId = null + } + handler?.let {h-> + if(h.ideaEditor != null){ + ApplicationManager.getApplication().invokeAndWait { + h.ideaEditor?.dispose() + } + }else{ + ApplicationManager.getApplication().invokeAndWait { + FileEditorManager.getInstance(project).allEditors.forEach { + if(it is DiffRequestProcessorEditor && handler.diff){ + val differ = it + differ.processor.activeRequest?.let { req -> + for (filesToRefresh in req.filesToRefresh) { + if(filesToRefresh.path == handler.document.uri.path){ + differ.dispose() + } + } + } + } + } + + } + } + } + scheduleUpdate() + } + } + } + + fun closeGroup(id:Int){ + tabManager.removeGroup(id); + } + + private fun getEditorHandleByTabId(id: String): EditorHolder? { + for ((_, handle) in editorHandles){ + if (handle.tab != null && handle.tab?.id == id) { + return handle + } + } + return null + } + + override fun dispose() { + messageBusConnection.dispose() + } + + fun didUpdateActive(handle: EditorHolder) { + if (handle.isActive) { + setActiveEditor(id = handle.id) + } else if (state.activeEditorId == handle.id) { + // If the current active editor is set to inactive, select the first active editor + editorHandles.values.firstOrNull { it.isActive }?.let { + setActiveEditor(id = it.id) + } + } + } + + private fun setActiveEditor(id: String) { + state.activeEditorId = id + scheduleUpdate() + } + + private fun scheduleUpdate() { + job?.cancel() + job = CoroutineScope(Dispatchers.IO).launch { + delay(10) + processUpdates() + } + } + private fun copy(state: DocumentsAndEditorsState): DocumentsAndEditorsState { + val rst = DocumentsAndEditorsState( + editors = ConcurrentHashMap(), + documents = ConcurrentHashMap(), + activeEditorId = state.activeEditorId + ) + rst.editors.putAll(state.editors) + rst.documents.putAll(state.documents) + return rst + } + private suspend fun processUpdates() { + val delta = state.delta(lastNotifiedState) + + // Update last notified state + lastNotifiedState = copy(state) + + // Send document and editor change notifications + delta.itemsDelta?.let { itemsDelta -> + + editorStateService.acceptDocumentsAndEditorsDelta(itemsDelta) + } + + // Send editor property change notifications + if (delta.editorDeltas.isNotEmpty()) { + editorStateService.acceptEditorPropertiesChanged( delta.editorDeltas) + } + + // Send document content change notifications + if (delta.documentDeltas.isNotEmpty()) { + editorStateService.acceptModelChanged(delta.documentDeltas) + } + } + + suspend fun updateDocumentAsync(document: ModelAddedData) { + // Check if the document exists + if (state.documents[document.uri] != null) { + state.documents[document.uri] = document + processUpdates() + } + } + + fun updateDocument(document: ModelAddedData) { + // Check if the document exists + if (state.documents[document.uri] != null) { + state.documents[document.uri] = document + scheduleUpdate() + } + } + + suspend fun syncUpdates() { + job?.cancel() + processUpdates() + } + + fun updateEditor(state: TextEditorAddData) { + if (this.state.editors[state.id] != null) { + this.state.editors[state.id] = state + scheduleUpdate() + } + } + + fun getIdeaDiffEditor(uri: URI): WeakReference? { + val editor = ideaOpenedEditor[uri.path] ?: return null + return WeakReference(editor) + } + + fun onIdeaDiffEditorCreated(url: URI, editor: Editor) { + ideaOpenedEditor.put(url.path,editor); + } + + fun onIdeaDiffEditorReleased(url: URI, editor: Editor) { + ideaOpenedEditor.remove(url.path) + } + +} + + +data class DocumentsAndEditorsState ( + var editors: MutableMap = ConcurrentHashMap(), + var documents: MutableMap = ConcurrentHashMap(), + var activeEditorId: String? = null +){ + + fun delta(lastState: DocumentsAndEditorsState): Delta { + // Calculate document changes + val currentDocumentUrls = documents.keys.toSet() + val lastDocumentUrls = lastState.documents.keys.toSet() + + val removedUrls = lastDocumentUrls - currentDocumentUrls + val addedUrls = currentDocumentUrls - lastDocumentUrls + + val addedDocuments = addedUrls.mapNotNull { documents[it] } + + // Calculate editor changes + val addedEditors = mutableListOf() + val editorDeltas = mutableMapOf() + + val currentEditorIds = editors.keys.toSet() + val lastEditorIds = lastState.editors.keys.toSet() + + val removedIds = lastEditorIds - currentEditorIds + + // Iterate through all current editors, handling additions and property changes simultaneously + editors.forEach { (id, editor) -> + lastState.editors[id]?.let { lastEditor -> + // Check for option changes + val optionsChanged = editor.options != lastEditor.options + + // Check for selection area changes + val selectionsChanged = editor.selections != lastEditor.selections + + // Check for visible range changes + val visibleRangesChanged = editor.visibleRanges != lastEditor.visibleRanges + + // If there are any changes, create EditorPropertiesChangeData + if (optionsChanged || selectionsChanged || visibleRangesChanged) { + editorDeltas[id] = EditorPropertiesChangeData( + options = if (optionsChanged) editor.options else null, + selections = if (selectionsChanged) SelectionChangeEvent( + selections = editor.selections, + source = null + ) else null, + visibleRanges = if (visibleRangesChanged) editor.visibleRanges else null + ) + } + } ?: run { + // Newly added editor + addedEditors.add(editor) + } + } + + // Calculate document content changes + val documentDeltas = mutableMapOf() + + // Iterate through all current documents, checking for content changes + documents.forEach { (uri, document) -> + lastState.documents[uri]?.let { lastDocument -> + // Check if the document has changes + val hasChanges = document.lines != lastDocument.lines || + document.EOL != lastDocument.EOL || + document.languageId != lastDocument.languageId || + document.isDirty != lastDocument.isDirty || + document.encoding != lastDocument.encoding + + if (hasChanges) { + // If content has changed, create changes for the entire document + val changes = listOf( + ModelContentChange( + range = Range( + startLineNumber = 1, + startColumn = 1, + endLineNumber = max(1, lastDocument.lines.size), + endColumn = max(1, (lastDocument.lines.lastOrNull()?.length ?: 0) + 1) + ), + rangeOffset = 0, + rangeLength = lastDocument.lines.joinToString(lastDocument.EOL).length, + text = document.lines.joinToString(document.EOL) + ) + ) + + documentDeltas[uri] = ModelChangedEvent( + changes = changes, + eol = document.EOL, + versionId = document.versionId, + isUndoing = false, + isRedoing = false, + isDirty = document.isDirty + ) + } + } + } + + val itemsDelta = DocumentsAndEditorsDelta( + removedDocuments = removedUrls.toList(), + addedDocuments = addedDocuments, + removedEditors = removedIds.toList(), + addedEditors = addedEditors, + newActiveEditor = if (activeEditorId != lastState.activeEditorId) activeEditorId else null + ) + + return Delta( + itemsDelta = if (itemsDelta.isEmpty()) null else itemsDelta, + editorDeltas = editorDeltas, + documentDeltas = documentDeltas + ) + } + +} +data class Delta( + val itemsDelta : DocumentsAndEditorsDelta?, + val editorDeltas : MutableMap, + val documentDeltas : MutableMap +) diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorCommands.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorCommands.kt new file mode 100644 index 0000000000..2a2d9a8a27 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorCommands.kt @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.diff.DiffContentFactory +import com.intellij.diff.contents.DiffContent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import ai.kilocode.jetbrains.commands.CommandRegistry +import ai.kilocode.jetbrains.commands.ICommand +import ai.kilocode.jetbrains.util.URI +import ai.kilocode.jetbrains.util.URIComponents +import java.io.File + +/** + * Registers commands related to editor API operations + * Currently registers the workbench diff command for file comparison + * + * @param project The current IntelliJ project + * @param registry The command registry to register commands with + */ +fun registerOpenEditorAPICommands(project: Project,registry: CommandRegistry) { + + registry.registerCommand( + object : ICommand{ + override fun getId(): String { + return "_workbench.diff" + } + override fun getMethod(): String { + return "workbench_diff" + } + + override fun handler(): Any { + return OpenEditorAPICommands(project) + } + + override fun returns(): String? { + return "void" + } + + } + ) +} + +/** + * Handles editor API commands for operations like opening diff editors + */ +class OpenEditorAPICommands(val project: Project) { + private val logger = Logger.getInstance(OpenEditorAPICommands::class.java) + + /** + * Opens a diff editor to compare two files + * + * @param left Map containing URI components for the left file + * @param right Map containing URI components for the right file + * @param title Optional title for the diff editor + * @param columnOrOptions Optional column or options for the diff editor + * @return null after operation completes + */ + suspend fun workbench_diff(left: Map, right : Map, title : String?,columnOrOptions : Any?): Any?{ + val rightURI = createURI(right) + val leftURI = createURI(left) + logger.info("Opening diff: ${rightURI.path}") + val content1 = createContent(left,project) + val content2 = createContent(right,project) + if (content1 != null && content2 != null){ + project.getService(EditorAndDocManager::class.java).openDiffEditor(leftURI,rightURI,title?:"File Comparison") + } + logger.info("Opening diff completed: ${rightURI.path}") + return null; + } + + /** + * Creates a DiffContent object from URI components + * + * @param uri Map containing URI components + * @param project The current IntelliJ project + * @return DiffContent object or null if creation fails + */ + fun createContent(uri: Map, project: Project) : DiffContent?{ + val path = uri["path"] + val scheme = uri["scheme"] + val query = uri["query"] + val fragment = uri["fragment"] + if(scheme != null){ + val contentFactory = DiffContentFactory.getInstance() + if(scheme == "file"){ + val vfs = LocalFileSystem.getInstance() + val fileIO = File(path as String) + if(!fileIO.exists()){ + fileIO.createNewFile() + vfs.refreshIoFiles(listOf(fileIO.parentFile)) + } + + val file = vfs.refreshAndFindFileByPath(path as String) ?: run { + logger.warn("File not found: $path") + return null + } + return contentFactory.create(project, file) + }else if(scheme == "cline-diff"){ + val string = if(query != null){ + val bytes = java.util.Base64.getDecoder().decode(query as String) + String(bytes) + }else "" + val content = contentFactory.create(project, string) + return content + } + return null + }else{ + return null + } + } +} + +/** + * Creates a URI object from a map of URI components + * + * @param map Map containing URI components (scheme, authority, path, query, fragment) + * @return URI object constructed from the components + */ +fun createURI(map: Map): URI { + val authority = if (map["authority"] != null) map["authority"] as String else "" + val query = if (map["query"] != null) map["query"] as String else "" + val fragment = if (map["fragment"] != null) map["fragment"] as String else "" + + val uriComponents = object : URIComponents { + override val scheme: String = map["scheme"] as String + override val authority: String = authority + override val path: String = map["path"] as String + override val query: String = query + override val fragment: String = fragment + } + return URI.from(uriComponents) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorHolder.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorHolder.kt new file mode 100644 index 0000000000..1f7af77080 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorHolder.kt @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.LogicalPosition +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.fileEditor.TextEditor +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.testFramework.utils.editor.saveToDisk +import kotlinx.coroutines.* +import java.io.File +import java.lang.ref.WeakReference +import kotlin.math.max +import kotlin.math.min + +/** + * Manages the state and behavior of an editor instance + * Handles synchronization between IntelliJ editor and VSCode editor state + * + * @param id Unique identifier for this editor + * @param state Editor state data + * @param document Document data associated with this editor + * @param diff Whether this is a diff editor + * @param stateManager Reference to the editor and document manager + */ +class EditorHolder( + val id: String, + var state: TextEditorAddData, + var document: ModelAddedData, + val diff: Boolean, + private val stateManager: EditorAndDocManager, +) { + + val logger = Logger.getInstance(EditorHolder::class.java) + + /** + * Indicates whether this editor is currently active. + */ + var isActive: Boolean = false + private set + + /** + * The underlying IntelliJ Document associated with this editor. + */ + private var editorDocument: Document? = null + + /** + * The IntelliJ FileEditor instance for this editor. + */ + var ideaEditor: FileEditor? = null + + /** + * The title of the editor tab, if any. + */ + var title: String? = null + + /** + * The tab group handle this editor belongs to. + */ + var group: TabGroupHandle? = null + + /** + * The tab handle for this editor. + */ + var tab: TabHandle? = null + + // Delayed update related fields + + /** + * Job for debounced editor state updates. + */ + private var editorUpdateJob: Job? = null + + /** + * Job for debounced document state updates. + */ + private var documentUpdateJob: Job? = null + + /** + * Delay in milliseconds for debounced updates. + */ + private val updateDelay: Long = 30 // 30ms delay + + /** + * Updates editor selections and triggers a state update + * + * @param selections List of selections to apply + */ + fun updateSelections(selections: List) { + state.selections = selections + debouncedUpdateState() + } + + fun updateVisibleRanges(ranges: List) { + state.visibleRanges = ranges + debouncedUpdateState() + } + + fun updatePosition(position: Int?) { + state.editorPosition = position + debouncedUpdateState() + } + + fun updateOptions(options: ResolvedTextEditorConfiguration) { + state.options = options + debouncedUpdateState() + } + + fun setActive(active: Boolean) { + if (isActive == active) return + isActive = active + if(editorDocument == null && active){ + val vfs = LocalFileSystem.getInstance() + val path = document.uri.path + ApplicationManager.getApplication().runReadAction { + val file = vfs.findFileByPath(path) + editorDocument = file?.let { FileDocumentManager.getInstance().getDocument(it) } + } + } + CoroutineScope(Dispatchers.IO).launch { + delay(100) + stateManager.didUpdateActive(this@EditorHolder) + } + } + + fun revealRange(range: Range) { + state.visibleRanges = listOf(range) + stateManager.getIdeaDiffEditor(document.uri)?.get()?.let {e-> + ApplicationManager.getApplication().invokeLater{ + val target = LogicalPosition(range.startLineNumber,0) + e.scrollingModel.scrollTo(target, ScrollType.RELATIVE) + } + } + debouncedUpdateState() + } + + /** + * Updates document content by applying a text edit + * + * @param edit Text edit to apply + * @return True if edit was applied successfully, false otherwise + */ + suspend fun applyEdit(edit: TextEdit): Boolean { + // Get current text content + val content = editorDocument?.text ?: "" + val lines = content.lines() + val lineCount = lines.size + + // Calculate range + val startLine = max(0, edit.textEdit.range.startLineNumber - 1) + val startColumn = max(0, edit.textEdit.range.startColumn - 1) + val endLine = min(lineCount - 1, edit.textEdit.range.endLineNumber - 1) + val endColumn = min(lines[endLine].length, edit.textEdit.range.endColumn - 1) + + // Calculate offsets + var startOffset = 0 + var endOffset = 0 + for (i in 0 until lineCount) { + if (i < startLine) { + startOffset += lines[i].length + 1 // +1 for newline + } else if (i == startLine) { + startOffset += min(startColumn, lines[i].length) + } + + if (i < endLine) { + endOffset += lines[i].length + 1 // +1 for newline + } else if (i == endLine) { + endOffset += min(endColumn, lines[i].length) + } + } + + // Ensure range is valid + val textLength = content.length + if (startOffset < 0 || endOffset > textLength || startOffset > endOffset) { + return false + } + val end = (endLine < (edit.textEdit.range.endLineNumber - 1)) + val newText = edit.textEdit.text.replace("\r\n", "\n") + val newContent = content.substring(0, startOffset) + newText + (if (!end) content.substring(endOffset) else "") + ApplicationManager.getApplication().invokeAndWait { + ApplicationManager.getApplication().runWriteAction { + editorDocument?.setText(newContent) + } + } + CoroutineScope(Dispatchers.IO).launch { + delay(1000) + val file = File(document.uri.path).parentFile + if(file.exists()){ + LocalFileSystem.getInstance().refreshIoFiles(listOf(file)) + } + } + val newDoc = ModelAddedData( + uri = document.uri, + versionId = document.versionId + 1, + lines = newContent.lines(), + EOL = document.EOL, + languageId = document.languageId, + isDirty = true, + encoding = document.encoding + ) + document = newDoc + stateManager.updateDocumentAsync(newDoc) + return true; + } + + suspend fun save(): Boolean { + ApplicationManager.getApplication().invokeLater { + ApplicationManager.getApplication().runWriteAction { + editorDocument?.saveToDisk() + } + } + val newDoc = ModelAddedData( + uri = document.uri, + versionId = document.versionId + 1, + lines = document.lines, + EOL = document.EOL, + languageId = document.languageId, + isDirty = false, + encoding = document.encoding + ) + document = newDoc + stateManager.updateDocumentAsync(newDoc) + return true + } + + fun updateDocumentContent(lines: List, versionId: Int? = null) { + document.lines = lines + document.versionId = versionId ?: (document.versionId + 1) + debouncedUpdateDocument() + } + + /** + * Updates the language ID of the document. + * @param languageId The new language ID. + */ + fun updateDocumentLanguage(languageId: String) { + document.languageId = languageId + debouncedUpdateDocument() + } + + /** + * Updates the encoding of the document. + * @param encoding The new encoding. + */ + fun updateDocumentEncoding(encoding: String) { + document.encoding = encoding + debouncedUpdateDocument() + } + + suspend fun updateDocumentDirty(isDirty: Boolean) { + if (document.isDirty == isDirty) return + + val newDoc = ModelAddedData( + uri = document.uri, + versionId = document.versionId + 1, + lines = document.lines, + EOL = document.EOL, + languageId = document.languageId, + isDirty = isDirty, + encoding = document.encoding + ) + document = newDoc + ApplicationManager.getApplication().invokeAndWait { + val fileDocumentManager = FileDocumentManager.getInstance() + editorDocument?.let { fileDocumentManager.saveDocument(it) } + } + + debouncedUpdateDocument() + } + + suspend fun syncDocumentState() { + documentUpdateJob?.cancel() + stateManager.updateDocument(document) + stateManager.syncUpdates() + } + + // Private methods + /** + * Updates editor state with debouncing to avoid excessive updates + */ + private fun debouncedUpdateState() { + editorUpdateJob?.cancel() + editorUpdateJob = CoroutineScope(Dispatchers.Default).launch { + delay(updateDelay) + stateManager.updateEditor(state) + } + } + + /** + * Updates document state with debouncing to avoid excessive updates. + */ + private fun debouncedUpdateDocument() { + documentUpdateJob?.cancel() + documentUpdateJob = CoroutineScope(Dispatchers.Default).launch { + delay(updateDelay) + stateManager.updateDocument(document) + } + } + } \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorListener.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorListener.kt new file mode 100644 index 0000000000..272de2715e --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorListener.kt @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.openapi.diff.impl.DiffUtil +import com.intellij.openapi.editor.event.EditorFactoryEvent +import com.intellij.openapi.editor.event.EditorFactoryListener +import com.intellij.openapi.fileEditor.FileDocumentManager +import ai.kilocode.jetbrains.util.URI +import java.lang.ref.WeakReference + + +class EditorListener : EditorFactoryListener { + override fun editorCreated(event: EditorFactoryEvent) { + val editor = event.editor + if(DiffUtil.isDiffEditor(editor)){ + FileDocumentManager.getInstance().getFile(editor.document)?.let { file-> + val manager = editor.project?.getService(EditorAndDocManager::class.java) + val url = URI.file(file.path) + manager?.onIdeaDiffEditorCreated(url,editor) + } + } + super.editorCreated(event) + } + + override fun editorReleased(event: EditorFactoryEvent) { + val editor = event.editor + if(DiffUtil.isDiffEditor(editor)){ + FileDocumentManager.getInstance().getFile(editor.document)?.let { file-> + val manager = editor.project?.getService(EditorAndDocManager::class.java) + val url = URI.file(file.path) + manager?.onIdeaDiffEditorReleased(url,editor) + } + } + super.editorReleased(event) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorStateService.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorStateService.kt new file mode 100644 index 0000000000..a91a1a2663 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorStateService.kt @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.openapi.project.Project +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostDocumentsAndEditorsProxy +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostDocumentsProxy +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostEditorTabsProxy +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostEditorsProxy +import ai.kilocode.jetbrains.util.URI + +class EditorStateService(val project: Project) { + var extHostDocumentsAndEditorsProxy : ExtHostDocumentsAndEditorsProxy? = null + var extHostEditorsProxy : ExtHostEditorsProxy? = null + var extHostDocumentsProxy : ExtHostDocumentsProxy? = null + + fun acceptDocumentsAndEditorsDelta(detail:DocumentsAndEditorsDelta){ + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if(extHostDocumentsAndEditorsProxy == null){ + extHostDocumentsAndEditorsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostDocumentsAndEditors) + } + extHostDocumentsAndEditorsProxy?.acceptDocumentsAndEditorsDelta(detail) + } + + fun acceptEditorPropertiesChanged(detail: Map){ + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if(extHostEditorsProxy == null){ + extHostEditorsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostEditors) + } + extHostEditorsProxy?.let { + for ((id, data) in detail){ + it.acceptEditorPropertiesChanged(id,data) + } + } + } + + fun acceptModelChanged( detail: Map){ + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if (extHostDocumentsProxy == null){ + extHostDocumentsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostDocuments) + } + extHostDocumentsProxy?.let { + for ((uri, data) in detail) { + it.acceptModelChanged(uri,data,data.isDirty) + } + } + } + +} + + +class TabStateService(val project: Project) { + var extHostEditorTabsProxy : ExtHostEditorTabsProxy? = null + + fun acceptEditorTabModel(detail: List){ + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if (extHostEditorTabsProxy == null){ + extHostEditorTabsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostEditorTabs) + } + extHostEditorTabsProxy?.acceptEditorTabModel(detail) + } + + fun acceptTabOperation(detail: TabOperation) { + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if (extHostEditorTabsProxy == null){ + extHostEditorTabsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostEditorTabs) + } + extHostEditorTabsProxy?.acceptTabOperation(detail) + } + + fun acceptTabGroupUpdate(detail: EditorTabGroupDto) { + val protocol = PluginContext.getInstance(project).getRPCProtocol() + if (extHostEditorTabsProxy == null){ + extHostEditorTabsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostEditorTabs) + } + extHostEditorTabsProxy?.acceptTabGroupUpdate(detail) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorTypes.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorTypes.kt new file mode 100644 index 0000000000..c9209b98bc --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/EditorTypes.kt @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import ai.kilocode.jetbrains.util.URI + + +data class ModelAddedData( + val uri: URI, + var versionId: Int, + var lines: List, + val EOL: String, + var languageId: String, + var isDirty: Boolean, + var encoding: String +) + +data class Selection( + val selectionStartLineNumber: Int, + val selectionStartColumn: Int, + val positionLineNumber: Int, + val positionColumn: Int +) + +data class Range( + val startLineNumber: Int, + val startColumn: Int, + val endLineNumber: Int, + val endColumn: Int +) + +data class ResolvedTextEditorConfiguration( + val tabSize: Int = 4, + val indentSize: Int = 4, + val originalIndentSize: Int = 4, + val insertSpaces: Boolean = true, + val cursorStyle: Int = 1, + val lineNumbers: Int = 1 +) + + + +data class TextEditorAddData( + val id: String, + val documentUri: URI, + var options: ResolvedTextEditorConfiguration, + var selections: List, + var visibleRanges: List, + var editorPosition: Int? +) + +data class ModelContentChange( + val range: Range, + val rangeOffset: Int, + val rangeLength: Int, + val text: String +) + +data class ModelChangedEvent( + val changes: List, + val eol: String, + val versionId: Int, + val isUndoing: Boolean, + val isRedoing: Boolean, + val isDirty: Boolean +) + +data class SelectionChangeEvent( + val selections: List, + val source: String? +) + +data class EditorPropertiesChangeData( + val options: ResolvedTextEditorConfiguration?, + val selections: SelectionChangeEvent?, + val visibleRanges: List? +) + +data class DocumentsAndEditorsDelta( + val removedDocuments: List?, + val addedDocuments: List?, + val removedEditors: List?, + val addedEditors: List?, + val newActiveEditor: String? +) { + fun isEmpty(): Boolean { + var isEmpty = true + if (!removedDocuments.isNullOrEmpty()) { + isEmpty = false + } + if (!addedDocuments.isNullOrEmpty()) { + isEmpty = false + } + if (!removedEditors.isNullOrEmpty()) { + isEmpty = false + } + if (!addedEditors.isNullOrEmpty()) { + isEmpty = false + } + if (!newActiveEditor.isNullOrEmpty()) { + isEmpty = false + } + return isEmpty + } +} + + +data class TextEditorChange( + val originalStartLineNumber : Int, + val originalEndLineNumberExclusive : Int, + val modifiedStartLineNumber : Int, + val modifiedEndLineNumberExclusive : Int +) + + +data class TextEditorDiffInformation( + val documentVersion: Int, + val original: URI?, + val modified: URI, + val changes: List +) + +enum class EditorGroupColumn(val value: Int) { + active(-1), + beside(-2), + one(1), + two(2), + three(3), + four(4), + five(5), + six(6), + seven(7), + eight(8), + nine(9); + + val groupIndex : Int + get() { + return when (this) { + active -> -1 + beside -> -2 + else -> this.value - 1 + } + } + + companion object { + fun fromValue(value: Int): EditorGroupColumn { + return when (value) { + -2 -> beside + -1 -> active + 1 -> one + 2 -> two + 3 -> three + 4 -> four + 5 -> five + 6 -> six + 7 -> seven + 8 -> eight + 9 -> nine + else -> {active} + } + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabStateManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabStateManager.kt new file mode 100644 index 0000000000..e9b0b206e8 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabStateManager.kt @@ -0,0 +1,484 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +/** + * Tab State Manager + * + * Manages the state of editor tabs and tab groups, handling operations like + * creating, updating, moving, and removing tabs and tab groups. + */ +class TabStateManager(var project: Project) { + private val logger = Logger.getInstance(TabStateManager::class.java) + // MARK: - State storage + private var state = TabsState() + private val tabHandles = ConcurrentHashMap() + private val groupHandles = ConcurrentHashMap() + private val tabStateService :TabStateService + init { + tabStateService = TabStateService(project) + } + + // MARK: - ID generation + private var nextGroupId = 1 + + // MARK: - Public methods + + /** + * Creates a new tab group + * + * @param viewColumn The view column for the group + * @param isActive Whether this group should be the active group + * @return Handle to the created tab group + */ + fun createTabGroup(viewColumn: Int, isActive: Boolean = false): TabGroupHandle { + // Generate new group ID + val groupId = nextGroupId++ + + // Create tab group + val group = EditorTabGroupDto( + groupId = groupId, + isActive = isActive, + viewColumn = viewColumn, + tabs = emptyList() + ) + state.groups[groupId] = group + + // If this is the active group, update the state of other groups + if (isActive) { + state.groups.forEach { (id, otherGroup) -> + if (id != groupId) { + state.groups[id] = otherGroup.copy(isActive = false) + } + } + } + + // Create tab group handle + val handle = TabGroupHandle( + groupId = groupId, + manager = this + ) + groupHandles[groupId] = handle + + // Send update event for all groups + tabStateService.acceptEditorTabModel(state.groups.values.toList()) + + return handle + } + + /** + * Removes a tab group + * + * @param groupId ID of the group to remove + */ + fun removeGroup(groupId: Int) { + state.groups.remove(groupId) + groupHandles.remove(groupId) + + // Remove handles for all tabs in this group + tabHandles.entries.removeAll { it.value.groupId == groupId } + + // Send update event for all groups +// tabStateService.acceptEditorTabModel(state.groups.values.toList()) + } + + /** + * Gets a handle to a tab group + * + * @param groupId ID of the group + * @return Handle to the tab group, or null if not found + */ + fun getTabGroupHandle(groupId: Int): TabGroupHandle? = groupHandles[groupId] + + // MARK: - Internal methods + + /** + * Creates a new tab in a group + * + * @param groupId ID of the group to create the tab in + * @param input Input for the tab content + * @param options Options for the tab + * @return Handle to the created tab + */ + internal suspend fun createTab(groupId: Int, input: TabInputBase, options: TabOptions): TabHandle { + if(input is EditorTabInput){ + logger.info("create tab s" + input.uri?.path) + } + if(input is TextDiffTabInput){ + logger.info("create tab d" + input.modified.path) + } + val group = state.groups[groupId] ?: error("Group not found: $groupId") +// Create tab +val tab = EditorTabDto( + id = UUID.randomUUID().toString(), + label = "", // Compatibility with Roocode 0.61+: API request gets stuck without label field + input = input, + isActive = options.isActive, + isPinned = options.isPinned, + isPreview = !options.isPinned, + isDirty = false +) + +// Add to group +val newTabs = group.tabs + tab +val newGroup = group.copy(tabs = newTabs) +state.groups[groupId] = newGroup + +// Create tab handle +val handle = TabHandle( + id = tab.id, + groupId = groupId, + manager = this +) +tabHandles[tab.id] = handle + + // Send tab operation event and group update event + tabStateService.acceptTabOperation(TabOperation( + groupId = groupId, + index = newTabs.size - 1, + tabDto = tab, + kind = TabModelOperationKind.TAB_OPEN.value, + oldIndex = null + )) + tabStateService.acceptTabGroupUpdate(newGroup) + return handle + } + + /** + * Removes a tab + * + * @param id ID of the tab to remove + * @return Handle to the removed tab, or null if not found + */ + internal fun removeTab(id: String) :TabHandle?{ + val handle = tabHandles[id] ?: return null + val group = state.groups[handle.groupId] ?: return null + + // Find the index of the tab in the group + val index = group.tabs.indexOfFirst { it.id == id } + if (index != -1) { + val tab = group.tabs[index] + + if(tab.input is EditorTabInput){ + logger.info("remove tab s" + tab.input.uri?.path) + } + if(tab.input is TextDiffTabInput){ + logger.info("remove tab d" + tab.input.modified.path) + } + + val newTabs = group.tabs.toMutableList().apply { removeAt(index) } + val newGroup = group.copy(tabs = newTabs) + state.groups[handle.groupId] = newGroup + state.groups[handle.groupId]?.isActive = false + tabHandles.remove(id) + + // Send tab operation event and group update event + tabStateService.acceptTabOperation(TabOperation( + groupId = handle.groupId, + index = index, + tabDto = tab, + kind = TabModelOperationKind.TAB_CLOSE.value, + oldIndex = null + )) + tabStateService.acceptTabGroupUpdate(newGroup) + } + return handle; + } + + /** + * Updates a tab using the provided update function + * + * @param id ID of the tab to update + * @param update Function that takes the current tab and returns an updated tab + */ + internal suspend fun updateTab(id: String, update: (EditorTabDto) -> EditorTabDto) { + val handle = tabHandles[id] ?: return + val group = state.groups[handle.groupId] ?: return + + // Find the index of the tab in the group + val index = group.tabs.indexOfFirst { it.id == id } + if (index != -1) { + val tab = update(group.tabs[index]) + val newTabs = group.tabs.toMutableList().apply { this[index] = tab } + state.groups[handle.groupId] = group.copy(tabs = newTabs) + + // Send tab operation event and group update event + tabStateService.acceptTabOperation(TabOperation( + groupId = handle.groupId, + index = index, + tabDto = tab, + kind = TabModelOperationKind.TAB_UPDATE.value, + oldIndex = null + )) +// tabStateService.acceptEditorTabModel(state.groups.values.toList()) + } + } + + /** + * Moves a tab to a new position, possibly in a different group + * + * @param id ID of the tab to move + * @param toGroupId ID of the destination group + * @param toIndex Index in the destination group + */ + internal suspend fun moveTab(id: String, toGroupId: Int, toIndex: Int) { + val handle = tabHandles[id] ?: return + val fromGroup = state.groups[handle.groupId] ?: return + val toGroup = state.groups[toGroupId] ?: return + + // Find the index of the tab in the source group + val fromIndex = fromGroup.tabs.indexOfFirst { it.id == id } + if (fromIndex != -1) { + val tab = fromGroup.tabs[fromIndex] + + // If moving within the same group + if (handle.groupId == toGroupId) { + val newTabs = fromGroup.tabs.toMutableList().apply { + removeAt(fromIndex) + add(toIndex, tab) + } + state.groups[handle.groupId] = fromGroup.copy(tabs = newTabs) + + // Send tab operation event and group update event + tabStateService.acceptTabOperation(TabOperation( + groupId = handle.groupId, + index = toIndex, + tabDto = tab, + kind = TabModelOperationKind.TAB_MOVE.value, + oldIndex = fromIndex + )) +// tabStateService.acceptEditorTabModel(state.groups.values.toList()) + } else { + // Moving between groups + val newFromTabs = fromGroup.tabs.toMutableList().apply { removeAt(fromIndex) } + val newToTabs = toGroup.tabs.toMutableList().apply { add(toIndex, tab) } + state.groups[handle.groupId] = fromGroup.copy(tabs = newFromTabs) + state.groups[toGroupId] = toGroup.copy(tabs = newToTabs) + + // Update the group ID in the tab handle + handle.groupId = toGroupId + + // Send tab operation event and group update event + tabStateService.acceptTabOperation(TabOperation( + groupId = toGroupId, + index = toIndex, + tabDto = tab, + kind = TabModelOperationKind.TAB_MOVE.value, + oldIndex = fromIndex + )) +// tabStateService.acceptEditorTabModel(state.groups.values.toList()) + } + } + } + + /** + * Updates tab state properties + * + * @param id ID of the tab to update + * @param isActive Whether the tab is active + * @param isDirty Whether the tab is dirty (has unsaved changes) + * @param isPinned Whether the tab is pinned + */ + internal suspend fun updateTab( + id: String, + isActive: Boolean? = null, + isDirty: Boolean? = null, + isPinned: Boolean? = null + ) { + updateTab(id) { tab -> + tab.copy( + isActive = isActive ?: tab.isActive, + isDirty = isDirty ?: tab.isDirty, + isPinned = isPinned ?: tab.isPinned, + isPreview = if (isPinned != null) !isPinned else tab.isPreview + ) + } + } + + /** + * Sets the active group + * + * @param groupId ID of the group to set as active + */ + fun setActiveGroup(groupId: Int) { + state.groups.forEach { (id, group) -> + state.groups[id] = group.copy(isActive = id == groupId) + } + + // Send update event for all groups + tabStateService.acceptEditorTabModel(state.groups.values.toList()) + } + + /** + * Gets a handle to a tab + * + * @param id ID of the tab + * @return Handle to the tab, or null if not found + */ + fun getTabHandle(id: String): TabHandle? = tabHandles[id] + + /** + * Gets a tab group + * + * @param groupId ID of the group + * @return The tab group, or null if not found + */ + internal fun getTabGroup(groupId: Int): EditorTabGroupDto? = state.groups[groupId] + + /** + * Gets all tab groups + * + * @return List of all tab groups + */ + fun getAllGroups(): List = state.groups.values.toList() + + /** + * Closes the manager and cleans up resources + */ + suspend fun close() { +// tabOperationEvents.resetReplayCache() +// groupUpdateEvents.resetReplayCache() +// allGroupsUpdateEvents.resetReplayCache() + } +} + +/** + * Tab state container + * Holds the state of all tab groups + */ +data class TabsState( + val groups: MutableMap = ConcurrentHashMap() +) + +/** + * Tab options + * Configuration options for creating tabs + */ +data class TabOptions( + val isActive: Boolean = false, + val isPinned: Boolean = false, + val isPreview: Boolean = false +) { + companion object { + val DEFAULT = TabOptions() + } +} +/** + * Tab group handle + * Provides operations for a specific tab group + */ +/** + * Provides operations for a specific tab group. + * @property groupId The unique identifier for this tab group. + * @property manager The TabStateManager instance managing this group. + */ +class TabGroupHandle( + /** + * The unique identifier for this tab group. + */ + val groupId: Int, + /** + * The TabStateManager instance managing this group. + */ + private val manager: TabStateManager +) { + + /** + * Gets the tab group data. + */ + val group: EditorTabGroupDto? + get() = manager.getTabGroup(groupId) + + /** + * Adds a tab to this group. + * + * @param input Input for the tab content. + * @param options Options for the tab. + * @return Handle to the created tab. + */ + suspend fun addTab(input: TabInputBase, options: TabOptions = TabOptions.DEFAULT): TabHandle? = + manager.createTab(groupId, input, options) + + /** + * Moves a tab to a specified position within this group. + * + * @param id ID of the tab to move. + * @param toIndex Destination index. + */ + suspend fun moveTab(id: String, toIndex: Int) { + manager.moveTab(id, groupId, toIndex) + } + + /** + * Gets a handle to a tab in this group. + * + * @param id ID of the tab. + * @return Handle to the tab, or null if not found. + */ + suspend fun getTabHandle(id: String): TabHandle? = manager.getTabHandle(id) + + /** + * Gets all tabs in this group. + */ + val tabs: List + get() = group?.tabs ?: emptyList() + + /** + * Removes this tab group and all its tabs. + * Closes all tabs in the group and removes the group from the manager. + */ + suspend fun remove() { + group?.tabs?.forEach { tab -> + getTabHandle(tab.id)?.close() + } + manager.removeGroup(groupId) + } +} +/** + * Tab handle + * Provides operations for a specific tab + */ +/** + * Provides operations for a specific tab. + * @property id The unique identifier for this tab. + * @property groupId The group ID this tab belongs to. + * @property manager The TabStateManager instance managing this tab. + */ +class TabHandle( + /** + * The unique identifier for this tab. + */ + val id: String, + /** + * The group ID this tab belongs to. + */ + var groupId: Int, + /** + * The TabStateManager instance managing this tab. + */ + private val manager: TabStateManager +) { + + /** + * Closes this tab by removing it from the manager. + */ + suspend fun close() { + manager.removeTab(id) + } + + /** + * Updates this tab using the provided update function. + * + * @param update Function that takes the current tab and returns an updated tab. + */ + suspend fun update(update: (EditorTabDto) -> EditorTabDto) { + manager.updateTab(id, update) + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabTypes.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabTypes.kt new file mode 100644 index 0000000000..cf751fb3b5 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/TabTypes.kt @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import ai.kilocode.jetbrains.util.URI + +/** + * Tab operation type, corresponding to VSCode's TabModelOperationKind + * Represents different kinds of operations that can be performed on tabs + */ +enum class TabModelOperationKind(val value: Int) { + TAB_OPEN(0), + TAB_CLOSE(1), + TAB_UPDATE(2), + TAB_MOVE(3) +} + + +/** + * Tab input type, corresponding to VSCode's TabInputKind + * Represents different types of content that can be displayed in a tab + */ +enum class TabInputKind(val value: Int) { + unknownInput(0), + textInput(1), + textDiffInput(2), + textMergeInput(3), + notebookInput(4), + notebookDiffInput(5), + customEditorInput(6), + webviewEditorInput(7), + terminalEditorInput(8), + interactiveEditorInput(9), + chatEditorInput(10), + multiDiffEditorInput(11) +} + +/** + * Tab operation, corresponding to VSCode's TabOperation + * Represents an operation performed on a tab (open, close, update, move) + */ +data class TabOperation( + val groupId: Int, + val index: Int, + val tabDto: EditorTabDto, + val kind: Int, + val oldIndex : Int? +) + +/** + * Tab data, corresponding to VSCode's IEditorTabDto + * Contains all information about a specific editor tab + */ +data class EditorTabDto( + val id: String, + val label: String, + val input: TabInputBase, + var isActive: Boolean, + var isPinned: Boolean, + var isPreview: Boolean, + var isDirty: Boolean +) + +/** + * Tab group data, corresponding to VSCode's IEditorTabGroupDto + * Represents a group of editor tabs that are displayed together + */ +data class EditorTabGroupDto( + val groupId: Int, + var isActive: Boolean, + var viewColumn: Int, + var tabs: List +) + +/** + * Base class for all tab input types. + * Stores the kind of input for the tab. + */ +open class TabInputBase( + var kind: Int = TabInputKind.unknownInput.value +) + +/** + * Tab input, corresponding to VSCode's IEditorTabInput + * Represents a text editor input for a tab + */ +data class EditorTabInput( + var uri: URI?, + var label: String? , + var languageId: String? +) : TabInputBase(TabInputKind.textInput.value) + +/** + * Tab input, corresponding to VSCode's IWebviewEditorTabInput + * Represents a webview editor input for a tab + */ +/** + * Represents a webview editor input for a tab. + * Used for tabs displaying webview content. + */ +class WebviewEditorTabInput(var viewType: String?) : TabInputBase(TabInputKind.webviewEditorInput.value) + +/** + * Tab input, corresponding to VSCode's ICustomEditorTabInput + * Represents a custom editor input for a tab + */ +/** + * Represents a custom editor input for a tab. + * Used for tabs with custom editor implementations. + */ +class CustomEditorTabInput( + var uri: URI? , + var viewType: String? +) : TabInputBase(TabInputKind.customEditorInput.value) + +/** + * Tab input, corresponding to VSCode's ITerminalEditorTabInput + * Represents a terminal editor input for a tab + */ +/** + * Represents a terminal editor input for a tab. + * Used for tabs displaying terminal sessions. + */ +class TerminalEditorTabInput() : TabInputBase(TabInputKind.terminalEditorInput.value) + +/** + * Tab input, corresponding to VSCode's INotebookEditorTabInput + * Represents a notebook editor input for a tab + */ +/** + * Represents a notebook editor input for a tab. + * Used for tabs displaying notebook files. + */ +class NotebookEditorTabInput( + var uri: URI, + val notebookType: String +) : TabInputBase(TabInputKind.notebookInput.value) + +/** + * Tab input, corresponding to VSCode's INotebookDiffEditorTabInput + * Represents a notebook diff editor input for comparing two notebooks + */ +data class NotebookDiffEditorTabInput( + var original: URI, + var modified: URI, + var notebookType: String +) : TabInputBase(TabInputKind.notebookDiffInput.value) + +/** + * Tab input, corresponding to VSCode's IInteractiveWindowInput + * Represents an interactive window input for a tab + */ +data class InteractiveWindowInput( + var uri: URI, + var inputBoxUri: URI +) : TabInputBase(TabInputKind.interactiveEditorInput.value) + +/** + * Tab input, corresponding to VSCode's IChatEditorTabInput + * Represents a chat editor input for a tab + */ +/** + * Represents a chat editor input for a tab. + * Used for tabs displaying chat interfaces. + */ +class ChatEditorTabInput() : TabInputBase(TabInputKind.chatEditorInput.value) + +/** + * Tab input, corresponding to VSCode's ITextDiffTabInput + * Represents a text diff editor input for comparing two text files + */ +data class TextDiffTabInput( + var original: URI, + var modified: URI +) : TabInputBase(TabInputKind.textDiffInput.value){ + companion object { + fun create(original: URI, modified: URI) : TextDiffTabInput { + return TextDiffTabInput(original, modified) + } + } +} + +/** + * Tab input, corresponding to VSCode's ITextMergeTabInput + * Represents a text merge editor input for merging multiple text files + */ +data class TextMergeTabInput( + var base: URI, + var input1: URI, + var input2: URI, + var result: URI +) : TabInputBase(TabInputKind.textMergeInput.value) + +/** + * Tab input, corresponding to VSCode's multi-diff editor input + * Represents a multi-diff editor input for comparing multiple text files + */ +data class TextMultiDiffTabInput( + var resources: List +) : TabInputBase(TabInputKind.multiDiffEditorInput.value) \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/WorkspaceEdit.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/WorkspaceEdit.kt new file mode 100644 index 0000000000..1dd7bcb8c8 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/editor/WorkspaceEdit.kt @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.editor + +import com.google.gson.Gson +import com.google.gson.JsonParser +import ai.kilocode.jetbrains.util.URI + +data class FileEdit( + val oldResource: URI?, + val newResource: URI?, + val options: Options?, + val metadata: Metadata? +) { + data class Options( + val overwrite: Boolean?, + val ignoreIfExists: Boolean?, + val copy: Boolean?, + val contents: String? + ) +} + +data class Metadata( + val isRefactoring: Boolean? +) + +data class Content( + val range: Range, + val text: String +) + +data class TextEdit( + val resource: URI, + val textEdit: Content, + val metadata: Metadata? +) + +class WorkspaceEdit{ + val files:MutableList = mutableListOf() + val texts:MutableList = mutableListOf() + companion object { + fun from(dto: String): WorkspaceEdit { + val rst = WorkspaceEdit() + val json = JsonParser.parseString(dto) + val edits = json.asJsonObject.get("edits") + if(edits.isJsonArray){ + val array = edits.asJsonArray + for(element in array){ + val edit = element.asJsonObject + if(edit.has("textEdit")){ + rst.texts.add(Gson().fromJson(edit, TextEdit::class.java)) + }else if(edit.has("fileEdit")){ + rst.files.add(Gson().fromJson(edit, FileEdit::class.java)) + } + } + return rst + }else{ + throw IllegalArgumentException("WorkspaceEdit must be an array") + } + } + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/EventBus.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/EventBus.kt new file mode 100644 index 0000000000..7877c34b83 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/EventBus.kt @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.events + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Disposer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch +import java.util.concurrent.ConcurrentHashMap + + +@Service +class EventBus : AbsEventBus() { + companion object { + fun get():EventBus{ + return service() + } + } +} + +/** + * Event bus for communication between plugin internal components + */ +@Service(Service.Level.PROJECT) // Consider other implementation approaches? +class ProjectEventBus : AbsEventBus() { +} + + +open class AbsEventBus : Disposable{ + private val logger = Logger.getInstance(ProjectEventBus::class.java) + + // All events are dispatched through this flow + private val _events = MutableSharedFlow>(extraBufferCapacity = 64) + val events: SharedFlow> = _events.asSharedFlow() + + // Event listener mapping, key is event type, value is listener list + private val listeners = ConcurrentHashMap, MutableList<(Any) -> Unit>>() + + /** + * Send event + */ + suspend fun emit(eventType: EventType, data: T) { + _events.emit(Event(eventType, data)) + + // Also notify regular listeners + @Suppress("UNCHECKED_CAST") + listeners[eventType]?.forEach { listener -> + try { + listener(data) + } catch (e: Exception) { + logger.error("Event handling exception", e) + } + } + } + + /** + * Send event in specified coroutine scope + */ + fun emitIn(scope: CoroutineScope, eventType: EventType, data: T) { + scope.launch { + emit(eventType, data) + } + } + + /** + * Send event in IntelliJ application context + * Use IntelliJ platform's thread-safe methods instead of coroutines + */ + fun emitInApplication(eventType: EventType, data: T) { + ApplicationManager.getApplication().invokeLater { + ApplicationManager.getApplication().runReadAction { + listeners[eventType]?.forEach { listener -> + @Suppress("UNCHECKED_CAST") + try { + listener(data) + } catch (e: Exception) { + logger.error("Event processing exception", e) + } + } + } + } + } + + /** + * Subscribe to specific event type in specified coroutine scope + */ + inline fun on( + scope: CoroutineScope, + eventType: EventType, + crossinline handler: suspend (T) -> Unit + ) { + scope.launch { + events + .filter { it.type == eventType } + .collect { event -> + @Suppress("UNCHECKED_CAST") + handler(event.data as T) + } + } + } + + /** + * Add event listener (no coroutines required) + * Provides IntelliJ platform compatible event listening method + */ + @Suppress("UNCHECKED_CAST") + fun addListener(eventType: EventType, handler: (T) -> Unit) { + listeners.getOrPut(eventType) { mutableListOf() }.add(handler as (Any) -> Unit) + } + + /** + * Add event listener with Disposable, automatically removes listener when Disposable is disposed + */ + @Suppress("UNCHECKED_CAST") + fun addListener(eventType: EventType, disposable: Disposable, handler: (T) -> Unit) { + val wrappedHandler = handler as (Any) -> Unit + listeners.getOrPut(eventType) { mutableListOf() }.add(wrappedHandler) + + // Use IntelliJ's Disposer API for resource cleanup + Disposer.register(disposable, Disposable { + removeListener(eventType, wrappedHandler) + }) + } + + /** + * Remove event listener + */ + fun removeListener(eventType: EventType, handler: (Any) -> Unit) { + listeners[eventType]?.remove(handler) + } + + /** + * Remove all listeners for specific event type + */ + fun removeAllListeners(eventType: EventType) { + listeners.remove(eventType) + } + + override fun dispose() { + } +} + +/** + * Event type marker interface + */ +interface EventType + +/** + * Event data class + */ +data class Event( + val type: EventType, + val data: T +) \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WebviewEvents.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WebviewEvents.kt new file mode 100644 index 0000000000..d5cf7bea44 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WebviewEvents.kt @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.events + +/** + * WebView view provider registration event type + */ +object WebviewViewProviderRegisterEvent : EventType + +/** + * WebView view provider data + */ +data class WebviewViewProviderData( + val extension: Map, + val viewType: String, + val options: Map +) + +/** + * WebView HTML content update event type + */ +object WebviewHtmlUpdateEvent : EventType + +/** + * WebView HTML content update data + */ +data class WebviewHtmlUpdateData( + val handle: String, + var htmlContent: String +) \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WorkspaceEvents.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WorkspaceEvents.kt new file mode 100644 index 0000000000..a0d1369617 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/events/WorkspaceEvents.kt @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.events + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile + +/** + * File change type + */ +enum class FileChangeType { + CREATED, + UPDATED, + DELETED +} + +/** + * File system item type + */ +enum class FileSystemItemType { + FILE, + DIRECTORY +} + +/** + * Workspace file change event type + */ +object WorkspaceFileChangeEvent : EventType + +/** + * Workspace directory change event type + */ +object WorkspaceDirectoryChangeEvent : EventType + +/** + * Workspace file change data + */ +data class WorkspaceFileChangeData( + val file: VirtualFile, + val changeType: FileChangeType, + val timestamp: Long = System.currentTimeMillis(), + val itemType: FileSystemItemType = if (file.isDirectory) FileSystemItemType.DIRECTORY else FileSystemItemType.FILE +) + +/** + * Workspace multiple files change event type + */ +object WorkspaceFilesChangeEvent : EventType + +/** + * Workspace directories change event type + */ +object WorkspaceDirectoriesChangeEvent: EventType + +/** + * Workspace multiple files change data + */ +data class WorkspaceFilesChangeData( + val changes: List +) + + +/** + * Workspace root change data class + * @param project The changed project + * @param oldPath Original workspace root path + * @param newPath New workspace root path + */ +data class WorkspaceRootChangeData( + val project: Project, + val oldPath: String?, + val newPath: String +) + +/** + * Workspace root change event + */ +object WorkspaceRootChangeEvent : EventType \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/BufferedEmitter.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/BufferedEmitter.kt new file mode 100644 index 0000000000..5ccd1c47c5 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/BufferedEmitter.kt @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.Executors +import kotlin.coroutines.CoroutineContext + +/** + * Buffered event emitter + * Ensures messages are not lost when there are no event listeners + * Corresponds to BufferedEmitter in VSCode + * @param T Event data type + */ +class BufferedEmitter { + private val listeners = mutableListOf<(T) -> Unit>() + private val bufferedMessages = ConcurrentLinkedQueue() + private var hasListeners = false + private var isDeliveringMessages = false + + private val coroutineContext = Dispatchers.IO + private val scope = CoroutineScope(coroutineContext) + + companion object { + private val LOG = Logger.getInstance(BufferedEmitter::class.java) + } + + /** + * Event listener property, similar to the event property in TypeScript version + */ + val event: EventListener = this::onEvent + + /** + * Add event listener + * @param listener Event listener + * @return Listener registration identifier for removing the listener + */ + fun onEvent(listener: (T) -> Unit): Disposable { + val wasEmpty = listeners.isEmpty() + listeners.add(listener) + + if (wasEmpty) { + hasListeners = true + // Use microtask queue to ensure these messages are delivered before other messages have a chance to be received + scope.launch { deliverMessages() } + } + + return Disposable { + synchronized(listeners) { + listeners.remove(listener) + if (listeners.isEmpty()) { + hasListeners = false + } + } + } + } + + /** + * Fire event + * @param event Event data + */ + fun fire(event: T) { + if (hasListeners) { + if (bufferedMessages.isNotEmpty()) { + bufferedMessages.offer(event) + } else { + synchronized(listeners) { + ArrayList(listeners).forEach { listener -> + try { + listener(event) + } catch (e: Exception) { + // Log exception but do not interrupt processing + LOG.warn("Error in event listener: ${e.message}", e) + } + } + } + } + } else { + bufferedMessages.offer(event) + } + } + + /** + * Clear buffer + */ + fun flushBuffer() { + bufferedMessages.clear() + } + + /** + * Deliver buffered messages + */ + private fun deliverMessages() { + if (isDeliveringMessages) { + return + } + + isDeliveringMessages = true + try { + while (hasListeners && bufferedMessages.isNotEmpty()) { + val event = bufferedMessages.poll() ?: break + synchronized(listeners) { + ArrayList(listeners).forEach { listener -> + try { + listener(event) + } catch (e: Exception) { + // Log exception but do not interrupt processing + LOG.warn("Error in event listener: ${e.message}", e) + } + } + } + } + } finally { + isDeliveringMessages = false + } + } +} + +/** + * Event listener type alias + */ +typealias EventListener = ((T) -> Unit) -> Disposable \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ChunkStream.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ChunkStream.kt new file mode 100644 index 0000000000..efaf5fee04 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ChunkStream.kt @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Chunk stream, used for buffering and processing binary data chunks + * Corresponds to ChunkStream in VSCode + */ +class ChunkStream { + private val chunks = mutableListOf() + + /** + * Get total byte length of data in the stream + */ + var byteLength: Int = 0 + private set + + /** + * Accept a data chunk + * @param buff Data chunk + */ + fun acceptChunk(buff: ByteArray) { + if (buff.isEmpty()) { + return + } + chunks.add(buff) + byteLength += buff.size + } + + /** + * Read specified number of bytes (removes data from stream) + * @param byteCount Number of bytes to read + * @return Data read + */ + fun read(byteCount: Int): ByteArray { + return _read(byteCount, true) + } + + /** + * Peek specified number of bytes (does not remove data from stream) + * @param byteCount Number of bytes to peek + * @return Data peeked + */ + fun peek(byteCount: Int): ByteArray { + return _read(byteCount, false) + } + + /** + * Internal read method + * @param byteCount Number of bytes to read + * @param advance Whether to remove data from stream + * @return Data read + */ + private fun _read(byteCount: Int, advance: Boolean): ByteArray { + if (byteCount == 0) { + return ByteArray(0) + } + + if (byteCount > byteLength) { + throw IllegalArgumentException("Cannot read so many bytes!") + } + + if (chunks[0].size == byteCount) { + // Fast path, first chunk is exactly the data to return + val result = chunks[0] + if (advance) { + chunks.removeAt(0) + byteLength -= byteCount + } + return result + } + + if (chunks[0].size > byteCount) { + // Fast path, data to read is completely in the first chunk + val firstChunk = chunks[0] + val result = ByteArray(byteCount) + System.arraycopy(firstChunk, 0, result, 0, byteCount) + + if (advance) { + val remaining = ByteArray(firstChunk.size - byteCount) + System.arraycopy(firstChunk, byteCount, remaining, 0, remaining.size) + chunks[0] = remaining + byteLength -= byteCount + } + + return result + } + + // General path, need to span multiple chunks + val result = ByteArray(byteCount) + var resultOffset = 0 + var chunkIndex = 0 + var remainingBytes = byteCount + + while (remainingBytes > 0) { + val chunk = chunks[chunkIndex] + + if (chunk.size > remainingBytes) { + // Current chunk will not be fully read + System.arraycopy(chunk, 0, result, resultOffset, remainingBytes) + + if (advance) { + val remaining = ByteArray(chunk.size - remainingBytes) + System.arraycopy(chunk, remainingBytes, remaining, 0, remaining.size) + chunks[chunkIndex] = remaining + byteLength -= remainingBytes + } + + resultOffset += remainingBytes + remainingBytes = 0 + } else { + // Current chunk will be fully read + System.arraycopy(chunk, 0, result, resultOffset, chunk.size) + resultOffset += chunk.size + remainingBytes -= chunk.size + + if (advance) { + chunks.removeAt(chunkIndex) + byteLength -= chunk.size + } else { + chunkIndex++ + } + } + } + + return result + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ILoadEstimator.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ILoadEstimator.kt new file mode 100644 index 0000000000..9f04b79cf5 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ILoadEstimator.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Load estimator interface + * Corresponds to ILoadEstimator in VSCode + */ +interface ILoadEstimator { + /** + * Check if currently in high load state + * @return true indicates high load + */ + fun hasHighLoad(): Boolean +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/IMessagePassingProtocol.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/IMessagePassingProtocol.kt new file mode 100644 index 0000000000..a47c557632 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/IMessagePassingProtocol.kt @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable +import kotlinx.coroutines.CompletableDeferred + +/** + * Message passing protocol interface + * Corresponds to IMessagePassingProtocol in VSCode + */ +interface IMessagePassingProtocol : Disposable { + /** + * Send message + * @param buffer Message data to send + */ + fun send(buffer: ByteArray) + + /** + * Add message receive listener + * @param listener Message receive listener + * @return Listener registration identifier for removing the listener + */ + fun onMessage(listener: MessageListener): Disposable + + /** + * Add protocol close listener + * @param listener Close event listener + * @return Listener registration identifier for removing the listener + */ + fun onDidDispose(listener: () -> Unit): Disposable + + /** + * Wait for all data to be sent + * @return Promise for async operation completion + */ + suspend fun drain(): Unit +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ISocket.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ISocket.kt new file mode 100644 index 0000000000..1c6aa3c781 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ISocket.kt @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable + +/** + * Socket interface, abstracts underlying communication + * Corresponds to ISocket in VSCode + */ +interface ISocket : Disposable { + /** + * Add data receive listener + * @param listener Data receive listener + * @return Registration identifier for removing the listener + */ + fun onData(listener: DataListener): Disposable + + /** + * Add close event listener + * @param listener Close event listener + * @return Registration identifier for removing the listener + */ + fun onClose(listener: CloseListener): Disposable + + /** + * Add end event listener + * @param listener End event listener + * @return Registration identifier for removing the listener + */ + fun onEnd(listener: () -> Unit): Disposable + + /** + * Send data + * @param buffer Data to send + */ + fun write(buffer: ByteArray) + + /** + * End connection + */ + fun end() + + /** + * Wait for all data to be sent + * @return Promise for async operation completion + */ + suspend fun drain() + + /** + * Trace socket event (for debugging) + * @param type Event type + * @param data Event data + */ + fun traceSocketEvent(type: SocketDiagnosticsEventType, data: Any? = null) + + /** + * Start receiving data + */ + fun startReceiving(); + + /** + * Data receive listener + */ + fun interface DataListener { + fun onData(data: ByteArray) + } + + /** + * Close event listener + */ + fun interface CloseListener { + fun onClose(event: SocketCloseEvent) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/LoadEstimator.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/LoadEstimator.kt new file mode 100644 index 0000000000..5991e573ce --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/LoadEstimator.kt @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.Executors + +/** + * Load estimator + * Corresponds to LoadEstimator in VSCode + */ +class LoadEstimator private constructor() : ILoadEstimator { + private val lastRuns = LongArray(HISTORY_LENGTH) + private val scheduler: ScheduledExecutorService + + init { + val now = System.currentTimeMillis() + + for (i in 0 until HISTORY_LENGTH) { + lastRuns[i] = now - 1000L * i + } + + scheduler = Executors.newSingleThreadScheduledExecutor { r -> + Thread(r, "LoadEstimator").apply { isDaemon = true } + } + + scheduler.scheduleAtFixedRate({ + for (i in HISTORY_LENGTH - 1 downTo 1) { + lastRuns[i] = lastRuns[i - 1] + } + lastRuns[0] = System.currentTimeMillis() + }, 0, 1000, TimeUnit.MILLISECONDS) + } + + /** + * Calculate current load estimate + * Returns an estimate number from 0 (low load) to 1 (high load) + * @return Load estimate value + */ + private fun load(): Double { + val now = System.currentTimeMillis() + val historyLimit = (1 + HISTORY_LENGTH) * 1000L + var score = 0 + + for (i in 0 until HISTORY_LENGTH) { + if (now - lastRuns[i] <= historyLimit) { + score++ + } + } + + return 1.0 - score.toDouble() / HISTORY_LENGTH + } + + override fun hasHighLoad(): Boolean { + return load() >= 0.5 + } + + companion object { + private const val HISTORY_LENGTH = 10 + private val INSTANCE = AtomicReference() + + /** + * Get singleton instance + * @return Load estimator instance + */ + @JvmStatic + fun getInstance(): LoadEstimator { + var instance = INSTANCE.get() + if (instance == null) { + instance = LoadEstimator() + if (!INSTANCE.compareAndSet(null, instance)) { + // Another thread has already set the instance + instance = INSTANCE.get() + } + } + return instance + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/MessageListener.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/MessageListener.kt new file mode 100644 index 0000000000..2a565cae95 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/MessageListener.kt @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Message listener + */ +fun interface MessageListener { + /** + * Handle received message + * @param data Received message data + */ + fun onMessage(data: ByteArray) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/NodeSocket.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/NodeSocket.kt new file mode 100644 index 0000000000..be01dd92f5 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/NodeSocket.kt @@ -0,0 +1,357 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.io.IOException +import java.net.Socket +import java.net.SocketException +import java.nio.channels.Channels +import java.nio.channels.SocketChannel +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.concurrent.thread + +/** + * NodeSocket implementation, wrapping Java Socket + * Corresponds to NodeSocket implementation in VSCode + */ +class NodeSocket : ISocket { + + private val logger = Logger.getInstance(NodeSocket::class.java) + private val dataListeners = ConcurrentHashMap() + private val closeListeners = ConcurrentHashMap() + private val endListeners = ConcurrentHashMap<() -> Unit, Unit>() + private val canWrite = AtomicBoolean(true) + private var receiveThread: Thread? = null + private val isDisposed = AtomicBoolean(false) + private var endTimeoutHandle: Thread? = null + private val socketEndTimeoutMs = 30_000L // 30 second timeout + private val debugLabel: String + private val input: java.io.InputStream + private val output: java.io.OutputStream + private val closeAction: () -> Unit + private val isSocket: Boolean + private val socket: Socket? + private val channel: SocketChannel? + private val writeAction: (ByteArray) -> Unit + + // Compatible with Socket/SocketChannel + constructor(socket: Socket, debugLabel: String = "") { + this.input = socket.getInputStream() + this.output = socket.getOutputStream() + this.closeAction = { socket.close() } + this.debugLabel = debugLabel + this.isSocket = true + this.socket = socket + this.channel = null + this.writeAction = { buffer -> + output.write(buffer) + output.flush() + } + traceSocketEvent(SocketDiagnosticsEventType.CREATED, mapOf("type" to "NodeSocket-TCP")) + } + + constructor(channel: SocketChannel, debugLabel: String = "") { + this.input = Channels.newInputStream(channel) + this.output = Channels.newOutputStream(channel) + this.closeAction = { channel.close() } + this.debugLabel = debugLabel + this.isSocket = false + this.socket = null + this.channel = channel + this.writeAction = { buffer -> + val byteBuffer = java.nio.ByteBuffer.wrap(buffer) + while (byteBuffer.hasRemaining()) { + channel.write(byteBuffer) + } + } + traceSocketEvent(SocketDiagnosticsEventType.CREATED, mapOf("type" to "NodeSocket-UDS")) + } + + override fun startReceiving() { + if (receiveThread != null) return + + receiveThread = thread(start = true, name = "NodeSocket-Receiver-$debugLabel") { + val buffer = ByteArray(8192) // 8KB buffer + try { + while (!isDisposed.get() && !Thread.currentThread().isInterrupted) { + try { + val bytesRead = input.read(buffer) + if (bytesRead == -1) { + // Stream ended + logger.info("Socket[$debugLabel] Read EOF, triggering onEndReceived()") + onEndReceived() + break + } else if (bytesRead > 0) { + val data = buffer.copyOfRange(0, bytesRead) + traceSocketEvent(SocketDiagnosticsEventType.READ, data) + // Notify all data listeners + dataListeners.keys.forEach { listener -> + try { + listener.onData(data) + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Data listener processing exception", e) + } + } + } + } catch (e: IOException) { + if (!isDisposed.get()) { + // Only report errors when socket is not actively closed + logger.error("Socket[$debugLabel] IO exception occurred while reading data", e) + handleSocketError(e) + } + break + } + } + } catch (e: Exception) { + if (!isDisposed.get()) { + logger.error("Socket[$debugLabel] Unhandled exception in receive thread", e) + handleSocketError(e) + } + } finally { + // Ensure Socket is closed + closeSocket(false) + } + } + } + + private fun onEndReceived() { + traceSocketEvent(SocketDiagnosticsEventType.NODE_END_RECEIVED) + logger.info("Socket[$debugLabel] Received END event, disabling write operations") + canWrite.set(false) + + // Notify all end listeners + endListeners.keys.forEach { listener -> + try { + listener.invoke() + } catch (e: Exception) { + logger.error("Socket[$debugLabel] END event listener processing exception", e) + } + } + + // Set delayed close timer + logger.info("Socket[$debugLabel] Will execute delayed close after ${socketEndTimeoutMs}ms") + endTimeoutHandle = thread(start = true, name = "NodeSocket-EndTimeout-$debugLabel") { + try { + Thread.sleep(socketEndTimeoutMs) + if (!isDisposed.get()) { + logger.info("Socket[$debugLabel] Executing delayed close") + closeAction() + } + } catch (e: InterruptedException) { + logger.info("Socket[$debugLabel] Delayed close thread interrupted") + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Delayed close processing exception", e) + } + } + } + + private fun handleSocketError(error: Exception) { + // Filter out EPIPE errors, which are common connection disconnect errors + val errorCode = when { + error.message?.contains("Broken pipe") == true -> "EPIPE" + error.message?.contains("Connection reset") == true -> "ECONNRESET" + else -> null + } + + traceSocketEvent( + SocketDiagnosticsEventType.ERROR, mapOf( + "code" to errorCode, + "message" to error.message + ) + ) + + // EPIPE errors don't need additional handling, socket will close itself + if (errorCode != "EPIPE") { + logger.warn("Socket[$debugLabel] Error: ${error.message}", error) + } + + // Close Socket + closeSocket(true) + } + + private fun closeSocket(hadError: Boolean) { + if (isDisposed.get()) return + logger.info("Socket[$debugLabel] Closing connection, hadError=$hadError") + try { + if (!isClosed()) { + logger.info("Socket[$debugLabel] Closing connection") + closeAction() + } + } catch (e: Exception) { + logger.warn("Socket[$debugLabel] Exception occurred while closing connection", e) + } + + // Stop end timeout thread + endTimeoutHandle?.interrupt() + endTimeoutHandle = null + + canWrite.set(false) + traceSocketEvent(SocketDiagnosticsEventType.CLOSE, mapOf("hadError" to hadError)) + + // Notify all close listeners + val closeEvent = SocketCloseEvent.NodeSocketCloseEvent(hadError, null) + closeListeners.keys.forEach { listener -> + try { + listener.onClose(closeEvent) + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Close listener processing exception", e) + } + } + } + + override fun onData(listener: ISocket.DataListener): Disposable { + dataListeners[listener] = Unit + return Disposable { + dataListeners.remove(listener) + } + } + + override fun onClose(listener: ISocket.CloseListener): Disposable { + closeListeners[listener] = Unit + return Disposable { + closeListeners.remove(listener) + } + } + + override fun onEnd(listener: () -> Unit): Disposable { + endListeners[listener] = Unit + return Disposable { + endListeners.remove(listener) + } + } + + override fun write(buffer: ByteArray) { + if (isDisposed.get()) { + logger.debug("Socket[$debugLabel] Write ignored: Socket disposed") + return + } + if (isClosed()) { + logger.info("Socket[$debugLabel] Write ignored: Socket closed") + return + } + if (!canWrite.get()) { + logger.info("Socket[$debugLabel] Write ignored: canWrite=false") + return + } + + try { + traceSocketEvent(SocketDiagnosticsEventType.WRITE, buffer) + writeAction(buffer) + } catch (e: java.nio.channels.ClosedChannelException) { + logger.warn("Socket[$debugLabel] ClosedChannelException detected during write, connection closed") + handleSocketError(e) + } catch (e: IOException) { + logger.error("Socket[$debugLabel] IO exception occurred during write", e) + // Filter out EPIPE errors + if (e.message?.contains("Broken pipe") == true) { + logger.warn("Socket[$debugLabel] Broken pipe detected during write") + return + } + handleSocketError(e) + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Unknown exception occurred during write", e) + handleSocketError(e) + } + } + + override fun end() { + if (isDisposed.get() || isClosed()) { + return + } + + traceSocketEvent(SocketDiagnosticsEventType.NODE_END_SENT) + logger.info("Socket[$debugLabel] Sending END signal") + try { + if (isSocket && socket != null) { + socket.shutdownOutput() + } else channel?.shutdownOutput() + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Exception occurred while sending END signal", e) + handleSocketError(e) + } + } + + override suspend fun drain(): Unit { + traceSocketEvent(SocketDiagnosticsEventType.NODE_DRAIN_BEGIN) + + try { + // Send an empty packet to trigger flush (TCP will flush, UDS writes directly) + writeAction(ByteArray(0)) + } catch (e: Exception) { + logger.error("Socket[$debugLabel] Exception occurred while executing drain", e) + handleSocketError(e) + } + + traceSocketEvent(SocketDiagnosticsEventType.NODE_DRAIN_END) + } + + override fun traceSocketEvent(type: SocketDiagnosticsEventType, data: Any?) { + // Actual debug log logic + if (logger.isDebugEnabled) { + logger.debug("Socket[$debugLabel] Event: $type, Data: $data") + } + } + + override fun dispose() { + if (isDisposed.getAndSet(true)) { + return + } + + traceSocketEvent(SocketDiagnosticsEventType.CLOSE) + logger.info("Socket[$debugLabel] Releasing resources") + + // Clean up listeners + dataListeners.clear() + closeListeners.clear() + endListeners.clear() + + // Close Socket + try { + if (!isClosed()) { + closeAction() + } + } catch (e: Exception) { + logger.warn("Socket[$debugLabel] Exception occurred while closing Socket during resource release", e) + } + + // Interrupt threads + receiveThread?.interrupt() + receiveThread = null + endTimeoutHandle?.interrupt() + endTimeoutHandle = null + + logger.info("Socket[$debugLabel] Resource release completed") + } + + // State exposure method + fun isClosed(): Boolean { + return when { + socket != null -> socket.isClosed + channel != null -> !channel.isOpen + else -> true + } + } + + fun isInputClosed(): Boolean { + return when { + socket != null -> socket.isClosed || socket.isInputShutdown + channel != null -> !channel.isOpen // NIO doesn't have direct input shutdown flag, can only use isOpen + else -> true + } + } + + fun isOutputClosed(): Boolean { + return when { + socket != null -> socket.isClosed || socket.isOutputShutdown + channel != null -> !channel.isOpen // NIO doesn't have direct output shutdown flag, can only use isOpen + else -> true + } + } + + fun isDisposed(): Boolean = isDisposed.get() +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/PersistentProtocol.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/PersistentProtocol.kt new file mode 100644 index 0000000000..84e4d890a0 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/PersistentProtocol.kt @@ -0,0 +1,443 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import java.util.concurrent.LinkedBlockingQueue +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong + +/** + * Persistent protocol implementation + * Corresponds to PersistentProtocol in VSCode + */ +class PersistentProtocol(opts: PersistentProtocolOptions, msgListener: ((data: ByteArray)->Unit)? = null) : IMessagePassingProtocol { + companion object { + private val LOG = Logger.getInstance(PersistentProtocol::class.java) + } + + /** + * Persistent protocol configuration + */ + class PersistentProtocolOptions( + val socket: ISocket, + val initialChunk: ByteArray? = null, + val loadEstimator: ILoadEstimator? = null, + val sendKeepAlive: Boolean = true + ) + + private val _isReconnecting = AtomicBoolean(false) + private val _didSendDisconnect = AtomicBoolean(false) + + private val _outgoingUnackMsg = LinkedBlockingQueue() + private val _outgoingMsgId = AtomicInteger(0) + private val _outgoingAckId = AtomicInteger(0) + private var _outgoingAckTimeout: Timer? = null + + private val _incomingMsgId = AtomicInteger(0) + private val _incomingAckId = AtomicInteger(0) + private val _incomingMsgLastTime = AtomicLong(0L) + private var _incomingAckTimeout: Timer? = null + + private var _keepAliveInterval: Timer? = null + + private val _lastReplayRequestTime = AtomicLong(0L) + private val _lastSocketTimeoutTime = AtomicLong(System.currentTimeMillis()) + + private var _socket: ISocket + private var _socketWriter: ProtocolWriter + private var _socketReader: ProtocolReader + private val _socketDisposables = mutableListOf() + + private val _loadEstimator: ILoadEstimator + private val _shouldSendKeepAlive: Boolean + + // Buffered event emitters + private val _onControlMessage = BufferedEmitter() + private val _onMessage = BufferedEmitter() + private val _onDidDispose = BufferedEmitter() + private val _onSocketClose = BufferedEmitter() + private val _onSocketTimeout = BufferedEmitter() + + private var _isDisposed = false + + /** + * Get unacknowledged message count + */ + val unacknowledgedCount: Int + get() = _outgoingMsgId.get() - _outgoingAckId.get() + + /** + * Check if protocol has been disposed + */ + fun isDisposed(): Boolean { + return _isDisposed + } + + init { + _loadEstimator = opts.loadEstimator ?: LoadEstimator.getInstance() + _shouldSendKeepAlive = opts.sendKeepAlive + _socket = opts.socket + + _socketWriter = ProtocolWriter(_socket) + _socketReader = ProtocolReader(_socket) + _socketDisposables.add(_socketReader.onMessage(this::_receiveMessage)) + _socketDisposables.add(_socket.onClose { event -> _onSocketClose.fire(event) }) + + if (opts.initialChunk != null) { + _socketReader.acceptChunk(opts.initialChunk) + } + + if(msgListener != null){ + this._onMessage.event { data -> + msgListener(data) + } + } + + _socket.startReceiving() + + if (_shouldSendKeepAlive) { + _keepAliveInterval = Timer().apply { + scheduleAtFixedRate(object : TimerTask() { + override fun run() { + _sendKeepAlive() + } + }, ProtocolConstants.KEEP_ALIVE_SEND_TIME.toLong(), ProtocolConstants.KEEP_ALIVE_SEND_TIME.toLong()) + } + } + } + + override fun dispose() { + _outgoingAckTimeout?.cancel() + _outgoingAckTimeout = null + + _incomingAckTimeout?.cancel() + _incomingAckTimeout = null + + _keepAliveInterval?.cancel() + _keepAliveInterval = null + + _socketDisposables.forEach { it.dispose() } + _socketDisposables.clear() + + _isDisposed = true + } + + override suspend fun drain() { + _socketWriter.drain() + } + + override fun send(buffer: ByteArray) { + val myId = _outgoingMsgId.incrementAndGet() + val currentIncomingAckId = _incomingMsgId.get() + _incomingAckId.set(currentIncomingAckId) + val msg = ProtocolMessage(ProtocolMessageType.REGULAR, myId, currentIncomingAckId, buffer) + _outgoingUnackMsg.add(msg) + if (!_isReconnecting.get()) { + _socketWriter.write(msg) + _recvAckCheck() + } + } + + override fun onMessage(listener: MessageListener): Disposable { + return _onMessage.event { data -> + listener.onMessage(data) + } + } + + override fun onDidDispose(listener: () -> Unit): Disposable { + return _onDidDispose.event { listener() } + } + + // Other public methods + fun onControlMessage(listener: (ByteArray) -> Unit): Disposable { + return _onControlMessage.event(listener) + } + + fun onSocketClose(listener: (SocketCloseEvent) -> Unit): Disposable { + return _onSocketClose.event(listener) + } + + fun onSocketTimeout(listener: (SocketTimeoutEvent) -> Unit): Disposable { + return _onSocketTimeout.event(listener) + } + + fun sendDisconnect() { + if (_didSendDisconnect.compareAndSet(false, true)) { + val msg = ProtocolMessage(ProtocolMessageType.DISCONNECT, 0, 0, ByteArray(0)) + _socketWriter.write(msg) + _socketWriter.flush() + } + } + + fun sendPause() { + val msg = ProtocolMessage(ProtocolMessageType.PAUSE, 0, 0, ByteArray(0)) + _socketWriter.write(msg) + } + + fun sendResume() { + val msg = ProtocolMessage(ProtocolMessageType.RESUME, 0, 0, ByteArray(0)) + _socketWriter.write(msg) + } + + fun pauseSocketWriting() { + _socketWriter.pause() + } + + fun getSocket(): ISocket { + return _socket + } + + fun getMillisSinceLastIncomingData(): Long { + return System.currentTimeMillis() - _socketReader.getLastReadTime() + } + + fun beginAcceptReconnection(socket: ISocket, initialDataChunk: ByteArray?) { + _isReconnecting.set(true) + + _socketDisposables.forEach { it.dispose() } + _socketDisposables.clear() + _onControlMessage.flushBuffer() + _onSocketClose.flushBuffer() + _onSocketTimeout.flushBuffer() + _socket.dispose() + + _lastReplayRequestTime.set(0) + _lastSocketTimeoutTime.set(System.currentTimeMillis()) + + _socket = socket + _socketWriter = ProtocolWriter(_socket) + _socketReader = ProtocolReader(_socket) + _socketDisposables.add(_socketReader.onMessage(this::_receiveMessage)) + _socketDisposables.add(_socket.onClose { event -> _onSocketClose.fire(event) }) + + if (initialDataChunk != null) { + _socketReader.acceptChunk(initialDataChunk) + } + } + + fun endAcceptReconnection() { + _isReconnecting.set(false) + + // After reconnection, let the other side know which messages have been received + val currentIncomingMsgId = _incomingMsgId.get() + _incomingAckId.set(currentIncomingMsgId) + val msg = ProtocolMessage(ProtocolMessageType.ACK, 0, currentIncomingMsgId, ByteArray(0)) + _socketWriter.write(msg) + + // Resend all unacknowledged messages + val toSend = _outgoingUnackMsg.toTypedArray() + for (message in toSend) { + _socketWriter.write(message) + } + _recvAckCheck() + } + + fun acceptDisconnect() { + _onDidDispose.fire(Unit) + } + + private fun _receiveMessage(msg: ProtocolMessage) { + val currentOutgoingAckId = _outgoingAckId.get() + if (msg.ack > currentOutgoingAckId) { + _outgoingAckId.set(msg.ack) + while (_outgoingUnackMsg.isNotEmpty()) { + val first = _outgoingUnackMsg.peek() + if (first != null && first.id <= msg.ack) { + _outgoingUnackMsg.poll() + } else { + break + } + } + } + + when (msg.type) { + ProtocolMessageType.NONE -> { + // N/A + } + ProtocolMessageType.REGULAR -> { + val currentIncomingMsgId = _incomingMsgId.get() + if (msg.id > currentIncomingMsgId) { + if (msg.id != currentIncomingMsgId + 1) { + // Some messages are lost, request the other side to resend + val now = System.currentTimeMillis() + val lastReplayTime = _lastReplayRequestTime.get() + if (now - lastReplayTime > 10000) { + // Send replay request at most once every 10 seconds + _lastReplayRequestTime.set(now) + _socketWriter.write(ProtocolMessage(ProtocolMessageType.REPLAY_REQUEST, 0, 0, ByteArray(0))) + } + } else { + _incomingMsgId.set(msg.id) + _incomingMsgLastTime.set(System.currentTimeMillis()) + _sendAckCheck() + _onMessage.fire(msg.data) + } + } + } + ProtocolMessageType.CONTROL -> { + _onControlMessage.fire(msg.data) + } + ProtocolMessageType.ACK -> { + // Already handled above + } + ProtocolMessageType.DISCONNECT -> { + _onDidDispose.fire(Unit) + } + ProtocolMessageType.REPLAY_REQUEST -> { + // Resend all unacknowledged messages + val toSend = _outgoingUnackMsg.toTypedArray() + for (message in toSend) { + _socketWriter.write(message) + } + _recvAckCheck() + } + ProtocolMessageType.PAUSE -> { + _socketWriter.pause() + } + ProtocolMessageType.RESUME -> { + _socketWriter.resume() + } + ProtocolMessageType.KEEP_ALIVE -> { + // No need to handle + } + } + } + + fun readEntireBuffer(): ByteArray { + return _socketReader.readEntireBuffer() + } + + fun flush() { + _socketWriter.flush() + } + + /** + * Send control message that doesn't participate in regular acknowledgment flow + */ + fun sendControl(buffer: ByteArray) { + val msg = ProtocolMessage(ProtocolMessageType.CONTROL, 0, 0, buffer) + _socketWriter.write(msg) + } + + private fun _sendAckCheck() { + val currentIncomingMsgId = _incomingMsgId.get() + val currentIncomingAckId = _incomingAckId.get() + + if (currentIncomingMsgId <= currentIncomingAckId) { + // No messages need acknowledgment + return + } + + if (_incomingAckTimeout != null) { + // There will be a check in the near future + return + } + + val timeSinceLastIncomingMsg = System.currentTimeMillis() - _incomingMsgLastTime.get() + if (timeSinceLastIncomingMsg >= ProtocolConstants.ACKNOWLEDGE_TIME) { + // Enough time has passed since receiving this message, + // and there are no messages that need to be sent from our side during this period, + // so we will send a message containing only acknowledgment. + _sendAck() + return + } + + _incomingAckTimeout = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + _incomingAckTimeout = null + _sendAckCheck() + } + }, ProtocolConstants.ACKNOWLEDGE_TIME - timeSinceLastIncomingMsg + 5) + } + } + + private fun _recvAckCheck() { + val currentOutgoingMsgId = _outgoingMsgId.get() + val currentOutgoingAckId = _outgoingAckId.get() + + if (currentOutgoingMsgId <= currentOutgoingAckId) { + // All messages have been acknowledged + return + } + + if (_outgoingAckTimeout != null) { + // There will be a check in the near future + return + } + + if (_isReconnecting.get()) { + // Don't trigger timeout during reconnection, + // because messages won't actually be written until `endAcceptReconnection` + return + } + + val oldestUnacknowledgedMsg = _outgoingUnackMsg.peek()!! + val timeSinceOldestUnacknowledgedMsg = System.currentTimeMillis() - oldestUnacknowledgedMsg.writtenTime + val timeSinceLastReceivedSomeData = System.currentTimeMillis() - _socketReader.getLastReadTime() + val timeSinceLastTimeout = System.currentTimeMillis() - _lastSocketTimeoutTime.get() + + if (timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.TIMEOUT_TIME && + timeSinceLastReceivedSomeData >= ProtocolConstants.TIMEOUT_TIME && + timeSinceLastTimeout >= ProtocolConstants.TIMEOUT_TIME) { + + // It's been a long time since messages we sent were acknowledged, + // and it's also been a long time since we received any data + + // But this might be because the event loop is busy and can't read messages + if (!_loadEstimator.hasHighLoad()) { + // Drop socket + _lastSocketTimeoutTime.set(System.currentTimeMillis()) + _onSocketTimeout.fire(SocketTimeoutEvent( + _outgoingUnackMsg.size, + timeSinceOldestUnacknowledgedMsg, + timeSinceLastReceivedSomeData + )) + return + } + } + + val minimumTimeUntilTimeout = maxOf( + ProtocolConstants.TIMEOUT_TIME - timeSinceOldestUnacknowledgedMsg, + ProtocolConstants.TIMEOUT_TIME - timeSinceLastReceivedSomeData, + ProtocolConstants.TIMEOUT_TIME - timeSinceLastTimeout, + 500 + ) + + _outgoingAckTimeout = Timer().apply { + schedule(object : TimerTask() { + override fun run() { + _outgoingAckTimeout = null + _recvAckCheck() + } + }, minimumTimeUntilTimeout) + } + } + + private fun _sendAck() { + val currentIncomingMsgId = _incomingMsgId.get() + val currentIncomingAckId = _incomingAckId.get() + + if (currentIncomingMsgId <= currentIncomingAckId) { + // No messages need acknowledgment + return + } + + _incomingAckId.set(currentIncomingMsgId) + val msg = ProtocolMessage(ProtocolMessageType.ACK, 0, currentIncomingMsgId, ByteArray(0)) + _socketWriter.write(msg) + } + + private fun _sendKeepAlive() { + val currentIncomingMsgId = _incomingMsgId.get() + _incomingAckId.set(currentIncomingMsgId) + val msg = ProtocolMessage(ProtocolMessageType.KEEP_ALIVE, 0, currentIncomingMsgId, ByteArray(0)) + _socketWriter.write(msg) + } +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolConstants.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolConstants.kt new file mode 100644 index 0000000000..b01fd70d0a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolConstants.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Protocol constants + * Corresponds to ProtocolConstants in VSCode + */ +object ProtocolConstants { + /** + * Message header length (bytes) + */ + const val HEADER_LENGTH = 13 + + /** + * Maximum delay time for sending acknowledgment messages (milliseconds) + */ + const val ACKNOWLEDGE_TIME = 2000 // 2 seconds + + /** + * If a sent message has not been acknowledged beyond this time, and no server data has been received during this period, + * the connection is considered timed out + */ + const val TIMEOUT_TIME = 20000 // 20 seconds + + /** + * If no reconnection occurs within this time range, the connection is considered permanently closed + */ + const val RECONNECTION_GRACE_TIME = 3 * 60 * 60 * 1000 // 3 hours + + /** + * Maximum grace time between first and last reconnection + */ + const val RECONNECTION_SHORT_GRACE_TIME = 5 * 60 * 1000 // 5 minutes + + /** + * Send a message at regular intervals to prevent the connection from being closed by the operating system + */ + const val KEEP_ALIVE_SEND_TIME = 5000 // 5 seconds +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessage.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessage.kt new file mode 100644 index 0000000000..4e1047c6f9 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessage.kt @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Protocol message + * Corresponds to ProtocolMessage in VSCode + */ +class ProtocolMessage( + /** + * Message type + */ + val type: ProtocolMessageType, + + /** + * Message ID + */ + val id: Int, + + /** + * Acknowledgment ID + */ + val ack: Int, + + /** + * Message data + */ + val data: ByteArray = ByteArray(0) +) { + /** + * Message write time (millisecond timestamp) + */ + var writtenTime: Long = 0 + + /** + * Get message size (bytes) + * @return Message size + */ + val size: Int + get() = data.size + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ProtocolMessage + + if (type != other.type) return false + if (id != other.id) return false + if (ack != other.ack) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + id + result = 31 * result + ack + result = 31 * result + data.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessageType.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessageType.kt new file mode 100644 index 0000000000..24bdb6b7b7 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolMessageType.kt @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Protocol message type + * Corresponds to ProtocolMessageType in VSCode + */ +enum class ProtocolMessageType(val value: Int) { + /** + * Undefined + */ + NONE(0), + + /** + * Regular message + */ + REGULAR(1), + + /** + * Control message + */ + CONTROL(2), + + /** + * Acknowledgment message + */ + ACK(3), + + /** + * Disconnect message + */ + DISCONNECT(5), + + /** + * Replay request message + */ + REPLAY_REQUEST(6), + + /** + * Pause message + */ + PAUSE(7), + + /** + * Resume message + */ + RESUME(8), + + /** + * Keep alive message + */ + KEEP_ALIVE(9); + + /** + * Get string description of enum type + * @return String description + */ + fun toTypeString(): String = when(this) { + NONE -> "None" + REGULAR -> "Regular" + CONTROL -> "Control" + ACK -> "Ack" + DISCONNECT -> "Disconnect" + REPLAY_REQUEST -> "ReplayRequest" + PAUSE -> "PauseWriting" + RESUME -> "ResumeWriting" + KEEP_ALIVE -> "KeepAlive" + } + + companion object { + /** + * Get corresponding enum by integer value + * @param value Integer value + * @return Corresponding enum, returns NONE if not found + */ + fun fromValue(value: Int): ProtocolMessageType { + return values().find { it.value == value } ?: NONE + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolReader.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolReader.kt new file mode 100644 index 0000000000..7262610f37 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolReader.kt @@ -0,0 +1,167 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.diagnostic.logger +import java.nio.ByteBuffer + +/** + * Protocol reader + * Corresponds to ProtocolReader in VSCode + */ +class ProtocolReader(private val socket: ISocket) : Disposable { + private var isDisposed = false + private val incomingData = ChunkStream() + private var lastReadTime = System.currentTimeMillis() + + private val messageListeners = mutableListOf<(ProtocolMessage) -> Unit>() + + // Read state + private val state = State() + + companion object { + private val LOG = Logger.getInstance(ProtocolReader::class.java) + } + + init { + socket.onData(this::acceptChunk) + } + + /** + * Add message listener + * @param listener Message listener + * @return Listener registration identifier for removing the listener + */ + fun onMessage(listener: (ProtocolMessage) -> Unit): Disposable { + messageListeners.add(listener) + return Disposable { messageListeners.remove(listener) } + } + + /** + * Receive data chunk + * @param data Data chunk + */ + fun acceptChunk(data: ByteArray) { + if (data.isEmpty()) { + return + } + lastReadTime = System.currentTimeMillis() + + incomingData.acceptChunk(data) + + while (incomingData.byteLength >= state.readLen) { + val buff = incomingData.read(state.readLen) + + if (state.readHead) { + // buff is message header + + // Parse message header + val buffer = ByteBuffer.wrap(buff) + val messageTypeByte = buffer.get(0) + val id = buffer.getInt(1) + val ack = buffer.getInt(5) + val messageSize = buffer.getInt(9) + + val messageType = ProtocolMessageType.fromValue(messageTypeByte.toInt()) + + // Save new state => next time will read message body + state.readHead = false + state.readLen = messageSize + state.messageType = messageType + state.id = id + state.ack = ack + + socket.traceSocketEvent( + SocketDiagnosticsEventType.PROTOCOL_HEADER_READ, + HeaderReadInfo( + messageType.toTypeString(), + id, + ack, + messageSize + ) + ) + } else { + // buff is message body + val messageType = state.messageType + val id = state.id + val ack = state.ack + + // Save new state => next time will read message header + state.readHead = true + state.readLen = ProtocolConstants.HEADER_LENGTH + state.messageType = ProtocolMessageType.NONE + state.id = 0 + state.ack = 0 + + socket.traceSocketEvent(SocketDiagnosticsEventType.PROTOCOL_MESSAGE_READ, buff) + + val message = ProtocolMessage(messageType, id, ack, buff) + + // Notify listeners + ArrayList(messageListeners).forEach { listener -> + try { + listener(message) + } catch (e: Exception) { + // Log exception but do not interrupt processing + LOG.warn("Error in message listener: ${e.message}", e) + } + } + + if (isDisposed) { + // Check if event listeners caused object to be disposed + break + } + } + } + } + + /** + * Read entire buffer + * @return All data in the buffer + */ + fun readEntireBuffer(): ByteArray { + return incomingData.read(incomingData.byteLength) + } + + /** + * Get last data read time + * @return Last read time (millisecond timestamp) + */ + fun getLastReadTime(): Long { + return lastReadTime + } + + override fun dispose() { + isDisposed = true + messageListeners.clear() + } + + /** + * Read state + */ + private class State { + var readHead = true + var readLen = ProtocolConstants.HEADER_LENGTH + var messageType = ProtocolMessageType.NONE + var id = 0 + var ack = 0 + } + + /** + * Message header read information (for debugging) + */ + private data class HeaderReadInfo( + val messageType: String, + val id: Int, + val ack: Int, + val messageSize: Int + ) { + override fun toString(): String { + return "HeaderReadInfo{messageType='$messageType', id=$id, ack=$ack, messageSize=$messageSize}" + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolWriter.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolWriter.kt new file mode 100644 index 0000000000..7bcd53fdaa --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/ProtocolWriter.kt @@ -0,0 +1,438 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.nio.ByteBuffer +import java.util.TreeMap +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Protocol writer + * Corresponds to ProtocolWriter in VSCode, but enhanced with message ordering functionality + */ +class ProtocolWriter( + private val socket: ISocket, + private val enableLogging: Boolean = false // Add logging control variable +) { + private val logger = Logger.getInstance(ProtocolWriter::class.java) + + // Core state variables + private val isDisposed = AtomicBoolean(false) + private val isPaused = AtomicBoolean(false) + private val lastWriteTime = AtomicLong(0) + + // Lock to protect message queue + private val queueLock = ReentrantLock() + + // Use TreeMap to sort by message ID + private val messageQueue = TreeMap() + + // Special message queue (messages that don't need ordering, like ACK and priority messages) + private val specialMessageQueue = mutableListOf() + + // Next expected message ID + private var nextExpectedId = 1 + + // Write scheduling state + private var isWriteScheduled = AtomicBoolean(false) + private var writeJob: Job? = null + + // Message blocking detection task + private var blockingDetectionJob: Job? = null + + // Coroutine scope + private val coroutineScope = CoroutineScope(Dispatchers.IO) + + init { + // Start message blocking detection task + startBlockingDetection() + } + + /** + * Start message blocking detection task + */ + private fun startBlockingDetection() { + blockingDetectionJob = coroutineScope.launch { + while (!isDisposed.get()) { + try { + delay(5000) // Check every 5 seconds + checkMessageBlocking() + } catch (e: Exception) { + if (!isDisposed.get()) { + logWarn("Error in blocking detection: ${e.message}", e) + } + break + } + } + } + } + + /** + * Check message blocking situation + */ + private fun checkMessageBlocking() { + if (isDisposed.get()) { + return + } + + queueLock.withLock { + // If message queue is not empty but doesn't contain the expected next ID, it indicates messages are blocked + if (messageQueue.isNotEmpty() && !messageQueue.containsKey(nextExpectedId)) { + val minId = messageQueue.firstKey() + val queueSize = messageQueue.size + val queueIds = messageQueue.keys.take(10).joinToString(", ") // Display first 10 IDs + + logWarn("Message blocking detected! " + + "Expected next ID: $nextExpectedId, " + + "Minimum ID in queue: $minId, " + + "Queue size: $queueSize, " + + "Queue IDs: [$queueIds${if (queueSize > 10) "..." else ""}]") + + // Check if there are consecutive ID segments that can be sent + val consecutiveIds = mutableListOf() + var currentId = minId + while (messageQueue.containsKey(currentId)) { + consecutiveIds.add(currentId) + currentId++ + } + + if (consecutiveIds.isNotEmpty()) { + logWarn("Consecutive IDs available from $minId: ${consecutiveIds.joinToString(", ")}") + } + + // Check missing ID ranges + if (minId > nextExpectedId) { + logWarn("Missing message IDs: ${nextExpectedId} to ${minId - 1} (${minId - nextExpectedId} messages)") + } + } + } + } + + /** + * Log messages, output based on enableLogging + */ + private fun logInfo(message: String) { + if (enableLogging) { + logger.info(message) + } + } + + private fun logDebug(message: String) { + if (enableLogging) { + logger.debug(message) + } + } + + private fun logWarn(message: String, throwable: Throwable? = null) { + if (enableLogging) { + logger.warn(message, throwable) + } + } + + private fun logError(message: String, throwable: Throwable? = null) { + if (enableLogging) { + logger.error(message, throwable) + } + } + + /** + * Message package structure + */ + private data class MessagePackage( + val id: Int, + val data: ByteArray + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is MessagePackage) return false + if (id != other.id) return false + return data.contentEquals(other.data) + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + data.contentHashCode() + return result + } + } + + companion object { + // Special message ID constants + private const val ACK_MESSAGE_ID = 0 + private const val PRIORITY_MESSAGE_ID = -1 + } + + /** + * Release resources + */ + fun dispose() { + if (isDisposed.getAndSet(true)) { + return + } + + // Stop blocking detection task + blockingDetectionJob?.cancel() + blockingDetectionJob = null + + try { + flush() + } catch (e: Exception) { + logWarn("Error flushing protocol writer: ${e.message}", e) + } + + writeJob?.cancel() + logInfo("ProtocolWriter disposed") + } + + /** + * Wait for all data to be sent + */ + suspend fun drain() { + flush() + return socket.drain() + } + + /** + * Flush and immediately send all sendable messages + */ + fun flush() { + writeNow() + } + + /** + * Pause writing + */ + fun pause() { + isPaused.set(true) + } + + /** + * Resume writing + */ + fun resume() { + if (!isPaused.getAndSet(false)) { + return + } + + scheduleWriting() + } + + /** + * Write message + * @param msg Message to write + */ + fun write(msg: ProtocolMessage) { + if (isDisposed.get()) { + // Resources released, ignore write request + logDebug("Ignoring write request, writer is disposed") + return + } + + if(msg.type != ProtocolMessageType.KEEP_ALIVE) { + logInfo("Writing message: id=${msg.id}, ack=${msg.ack}, type=${msg.type}, data size=${msg.data.size}") + } + // Record write time + msg.writtenTime = System.currentTimeMillis() + lastWriteTime.set(System.currentTimeMillis()) + + // Create message header + val headerBuffer = ByteBuffer.allocate(ProtocolConstants.HEADER_LENGTH) + headerBuffer.put(0, msg.type.value.toByte()) + headerBuffer.putInt(1, msg.id) + headerBuffer.putInt(5, msg.ack) + headerBuffer.putInt(9, msg.data.size) + + val header = headerBuffer.array() + + // Trace event + socket.traceSocketEvent( + SocketDiagnosticsEventType.PROTOCOL_HEADER_WRITE, + mapOf( + "messageType" to msg.type.toTypeString(), + "id" to msg.id, + "ack" to msg.ack, + "messageSize" to msg.data.size + ) + ) + socket.traceSocketEvent(SocketDiagnosticsEventType.PROTOCOL_MESSAGE_WRITE, msg.data) + + // Merge header and data + val combined = ByteArray(header.size + msg.data.size) + System.arraycopy(header, 0, combined, 0, header.size) + System.arraycopy(msg.data, 0, combined, header.size, msg.data.size) + + // Add to queue and schedule writing + addMessageToQueue(msg.id, combined) + } + + /** + * Add message to queue + * @param id Message ID + * @param data Complete message data (header + content) + */ + private fun addMessageToQueue(id: Int, data: ByteArray) { + val pkg = MessagePackage(id, data) + + queueLock.withLock { + // Special messages (ACK or priority messages) are directly added to special queue + if (id == ACK_MESSAGE_ID || id == PRIORITY_MESSAGE_ID) { + specialMessageQueue.add(pkg) + logDebug("Added special message to queue: id=$id") + } else { + // Regular messages are sorted by ID + messageQueue[id] = pkg + logDebug("Added message to sorted queue: id=$id, queue size=${messageQueue.size}") + } + } + + // Schedule writing + scheduleWriting() + } + + /** + * Schedule write task + */ + private fun scheduleWriting() { + if (isPaused.get() || isDisposed.get()) { + return + } + + // If there's already a scheduled task, don't reschedule + if (!isWriteScheduled.compareAndSet(false, true)) { + return + } + + writeJob = coroutineScope.launch { + try { + // Write once immediately + writeNow() + + // Reset scheduling state, allow subsequent scheduling + isWriteScheduled.set(false) + + // Check if there is still data to write + if (hasDataToWrite()) { + scheduleWriting() + } + } catch (e: Exception) { + logError("Error in write job: ${e.message}", e) + // Reset scheduling state, allow subsequent scheduling + isWriteScheduled.set(false) + // If the error is not because the socket is closed, try to reschedule + if (!isDisposed.get() && hasDataToWrite()) { + delay(100) // Retry after a short delay + scheduleWriting() + } + } + } + } + + /** + * Check if there is data to write + */ + private fun hasDataToWrite(): Boolean { + return queueLock.withLock { + specialMessageQueue.isNotEmpty() || messageQueue.isNotEmpty() + } + } + + /** + * Immediately write all sendable messages + */ + private fun writeNow() { + if (isPaused.get() || isDisposed.get()) { + return + } + + val dataToWrite = queueLock.withLock { + // If no messages, return directly + if (specialMessageQueue.isEmpty() && messageQueue.isEmpty()) { + return@withLock null + } + + // First check if there are special messages + var specialData: ByteArray? = null + if (specialMessageQueue.isNotEmpty()) { + specialData = specialMessageQueue.flatMap { it.data.toList() }.toByteArray() + specialMessageQueue.clear() + } + + // Check regular message queue + if (messageQueue.isEmpty()) { + return@withLock specialData + } + + // Find consecutive messages starting from nextExpectedId + val messagesToSend = mutableListOf() + var currentId = nextExpectedId + + // Strictly check if there's the expected next ID + // If the smallest ID in queue is greater than expected ID, don't send any messages, wait for missing messages + if (!messageQueue.containsKey(nextExpectedId)) { + // No expected next ID in queue, don't send any messages + logInfo("Waiting for message with ID=$nextExpectedId before sending later messages, messageQueue: ${messageQueue.size}") + return@withLock specialData + } + + // Collect consecutive messages + while (messageQueue.containsKey(currentId)) { + val message = messageQueue[currentId]!! + messagesToSend.add(message) + messageQueue.remove(currentId) + currentId++ + } + + // Update next expected ID + if (messagesToSend.isNotEmpty()) { + nextExpectedId = currentId + logDebug("Next expected ID updated to $nextExpectedId") + + // Merge all message data + if (specialData != null) { + return@withLock specialData + messagesToSend.flatMap { it.data.toList() }.toByteArray() + }else { + return@withLock messagesToSend.flatMap { it.data.toList() }.toByteArray() + } + } + + // No consecutive messages to send + specialData + } + + // Write data (perform I/O outside the lock) + if (dataToWrite != null && dataToWrite.isNotEmpty()) { + try { + logInfo("Writing ${dataToWrite.size} bytes to socket") + socket.traceSocketEvent( + SocketDiagnosticsEventType.PROTOCOL_WRITE, + mapOf("byteLength" to dataToWrite.size) + ) + socket.write(dataToWrite) + } catch (e: Exception) { + logError("Error writing to socket: ${e.message}", e) + if (!isDisposed.get()) { + isDisposed.set(true) + } + throw e + } + } + } + + /** + * Get last write time + */ + fun getLastWriteTime(): Long { + return lastWriteTime.get() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketCloseEvent.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketCloseEvent.kt new file mode 100644 index 0000000000..f6388a0179 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketCloseEvent.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Socket close event + * Corresponds to SocketCloseEvent type in VSCode + */ +sealed interface SocketCloseEvent { + /** + * Get close event type + * @return Close event type + */ + val type: SocketCloseEventType + + /** + * Socket close event type + */ + enum class SocketCloseEventType { + /** + * Node socket close event + */ + NODE_SOCKET_CLOSE_EVENT, + + /** + * WebSocket close event + */ + WEB_SOCKET_CLOSE_EVENT + } + + /** + * Node Socket close event implementation + */ + data class NodeSocketCloseEvent( + /** + * Whether socket had transmission error + */ + val hadError: Boolean, + + /** + * Underlying error + */ + val error: Throwable? + ) : SocketCloseEvent { + override val type: SocketCloseEventType = SocketCloseEventType.NODE_SOCKET_CLOSE_EVENT + } + + /** + * WebSocket close event implementation + */ + data class WebSocketCloseEvent( + /** + * WebSocket close code + */ + val code: Int, + + /** + * WebSocket close reason + */ + val reason: String, + + /** + * Whether connection was cleanly closed + */ + val wasClean: Boolean, + + /** + * Underlying event + */ + val event: Any? + ) : SocketCloseEvent { + override val type: SocketCloseEventType = SocketCloseEventType.WEB_SOCKET_CLOSE_EVENT + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketDiagnosticsEventType.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketDiagnosticsEventType.kt new file mode 100644 index 0000000000..f23a090737 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketDiagnosticsEventType.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Socket diagnostics event type + * Corresponds to SocketDiagnosticsEventType in VSCode + */ +enum class SocketDiagnosticsEventType { + CREATED, + READ, + WRITE, + OPEN, + ERROR, + CLOSE, + + BROWSER_WEB_SOCKET_BLOB_RECEIVED, + + NODE_END_RECEIVED, + NODE_END_SENT, + NODE_DRAIN_BEGIN, + NODE_DRAIN_END, + + ZLIB_INFLATE_ERROR, + ZLIB_INFLATE_DATA, + ZLIB_INFLATE_INITIAL_WRITE, + ZLIB_INFLATE_INITIAL_FLUSH_FIRED, + ZLIB_INFLATE_WRITE, + ZLIB_INFLATE_FLUSH_FIRED, + ZLIB_DEFLATE_ERROR, + ZLIB_DEFLATE_DATA, + ZLIB_DEFLATE_WRITE, + ZLIB_DEFLATE_FLUSH_FIRED, + + WEB_SOCKET_NODE_SOCKET_WRITE, + WEB_SOCKET_NODE_SOCKET_PEEKED_HEADER, + WEB_SOCKET_NODE_SOCKET_READ_HEADER, + WEB_SOCKET_NODE_SOCKET_READ_DATA, + WEB_SOCKET_NODE_SOCKET_UNMASKED_DATA, + WEB_SOCKET_NODE_SOCKET_DRAIN_BEGIN, + WEB_SOCKET_NODE_SOCKET_DRAIN_END, + + PROTOCOL_HEADER_READ, + PROTOCOL_MESSAGE_READ, + PROTOCOL_HEADER_WRITE, + PROTOCOL_MESSAGE_WRITE, + PROTOCOL_WRITE +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketTimeoutEvent.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketTimeoutEvent.kt new file mode 100644 index 0000000000..ae01016fa1 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/SocketTimeoutEvent.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc + +/** + * Socket timeout event + * Corresponds to SocketTimeoutEvent in VSCode + */ +data class SocketTimeoutEvent( + /** + * Unacknowledged message count + */ + val unacknowledgedMsgCount: Int, + + /** + * Time since oldest unacknowledged message (milliseconds) + */ + val timeSinceOldestUnacknowledgedMsg: Long, + + /** + * Time since last received some data (milliseconds) + */ + val timeSinceLastReceivedSomeData: Long +) \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/IRPCProtocol.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/IRPCProtocol.kt new file mode 100644 index 0000000000..caedb87732 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/IRPCProtocol.kt @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import com.intellij.openapi.Disposable + +/** + * RPC protocol interface + * Corresponds to IRPCProtocol in VSCode + */ +interface IRPCProtocol : Disposable { + /** + * Current responsive state + */ + val responsiveState: ResponsiveState + + /** + * Get proxy object + * @param identifier Proxy identifier + * @return Proxy object + */ + fun getProxy(identifier: ProxyIdentifier): T + + /** + * Set local object instance + * @param identifier Proxy identifier + * @param instance Instance object + * @return Instance object + */ + fun set(identifier: ProxyIdentifier, instance: R): R + + /** + * Assert identifiers are registered + * @param identifiers List of proxy identifiers + */ + fun assertRegistered(identifiers: List>) + + /** + * Wait for the write buffer (if any) to become empty + */ + suspend fun drain() +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/LazyPromise.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/LazyPromise.kt new file mode 100644 index 0000000000..3eb5bc34ed --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/LazyPromise.kt @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import kotlinx.coroutines.CompletableDeferred + +/** + * Lazy Promise implementation + * Corresponds to LazyPromise in VSCode + */ +open class LazyPromise : CompletableDeferred by CompletableDeferred() { + /** + * Resolve Promise successfully + * @param value Result value + */ + fun resolveOk(value: Any?) { + complete(value) + } + + /** + * Reject Promise + * @param err Error object + */ + fun resolveErr(err: Throwable) { + completeExceptionally(err) + } +} + +/** + * Canceled Lazy Promise implementation + * Corresponds to CanceledLazyPromise in VSCode + */ +class CanceledLazyPromise : LazyPromise() { + init { + // Immediately complete with cancellation exception + completeExceptionally(CanceledException()) + } +} + +/** + * Cancellation exception + */ +class CanceledException : Exception("Operation cancelled") \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageBuffer.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageBuffer.kt new file mode 100644 index 0000000000..4b449fbe12 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageBuffer.kt @@ -0,0 +1,335 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import java.nio.ByteBuffer +import java.nio.ByteOrder + +/** + * Argument type enum + * Corresponds to ArgType in VSCode + */ +enum class ArgType(val value: Int) { + /** + * String type + */ + String(1), + + /** + * Binary buffer type + */ + VSBuffer(2), + + /** + * Serialized object with buffers type + */ + SerializedObjectWithBuffers(3), + + /** + * Undefined type + */ + Undefined(4); + + companion object { + /** + * Get type by value + */ + fun fromValue(value: Int): ArgType? = values().find { it.value == value } + } +} + +/** + * Mixed argument type + */ +sealed class MixedArg { + /** + * String argument + */ + data class StringArg(val value: ByteArray) : MixedArg() + + /** + * Binary buffer argument + */ + data class VSBufferArg(val value: ByteArray) : MixedArg() + + /** + * Serialized object with buffers argument + */ + data class SerializedObjectWithBuffersArg(val value: ByteArray, val buffers: List) : MixedArg() + + /** + * Undefined argument + */ + object UndefinedArg : MixedArg() +} + +/** + * Message buffer + * Corresponds to MessageBuffer in VSCode + */ +class MessageBuffer private constructor( + private val buffer: ByteBuffer +) { + companion object { + /** + * Allocate message buffer of specified size + */ + fun alloc(type: MessageType, req: Int, messageSize: Int): MessageBuffer { + val totalSize = messageSize + 1 /* type */ + 4 /* req */ + val buffer = ByteBuffer.allocate(totalSize).order(ByteOrder.BIG_ENDIAN) + val result = MessageBuffer(buffer) + result.writeUInt8(type.value) + result.writeUInt32(req) + return result + } + + /** + * Read message buffer from byte array + */ + fun read(buff: ByteArray, offset: Int): MessageBuffer { + val buffer = ByteBuffer.wrap(buff).order(ByteOrder.BIG_ENDIAN) + buffer.position(offset) + return MessageBuffer(buffer) + } + + /** + * UInt8 size + */ + const val sizeUInt8: Int = 1 + + /** + * UInt32 size + */ + const val sizeUInt32: Int = 4 + + /** + * Calculate short string size + */ + fun sizeShortString(str: ByteArray): Int { + return sizeUInt8 /* string length */ + str.size /* actual string */ + } + + /** + * Calculate long string size + */ + fun sizeLongString(str: ByteArray): Int { + return sizeUInt32 /* string length */ + str.size /* actual string */ + } + + /** + * Calculate binary buffer size + */ + fun sizeVSBuffer(buff: ByteArray): Int { + return sizeUInt32 /* buffer length */ + buff.size /* actual buffer */ + } + + /** + * Calculate mixed array size + */ + fun sizeMixedArray(arr: List): Int { + var size = 0 + size += 1 // arr length + for (el in arr) { + size += 1 // arg type + when (el) { + is MixedArg.StringArg -> + size += sizeLongString(el.value) + is MixedArg.VSBufferArg -> + size += sizeVSBuffer(el.value) + is MixedArg.SerializedObjectWithBuffersArg -> { + size += sizeUInt32 // buffer count + size += sizeLongString(el.value) + for (buffer in el.buffers) { + size += sizeVSBuffer(buffer) + } + } + is MixedArg.UndefinedArg -> + // empty... + Unit + } + } + return size + } + } + + /** + * Get underlying buffer + */ + val bytes: ByteArray + get() = buffer.array() + + /** + * Get buffer size + */ + val byteLength: Int + get() = buffer.array().size + + /** + * Write UInt8 + */ + fun writeUInt8(n: Int) { + buffer.put(n.toByte()) + } + + /** + * Read UInt8 + */ + fun readUInt8(): Int { + return buffer.get().toInt() and 0xFF + } + + /** + * Write UInt32 + */ + fun writeUInt32(n: Int) { + buffer.putInt(n) + } + + /** + * Read UInt32 + */ + fun readUInt32(): Int { + return buffer.getInt() + } + + /** + * Write short string + */ + fun writeShortString(str: ByteArray) { + buffer.put(str.size.toByte()) + buffer.put(str) + } + + /** + * Read short string + */ + fun readShortString(): String { + val strByteLength = buffer.get().toInt() and 0xFF + val strBuff = ByteArray(strByteLength) + buffer.get(strBuff) + return String(strBuff) + } + + /** + * Write long string + */ + fun writeLongString(str: ByteArray) { + buffer.putInt(str.size) + buffer.put(str) + } + + /** + * Read long string + */ + fun readLongString(): String { + val strByteLength = buffer.getInt() + val strBuff = ByteArray(strByteLength) + buffer.get(strBuff) + return String(strBuff) + } + + /** + * Write buffer + */ + fun writeBuffer(buff: ByteArray) { + buffer.putInt(buff.size) + buffer.put(buff) + } + + /** + * Write VSBuffer + */ + fun writeVSBuffer(buff: ByteArray) { + buffer.putInt(buff.size) + buffer.put(buff) + } + + /** + * Read VSBuffer + */ + fun readVSBuffer(): ByteArray { + val buffLength = buffer.getInt() + val buff = ByteArray(buffLength) + buffer.get(buff) + return buff + } + + /** + * Write mixed array + */ + fun writeMixedArray(arr: List) { + buffer.put(arr.size.toByte()) + for (el in arr) { + when (el) { + is MixedArg.StringArg -> { + writeUInt8(ArgType.String.value) + writeLongString(el.value) + } + is MixedArg.VSBufferArg -> { + writeUInt8(ArgType.VSBuffer.value) + writeVSBuffer(el.value) + } + is MixedArg.SerializedObjectWithBuffersArg -> { + writeUInt8(ArgType.SerializedObjectWithBuffers.value) + writeUInt32(el.buffers.size) + writeLongString(el.value) + for (buffer in el.buffers) { + writeBuffer(buffer) + } + } + is MixedArg.UndefinedArg -> { + writeUInt8(ArgType.Undefined.value) + } + } + } + } + + /** + * Read mixed array + */ + fun readMixedArray(): List { + val arrLen = readUInt8() + val arr = ArrayList(arrLen) + + for (i in 0 until arrLen) { + val argType = ArgType.fromValue(readUInt8()) ?: ArgType.Undefined + when (argType) { + ArgType.String -> { + arr.add(readLongString()) + } + ArgType.VSBuffer -> { + arr.add(readVSBuffer()) + } + ArgType.SerializedObjectWithBuffers -> { + val bufferCount = readUInt32() + val jsonString = readLongString() + val buffers = ArrayList(bufferCount) + for (j in 0 until bufferCount) { + buffers.add(readVSBuffer()) + } + arr.add(SerializableObjectWithBuffers(parseJsonAndRestoreBufferRefs(jsonString, buffers, null))) + } + ArgType.Undefined -> { + arr.add(null) + } + } + } + return arr + } +} + +/** + * Parse JSON and restore buffer references + * Corresponds to parseJsonAndRestoreBufferRefs in VSCode + */ +fun parseJsonAndRestoreBufferRefs( + jsonString: String, + buffers: List, + uriTransformer: ((String, Any?) -> Any?)? = null +): Any { + // In actual project, should implement more complete functionality + // Need to parse JSON string, restore buffer references, and apply URI transformation + return jsonString +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageIO.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageIO.kt new file mode 100644 index 0000000000..21424de988 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageIO.kt @@ -0,0 +1,463 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.lang.Exception + + // Symbol name for buffer reference during serialization +private const val REF_SYMBOL_NAME = "\$\$ref\$\$" + +// Undefined reference +private val UNDEFINED_REF = mapOf(REF_SYMBOL_NAME to -1) + +/** + * JSON string with buffer references + */ +data class StringifiedJsonWithBufferRefs( + val jsonString: String, + val referencedBuffers: List +) { + // data class auto-generates component1() and component2() functions, supports destructuring +} + +/** + * Serialize JSON to string with buffer references + */ +fun stringifyJsonWithBufferRefs(obj: Any?, replacer: ((String, Any?) -> Any?)? = null, useSafeStringify: Boolean = false): StringifiedJsonWithBufferRefs { + val foundBuffers = mutableListOf() + + // Process object recursively, identify and replace buffers + fun processObject(value: Any?): Any? { + return when (value) { + null -> null + is ByteArray -> { + val bufferIndex = foundBuffers.size + foundBuffers.add(value) + mapOf(REF_SYMBOL_NAME to bufferIndex) + } + is Map<*, *> -> { + val result = mutableMapOf() + value.forEach { (k, v) -> + val key = k.toString() + val processedValue = processObject(v) + val finalValue = replacer?.invoke(key, processedValue) ?: processedValue + result[key] = finalValue + } + result + } + is List<*> -> { + value.map { processObject(it) } + } + is Array<*> -> { + value.map { processObject(it) } + } + is SerializableObjectWithBuffers<*> -> { + // Process serializable object + processObject(value.value) + } + else -> { + // If it is other basic types, return directly + value + } + } + } + + // Process object, collect buffers + val processedObj = processObject(obj) + + // Use GSON for serialization + val gson = Gson() + val serialized = try { + gson.toJson(processedObj) + } catch (e: Exception) { + if (useSafeStringify) "null" else throw e + } + + return StringifiedJsonWithBufferRefs(serialized, foundBuffers) +} + +/** + * Request argument serialization type + */ +sealed class SerializedRequestArguments { + /** + * Simple type argument + */ + data class Simple(val args: String) : SerializedRequestArguments(){ + override fun toString(): String { + return args + } + } + + /** + * Mixed type argument + */ + data class Mixed(val args: List) : SerializedRequestArguments(){ + override fun toString(): String { + return args.joinToString { "\n" } + } + } +} + +/** + * Message IO utility class + * Corresponds to MessageIO in VSCode + */ +object MessageIO { + /** + * Check whether to use mixed argument serialization + */ + private fun useMixedArgSerialization(arr: List): Boolean { + for (arg in arr) { + if (arg is ByteArray || arg is SerializableObjectWithBuffers<*> || arg == null) { + return true + } + } + return false + } + + /** + * Serialize request arguments + */ + fun serializeRequestArguments(args: List, replacer: ((String, Any?) -> Any?)? = null): SerializedRequestArguments { + if (useMixedArgSerialization(args)) { + val massagedArgs = mutableListOf() + for (i in args.indices) { + val arg = args[i] + when { + arg is ByteArray -> + massagedArgs.add(MixedArg.VSBufferArg(arg)) + arg == null -> + massagedArgs.add(MixedArg.UndefinedArg) + arg is SerializableObjectWithBuffers<*> -> { + val result = stringifyJsonWithBufferRefs(arg.value, replacer) + massagedArgs.add(MixedArg.SerializedObjectWithBuffersArg( + result.jsonString.toByteArray(), + result.referencedBuffers + )) + } + else -> { + val gson = Gson() + massagedArgs.add(MixedArg.StringArg(gson.toJson(arg).toByteArray())) + } + } + } + return SerializedRequestArguments.Mixed(massagedArgs) + } + + val gson = Gson() + return SerializedRequestArguments.Simple(gson.toJson(args)) + } + + /** + * Serialize request + */ + fun serializeRequest( + req: Int, + rpcId: Int, + method: String, + serializedArgs: SerializedRequestArguments, + usesCancellationToken: Boolean + ): ByteArray { + return when (serializedArgs) { + is SerializedRequestArguments.Simple -> + requestJSONArgs(req, rpcId, method, serializedArgs.args, usesCancellationToken) + is SerializedRequestArguments.Mixed -> + requestMixedArgs(req, rpcId, method, serializedArgs.args, usesCancellationToken) + } + } + + /** + * Serialize JSON argument request + */ + private fun requestJSONArgs( + req: Int, + rpcId: Int, + method: String, + args: String, + usesCancellationToken: Boolean + ): ByteArray { + val methodBuff = method.toByteArray() + val argsBuff = args.toByteArray() + + var len = 0 + len += MessageBuffer.sizeUInt8 // use constant directly, not function call + len += MessageBuffer.sizeShortString(methodBuff) + len += MessageBuffer.sizeLongString(argsBuff) + + val messageType = if (usesCancellationToken) + MessageType.RequestJSONArgsWithCancellation + else + MessageType.RequestJSONArgs + + val result = MessageBuffer.alloc(messageType, req, len) + result.writeUInt8(rpcId) + result.writeShortString(methodBuff) + result.writeLongString(argsBuff) + return result.bytes + } + + /** + * Deserialize JSON argument request + */ + fun deserializeRequestJSONArgs(buff: MessageBuffer): Triple> { + val rpcId = buff.readUInt8() + var method = buff.readShortString() + if (method.startsWith("\$")) { + method = method.substring(1) + } + val argsJson = buff.readLongString() + + val gson = Gson() + val listType = object : TypeToken>() {}.type + val args = gson.fromJson>(argsJson, listType) + + return Triple(rpcId, method, args) + } + + /** + * Serialize mixed argument request + */ + private fun requestMixedArgs( + req: Int, + rpcId: Int, + method: String, + args: List, + usesCancellationToken: Boolean + ): ByteArray { + val methodBuff = method.toByteArray() + + var len = 0 + len += MessageBuffer.sizeUInt8 // use constant directly, not function call + len += MessageBuffer.sizeShortString(methodBuff) + len += MessageBuffer.sizeMixedArray(args) + + val messageType = if (usesCancellationToken) + MessageType.RequestMixedArgsWithCancellation + else + MessageType.RequestMixedArgs + + val result = MessageBuffer.alloc(messageType, req, len) + result.writeUInt8(rpcId) + result.writeShortString(methodBuff) + result.writeMixedArray(args) + return result.bytes + } + + /** + * Deserialize mixed argument request + */ + fun deserializeRequestMixedArgs(buff: MessageBuffer): Triple> { + val rpcId = buff.readUInt8() + var method = buff.readShortString() + if (method.startsWith("\$")) { + method = method.substring(1) + } + val rawArgs = buff.readMixedArray() + val args = rawArgs.mapIndexed { _, rawArg -> + when (rawArg) { + is String -> { + val gson = Gson() + gson.fromJson(rawArg, Any::class.java) + } + else -> rawArg + } + } + + return Triple(rpcId, method, args) + } + + /** + * Serialize acknowledged message + */ + fun serializeAcknowledged(req: Int): ByteArray { + return MessageBuffer.alloc(MessageType.Acknowledged, req, 0).bytes + } + + /** + * Serialize cancel message + */ + fun serializeCancel(req: Int): ByteArray { + return MessageBuffer.alloc(MessageType.Cancel, req, 0).bytes + } + + /** + * Serialize OK reply + */ + fun serializeReplyOK(req: Int, res: Any?, replacer: ((String, Any?) -> Any?)? = null): ByteArray { + return when { + res == null -> serializeReplyOKEmpty(req) + res is ByteArray -> serializeReplyOKVSBuffer(req, res) + res is SerializableObjectWithBuffers<*> -> { + val result = stringifyJsonWithBufferRefs(res.value, replacer, true) + serializeReplyOKJSONWithBuffers(req, result.jsonString, result.referencedBuffers) + } + else -> { + val gson = Gson() + val jsonStr = try { + gson.toJson(res) + } catch (e: Exception) { + "null" + } + serializeReplyOKJSON(req, jsonStr) + } + } + } + + /** + * Serialize empty OK reply + */ + private fun serializeReplyOKEmpty(req: Int): ByteArray { + return MessageBuffer.alloc(MessageType.ReplyOKEmpty, req, 0).bytes + } + + /** + * Serialize OK reply with binary buffer + */ + private fun serializeReplyOKVSBuffer(req: Int, res: ByteArray): ByteArray { + var len = 0 + len += MessageBuffer.sizeVSBuffer(res) + + val result = MessageBuffer.alloc(MessageType.ReplyOKVSBuffer, req, len) + result.writeVSBuffer(res) + return result.bytes + } + + /** + * Deserialize OK reply with binary buffer + */ + fun deserializeReplyOKVSBuffer(buff: MessageBuffer): ByteArray { + return buff.readVSBuffer() + } + + /** + * Serialize OK reply with JSON + */ + private fun serializeReplyOKJSON(req: Int, res: String): ByteArray { + val resBuff = res.toByteArray() + + var len = 0 + len += MessageBuffer.sizeLongString(resBuff) + + val result = MessageBuffer.alloc(MessageType.ReplyOKJSON, req, len) + result.writeLongString(resBuff) + return result.bytes + } + + /** + * Serialize OK reply with JSON and buffers + */ + private fun serializeReplyOKJSONWithBuffers(req: Int, res: String, buffers: List): ByteArray { + val resBuff = res.toByteArray() + + var len = 0 + len += MessageBuffer.sizeUInt32 // use constant directly, not function call + len += MessageBuffer.sizeLongString(resBuff) + for (buffer in buffers) { + len += MessageBuffer.sizeVSBuffer(buffer) + } + + val result = MessageBuffer.alloc(MessageType.ReplyOKJSONWithBuffers, req, len) + result.writeUInt32(buffers.size) + result.writeLongString(resBuff) + for (buffer in buffers) { + result.writeBuffer(buffer) + } + + return result.bytes + } + + /** + * Deserialize OK reply with JSON + */ + fun deserializeReplyOKJSON(buff: MessageBuffer): Any? { + val res = buff.readLongString() + val gson = Gson() + return gson.fromJson(res, Any::class.java) + } + + /** + * Deserialize OK reply with JSON and buffers + */ + fun deserializeReplyOKJSONWithBuffers(buff: MessageBuffer, uriTransformer: ((String, Any?) -> Any?)? = null): SerializableObjectWithBuffers<*> { + val bufferCount = buff.readUInt32() + val res = buff.readLongString() + + val buffers = mutableListOf() + for (i in 0 until bufferCount) { + buffers.add(buff.readVSBuffer()) + } + + return SerializableObjectWithBuffers(parseJsonAndRestoreBufferRefs(res, buffers, uriTransformer)) + } + + /** + * Serialize error reply + */ + fun serializeReplyErr(req: Int, err: Throwable?): ByteArray { + val errStr = if (err != null) { + try { + val gson = Gson() + gson.toJson(transformErrorForSerialization(err)) + } catch (e: Exception) { + null + } + } else null + + return if (errStr != null) { + val errBuff = errStr.toByteArray() + + var len = 0 + len += MessageBuffer.sizeLongString(errBuff) + + val result = MessageBuffer.alloc(MessageType.ReplyErrError, req, len) + result.writeLongString(errBuff) + result.bytes + } else { + serializeReplyErrEmpty(req) + } + } + + /** + * Deserialize error reply + */ + fun deserializeReplyErrError(buff: MessageBuffer): Throwable { + val err = buff.readLongString() + val gson = Gson() + val errorMap = gson.fromJson(err, Map::class.java) + + // Create custom exception + val exception = Exception(errorMap["message"] as? String ?: "Unknown error") + + // Set stack and other properties + if (errorMap.containsKey("stack")) { + // Note: Java/Kotlin cannot directly set stack, this is just a demonstration + // In actual implementation, may need custom exception type or other methods + } + + return exception + } + + /** + * Serialize empty error reply + */ + private fun serializeReplyErrEmpty(req: Int): ByteArray { + return MessageBuffer.alloc(MessageType.ReplyErrEmpty, req, 0).bytes + } + + /** + * Transform error for serialization + */ + private fun transformErrorForSerialization(error: Throwable): Map { + return mapOf( + "\$isError" to true, + "name" to error.javaClass.simpleName, + "message" to error.message, + "stack" to error.stackTraceToString() + ) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageType.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageType.kt new file mode 100644 index 0000000000..5a474338cd --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/MessageType.kt @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +/** + * RPC message type + * Corresponds to MessageType enum in VSCode + */ +enum class MessageType(val value: Int) { + /** + * Request with JSON arguments + */ + RequestJSONArgs(1), + + /** + * Request with JSON arguments and cancellation token + */ + RequestJSONArgsWithCancellation(2), + + /** + * Request with mixed arguments + */ + RequestMixedArgs(3), + + /** + * Request with mixed arguments and cancellation token + */ + RequestMixedArgsWithCancellation(4), + + /** + * Acknowledged message + */ + Acknowledged(5), + + /** + * Cancel message + */ + Cancel(6), + + /** + * Empty OK reply + */ + ReplyOKEmpty(7), + + /** + * OK reply with binary buffer + */ + ReplyOKVSBuffer(8), + + /** + * OK reply in JSON format + */ + ReplyOKJSON(9), + + /** + * OK reply in JSON format with buffers + */ + ReplyOKJSONWithBuffers(10), + + /** + * Error reply + */ + ReplyErrError(11), + + /** + * Empty error reply + */ + ReplyErrEmpty(12); + + companion object { + /** + * Get type by value + */ + fun fromValue(value: Int): MessageType? = values().find { it.value == value } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/PendingRPCReply.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/PendingRPCReply.kt new file mode 100644 index 0000000000..3c5a83af49 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/PendingRPCReply.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import com.intellij.openapi.Disposable + +/** + * Pending RPC reply + * Corresponds to PendingRPCReply in VSCode + */ +class PendingRPCReply( + private val promise: LazyPromise, + private val disposable: Disposable +) { + /** + * Resolve reply successfully + * @param value Result value + */ + fun resolveOk(value: Any?) { + promise.resolveOk(value) + disposable.dispose() + } + + /** + * Resolve reply with error + * @param err Error object + */ + fun resolveErr(err: Throwable) { + promise.resolveErr(err) + disposable.dispose() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/ProxyIdentifier.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/ProxyIdentifier.kt new file mode 100644 index 0000000000..56fb575c22 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/ProxyIdentifier.kt @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +/** + * Proxy identifier class + * Corresponds to ProxyIdentifier in VSCode + */ +class ProxyIdentifier private constructor( + /** + * String identifier + */ + val sid: String, + + /** + * Numeric identifier + */ + val nid: Int +) { + companion object { + /** + * Identifier counter + */ + var count = 0 + private set + + /** + * Create new ProxyIdentifier instance + */ + internal fun create(sid: String): ProxyIdentifier { + return ProxyIdentifier(sid, ++count) + } + + /** + * Create placeholder ProxyIdentifier, does not increment counter + */ + internal fun createPlaceholder(sid: String, nid: Int): ProxyIdentifier { + return ProxyIdentifier(sid, nid) + } + } + + override fun toString(): String { + return this.sid + } +} + +/** + * Stores created proxy identifiers + */ +private val identifiers = mutableListOf>() + +/** + * Create proxy identifier + * @param identifier String identifier + * @return Proxy identifier instance + */ +fun createProxyIdentifier(identifier: String): ProxyIdentifier { + val result = ProxyIdentifier.create(identifier) + while (identifiers.size <= result.nid) { + identifiers.add(ProxyIdentifier.createPlaceholder("placeholder", identifiers.size)) + } + identifiers[result.nid] = result + return result +} + +/** + * Get string identifier by proxy ID + * @param nid Proxy ID + * @return String identifier + */ +fun getStringIdentifierForProxy(nid: Int): String { + return identifiers[nid].sid +} + +/** + * Serializable object with buffers + * Corresponds to SerializableObjectWithBuffers in VSCode + * @param value Value to serialize + */ +class SerializableObjectWithBuffers(val value: T) \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/RPCProtocol.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/RPCProtocol.kt new file mode 100644 index 0000000000..d63f793d34 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/RPCProtocol.kt @@ -0,0 +1,794 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.ipc.IMessagePassingProtocol +import ai.kilocode.jetbrains.ipc.proxy.uri.IURITransformer +import ai.kilocode.jetbrains.ipc.proxy.uri.UriReplacer +import ai.kilocode.jetbrains.util.doInvokeMethod +import kotlinx.coroutines.* +import java.lang.reflect.Proxy +import java.util.concurrent.ConcurrentHashMap +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.full.functions + +/** + * Request initiator + */ +enum class RequestInitiator { + /** + * Initiated locally + */ + LocalSide, + + /** + * Initiated by the other side + */ + OtherSide +} + +/** + * Responsive state + */ +enum class ResponsiveState { + /** + * Responsive + */ + Responsive, + + /** + * Unresponsive + */ + Unresponsive +} + +/** + * RPC protocol logger interface + */ +interface IRPCProtocolLogger { + /** + * Log incoming message + */ + fun logIncoming(msgLength: Int, req: Int, initiator: RequestInitiator, str: String, data: Any? = null) + + /** + * Log outgoing message + */ + fun logOutgoing(msgLength: Int, req: Int, initiator: RequestInitiator, str: String, data: Any? = null) +} + +/** + * RPC protocol implementation + * Corresponds to RPCProtocol in VSCode + */ +class RPCProtocol( + private val protocol: IMessagePassingProtocol, + private val logger: IRPCProtocolLogger? = null, + private val uriTransformer: IURITransformer? = null +) : IRPCProtocol, Disposable { + + companion object { + private val LOG = Logger.getInstance(RPCProtocol::class.java) + + /** + * Unresponsive time threshold (milliseconds) + */ + private const val UNRESPONSIVE_TIME = 3 * 1000 // 3s, same as TS implementation + + /** + * RPC protocol symbol (used to identify objects implementing this interface) + */ + private val RPC_PROTOCOL_SYMBOL = "rpcProtocol" + + /** + * RPC proxy symbol (used to identify proxy objects) + */ + private val RPC_PROXY_SYMBOL = "rpcProxy" + + /** + * Dollar sign character code + */ + private const val DOLLAR_SIGN_CHAR_CODE = 36 // '$' + + /** + * No operation + */ + private val noop: () -> Unit = {} + } + + /** + * Coroutine scope + */ + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + /** + * URI replacer + */ + private val uriReplacer: ((String, Any?) -> Any?)? = if (uriTransformer != null) UriReplacer(uriTransformer) else null + + /** + * Whether disposed + */ + private var isDisposed = false + + /** + * Local object list + */ + private val locals = arrayOfNulls(ProxyIdentifier.count + 1) + + /** + * Proxy object list + */ + private val proxies = arrayOfNulls(ProxyIdentifier.count + 1) + + /** + * Last message ID + */ + private var lastMessageId = 0 + + /** + * Cancelled handlers + */ + private val cancelInvokedHandlers = ConcurrentHashMap Unit>() + + /** + * Pending RPC replies + */ + private val pendingRPCReplies = ConcurrentHashMap() + + /** + * Responsive state + */ + override var responsiveState = ResponsiveState.Responsive + private set + + /** + * Unacknowledged count + */ + private var unacknowledgedCount = 0 + + /** + * Unresponsive time + */ + private var unresponsiveTime = 0L + + /** + * Asynchronous unresponsive check job + */ + private var asyncCheckUnresponsiveJob: Job? = null + + /** + * Responsive state change event listeners + */ + private val onDidChangeResponsiveStateListeners = mutableListOf<(ResponsiveState) -> Unit>() + + init { + protocol.onMessage { data -> receiveOneMessage(data) } + } + + /** + * Add responsive state change event listener + */ + fun onDidChangeResponsiveState(listener: (ResponsiveState) -> Unit): Disposable { + onDidChangeResponsiveStateListeners.add(listener) + return Disposable { + onDidChangeResponsiveStateListeners.remove(listener) + } + } + + override fun dispose() { + isDisposed = true + + // Cancel all coroutines + coroutineScope.cancel() + + // Release all pending replies with cancel error + pendingRPCReplies.keys.forEach { msgId -> + val pending = pendingRPCReplies[msgId] + pendingRPCReplies.remove(msgId) + pending?.resolveErr(CanceledException()) + } + } + + override suspend fun drain(): Unit { + protocol.drain() + } + + /** + * Triggered before sending a request + */ + private fun onWillSendRequest(req: Int) { + if (unacknowledgedCount == 0) { + // This is the first request we've sent in a while + // Mark this moment as the start of the unresponsive countdown + unresponsiveTime = System.currentTimeMillis() + UNRESPONSIVE_TIME + LOG.info("Set initial unresponsive check time, request ID: $req, unresponsive time: ${unresponsiveTime}ms") + } + unacknowledgedCount++ + + // Check every 2 seconds for unresponsiveness + if (asyncCheckUnresponsiveJob == null || asyncCheckUnresponsiveJob?.isActive == false) { + LOG.debug("Start unresponsive check task") + asyncCheckUnresponsiveJob = coroutineScope.launch { + while (isActive) { + checkUnresponsive() + delay(2000) + } + } + } + } + + /** + * Triggered when an acknowledge response is received + */ + private fun onDidReceiveAcknowledge(req: Int) { + // The next possible unresponsive time is now + increment + unresponsiveTime = System.currentTimeMillis() + UNRESPONSIVE_TIME + unacknowledgedCount-- +// LOG.debug("Received acknowledge, request ID: $req, unacknowledged count decreased: $unacknowledgedCount, updated unresponsive time: ${unresponsiveTime}ms") + + if (unacknowledgedCount == 0) { + // No longer need to check for unresponsiveness + LOG.debug("No unacknowledged requests, cancel unresponsive check task") + asyncCheckUnresponsiveJob?.cancel() + asyncCheckUnresponsiveJob = null + } + + // The other side is responsive! + setResponsiveState(ResponsiveState.Responsive) + } + + /** + * Check for unresponsiveness + */ + private fun checkUnresponsive() { + if (unacknowledgedCount == 0) { + // Not waiting for anything => cannot determine responsiveness + return + } + + val currentTime = System.currentTimeMillis() + if (currentTime > unresponsiveTime) { + // Unresponsive!! + LOG.warn("Detected unresponsive state: current time ${currentTime}ms > unresponsive threshold ${unresponsiveTime}ms, unacknowledged requests: $unacknowledgedCount") + setResponsiveState(ResponsiveState.Unresponsive) + } else { + // Not yet unresponsive, log time info + if (LOG.isDebugEnabled) { + val remainingTime = unresponsiveTime - currentTime + LOG.debug("Connection responsive, time left before unresponsive threshold: ${remainingTime}ms, unacknowledged requests: $unacknowledgedCount") + } + } + } + + /** + * Set responsive state + */ + private fun setResponsiveState(newResponsiveState: ResponsiveState) { + if (responsiveState == newResponsiveState) { + // No change + return + } + + LOG.info("Responsive state changed from $responsiveState to $newResponsiveState") + responsiveState = newResponsiveState + + // Notify listeners + onDidChangeResponsiveStateListeners.forEach { it(responsiveState) } + } + + /** + * Transform incoming URIs + */ + fun transformIncomingURIs(obj: T): T { + if (uriTransformer == null) { + return obj + } + + @Suppress("UNCHECKED_CAST") + return when (obj) { + // If the object is a URI, convert directly + is java.net.URI -> uriTransformer.transformIncoming(obj) as T + + // If the object is a string and looks like a URI + is String -> { + try { + val uri = java.net.URI(obj) + uriTransformer.transformIncoming(uri).toString() as T + } catch (e: Exception) { + obj + } + } + + // If the object is a list, recursively convert each element + is List<*> -> { + obj.map { item -> transformIncomingURIs(item) } as T + } + + // If the object is a map, recursively convert each value, especially for URI-related keys + is Map<*, *> -> { + val result = mutableMapOf() + obj.forEach { (key, value) -> + val transformedValue = if (key is String && ( + key == "uri" || + key == "documentUri" || + key == "targetUri" || + key == "sourceUri" || + key.endsWith("Uri")) + ) { + transformIncomingURIs(value) + } else { + transformIncomingURIs(value) + } + result[key] = transformedValue + } + result as T + } + + // Other objects, if custom class, may need further handling + else -> obj + } + } + + override fun getProxy(identifier: ProxyIdentifier): T { + val rpcId = identifier.nid + val sid = identifier.sid + + if (proxies[rpcId] == null) { + proxies[rpcId] = createProxy(rpcId, sid) + } + + @Suppress("UNCHECKED_CAST") + return proxies[rpcId] as T + } + + /** + * Create proxy object + */ + @Suppress("UNCHECKED_CAST") + private fun createProxy(rpcId: Int, debugName: String): T { + // Try to get T's Class object + val interfaces = mutableListOf>() + +// // Add default Any interface +// interfaces.add(Any::class.java) + + // Try to get interface info from generic parameter + try { + val classLoader = javaClass.classLoader + val proxyClass = classLoader.loadClass(debugName) + if (proxyClass.isInterface) { + interfaces.add(proxyClass) + } + } catch (e: Exception) { + LOG.warn("Failed to load interface class $debugName: ${e.message}") + } + + // Use Java dynamic proxy to create proxy object + return Proxy.newProxyInstance( + javaClass.classLoader, + interfaces.toTypedArray() + ) { _, method, args -> + val name = method.name + + // Handle special methods + if (name == "toString") { + return@newProxyInstance "Proxy($debugName)" + } + + // Handle special symbols + if (name == RPC_PROXY_SYMBOL) { + return@newProxyInstance debugName + } + + // Call remote method + if (name.isNotEmpty()) { + return@newProxyInstance remoteCall(rpcId, "\$$name", args ?: emptyArray()) + } + + null + } as T + } + + override fun set(identifier: ProxyIdentifier, instance: R): R { + locals[identifier.nid] = instance + return instance + } + + override fun assertRegistered(identifiers: List>) { + for (identifier in identifiers) { + if (locals[identifier.nid] == null) { + throw IllegalStateException("Missing proxy instance ${identifier.sid}") + } + } + } + + /** + * Remote call + */ + private fun remoteCall(rpcId: Int, methodName: String, args: Array): Any { + if (isDisposed) { + throw CanceledException() + } + LOG.info("remoteCall: $rpcId.$methodName.${lastMessageId+1}") + + // Check if the last argument is a cancellation token + var cancellationToken: Any? = null + val effectiveArgs = if (args.isNotEmpty()) { + // There should be more complex logic for detecting cancellation token + val lastArg = args.last() + if (lastArg != null && lastArg::class.java.simpleName == "CancellationToken") { + cancellationToken = lastArg + args.dropLast(1).toTypedArray() + } else { + args + } + } else { + args + } + + val serializedRequestArguments = MessageIO.serializeRequestArguments(args.toList(), uriReplacer) + + val req = ++lastMessageId + val callId = req.toString() + val result = LazyPromise() + + // Use LazyPromise to implement Promise functionality + val deferred = LazyPromise() + + // Create Disposable object for cleanup on cancel + val disposable = Disposable { + if (!deferred.isCompleted) { + deferred.cancel() + } + } + + pendingRPCReplies[callId] = PendingRPCReply(result, disposable) + onWillSendRequest(req) + + val usesCancellationToken = cancellationToken != null + val msg = MessageIO.serializeRequest(req, rpcId, methodName, serializedRequestArguments, usesCancellationToken) + + logger?.logOutgoing( + msg.size, + req, + RequestInitiator.LocalSide, + "request: ${getStringIdentifierForProxy(rpcId)}.$methodName(", + args + ) + + protocol.send(msg) + + // Directly return Promise, do not block current thread + return result + } + + /** + * Receive a message + */ + private fun receiveOneMessage(rawmsg: ByteArray) { + if (isDisposed) { + return + } + + val msgLength = rawmsg.size + val buff = MessageBuffer.read(rawmsg, 0) + val messageType = MessageType.fromValue(buff.readUInt8()) ?: return + val req = buff.readUInt32() + +// LOG.info("receiveOneMessage: $messageType, req: $req, length: $msgLength") + when (messageType) { + MessageType.RequestJSONArgs, MessageType.RequestJSONArgsWithCancellation -> { + val (rpcId, method, args) = MessageIO.deserializeRequestJSONArgs(buff) + // Transform URI + val transformedArgs = transformIncomingURIs(args) + receiveRequest( + msgLength, + req, + rpcId, + method, + transformedArgs, + messageType == MessageType.RequestJSONArgsWithCancellation + ) + } + MessageType.RequestMixedArgs, MessageType.RequestMixedArgsWithCancellation -> { + val (rpcId, method, args) = MessageIO.deserializeRequestMixedArgs(buff) + // Transform URI + val transformedArgs = transformIncomingURIs(args) + receiveRequest( + msgLength, + req, + rpcId, + method, + transformedArgs, + messageType == MessageType.RequestMixedArgsWithCancellation + ) + } + MessageType.Acknowledged -> { + logger?.logIncoming(msgLength, req, RequestInitiator.LocalSide, "ack") + onDidReceiveAcknowledge(req) + } + MessageType.Cancel -> { + receiveCancel(msgLength, req) + } + MessageType.ReplyOKEmpty -> { + receiveReply(msgLength, req, null) + } + MessageType.ReplyOKJSON -> { + val value = MessageIO.deserializeReplyOKJSON(buff) + // Transform URI + val transformedValue = transformIncomingURIs(value) + receiveReply(msgLength, req, transformedValue) + } + MessageType.ReplyOKJSONWithBuffers -> { + val value = MessageIO.deserializeReplyOKJSONWithBuffers(buff, uriReplacer) + receiveReply(msgLength, req, value) + } + MessageType.ReplyOKVSBuffer -> { + val value = MessageIO.deserializeReplyOKVSBuffer(buff) + receiveReply(msgLength, req, value) + } + MessageType.ReplyErrError -> { + val err = MessageIO.deserializeReplyErrError(buff) + // Transform URI + val transformedErr = transformIncomingURIs(err) + receiveReplyErr(msgLength, req, transformedErr) + } + MessageType.ReplyErrEmpty -> { + receiveReplyErr(msgLength, req, null) + } + } + } + + /** + * Receive request + */ + private fun receiveRequest( + msgLength: Int, + req: Int, + rpcId: Int, + method: String, + args: List, + usesCancellationToken: Boolean + ) { + LOG.info("receiveRequest:$req.$rpcId.$method()") + logger?.logIncoming( + msgLength, + req, + RequestInitiator.OtherSide, + "receiveRequest ${getStringIdentifierForProxy(rpcId)}.$method(", + args + ) + + val callId = req.toString() + + val promise: Deferred + val cancel: () -> Unit + + // Use coroutine to handle request + if (usesCancellationToken) { + // Create coroutine job, can be cancelled + val job = Job() + + // Create coroutine context + val context: CoroutineContext = job + Dispatchers.Default + + // Start coroutine + promise = coroutineScope.async(context) { + // Add cancellation token + val argsList = args.toMutableList() + // Note: should add a CancellationToken object here + // But in Kotlin, we can use coroutine's cancel mechanism + invokeHandler(rpcId, method, argsList) + } + + cancel = { job.cancel() } + } else { + // Cannot be cancelled + promise = coroutineScope.async { + invokeHandler(rpcId, method, args) + } + cancel = noop + } + + cancelInvokedHandlers[callId] = cancel + + // Acknowledge request + val msg = MessageIO.serializeAcknowledged(req) + logger?.logOutgoing(msg.size, req, RequestInitiator.OtherSide, "ack") + protocol.send(msg) + + // Handle request result + coroutineScope.launch { + try { + val result = promise.await() +// LOG.info("response: $req.$rpcId.$method") + cancelInvokedHandlers.remove(callId) + val msg = MessageIO.serializeReplyOK(req, result, uriReplacer) + logger?.logOutgoing(msg.size, req, RequestInitiator.OtherSide, "reply:", result) + protocol.send(msg) + } catch (err: Throwable) { + cancelInvokedHandlers.remove(callId) + val msg = MessageIO.serializeReplyErr(req, err) + logger?.logOutgoing(msg.size, req, RequestInitiator.OtherSide, "replyErr:", err) + protocol.send(msg) + } + } + } + + /** + * Receive cancel + */ + private fun receiveCancel(msgLength: Int, req: Int) { + logger?.logIncoming(msgLength, req, RequestInitiator.OtherSide, "receiveCancel") + val callId = req.toString() + cancelInvokedHandlers[callId]?.invoke() + } + + /** + * Receive reply + */ + private fun receiveReply(msgLength: Int, req: Int, value: Any?) { + logger?.logIncoming(msgLength, req, RequestInitiator.LocalSide, "receiveReply:", value) + val callId = req.toString() + if (!pendingRPCReplies.containsKey(callId)) { + return + } + + val pendingReply = pendingRPCReplies[callId] ?: return + pendingRPCReplies.remove(callId) + + pendingReply.resolveOk(value) + } + + /** + * Receive error reply + */ + private fun receiveReplyErr(msgLength: Int, req: Int, value: Throwable?) { + logger?.logIncoming(msgLength, req, RequestInitiator.LocalSide, "receiveReplyErr:", value) + + val callId = req.toString() + if (!pendingRPCReplies.containsKey(callId)) { + return + } + + val pendingReply = pendingRPCReplies[callId] ?: return + pendingRPCReplies.remove(callId) + + val err = value ?: Exception("Unknown error") + pendingReply.resolveErr(err) + } + + /** + * Invoke handler + */ + private suspend fun invokeHandler(rpcId: Int, methodName: String, args: List): Any? { + return try { + doInvokeHandler(rpcId, methodName, args) + } catch (err: Throwable) { +// throw err + LOG.error("Error invoking handler: $methodName(${args.joinToString(", ")})", err) + null + } + } + + /** + * Execute handler invocation + */ + private suspend fun doInvokeHandler(rpcId: Int, methodName: String, args: List): Any? { + val actor = locals[rpcId] ?: throw IllegalStateException("Unknown actor ${getStringIdentifierForProxy(rpcId)}") + // Use reflection to get method with parameter type matching + val method = try { + findBestMatchingMethod(actor, methodName, args) + } catch (e: Exception) { + throw IllegalStateException("Unknown method $methodName on actor ${getStringIdentifierForProxy(rpcId)}") + } + + return doInvokeMethod(method, args, actor) + } + + /** + * Find the best matching method based on method name and argument types + */ + private fun findBestMatchingMethod(actor: Any, methodName: String, args: List): kotlin.reflect.KFunction<*> { + val candidateMethods = actor::class.functions.filter { it.name == methodName } + + if (candidateMethods.isEmpty()) { + throw NoSuchMethodException("No method named '$methodName' found") + } + + if (candidateMethods.size == 1) { + return candidateMethods.first() + } + + // Find method with matching parameter count (excluding the receiver parameter) + val methodsWithMatchingParamCount = candidateMethods.filter { method -> + val paramCount = method.parameters.size - 1 // Exclude receiver parameter + paramCount == args.size + } + + if (methodsWithMatchingParamCount.isEmpty()) { + // If no exact parameter count match, try to find a method that can accept the arguments + val compatibleMethods = candidateMethods.filter { method -> + val paramCount = method.parameters.size - 1 + paramCount >= args.size // Method can accept fewer arguments (with defaults) + } + if (compatibleMethods.isNotEmpty()) { + return compatibleMethods.first() + } + throw NoSuchMethodException("No method '$methodName' with ${args.size} parameters found") + } + + if (methodsWithMatchingParamCount.size == 1) { + return methodsWithMatchingParamCount.first() + } + + // Multiple methods with same parameter count, try to match by type + for (method in methodsWithMatchingParamCount) { + if (isMethodCompatible(method, args)) { + return method + } + } + + // If no perfect match, return the first one with matching parameter count + return methodsWithMatchingParamCount.first() + } + + /** + * Check if a method is compatible with the given arguments + */ + private fun isMethodCompatible(method: kotlin.reflect.KFunction<*>, args: List): Boolean { + val parameters = method.parameters.drop(1) // Skip receiver parameter + + if (parameters.size != args.size) { + return false + } + + for (i in parameters.indices) { + val param = parameters[i] + val arg = args[i] + + if (arg == null) { + // Null argument is compatible with nullable parameters + if (!param.type.isMarkedNullable) { + return false + } + } else { + // Check type compatibility + val argClass = arg::class.java + val paramClass = param.type.classifier as? kotlin.reflect.KClass<*> + + if (paramClass != null) { + val paramJavaClass = paramClass.java + + // Handle primitive type conversions (similar to doInvokeMethod) + val isCompatible = when { + paramJavaClass.isAssignableFrom(argClass) -> true + // Handle Double to numeric type conversions + arg is Double && (paramJavaClass == Int::class.java || + paramJavaClass == Long::class.java || + paramJavaClass == Float::class.java || + paramJavaClass == Short::class.java || + paramJavaClass == Byte::class.java || + paramJavaClass == Boolean::class.java) -> true + // Handle String compatibility + arg is String && paramJavaClass == String::class.java -> true + else -> false + } + + if (!isCompatible) { + return false + } + } + } + } + + return true + } + + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostCommandsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostCommandsProxy.kt new file mode 100644 index 0000000000..6f6ea81d1a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostCommandsProxy.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.ipc.proxy.LazyPromise + +//export interface ExtHostCommandsShape { +// $executeContributedCommand(id: string, ...args: any[]): Promise; +// $getContributedCommandMetadata(): Promise<{ [id: string]: string | ICommandMetadataDto }>; +//} + +interface ExtHostCommandsProxy { + fun executeContributedCommand(id: String, args: List) : LazyPromise + fun getContributedCommandMetadata() : LazyPromise +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostConfigurationProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostConfigurationProxy.kt new file mode 100644 index 0000000000..45835de35e --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostConfigurationProxy.kt @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +/** + * Extension host configuration service interface + * Corresponds to ExtHostConfiguration in VSCode + */ +interface ExtHostConfigurationProxy { + /** + * Initialize configuration + * @param configModel Configuration model + */ + fun initializeConfiguration(configModel: Map) + + /** + * Update configuration + * @param configModel Configuration model + */ + fun updateConfiguration(configModel: Map) + + /** + * Get configuration + * @param key Configuration key + * @param section Configuration section + * @param scopeToLanguage Whether to scope to language + * @return Configuration value + */ + fun getConfiguration(key: String, section: String?, scopeToLanguage: Boolean): Any? +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsAndEditorsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsAndEditorsProxy.kt new file mode 100644 index 0000000000..5534e2bee4 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsAndEditorsProxy.kt @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.editor.DocumentsAndEditorsDelta + + +interface ExtHostDocumentsAndEditorsProxy { + fun acceptDocumentsAndEditorsDelta(d: DocumentsAndEditorsDelta) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsProxy.kt new file mode 100644 index 0000000000..2ba4df1880 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostDocumentsProxy.kt @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.editor.ModelChangedEvent +import ai.kilocode.jetbrains.util.URI + +interface ExtHostDocumentsProxy { + fun acceptModelLanguageChanged(strURL: URI, newLanguageId: String) + fun acceptModelSaved(strURL: URI) + fun acceptDirtyStateChanged(strURL: URI, isDirty: Boolean) + fun acceptEncodingChanged(strURL: URI, encoding: String) + fun acceptModelChanged(strURL: URI, e: ModelChangedEvent, isDirty: Boolean) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorTabsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorTabsProxy.kt new file mode 100644 index 0000000000..609a38cd06 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorTabsProxy.kt @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.editor.EditorTabGroupDto +import ai.kilocode.jetbrains.editor.TabOperation + +interface ExtHostEditorTabsProxy { + fun acceptEditorTabModel(tabGroups: List) + fun acceptTabGroupUpdate(groupDto: EditorTabGroupDto) + fun acceptTabOperation(operation: TabOperation) +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorsProxy.kt new file mode 100644 index 0000000000..a8b2639526 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostEditorsProxy.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.editor.EditorPropertiesChangeData +import ai.kilocode.jetbrains.editor.TextEditorDiffInformation + + +interface ExtHostEditorsProxy { + fun acceptEditorPropertiesChanged(id: String, props: EditorPropertiesChangeData) + fun acceptEditorPositionData(data: Map) + fun acceptEditorDiffInformation(id: String, diffInformation: List?) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostExtensionServiceProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostExtensionServiceProxy.kt new file mode 100644 index 0000000000..b704375b26 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostExtensionServiceProxy.kt @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.ipc.proxy.LazyPromise + +/** + * Extension host extension service interface + * Corresponds to ExtHostExtensionServiceShape in VSCode + */ +interface ExtHostExtensionServiceProxy { + /** + * Resolve remote authority + * @param remoteAuthority Remote authority identifier + * @param resolveAttempt Number of resolve attempts + * @return Resolve result + */ + fun resolveAuthority(remoteAuthority: String, resolveAttempt: Int): LazyPromise + + /** + * Get canonical URI + * Returns null if no resolver is found for remoteAuthority + * @param remoteAuthority Remote authority identifier + * @param uri URI components + * @return Canonical URI components or null + */ + fun getCanonicalURI(remoteAuthority: String, uri: Map): LazyPromise + + /** + * Start extension host + * @param extensionsDelta Extension description delta + */ + fun startExtensionHost(extensionsDelta: Map): LazyPromise + + /** + * Execute extension tests + * @return Test result code + */ + fun extensionTestsExecute(): LazyPromise + + /** + * Activate extension by event + * @param activationEvent Activation event + * @param activationKind Activation kind + */ + fun activateByEvent(activationEvent: String, activationKind: Int): LazyPromise + + /** + * Activate extension + * @param extensionId Extension ID + * @param reason Activation reason + * @return Whether activation succeeded + */ + fun activate(extensionId: String, reason: Map): LazyPromise + + /** + * Set remote environment + * @param env Environment variables + */ + fun setRemoteEnvironment(env: Map): LazyPromise + + /** + * Update remote connection data + * @param connectionData Connection data + */ + fun updateRemoteConnectionData(connectionData: Map): LazyPromise + + /** + * Delta update extensions + * @param extensionsDelta Extension description delta + */ + fun deltaExtensions(extensionsDelta: Map): LazyPromise + + /** + * Test latency + * @param n Test parameter + * @return Latency value + */ + fun test_latency(n: Int): LazyPromise + + /** + * Test upload + * @param b Binary buffer + * @return Result + */ + fun test_up(b: ByteArray): LazyPromise + + /** + * Test download + * @param size Size + * @return Binary buffer + */ + fun test_down(size: Int): LazyPromise +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostFileSystemEventServiceProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostFileSystemEventServiceProxy.kt new file mode 100644 index 0000000000..f8ecd7284c --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostFileSystemEventServiceProxy.kt @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import java.util.concurrent.CompletableFuture + +/** + * File system events interface + * Corresponds to FileSystemEvents in VSCode + */ +data class FileSystemEvents( + val session: String? = null, + val created: List>, // UriComponents + val changed: List>, // UriComponents + val deleted: List> // UriComponents +) + +/** + * Source-target file pair + * Corresponds to SourceTargetPair in VSCode + */ +data class SourceTargetPair( + val source: Map? = null, // UriComponents + val target: Map // UriComponents +) + +/** + * File operation participation response + * Corresponds to IWillRunFileOperationParticipation in VSCode + */ +data class FileOperationParticipation( + val edit: Map, // IWorkspaceEditDto + val extensionNames: List +) + +/** + * File operation type + * Corresponds to FileOperation in VSCode + */ +enum class FileOperation { + CREATE, + DELETE, + RENAME, + COPY, + MOVE +} + +/** + * Extension host file system event service interface + * Corresponds to ExtHostFileSystemEventServiceShape in VSCode + */ +interface ExtHostFileSystemEventServiceProxy { + /** + * File event notification + * Corresponds to $onFileEvent in VSCode + * @param events File system events + */ + fun onFileEvent(events: FileSystemEvents) + + /** + * Will run file operation notification + * Corresponds to $onWillRunFileOperation in VSCode + * @param operation File operation type + * @param files List of source-target file pairs + * @param timeout Timeout + * @param token Cancellation token + * @return File operation participation response + */ + fun onWillRunFileOperation( + operation: FileOperation, + files: List, + timeout: Int, + token: Any? + ): CompletableFuture + + /** + * Did run file operation notification + * Corresponds to $onDidRunFileOperation in VSCode + * @param operation File operation type + * @param files List of source-target file pairs + */ + fun onDidRunFileOperation( + operation: FileOperation, + files: List + ) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalServiceProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalServiceProxy.kt new file mode 100644 index 0000000000..59642ca73e --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalServiceProxy.kt @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.ipc.proxy.LazyPromise + +data class TerminalCommandDto( + val commandLine: String?, + val cwd: String?, + val exitCode: Int?, + val output: String? +) + +data class TerminalTabAction( + val id: String, + val label: String, + val icon: Any? +) + +data class ReconnectionProperties( + val ownerId: String, + val data: Any? +) + +data class ShellLaunchConfigDto( + val name: String?, + val executable: String?, + val args: List?, + val cwd: String?, + val env: Map?, + val useShellEnvironment: Boolean?, + val hideFromUser: Boolean?, + val reconnectionProperties: Map?, + val type: String?, + val isFeatureTerminal: Boolean?, + val tabActions: List?, + val shellIntegrationEnvironmentReporting: Boolean? +) + +data class TerminalDimensionsDto( + val columns: Int, + val rows: Int +) + +data class TerminalLaunchError( + val message: String, + val code: Int? +) + +data class TerminalProfile( + val profileName: String, + val path: String, + val isDefault: Boolean, + /** + * Whether the terminal profile contains a potentially unsafe {@link path}. For example, the path + * `C:\Cygwin` is the default install for Cygwin on Windows, but it could be created by any + * user in a multi-user environment. As such, we don't want to blindly present it as a profile + * without a warning. + */ + val isUnsafePath: Boolean?, + /** + * An additional unsafe path that must exist, for example a script that appears in {@link args}. + */ + val requiresUnsafePath: String?, + val isAutoDetected: Boolean?, + /** + * Whether the profile path was found on the `$PATH` environment variable, if so it will be + * cleaner to display this profile in the UI using only `basename(path)`. + */ + val isFromPath: Boolean?, + val args: List?, + val env: Map?, + val overrideName: Boolean?, + val color: String?, + val icon: Any? +) + +//export interface ExtHostTerminalServiceShape { +// $acceptTerminalClosed(id: number, exitCode: number | undefined, exitReason: TerminalExitReason): void; +// $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; +// $acceptActiveTerminalChanged(id: number | null): void; +// $acceptTerminalProcessId(id: number, processId: number): void; +// $acceptTerminalProcessData(id: number, data: string): void; +// $acceptDidExecuteCommand(id: number, command: ITerminalCommandDto): void; +// $acceptTerminalTitleChange(id: number, name: string): void; +// $acceptTerminalDimensions(id: number, cols: number, rows: number): void; +// $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; +// $acceptTerminalInteraction(id: number): void; +// $acceptTerminalSelection(id: number, selection: string | undefined): void; +// $acceptTerminalShellType(id: number, shellType: TerminalShellType | undefined): void; +// $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise; +// $acceptProcessAckDataEvent(id: number, charCount: number): void; +// $acceptProcessInput(id: number, data: string): void; +// $acceptProcessResize(id: number, cols: number, rows: number): void; +// $acceptProcessShutdown(id: number, immediate: boolean): void; +// $acceptProcessRequestInitialCwd(id: number): void; +// $acceptProcessRequestCwd(id: number): void; +// $acceptProcessRequestLatency(id: number): Promise; +// $provideLinks(id: number, line: string): Promise; +// $activateLink(id: number, linkId: number): void; +// $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; +// $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void; +// $createContributedProfileTerminal(id: string, options: ICreateContributedTerminalProfileOptions): Promise; +// $provideTerminalQuickFixes(id: string, matchResult: TerminalCommandMatchResultDto, token: CancellationToken): Promise | undefined>; +// $provideTerminalCompletions(id: string, options: ITerminalCompletionContextDto, token: CancellationToken): Promise; +//} + +interface ExtHostTerminalServiceProxy { + fun acceptTerminalClosed(id: Int, exitCode: Int?, exitReason: Int) + fun acceptTerminalOpened(id: Int, extHostTerminalId: String?, name: String, shellLaunchConfig: ShellLaunchConfigDto) + fun acceptActiveTerminalChanged(id: Int?) + fun acceptTerminalProcessId(id: Int, processId: Int) + fun acceptTerminalProcessData(id: Int, data: String) + fun acceptDidExecuteCommand(id: Int, command: TerminalCommandDto) + fun acceptTerminalTitleChange(id: Int, name: String) + fun acceptTerminalDimensions(id: Int, cols: Int, rows: Int) + fun acceptTerminalMaximumDimensions(id: Int, cols: Int, rows: Int) + fun acceptTerminalInteraction(id: Int) + fun acceptTerminalSelection(id: Int, selection: String?) + fun acceptTerminalShellType(id: Int, shellType: String?) + fun startExtensionTerminal(id: Int, initialDimensions: TerminalDimensionsDto?): LazyPromise + fun acceptProcessAckDataEvent(id: Int, charCount: Int) + fun acceptProcessInput(id: Int, data: String) + fun acceptProcessResize(id: Int, cols: Int, rows: Int) + fun acceptProcessShutdown(id: Int, immediate: Boolean) + fun acceptProcessRequestInitialCwd(id: Int) + fun acceptProcessRequestCwd(id: Int) + fun acceptProcessRequestLatency(id: Int): LazyPromise + fun provideLinks(id: Int, line: String): LazyPromise + fun activateLink(id: Int, linkId: Int) + fun initEnvironmentVariableCollections(collections: List>) + fun acceptDefaultProfile(profile: TerminalProfile, automationProfile: TerminalProfile) + fun createContributedProfileTerminal(id: String, options: Any): LazyPromise + fun provideTerminalQuickFixes(id: String, matchResult: Any, token: Any): LazyPromise + fun provideTerminalCompletions(id: String, options: Any, token: Any): LazyPromise +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalShellIntegrationProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalShellIntegrationProxy.kt new file mode 100644 index 0000000000..99e716c40c --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostTerminalShellIntegrationProxy.kt @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.util.URI + + +interface ExtHostTerminalShellIntegrationProxy { + fun shellIntegrationChange(instanceId: Int) + fun shellExecutionStart(instanceId: Int, commandLineValue: String, commandLineConfidence: Int, isTrusted: Boolean, cwd: URI? ) + fun shellExecutionEnd(instanceId: Int, commandLineValue: String, commandLineConfidence: Int, isTrusted: Boolean, exitCode: Int?) + fun shellExecutionData(instanceId: Int, data: String) + fun shellEnvChange(instanceId: Int, shellEnvKeys: Array, shellEnvValues: Array, isTrusted: Boolean) + fun cwdChange(instanceId: Int, cwd: URI?) + fun closeTerminal(instanceId: Int) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewViewsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewViewsProxy.kt new file mode 100644 index 0000000000..a6f233a0b1 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewViewsProxy.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +/** + * Extension host WebView views service interface + * Corresponds to ExtHostWebviewViewsShape in VSCode + */ +interface ExtHostWebviewViewsProxy { + /** + * Resolve WebView view + * @param webviewHandle WebView handle + * @param viewType View type + * @param title Title + * @param state State data + * @param cancellation Cancellation token + * @return Promise when completed + */ + fun resolveWebviewView( + webviewHandle: String, + viewType: String, + title: String?, + state: Any?, + cancellation: Any? + ) + + /** + * Triggered when WebView view visibility changes + * @param webviewHandle WebView handle + * @param visible Whether visible + */ + fun onDidChangeWebviewViewVisibility( + webviewHandle: String, + visible: Boolean + ) + + /** + * Dispose WebView view + * @param webviewHandle WebView handle + */ + fun disposeWebviewView( + webviewHandle: String + ) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewsProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewsProxy.kt new file mode 100644 index 0000000000..00666c38d4 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWebviewsProxy.kt @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.actors.WebviewHandle +import ai.kilocode.jetbrains.ipc.proxy.SerializableObjectWithBuffers + +/** + * Extension host Webviews shape interface + * Corresponds to ExtHostWebviewsShape interface in TypeScript + */ +interface ExtHostWebviewsProxy { + /** + * Handle message received from Webview + * Corresponds to $onMessage method in TypeScript interface + * + * @param handle Webview handle + * @param jsonSerializedMessage JSON serialized message + * @param buffers Array of binary buffers + */ + fun onMessage( + handle: WebviewHandle, + jsonSerializedMessage: String, + buffers: SerializableObjectWithBuffers> + ) + + /** + * Handle missing Content Security Policy (CSP) + * Corresponds to $onMissingCsp method in TypeScript interface + * + * @param handle Webview handle + * @param extensionId Extension ID + */ + fun onMissingCsp( + handle: WebviewHandle, + extensionId: String + ) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWorkspaceProxy.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWorkspaceProxy.kt new file mode 100644 index 0000000000..6fbe34f971 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/interfaces/ExtHostWorkspaceProxy.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.interfaces + +import ai.kilocode.jetbrains.model.WorkspaceData +import ai.kilocode.jetbrains.util.URIComponents +import java.net.URI + +/** + * Extension host workspace service interface + * Corresponds to ExtHostWorkspace in VSCode + */ +interface ExtHostWorkspaceProxy { + /** + * Initialize workspace + * @param workspace Workspace configuration + * @param trusted Whether trusted + */ + fun initializeWorkspace(workspace: WorkspaceData?, trusted: Boolean) + + /** + * Accept workspace data + * @param workspace Workspace data + */ + fun acceptWorkspaceData(workspace: WorkspaceData?) + + /** + * Handle text search result + */ + fun handleTextSearchResult(result: Any, requestId: Long) + + /** + * Grant workspace trust + */ + fun onDidGrantWorkspaceTrust() + + /** + * Get edit session identifier + */ + fun getEditSessionIdentifier(folder: URIComponents, token: Any): String? +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/logger/FileRPCProtocolLogger.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/logger/FileRPCProtocolLogger.kt new file mode 100644 index 0000000000..9e9fc19d40 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/logger/FileRPCProtocolLogger.kt @@ -0,0 +1,278 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.logger + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocolLogger +import ai.kilocode.jetbrains.ipc.proxy.RequestInitiator +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.concurrent.thread + +/** + * File-based RPC protocol logger + * Logs RPC communication to a file + */ +class FileRPCProtocolLogger : IRPCProtocolLogger, Disposable { + private val logger = Logger.getInstance(FileRPCProtocolLogger::class.java) + + // Total incoming bytes + private var totalIncoming = 0 + + // Total outgoing bytes + private var totalOutgoing = 0 + + // Log directory + private var logDir: Path? = null + + // Log file + private var logFile: File? = null + + // Log file writer + private var writer: BufferedWriter? = null + + // Log queue + private val logQueue = LinkedBlockingQueue() + + // Whether initialized + private val isInitialized = AtomicBoolean(false) + + // Whether disposed + private val isDisposed = AtomicBoolean(false) + + // Logger thread + private var loggerThread: Thread? = null + + // Coroutine scope + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + // Whether logging is enabled + private val isEnabled = false + + init { + if(!isEnabled) { + logger.warn("FileRPCProtocolLogger not enabled") + }else { + // Create log directory + val userHome = System.getProperty("user.home") + logDir = Paths.get(userHome, ".ext_host", "log") + + // Ensure directory exists + if (!Files.exists(logDir)) { + Files.createDirectories(logDir) + } + + // Create log filename, use timestamp for uniqueness + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) + logFile = logDir?.resolve("rpc_${timestamp}-idea.log")?.toFile() + + // Create file writer + writer = BufferedWriter(FileWriter(logFile)) + + // Start logger thread + startLoggerThread() + + // Write log header + val startTime = formatTimestampWithMilliseconds(Date()) + val header = """ + |------------------------------------------------------------- + | IDEA RPC Protocol Logger + | Started at: $startTime + | Log file: ${logFile?.absolutePath} + |------------------------------------------------------------- + + """.trimMargin() + + logQueue.add(header) + + isInitialized.set(true) + logger.info("FileRPCProtocolLogger initialized successfully, log file: ${logFile?.absolutePath}") + } + } + + /** + * Start logger thread + */ + private fun startLoggerThread() { + loggerThread = thread(start = true, isDaemon = true, name = "RPC-Logger") { + try { + while (!isDisposed.get()) { + val logEntry = logQueue.take() + try { + writer?.write(logEntry) + writer?.newLine() + writer?.flush() + } catch (e: Exception) { + logger.error("Failed to write log file", e) + } + } + } catch (e: InterruptedException) { + // Thread interrupted, exit normally + } catch (e: Exception) { + logger.error("Logger thread exception", e) + } + } + } + + /** + * Log incoming message + */ + override fun logIncoming(msgLength: Int, req: Int, initiator: RequestInitiator, str: String, data: Any?) { + if (!isInitialized.get()) { + return + } + + totalIncoming += msgLength + logMessage("Ext → IDEA", totalIncoming, msgLength, req, initiator, str, data) + } + + /** + * Log outgoing message + */ + override fun logOutgoing(msgLength: Int, req: Int, initiator: RequestInitiator, str: String, data: Any?) { + if (!isInitialized.get()) { + return + } + + totalOutgoing += msgLength + logMessage("IDEA → Ext", totalOutgoing, msgLength, req, initiator, str, data) + } + + /** + * Log message + */ + private fun logMessage( + direction: String, + totalLength: Int, + msgLength: Int, + req: Int, + initiator: RequestInitiator, + str: String, + data: Any? + ) { + try { + val timestamp = formatTimestampWithMilliseconds(Date()) + val initiatorStr = when (initiator) { + RequestInitiator.LocalSide -> "Local" + RequestInitiator.OtherSide -> "Other" + } + + val logEntry = StringBuilder() + logEntry.append("[$timestamp] ") + logEntry.append("[$direction] ") + logEntry.append("[Total: ${totalLength.toString().padStart(7)}] ") + logEntry.append("[Len: ${msgLength.toString().padStart(5)}] ") + logEntry.append("[${req.toString().padStart(5)}] ") + logEntry.append("[$initiatorStr] ") + logEntry.append(str) + + if (data != null) { + val dataStr = if (str.endsWith("(")) { + "$data)" + } else { + data.toString() + } + logEntry.append(" ").append(dataStr) + } + + // Use coroutine to asynchronously add to queue + coroutineScope.launch(Dispatchers.IO) { + logQueue.add(logEntry.toString()) + } + } catch (e: Exception) { + logger.error("Failed to format log message", e) + } + } + + /** + * Safely convert data to string + */ + private fun stringify(data: Any?): String { + return try { + when (data) { + is Map<*, *> -> data.toString() + is Collection<*> -> data.toString() + is Array<*> -> data.contentToString() + else -> data.toString() + } + } catch (e: Exception) { + "Unserializable data: ${e.message}" + } + } + + /** + * Format timestamp with milliseconds + */ + private fun formatTimestampWithMilliseconds(date: Date): String { + val calendar = Calendar.getInstance() + calendar.time = date + + val year = calendar.get(Calendar.YEAR) + val month = (calendar.get(Calendar.MONTH) + 1).toString().padStart(2, '0') + val day = calendar.get(Calendar.DAY_OF_MONTH).toString().padStart(2, '0') + val hours = calendar.get(Calendar.HOUR_OF_DAY).toString().padStart(2, '0') + val minutes = calendar.get(Calendar.MINUTE).toString().padStart(2, '0') + val seconds = calendar.get(Calendar.SECOND).toString().padStart(2, '0') + val milliseconds = calendar.get(Calendar.MILLISECOND).toString().padStart(3, '0') + + return "$year-$month-$day $hours:$minutes:$seconds.$milliseconds" + } + + /** + * Release resources + */ + override fun dispose() { + if (isDisposed.getAndSet(true)) { + return + } + + try { + // Write log footer + val endTime = formatTimestampWithMilliseconds(Date()) + val footer = """ + |------------------------------------------------------------- + | IDEA RPC Protocol Logger + | Ended at: $endTime + | Total incoming: $totalIncoming bytes + | Total outgoing: $totalOutgoing bytes + |------------------------------------------------------------- + """.trimMargin() + + logQueue.add(footer) + + // Wait for log queue to empty + var retries = 0 + while (logQueue.isNotEmpty() && retries < 10) { + Thread.sleep(100) + retries++ + } + + // Close writer + writer?.close() + writer = null + + // Interrupt logger thread + loggerThread?.interrupt() + loggerThread = null + + logger.info("FileRPCProtocolLogger released") + } catch (e: Exception) { + logger.error("Failed to release FileRPCProtocolLogger", e) + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriIpc.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriIpc.kt new file mode 100644 index 0000000000..fe968a9712 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriIpc.kt @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.uri + +import java.net.URI + +/** + * URI parts + * Corresponds to UriParts in VSCode + */ +data class UriParts( + val scheme: String, + val authority: String? = null, + val path: String, + val query: String? = null, + val fragment: String? = null +) + +/** + * Raw URI transformer interface + * Corresponds to IRawURITransformer in VSCode + */ +interface IRawURITransformer { + /** + * Transform incoming URI + */ + fun transformIncoming(uri: UriParts): UriParts + + /** + * Transform outgoing URI + */ + fun transformOutgoing(uri: UriParts): UriParts + + /** + * Transform outgoing scheme + */ + fun transformOutgoingScheme(scheme: String): String +} + +/** + * URI transformer interface + * Corresponds to IURITransformer in VSCode + */ +interface IURITransformer { + /** + * Transform incoming URI + */ + fun transformIncoming(uri: URI): URI + + /** + * Transform outgoing URI + */ + fun transformOutgoing(uri: URI): URI + + /** + * Transform outgoing URI string + */ + fun transformOutgoingURI(uri: String): String +} + +/** + * URI transformer + * Corresponds to URITransformer in VSCode + */ +class URITransformer(private val transformer: IRawURITransformer) : IURITransformer { + + override fun transformIncoming(uri: URI): URI { + val uriParts = UriParts( + scheme = uri.scheme, + authority = uri.authority, + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + + val transformedParts = transformer.transformIncoming(uriParts) + + return buildURI(transformedParts) + } + + override fun transformOutgoing(uri: URI): URI { + val uriParts = UriParts( + scheme = uri.scheme, + authority = uri.authority, + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + + val transformedParts = transformer.transformOutgoing(uriParts) + + return buildURI(transformedParts) + } + + override fun transformOutgoingURI(uri: String): String { + try { + return transformOutgoing(URI(uri)).toString() + } catch (e: Exception) { + // If the URI is invalid, try to convert only the scheme part + val schemeEndIndex = uri.indexOf(':') + if (schemeEndIndex > 0) { + val scheme = uri.substring(0, schemeEndIndex) + val transformedScheme = transformer.transformOutgoingScheme(scheme) + if (transformedScheme !== scheme) { + return transformedScheme + uri.substring(schemeEndIndex) + } + } + return uri + } + } + + /** + * Build URI from UriParts + */ + private fun buildURI(parts: UriParts): URI { + val builder = StringBuilder() + + // Add scheme + builder.append(parts.scheme).append(":") + + // Add authority (if present) + if (!parts.authority.isNullOrEmpty()) { + builder.append("//").append(parts.authority) + } + + // Add path + builder.append(parts.path) + + // Add query (if present) + if (!parts.query.isNullOrEmpty()) { + builder.append("?").append(parts.query) + } + + // Add fragment (if present) + if (!parts.fragment.isNullOrEmpty()) { + builder.append("#").append(parts.fragment) + } + + return URI(builder.toString()) + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformer.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformer.kt new file mode 100644 index 0000000000..61eddf09b6 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformer.kt @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.uri + +/** + * Create raw URI transformer + * Corresponds to createRawURITransformer in VSCode + */ +fun createRawURITransformer(remoteAuthority: String): IRawURITransformer { + return object : IRawURITransformer { + override fun transformIncoming(uri: UriParts): UriParts { + return when (uri.scheme) { + "vscode-remote" -> UriParts( + scheme = "file", + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + "file" -> UriParts( + scheme = "vscode-local", + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + else -> uri + } + } + + override fun transformOutgoing(uri: UriParts): UriParts { + return when (uri.scheme) { + "file" -> UriParts( + scheme = "vscode-remote", + authority = remoteAuthority, + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + "vscode-local" -> UriParts( + scheme = "file", + path = uri.path, + query = uri.query, + fragment = uri.fragment + ) + else -> uri + } + } + + override fun transformOutgoingScheme(scheme: String): String { + return when (scheme) { + "file" -> "vscode-remote" + "vscode-local" -> "file" + else -> scheme + } + } + } +} + +/** + * Create URI transformer + * Corresponds to createURITransformer in VSCode + */ +fun createURITransformer(remoteAuthority: String): IURITransformer { + return URITransformer(createRawURITransformer(remoteAuthority)) +} + +/** + * JSON converter for URI transformation + * Used for conversion between string and URI + */ +class UriReplacer(private val transformer: IURITransformer) : (String, Any?) -> Any? { + + override fun invoke(key: String, value: Any?): Any? { + if (value is String && ( + key == "uri" || + key == "documentUri" || + key == "targetUri" || + key == "sourceUri" || + key.endsWith("Uri")) + ) { + return transformer.transformOutgoingURI(value) + } + return value + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformerExample.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformerExample.kt new file mode 100644 index 0000000000..29f1525ea2 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/ipc/proxy/uri/UriTransformerExample.kt @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.ipc.proxy.uri + +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.ipc.IMessagePassingProtocol +import ai.kilocode.jetbrains.ipc.ISocket +import ai.kilocode.jetbrains.ipc.PersistentProtocol +import ai.kilocode.jetbrains.ipc.proxy.RPCProtocol +import ai.kilocode.jetbrains.ipc.proxy.createProxyIdentifier +import java.net.URI + +/** + * Example usage of URI transformer + */ +object UriTransformerExample { + private val LOG = Logger.getInstance(UriTransformerExample::class.java) + + /** + * Example: Create and use URI transformer + */ + fun uriTransformerExample() { + // Create URI transformer + val remoteAuthority = "your-remote-host.example.com" + val uriTransformer = createURITransformer(remoteAuthority) + + // Test URI transformation + val localUri = URI("file:///path/to/file.txt") + val remoteUri = uriTransformer.transformOutgoing(localUri) + LOG.info("Transformed URI: $remoteUri") + + // Transform back + val convertedBackUri = uriTransformer.transformIncoming(remoteUri) + LOG.info("Transformed back URI: $convertedBackUri") + + // Create UriReplacer for URI transformation in JSON objects + val uriReplacer = UriReplacer(uriTransformer) + val result = uriReplacer("documentUri", "file:///path/to/document.txt") + LOG.info("Replaced URI: $result") + } + + /** + * Example: Use URI transformer in RPC protocol + */ + fun rpcWithUriTransformerExample(socket: ISocket) { + // Create URI transformer + val remoteAuthority = "your-remote-host.example.com" + val uriTransformer = createURITransformer(remoteAuthority) + + // Create protocol object + val persistentProtocol = PersistentProtocol(PersistentProtocol.PersistentProtocolOptions(socket)) + + // Create RPC protocol object, pass in URI transformer + val rpcProtocol = RPCProtocol(persistentProtocol, null, uriTransformer) + + // Now the RPC protocol will automatically handle URI transformation + // During serialization and deserialization, URIs will be transformed according to the configured rules + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/model/WorkspaceData.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/model/WorkspaceData.kt new file mode 100644 index 0000000000..62d5d1094d --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/model/WorkspaceData.kt @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.model + +import ai.kilocode.jetbrains.util.URI +import ai.kilocode.jetbrains.util.URIComponents + +/** + * Workspace base data + * Corresponds to IStaticWorkspaceData in VSCode + */ +data class StaticWorkspaceData( + val id: String, + val name: String, + val transient: Boolean? = null, + val configuration: URI? = null, + val isUntitled: Boolean? = null +) + +/** + * Workspace folder + * Corresponds to elements in IWorkspaceData.folders in VSCode + */ +data class WorkspaceFolder( + val uri: URI, + val name: String, + val index: Int +) + +/** + * Workspace data + * Corresponds to IWorkspaceData in VSCode + */ +data class WorkspaceData( + val id: String, + val name: String, + val transient: Boolean? = null, + val configuration: URI? = null, + val isUntitled: Boolean? = null, + val folders: List = emptyList() +) { + // Create WorkspaceData from StaticWorkspaceData + constructor(staticData: StaticWorkspaceData, folders: List = emptyList()) : this( + id = staticData.id, + name = staticData.name, + transient = staticData.transient, + configuration = staticData.configuration, + isUntitled = staticData.isUntitled, + folders = folders + ) +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/SystemObjectProvider.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/SystemObjectProvider.kt new file mode 100644 index 0000000000..aa77523eaf --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/SystemObjectProvider.kt @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.plugin + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import java.util.concurrent.ConcurrentHashMap + +/** + * System Object Provider + * Provides unified access to IDEA system objects + */ +object SystemObjectProvider { + private val logger = Logger.getInstance(SystemObjectProvider::class.java) + + // Mapping for storing system objects + private val systemObjects = ConcurrentHashMap() + + + /** + * System object keys + */ + object Keys { + const val APPLICATION = "application" + // More system object keys can be added + } + + /** + * Initialize the system object provider + * @param project current project + */ + fun initialize(project: Project) { + logger.info("Initializing SystemObjectProvider with project: ${project.name}") + + register(Keys.APPLICATION, ApplicationManager.getApplication()) + } + + /** + * Register a system object + * @param key object key + * @param obj object instance + */ + fun register(key: String, obj: Any) { + systemObjects[key] = obj + logger.debug("Registered system object: $key") + } + + /** + * Get a system object + * @param key object key + * @return object instance or null + */ + @Suppress("UNCHECKED_CAST") + fun get(key: String): T? { + return systemObjects[key] as? T + } + + + /** + * Clean up resources + */ + fun dispose() { + logger.info("Disposing SystemObjectProvider") + systemObjects.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/WecoderPlugin.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/WecoderPlugin.kt new file mode 100644 index 0000000000..79d7879400 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/plugin/WecoderPlugin.kt @@ -0,0 +1,406 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.plugin + +import java.nio.file.Files +import java.nio.file.StandardCopyOption + +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupActivity +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.Disposable +import ai.kilocode.jetbrains.core.ExtensionProcessManager +import ai.kilocode.jetbrains.core.ExtensionSocketServer +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import ai.kilocode.jetbrains.webview.WebViewManager +import ai.kilocode.jetbrains.workspace.WorkspaceFileChangeManager +import java.util.concurrent.CompletableFuture +import kotlinx.coroutines.* +import java.util.Properties +import java.io.InputStream +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.util.SystemInfo +import com.intellij.ui.jcef.JBCefApp +import com.intellij.openapi.application.ApplicationInfo +import ai.kilocode.jetbrains.core.* +import ai.kilocode.jetbrains.util.ExtensionUtils +import ai.kilocode.jetbrains.util.PluginConstants +import ai.kilocode.jetbrains.util.PluginResourceUtil +import java.io.File + +/** + * WeCode IDEA plugin entry class + * Responsible for plugin initialization and lifecycle management + */ +class WecoderPlugin : StartupActivity.DumbAware { + companion object { + private val LOG = Logger.getInstance(WecoderPlugin::class.java) + + /** + * Get plugin service instance + */ + fun getInstance(project: Project): WecoderPluginService { + return project.getService(WecoderPluginService::class.java) + ?: error("WecoderPluginService not found") + } + + /** + * Get the basePath of the current project + */ + @JvmStatic + fun getProjectBasePath(project: Project): String? { + return project.basePath + } + } + + override fun runActivity(project: Project) { + val appInfo = ApplicationInfo.getInstance() + val plugin = PluginManagerCore.getPlugin(PluginId.getId(PluginConstants.PLUGIN_ID)) + val pluginVersion = plugin?.version ?: "unknown" + val osName = System.getProperty("os.name") + val osVersion = System.getProperty("os.version") + val osArch = System.getProperty("os.arch") + + LOG.info( + "Initializing Kilo Code plugin for project: ${project.name}, " + + "OS: $osName $osVersion ($osArch), " + + "IDE: ${appInfo.fullApplicationName} (build ${appInfo.build}), " + + "Plugin version: $pluginVersion, " + + "JCEF supported: ${JBCefApp.isSupported()}" + ) + + try { + // Initialize plugin service + val pluginService = getInstance(project) + pluginService.initialize(project) + + // Initialize WebViewManager and register to project Disposer + val webViewManager = project.getService(WebViewManager::class.java) + Disposer.register(project, webViewManager) + + // Register project-level resource disposal + Disposer.register(project, Disposable { + LOG.info("Disposing Kilo Code plugin for project: ${project.name}") + pluginService.dispose() + SystemObjectProvider.dispose() + }) + + LOG.info("Kilo Code plugin initialized successfully for project: ${project.name}") + } catch (e: Exception) { + LOG.error("Failed to initialize Kilo Code plugin", e) + } + } +} + +/** + * Debug mode enum + */ +enum class DEBUG_MODE { + ALL, // All debug modes + IDEA, // Only IDEA plugin debug + NONE; // Debug not enabled + + companion object { + /** + * Parse debug mode from string + * @param value String value + * @return Corresponding debug mode + */ + fun fromString(value: String): DEBUG_MODE { + return when (value.lowercase()) { + "all" -> ALL + "idea" -> IDEA + "true" -> ALL // backward compatibility + else -> NONE + } + } + } +} + +/** + * Plugin service class, provides global access point and core functionality + */ +@Service(Service.Level.PROJECT) +class WecoderPluginService(private var currentProject: Project) : Disposable { + private val LOG = Logger.getInstance(WecoderPluginService::class.java) + + // Whether initialized + @Volatile + private var isInitialized = false + + // Plugin initialization complete flag + private val initializationComplete = CompletableFuture() + + // Coroutine scope + private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // Service instances + private val socketServer = ExtensionSocketServer() + private val udsSocketServer = ExtensionUnixDomainSocketServer() + private val processManager = ExtensionProcessManager() + + companion object { + // Debug mode switch + @Volatile + private var DEBUG_TYPE: DEBUG_MODE = ai.kilocode.jetbrains.plugin.DEBUG_MODE.NONE + + @Volatile + private var DEBUG_RESOURCE: String? = null + + // Debug mode connection address + private const val DEBUG_HOST = "127.0.0.1" + + // Debug mode connection port + private const val DEBUG_PORT = 51234 + + + // Initialize configuration at class load + init { + try { + // Read debug mode setting from config file + val properties = Properties() + val configStream: InputStream? = WecoderPluginService::class.java.getResourceAsStream("/ai/kilocode/jetbrains/plugin/config/plugin.properties") + + if (configStream != null) { + properties.load(configStream) + configStream.close() + + // Read debug mode config + val debugModeStr = properties.getProperty("debug.mode", "none").lowercase() + DEBUG_TYPE = DEBUG_MODE.fromString(debugModeStr) + DEBUG_RESOURCE = properties.getProperty("debug.resource", null) + + Logger.getInstance(WecoderPluginService::class.java).info("Read debug mode from config file: $DEBUG_MODE") + } else { + Logger.getInstance(WecoderPluginService::class.java).warn("Cannot load config file, use default debug mode: $DEBUG_MODE") + } + } catch (e: Exception) { + Logger.getInstance(WecoderPluginService::class.java).warn("Error reading config file, use default debug mode: $DEBUG_MODE", e) + } + } + + /** + * Get current debug mode + * @return Debug mode + */ + @JvmStatic + fun getDebugMode(): DEBUG_MODE { + return DEBUG_TYPE + } + + /** + * Get debug resource path + * @return Debug resource path + */ + @JvmStatic + fun getDebugResource(): String? { + return DEBUG_RESOURCE + } + } + + /** + * Initialize plugin service + */ + fun initialize(project: Project) { + // DEBUG_MODE is no longer set directly in code, now read from config file + if (isInitialized) { + LOG.info("WecoderPluginService already initialized") + return + } + + LOG.info("Initializing WecoderPluginService, debug mode: $DEBUG_TYPE") + // Initialize system object provider + SystemObjectProvider.initialize(project) + this.currentProject = project + socketServer.project = project + udsSocketServer.project = project + + // Register to system object provider + SystemObjectProvider.register("pluginService", this) + + // Start initialization in background thread + coroutineScope.launch { + try { + initPlatformFiles() + // Get project path + val projectPath = project.basePath ?: "" + + // Initialize service registration + project.getService(ServiceProxyRegistry::class.java).initialize() +// ServiceProxyRegistry.getInstance().initialize() + + if (DEBUG_TYPE == ai.kilocode.jetbrains.plugin.DEBUG_MODE.ALL) { + // Debug mode: directly connect to extension process in debug + LOG.info("Running in debug mode: ${DEBUG_TYPE}, will directly connect to $DEBUG_HOST:$DEBUG_PORT") + + // connet to debug port + socketServer.connectToDebugHost(DEBUG_HOST, DEBUG_PORT) + + // Initialization successful + isInitialized = true + initializationComplete.complete(true) + LOG.info("Debug mode connection successful, WecoderPluginService initialized") + } else { + // Normal mode: start Socket server and extension process + // 1. Start Socket server according to system, use UDS except on Windows + val server: ISocketServer = if (SystemInfo.isWindows) socketServer else udsSocketServer + val portOrPath = server.start(projectPath) + if (!ExtensionUtils.isValidPortOrPath(portOrPath)) { + LOG.error("Failed to start socket server") + initializationComplete.complete(false) + return@launch + } + + LOG.info("Socket server started on: $portOrPath") + // 2. Start extension process + if (!processManager.start(portOrPath)) { + LOG.error("Failed to start extension process") + server.stop() + initializationComplete.complete(false) + return@launch + } + // Initialization successful + isInitialized = true + initializationComplete.complete(true) + LOG.info("WecoderPluginService initialization completed") + } + } catch (e: Exception) { + LOG.error("Error during WecoderPluginService initialization", e) + cleanup() + initializationComplete.complete(false) + } + } + } + + private fun initPlatformFiles() { + // Initialize platform related files + val platformSuffix = when { + SystemInfo.isWindows -> "windows-x64" + SystemInfo.isMac -> when (System.getProperty("os.arch")) { + "x86_64" -> "darwin-x64" + "aarch64" -> "darwin-arm64" + else -> "" + } + SystemInfo.isLinux -> "linux-x64" + else -> "" + } + if (platformSuffix.isNotEmpty()) { + val pluginDir = PluginResourceUtil.getResourcePath(PluginConstants.PLUGIN_ID, "") + ?: throw IllegalStateException("Cannot get plugin directory") + + val platformFile = File(pluginDir, "platform.txt") + if (platformFile.exists()) { + platformFile.readLines() + .filter { it.isNotBlank() && !it.startsWith("#") } + .forEach { originalPath -> + val suffixedPath = "$originalPath$platformSuffix" + val originalFile = File(pluginDir, "node_modules/$originalPath") + val suffixedFile = File(pluginDir, "node_modules/$suffixedPath") + + if (suffixedFile.exists()) { + if (originalFile.exists()) { + originalFile.delete() + } + Files.move( + suffixedFile.toPath(), + originalFile.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + originalFile.setExecutable(true) + } + } + } + platformFile.delete() + } + } + + /** + * Wait for initialization to complete + * @return Whether initialization was successful + */ + fun waitForInitialization(): Boolean { + return initializationComplete.get() + } + + /** + * Clean up resources + */ + private fun cleanup() { + try { + // Stop extension process, only needed in non-debug mode + if (DEBUG_TYPE == ai.kilocode.jetbrains.plugin.DEBUG_MODE.NONE) { + processManager.stop() + } + } catch (e: Exception) { + LOG.error("Error stopping process manager", e) + } + + try { + // Stop Socket server + socketServer.stop() + udsSocketServer.stop() + } catch (e: Exception) { + LOG.error("Error stopping socket server", e) + } + + // Unregister workspace file change listener + currentProject.getService(WorkspaceFileChangeManager::class.java).dispose() +// WorkspaceFileChangeManager.disposeInstance() + + isInitialized = false + } + + /** + * Get whether initialized + */ + fun isInitialized(): Boolean { + return isInitialized + } + + /** + * Get Socket server + */ + fun getSocketServer(): ExtensionSocketServer { + return socketServer + } + + /** + * Get process manager + */ + fun getProcessManager(): ExtensionProcessManager { + return processManager + } + + /** + * Get current project + */ + fun getCurrentProject(): Project? { + return currentProject + } + + /** + * Close service + */ + override fun dispose() { + if (!isInitialized) { + return + } + + LOG.info("Disposing WecoderPluginService") + + currentProject?.getService(WebViewManager::class.java)?.dispose() + + // Cancel all coroutines + coroutineScope.cancel() + + // Clean up resources + cleanup() + + LOG.info("WecoderPluginService disposed") + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/DocumentSyncService.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/DocumentSyncService.kt new file mode 100644 index 0000000000..085de4394c --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/DocumentSyncService.kt @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.service + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import ai.kilocode.jetbrains.core.PluginContext +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import ai.kilocode.jetbrains.editor.EditorAndDocManager +import ai.kilocode.jetbrains.editor.ModelAddedData +import ai.kilocode.jetbrains.editor.createURI +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostDocumentsProxy + +class DocumentSyncService(private val project: Project) { + + private val logger = Logger.getInstance(DocumentSyncService::class.java) + private var extHostDocumentsProxy: ExtHostDocumentsProxy? = null + + private fun getExtHostDocumentsProxy(): ExtHostDocumentsProxy? { + if (extHostDocumentsProxy == null) { + try { + val protocol = PluginContext.getInstance(project).getRPCProtocol() + extHostDocumentsProxy = protocol?.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostDocuments) + logger.debug("ExtHostDocumentsProxy initialized in DocumentSyncService") + } catch (e: Exception) { + logger.error("Failed to get ExtHostDocumentsProxy in DocumentSyncService", e) + } + } + return extHostDocumentsProxy + } + + suspend fun syncDocumentStateOnSave(virtualFile: VirtualFile, document: Document) { + logger.info("Starting to sync document save state: ${virtualFile.path}") + try { + // Create URI object + val uriMap = mapOf( + "scheme" to "file", + "authority" to "", + "path" to virtualFile.path, + "query" to "", + "fragment" to "" + ) + val uri = createURI(uriMap) + + // Get EditorAndDocManager to manage document state + val editorAndDocManager = project.getService(EditorAndDocManager::class.java) + + // Find corresponding EditorHolder + val editorHandles = editorAndDocManager.getEditorHandleByUri(uri) + + if (editorHandles.isNotEmpty()) { + // If corresponding editor exists, update its state + for (handle in editorHandles) { + // Read latest document content + val text = ApplicationManager.getApplication().runReadAction { + document.text + } + + // Create updated document data + val updatedDocument = ModelAddedData( + uri = handle.document.uri, + versionId = handle.document.versionId + 1, + lines = text.lines(), + EOL = handle.document.EOL, + languageId = handle.document.languageId, + isDirty = false, // Set to false after save + encoding = handle.document.encoding + ) + + // Update document state in EditorHolder + handle.document = updatedDocument + + // Trigger state sync to extension side + editorAndDocManager.updateDocumentAsync(updatedDocument) + } + + // Send save event to extension process + getExtHostDocumentsProxy()?.let { proxy -> + proxy.acceptModelSaved(uri) + logger.info("Document save event and state synced to extension host: ${virtualFile.path}") + } + } + } catch (e: Exception) { + logger.error("Error syncing document state on save", e) + } + } + + fun shouldHandleFileEvent(virtualFile: VirtualFile): Boolean { + // Filter: only process real files (non-directory) and not temporary files + return !virtualFile.isDirectory && + virtualFile.isInLocalFileSystem && + !virtualFile.path.contains("/.idea/") && // Exclude IDE configuration files + !virtualFile.path.contains("/target/") && // Exclude build output files + !virtualFile.path.contains("/build/") && + !virtualFile.path.contains("/node_modules/") && + virtualFile.extension != null && // Ensure file has extension + !isTooLargeForSyncing(virtualFile) && // Exclude files that are too large for syncing + !isForSimpleWidget(virtualFile) // Exclude simple widget files + } + + /** + * Check if file is too large for syncing + * Reference VS Code implementation, exclude files over 2MB + */ + private fun isTooLargeForSyncing(virtualFile: VirtualFile): Boolean { + return try { + val maxSizeBytes = 2 * 1024 * 1024L // 2MB + virtualFile.length > maxSizeBytes + } catch (e: Exception) { + logger.warn("Failed to check file size for: ${virtualFile.path}", e) + false + } + } + + /** + * Check if file is for simple widget use + * Exclude special purpose file types + */ + private fun isForSimpleWidget(virtualFile: VirtualFile): Boolean { + return try { + // Exclude special file types + val fileName = virtualFile.name.lowercase() + val extension = virtualFile.extension?.lowercase() + + // Temporary files, cache files, backup files, etc. + fileName.startsWith(".") || + fileName.endsWith(".tmp") || + fileName.endsWith(".temp") || + fileName.endsWith(".bak") || + fileName.endsWith(".backup") || + fileName.contains("~") || + // Binary file extensions + extension in setOf( + "exe", "dll", "so", "dylib", "bin", "obj", "o", "a", "lib", + "zip", "tar", "gz", "rar", "7z", "jar", "war", "ear", + "png", "jpg", "jpeg", "gif", "bmp", "ico", "tiff", + "mp3", "mp4", "avi", "mov", "wav", "flv", "wmv", + "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx" + ) || + // Special paths + virtualFile.path.contains("/.git/") || + virtualFile.path.contains("/.svn/") || + virtualFile.path.contains("/.hg/") || + virtualFile.path.contains("/vendor/") || + virtualFile.path.contains("/dist/") || + virtualFile.path.contains("/out/") + } catch (e: Exception) { + logger.warn("Failed to check if file is for simple widget: ${virtualFile.path}", e) + false + } + } + + fun dispose() { + extHostDocumentsProxy = null + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/ExtensionStorageService.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/ExtensionStorageService.kt new file mode 100644 index 0000000000..fda59c795a --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/service/ExtensionStorageService.kt @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.service + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.util.xmlb.XmlSerializerUtil +import com.google.gson.Gson + +@Service +@State( + name = "ai.kilocode.jetbrains.service.ExtensionStorageService", + storages = [Storage("roo-cline-extension-storage.xml")] +) +class ExtensionStorageService() : PersistentStateComponent { + private val gson = Gson() + var storageMap: MutableMap = mutableMapOf() + + override fun getState(): ExtensionStorageService = this + + override fun loadState(state: ExtensionStorageService) { + XmlSerializerUtil.copyBean(state, this) + } + + fun setValue(key: String, value: Any) { + storageMap[key] = when (value) { + is String -> value + else -> gson.toJson(value) + } + } + + fun getValue(key: String): String? { + return storageMap[key] + } + + fun removeValue(key: String) { + storageMap.remove(key) + } + + fun clear() { + storageMap.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ProxyPtyProcess.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ProxyPtyProcess.kt new file mode 100644 index 0000000000..675957917e --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ProxyPtyProcess.kt @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.pty4j.PtyProcess +import com.intellij.openapi.diagnostic.Logger +import com.pty4j.WinSize + +/** + * ProxyPtyProcess callback interface + * Simplified version, only provides raw data callback + */ +interface ProxyPtyProcessCallback { + /** + * Raw data callback + * @param data Raw string data + * @param streamType Stream type (STDOUT/STDERR) + */ + fun onRawData(data: String, streamType: String) +} + +/** + * ProxyPtyProcess implementation + * Intercepts input/output stream operations and provides raw data callback + */ +class ProxyPtyProcess( + private val originalProcess: PtyProcess, + private val callback: ProxyPtyProcessCallback? = null +) : PtyProcess() { + + // Create proxy input stream (process standard output) + private val proxyInputStream: ProxyInputStream = ProxyInputStream( + originalProcess.inputStream, + "STDOUT", + callback + ) + + // Create proxy error stream (process error output) + private val proxyErrorStream: ProxyInputStream = ProxyInputStream( + originalProcess.errorStream, + "STDERR", + callback + ) + + // Override methods that require special handling + override fun getInputStream(): java.io.InputStream = proxyInputStream + override fun getErrorStream(): java.io.InputStream = proxyErrorStream + override fun getOutputStream(): java.io.OutputStream = originalProcess.outputStream + + // Delegate all other methods to the original process + override fun isAlive(): Boolean = originalProcess.isAlive() + override fun pid(): Long = originalProcess.pid() + override fun exitValue(): Int = originalProcess.exitValue() + override fun waitFor(): Int = originalProcess.waitFor() + override fun waitFor(timeout: Long, unit: java.util.concurrent.TimeUnit): Boolean = + originalProcess.waitFor(timeout, unit) + override fun destroy() = originalProcess.destroy() + override fun destroyForcibly(): Process = originalProcess.destroyForcibly() + override fun info(): ProcessHandle.Info = originalProcess.info() + override fun children(): java.util.stream.Stream = originalProcess.children() + override fun descendants(): java.util.stream.Stream = originalProcess.descendants() + override fun setWinSize(winSize: WinSize) = originalProcess.setWinSize(winSize) + override fun toHandle(): ProcessHandle = originalProcess.toHandle() + override fun onExit(): java.util.concurrent.CompletableFuture = originalProcess.onExit() + + // PtyProcess specific methods + override fun getWinSize(): WinSize = originalProcess.winSize + override fun isConsoleMode(): Boolean = originalProcess.isConsoleMode +} + +/** + * Proxy InputStream implementation + * Intercepts read operations and provides raw data callback + */ +class ProxyInputStream( + private val originalStream: java.io.InputStream, + private val streamType: String, + private val callback: ProxyPtyProcessCallback? +) : java.io.InputStream() { + + override fun read(): Int { + val result = originalStream.read() + if (result != -1 && callback != null) { + // Convert single byte to string and callback + val dataString = String(byteArrayOf(result.toByte()), Charsets.UTF_8) + callback.onRawData(dataString, streamType) + } + return result + } + + override fun read(b: ByteArray): Int { + val result = originalStream.read(b) + if (result > 0 && callback != null) { + // Convert to string and callback + val dataString = String(b, 0, result, Charsets.UTF_8) + callback.onRawData(dataString, streamType) + } + return result + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + val result = originalStream.read(b, off, len) + if (result > 0 && callback != null) { + // Convert to string and callback + val dataString = String(b, off, result, Charsets.UTF_8) + callback.onRawData(dataString, streamType) + } + return result + } + + // Delegate other methods to the original stream + override fun available(): Int = originalStream.available() + override fun close() = originalStream.close() + override fun mark(readlimit: Int) = originalStream.mark(readlimit) + override fun reset() = originalStream.reset() + override fun markSupported(): Boolean = originalStream.markSupported() +} diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ShellIntegrationOutputState.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ShellIntegrationOutputState.kt new file mode 100644 index 0000000000..6959e2e081 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/ShellIntegrationOutputState.kt @@ -0,0 +1,394 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.* +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong + +/** + * Shell integration event types + */ +sealed class ShellEvent { + data class ShellExecutionStart(val commandLine: String, val cwd: String) : ShellEvent() + data class ShellExecutionEnd(val commandLine: String, val exitCode: Int?) : ShellEvent() + data class ShellExecutionData(val data: String) : ShellEvent() + data class CwdChange(val cwd: String) : ShellEvent() +} + +/** + * Shell integration event listener + */ +interface ShellEventListener { + fun onShellExecutionStart(commandLine: String, cwd: String) + fun onShellExecutionEnd(commandLine: String, exitCode: Int?) + fun onShellExecutionData(data: String) + fun onCwdChange(cwd: String) +} + +/** + * Shell integration output state manager + * Refer to VSCode Shell Integration implementation + * Reference: https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/common/terminalShellIntegration.ts + */ +class ShellIntegrationOutputState { + private val logger = Logger.getInstance(ShellIntegrationOutputState::class.java) + + // Event listeners + private val listeners = mutableListOf() + + // State properties + @Volatile + var isCommandRunning: Boolean = false + private set + + @Volatile + var currentCommand: String = "" + private set + + @Volatile + var currentNonce: String = "" + private set + + @Volatile + var commandStatus: Int? = null + private set + + @Volatile + var currentDirectory: String = "" + private set + + @Volatile + var output: String = "" + private set + + // Pending output buffer + private val pendingOutput = StringBuilder() + private val pendingOutputLock = Any() + private val lastAppendTime = AtomicLong(0) + private val isFlushScheduled = AtomicBoolean(false) + + // Coroutine scope + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + /** + * Add event listener + */ + fun addListener(listener: ShellEventListener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + /** + * Remove event listener + */ + fun removeListener(listener: ShellEventListener) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + /** + * Notify all listeners of an event + */ + private fun notifyListeners(event: ShellEvent) { + synchronized(listeners) { + listeners.forEach { listener -> + try { + when (event) { + is ShellEvent.ShellExecutionStart -> + listener.onShellExecutionStart(event.commandLine, event.cwd) + is ShellEvent.ShellExecutionEnd -> + listener.onShellExecutionEnd(event.commandLine, event.exitCode) + is ShellEvent.ShellExecutionData -> + listener.onShellExecutionData(event.data) + is ShellEvent.CwdChange -> + listener.onCwdChange(event.cwd) + } + } catch (e: Exception) { + logger.warn("Failed to notify Shell event listener", e) + } + } + } + } + + /** + * Append output data (with buffering and delayed sending) + */ + private fun appendOutput(text: String) { + logger.debug("📝 appendOutput called: '${text}', length=${text.length}") + synchronized(pendingOutputLock) { + pendingOutput.append(text) + logger.debug("📝 pendingOutput updated length: ${pendingOutput.length}") + } + + val currentTime = System.currentTimeMillis() + lastAppendTime.set(currentTime) + + // If no flush task is scheduled, schedule one + if (isFlushScheduled.compareAndSet(false, true)) { + logger.debug("📝 Scheduling flush task, will execute after 50ms") + scope.launch { + delay(50) // 50ms delay + flushPendingOutput() + } + } else { + logger.debug("📝 Flush task already scheduled, skipping") + } + } + + /** + * Flush pending output + */ + private fun flushPendingOutput() { + logger.debug("🚀 flushPendingOutput called") + val textToFlush = synchronized(pendingOutputLock) { + if (pendingOutput.isNotEmpty()) { + val text = pendingOutput.toString() + pendingOutput.clear() + logger.debug("🚀 Ready to flush text: '${text}', length=${text.length}") + text + } else { + logger.debug("🚀 pendingOutput is empty, no need to flush") + null + } + } + + isFlushScheduled.set(false) + + textToFlush?.let { text -> + output += text + logger.info("🚀 Sending ShellExecutionData event: '${text}', length=${text.length}") + notifyListeners(ShellEvent.ShellExecutionData(text)) + } + } + + /** + * Clear output + */ + fun clearOutput() { + synchronized(pendingOutputLock) { + output = "" + pendingOutput.clear() + currentNonce = "" + } + isFlushScheduled.set(false) + } + + /** + * Terminate current state + */ + fun terminate() { + isCommandRunning = false + flushPendingOutput() + } + + /** + * Process raw output data + * Parse Shell Integration markers and extract clean content + */ + fun appendRawOutput(output: String) { + logger.debug("📥 Processing raw output: ${output.length} chars, isCommandRunning=$isCommandRunning") + logger.debug("📥 Raw output content: '${output.replace("\u001b", "\\u001b").replace("\u0007", "\\u0007")}'") + + var currentIndex = 0 + var hasShellIntegrationMarkers = false + + while (currentIndex < output.length) { + // Find Shell Integration marker: \u001b]633; + val markerIndex = output.indexOf("\u001b]633;", currentIndex) + + if (markerIndex == -1) { + // No marker found + val remainingContent = output.substring(currentIndex) + logger.debug("📤 No Shell Integration marker found, remaining content: '${remainingContent}', isCommandRunning=$isCommandRunning") + + if (!hasShellIntegrationMarkers && remainingContent.isNotEmpty()) { + // If there is no Shell Integration marker in the entire output, treat all content as command output + logger.debug("📤 No Shell Integration marker, treat all content as command output") + appendOutput(remainingContent) + } else if (isCommandRunning && currentIndex < output.length) { + logger.debug("📤 Append remaining content to output: '${remainingContent}'") + appendOutput(remainingContent) + } else if (!isCommandRunning) { + logger.debug("⚠️ Command not running, ignore output: '${remainingContent}'") + } + break + } + + hasShellIntegrationMarkers = true + + // If command is running, append content before marker + if (isCommandRunning && currentIndex < markerIndex) { + val beforeMarker = output.substring(currentIndex, markerIndex) + logger.debug("📤 Append content before marker: '${beforeMarker}'") + appendOutput(beforeMarker) + } else if (!isCommandRunning && currentIndex < markerIndex) { + val beforeMarker = output.substring(currentIndex, markerIndex) + logger.debug("⚠️ Command not running, ignore content before marker: '${beforeMarker}'") + } + + // Parse marker + val typeStart = markerIndex + 6 // "\u001b]633;".length + if (typeStart >= output.length) { + if (isCommandRunning && currentIndex < output.length) { + appendOutput(output.substring(currentIndex)) + } + break + } + + val type = MarkerType.fromChar(output[typeStart]) + val paramStart = typeStart + 1 + + // Find marker end: \u0007 + val paramEnd = output.indexOf('\u0007', paramStart) + if (paramEnd == -1) { + logger.debug("⚠️ Marker end not found, skip") + currentIndex = typeStart + continue + } + + // Extract parameters + val params = if (paramStart < paramEnd) { + output.substring(paramStart, paramEnd) + } else { + "" + } + + val components = if (params.startsWith(";")) { + params.substring(1).split(";") + } else { + listOf(params) + } + + logger.debug("🔍 Parse Shell Integration marker: type=$type, params='$params', components=$components") + + // Handle different marker types + when (type) { + MarkerType.COMMAND_LINE -> { + logger.info("🎯 Shell Integration - Detected command line marker") + if (components.isNotEmpty() && components[0].isNotEmpty()) { + currentCommand = components[0] + currentNonce = if (components.size >= 2) components[1] else "" + logger.info("🎯 Shell Integration - Command line: '$currentCommand'") + } + } + + MarkerType.COMMAND_EXECUTED -> { + logger.info("🚀 Shell Integration - Detected command executed marker") + isCommandRunning = true + if (currentCommand.isNotEmpty()) { + logger.info("🚀 Shell Integration - Command started: '$currentCommand', isCommandRunning=$isCommandRunning") + notifyListeners(ShellEvent.ShellExecutionStart(currentCommand, currentDirectory)) + // Include marker itself in output + appendOutput(output.substring(markerIndex, paramEnd + 1)) + } + } + + MarkerType.COMMAND_FINISHED -> { + logger.info("🏁 Shell Integration - Detected command finished marker") + if (currentCommand.isNotEmpty()) { + // Include marker itself in output + appendOutput(output.substring(markerIndex, paramEnd + 1)) + flushPendingOutput() // Ensure all pending data is sent before command ends + + commandStatus = components.firstOrNull()?.toIntOrNull() + logger.info("🏁 Shell Integration - Command finished: '$currentCommand' (exit code: $commandStatus)") + notifyListeners(ShellEvent.ShellExecutionEnd(currentCommand, commandStatus)) + currentCommand = "" + } + isCommandRunning = false + } + + MarkerType.PROPERTY -> { + logger.debug("📋 Shell Integration - Detected property marker") + if (components.isNotEmpty()) { + val property = components[0] + if (property.startsWith("Cwd=")) { + val cwdValue = property.substring(4) // "Cwd=".length + if (cwdValue != currentDirectory) { + currentDirectory = cwdValue + logger.info("📁 Shell Integration - Directory changed: '$cwdValue'") + notifyListeners(ShellEvent.CwdChange(cwdValue)) + } + } + } + } + + MarkerType.PROMPT_START -> { + logger.debug("🎯 Shell Integration - Prompt start") + } + + MarkerType.COMMAND_START -> { + logger.debug("🎯 Shell Integration - Command input start") + } + + else -> { + logger.debug("🔍 Shell Integration - Unhandled marker type: $type") + } + } + + currentIndex = paramEnd + 1 + } + } + + /** + * Get clean output with Shell Integration markers removed + */ + fun getCleanOutput(rawOutput: String): String { + var result = rawOutput + + // Remove all Shell Integration markers + val markerPattern = Regex("\u001b\\]633;[^\\u0007]*\\u0007") + result = markerPattern.replace(result, "") + + return result + } + + /** + * Dispose resources + */ + fun dispose() { + scope.cancel() + synchronized(listeners) { + listeners.clear() + } + } + + /** + * VSCode Shell Integration marker types + * Reference: https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/common/terminalShellIntegration.ts + */ + private enum class MarkerType(val char: Char) { + // Implemented types + COMMAND_LINE('E'), // Command line content, format: OSC 633 ; E ; [; ] ST + COMMAND_FINISHED('D'), // Command finished, format: OSC 633 ; D [; ] ST + COMMAND_EXECUTED('C'), // Command output started, format: OSC 633 ; C ST + PROPERTY('P'), // Property set, format: OSC 633 ; P ; = ST + + // Prompt related + PROMPT_START('A'), // Prompt start, format: OSC 633 ; A ST + COMMAND_START('B'), // Command input start, format: OSC 633 ; B ST + + // Line continuation related (not completed) + CONTINUATION_START('F'), // Line continuation start, format: OSC 633 ; F ST + CONTINUATION_END('G'), // Line continuation end, format: OSC 633 ; G ST + + // Right prompt related (not completed) + RIGHT_PROMPT_START('H'), // Right prompt start, format: OSC 633 ; H ST + RIGHT_PROMPT_END('I'), // Right prompt end, format: OSC 633 ; I ST + + UNKNOWN('?'); + + companion object { + fun fromChar(char: Char): MarkerType { + return values().find { it.char == char } ?: UNKNOWN + } + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalCommands.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalCommands.kt new file mode 100644 index 0000000000..99737983e8 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalCommands.kt @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.ui.content.Content +import ai.kilocode.jetbrains.actors.MainThreadClipboard +import ai.kilocode.jetbrains.commands.CommandRegistry +import ai.kilocode.jetbrains.commands.ICommand +import org.jetbrains.plugins.terminal.TerminalToolWindowManager + + +/** + * Registers commands related to terminal API operations + * Currently registers the workbench.action.terminal.copySelection command for copying terminal output to clipboard + * + * @param project The current IntelliJ project + * @param registry The command registry to register commands with + */ +fun registerTerminalAPICommands(project: Project, registry: CommandRegistry) { + registry.registerCommand( + object : ICommand { + override fun getId(): String { + return "workbench.action.terminal.copySelection" + } + override fun getMethod(): String { + return "workbench_action_terminal_copySelection" + } + + override fun handler(): Any { + return TerminalAPICommands(project) + } + + override fun returns(): String? { + return "void" + } + } + ) +} + +/** + * Handles terminal API commands for operations like copying terminal output to clipboard + */ +class TerminalAPICommands(val project: Project) { + private val logger = Logger.getInstance(TerminalAPICommands::class.java) + private val clipboard = MainThreadClipboard() + + /** + * Copies the last command output from the current terminal to clipboard + * + * @return null after operation completes + */ + suspend fun workbench_action_terminal_copySelection(): Any? { + logger.info("Copying terminal output to clipboard") + + val textToCopy = try { + getTerminalText() ?: "" + } catch (e: Exception) { + logger.error("Failed to copy terminal output to clipboard", e) + "" + } + + clipboard.writeText(textToCopy) + if (textToCopy.isNotEmpty()) { + logger.info("Successfully copied terminal output to clipboard") + } else { + logger.info("Copied empty terminal output to clipboard") + } + + return null + } + + /** + * Get terminal text content + * + * @return Terminal text content, returns null if failed to get + */ + private fun getTerminalText(): String? { + val window = ToolWindowManager.getInstance(project) + .getToolWindow("Terminal") // or TerminalToolWindowFactory.TOOL_WINDOW_ID + ?: return null + + val selected = window.getContentManager().getSelectedContent() + ?: return null + + val widget = TerminalToolWindowManager.getWidgetByContent(selected) + ?: return null + + return widget.text.takeIf { it.isNotEmpty() } + } + +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstance.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstance.kt new file mode 100644 index 0000000000..19502657b1 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstance.kt @@ -0,0 +1,689 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.terminal.JBTerminalWidget +import com.intellij.terminal.ui.TerminalWidget +import com.pty4j.PtyProcess +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ExtHostTerminalShellIntegrationProxy +import ai.kilocode.jetbrains.ipc.proxy.interfaces.ShellLaunchConfigDto +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner +import org.jetbrains.plugins.terminal.ShellStartupOptions +import org.jetbrains.plugins.terminal.ShellTerminalWidget + +/** + * Terminal instance class + * + * Manages the lifecycle and operations of a single terminal, including: + * - Terminal creation and initialization + * - RPC communication with ExtHost process + * - Shell integration management + * - Terminal show and hide + * - Text sending and command execution + * - Resource cleanup and disposal + * + * @property extHostTerminalId Terminal identifier in ExtHost process + * @property numericId Numeric ID for RPC communication + * @property project IDEA project instance + * @property config Terminal configuration parameters + * @property rpcProtocol RPC protocol instance + */ +class TerminalInstance( + val extHostTerminalId: String, + val numericId: Int, + val project: Project, + private val config: TerminalConfig, + private val rpcProtocol: IRPCProtocol +) : Disposable { + + companion object { + private const val DEFAULT_TERMINAL_NAME = "roo-cline" + private const val TERMINAL_TOOL_WINDOW_ID = "Terminal" + } + + private val logger = Logger.getInstance(TerminalInstance::class.java) + + // Terminal components + private var terminalWidget: TerminalWidget? = null + private var shellWidget: ShellTerminalWidget? = null + + // State management + private val state = TerminalState() + + // Coroutine scope - use IO dispatcher to avoid Main Dispatcher issues + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + // Shell integration manager + private val terminalShellIntegration = TerminalShellIntegration(extHostTerminalId, numericId, rpcProtocol) + + // Event callback manager + private val callbackManager = TerminalCallbackManager() + + /** + * Add terminal close listener + */ + fun addTerminalCloseCallback(callback: () -> Unit) { + callbackManager.addCloseCallback(callback) + } + + /** + * Initialize terminal instance + * + * @throws IllegalStateException if terminal is already initialized or disposed + * @throws Exception if error occurs during initialization + */ + fun initialize() { + state.checkCanInitialize(extHostTerminalId) + + try { + logger.info("🚀 Initializing terminal instance: $extHostTerminalId (numericId: $numericId)") + + // 🎯 First register to project's Disposer to avoid memory leaks + registerToProjectDisposer() + + // Switch to EDT thread for UI operations + ApplicationManager.getApplication().invokeAndWait { + performInitialization() + } + } catch (e: Exception) { + logger.error("❌ Failed to initialize terminal instance: $extHostTerminalId", e) + throw e + } + } + + /** + * Register to project Disposer + */ + private fun registerToProjectDisposer() { + try { + // Register TerminalInstance as a child Disposable of the project + Disposer.register(project, this) + logger.info("✅ Terminal instance registered to project Disposer: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to register terminal instance to project Disposer: $extHostTerminalId", e) + throw e + } + } + + /** + * Perform initialization steps + */ + private fun performInitialization() { + try { + createTerminalWidget() + setupShellIntegration() + finalizeInitialization() + } catch (e: Exception) { + logger.error("❌ Failed to initialize terminal in EDT thread: $extHostTerminalId", e) + throw e + } + } + + /** + * Setup shell integration + */ + private fun setupShellIntegration() { + terminalShellIntegration.setupShellIntegration() + } + + /** + * Finalize initialization + */ + private fun finalizeInitialization() { + state.markInitialized() + logger.info("✅ Terminal instance initialization complete: $extHostTerminalId") + + // 🎯 Add terminalWidget to Terminal tool window + addToTerminalToolWindow() + + notifyTerminalOpened() + notifyShellIntegrationChange() + handleInitialText() + } + + /** + * Handle initial text + */ + private fun handleInitialText() { + config.initialText?.let { initialText -> + sendText(initialText, shouldExecute = false) + } + } + + /** + * Create terminal widget + */ + private fun createTerminalWidget() { + try { + val customRunner = createCustomRunner() + val startupOptions = createStartupOptions() + + logger.info("🚀 Calling startShellTerminalWidget...") + + terminalWidget = customRunner.startShellTerminalWidget( + this, // parent disposable + startupOptions, + false // deferSessionStartUntilUiShown - start session immediately, must be false + ) + + logger.info("✅ startShellTerminalWidget call complete, returned widget: ${terminalWidget?.javaClass?.name}") + + initializeWidgets() + setupTerminalCloseListener() + + logger.info("✅ Terminal widget created successfully") + + } catch (e: Exception) { + logger.error("❌ Failed to create terminal widget", e) + throw e + } + } + + /** + * Create custom runner + */ + private fun createCustomRunner(): LocalTerminalDirectRunner { + return object : LocalTerminalDirectRunner(project) { + override fun createProcess(options: ShellStartupOptions): PtyProcess { + logger.info("🔧 Custom createProcess method called...") + logger.info("Startup options: $options") + + val originalProcess = super.createProcess(options) + logger.info("✅ Original Process created: ${originalProcess.javaClass.name}") + + return createProxyPtyProcess(originalProcess) + } + + override fun createShellTerminalWidget( + parent: Disposable, + startupOptions: ShellStartupOptions + ): TerminalWidget { + logger.info("🔧 Custom createShellTerminalWidget method called...") + return super.createShellTerminalWidget(parent, startupOptions) + } + + override fun configureStartupOptions(baseOptions: ShellStartupOptions): ShellStartupOptions { + logger.info("🔧 Custom configureStartupOptions method called...") + return super.configureStartupOptions(baseOptions) + } + } + } + + /** + * Create startup options + */ + private fun createStartupOptions(): ShellStartupOptions { + val fullShellCommand = buildShellCommand() + + logger.info("🔧 Shell config: shellPath=${config.shellPath}, shellArgs=${config.shellArgs}") + logger.info("🔧 Full shell command: $fullShellCommand") + + return ShellStartupOptions.Builder() + .workingDirectory(config.cwd ?: project.basePath) + .shellCommand(fullShellCommand) + .build() + } + + /** + * Build shell command + */ + private fun buildShellCommand(): List? { + return buildList { + config.shellPath?.let { add(it) } + config.shellArgs?.let { addAll(it) } + }.takeIf { it.isNotEmpty() } + } + + /** + * Initialize widget components + */ + private fun initializeWidgets() { + shellWidget = JBTerminalWidget.asJediTermWidget(terminalWidget!!) as? ShellTerminalWidget + ?: throw IllegalStateException("Cannot get ShellTerminalWidget") + + // Set terminal title + terminalWidget!!.terminalTitle.change { + userDefinedTitle = config.name ?: DEFAULT_TERMINAL_NAME + } + } + + /** + * Set terminal close event listener + */ + private fun setupTerminalCloseListener() { + try { + Disposer.register(terminalWidget!!) { + logger.info("🔔 TerminalWidget dispose event: $extHostTerminalId") + if (!state.isDisposed) { + onTerminalClosed() + } + } + } catch (e: Exception) { + logger.error("❌ Failed to set terminal close event listener: $extHostTerminalId", e) + } + } + + /** + * Create proxy PtyProcess to intercept input/output streams + */ + private fun createProxyPtyProcess(originalProcess: PtyProcess): PtyProcess { + logger.info("🔧 Creating proxy PtyProcess to intercept input/output streams...") + + val rawDataCallback = createRawDataCallback() + return ProxyPtyProcess(originalProcess, rawDataCallback) + } + + /** + * Create raw data callback handler + */ + private fun createRawDataCallback(): ProxyPtyProcessCallback { + return object : ProxyPtyProcessCallback { + override fun onRawData(data: String, streamType: String) { + logger.debug("📥 Raw data [$streamType]: ${data.length} chars") + + try { + sendRawDataToExtHost(data) + terminalShellIntegration.appendRawOutput(data) + } catch (e: Exception) { + logger.error("❌ Failed to process raw data (terminal: $extHostTerminalId)", e) + } + } + } + } + + /** + * Send raw data to ExtHost + */ + private fun sendRawDataToExtHost(data: String) { + val extHostTerminalServiceProxy = + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostTerminalService) + extHostTerminalServiceProxy.acceptTerminalProcessData( + id = numericId, + data = data + ) + logger.debug("✅ Sent raw data to exthost: ${data.length} chars (terminal: $extHostTerminalId)") + } + + /** + * Show terminal + */ + fun show(preserveFocus: Boolean = false) { + if (!state.canOperate()) { + logger.warn("Terminal not initialized or disposed, cannot show: $extHostTerminalId") + return + } + + ApplicationManager.getApplication().invokeLater { + try { + showTerminalToolWindow() + shellWidget?.show(preserveFocus) + logger.info("✅ Terminal shown: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to show terminal: $extHostTerminalId", e) + } + } + } + + /** + * Hide terminal + */ + fun hide() { + if (!state.canOperate()) { + logger.warn("Terminal not initialized or disposed, cannot hide: $extHostTerminalId") + return + } + + ApplicationManager.getApplication().invokeLater { + try { + hideTerminalToolWindow() + shellWidget?.hide() + logger.info("✅ Terminal hidden: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to hide terminal: $extHostTerminalId", e) + } + } + } + + /** + * Show terminal tool window and activate current terminal tab + */ + private fun showTerminalToolWindow() { + try { + val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(TERMINAL_TOOL_WINDOW_ID) + toolWindow?.show(null) + } catch (e: Exception) { + logger.error("❌ Failed to show terminal tool window", e) + } + } + + /** + * Add terminalWidget to Terminal tool window + */ + private fun addToTerminalToolWindow() { + if (terminalWidget == null) { + logger.warn("TerminalWidget is null, cannot add to tool window") + return + } + + try { + val terminalToolWindowManager = org.jetbrains.plugins.terminal.TerminalToolWindowManager.getInstance(project) + val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(TERMINAL_TOOL_WINDOW_ID) + + if (toolWindow == null) { + logger.warn("Terminal tool window does not exist") + return + } + + // Use TerminalToolWindowManager's newTab method to create new Content + val content = terminalToolWindowManager.newTab(toolWindow, terminalWidget!!) + content.displayName = config.name ?: DEFAULT_TERMINAL_NAME + + logger.info("✅ Added terminalWidget to Terminal tool window: ${content.displayName}") + } catch (e: Exception) { + logger.error("❌ Failed to add terminalWidget to tool window", e) + } + } + + /** + * Hide terminal tool window + */ + private fun hideTerminalToolWindow() { + val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(TERMINAL_TOOL_WINDOW_ID) + toolWindow?.hide(null) + } + + /** + * Send text to terminal + */ + fun sendText(text: String, shouldExecute: Boolean = false) { + if (!state.canOperate()) { + logger.warn("Terminal not initialized or disposed, cannot send text: $extHostTerminalId") + return + } + + ApplicationManager.getApplication().invokeLater { + try { + val shell = shellWidget ?: return@invokeLater + + if (shouldExecute) { + shell.executeCommand(text) + logger.info("✅ Command executed: $text (terminal: $extHostTerminalId)") + } else { + shell.writePlainMessage(text) + logger.info("✅ Text sent: $text (terminal: $extHostTerminalId)") + } + } catch (e: Exception) { + logger.error("❌ Failed to send text: $extHostTerminalId", e) + } + } + } + + /** + * Notify exthost process that terminal is opened + */ + private fun notifyTerminalOpened() { + try { + logger.info("📤 Notify exthost process terminal opened: $extHostTerminalId (numericId: $numericId)") + + val shellLaunchConfigDto = config.toShellLaunchConfigDto(project.basePath) + val extHostTerminalServiceProxy = + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostTerminalService) + + extHostTerminalServiceProxy.acceptTerminalOpened( + id = numericId, + extHostTerminalId = extHostTerminalId, + name = config.name ?: DEFAULT_TERMINAL_NAME, + shellLaunchConfig = shellLaunchConfigDto + ) + + logger.info("✅ Successfully notified exthost process terminal opened: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to notify exthost process terminal opened: $extHostTerminalId", e) + } + } + + /** + * Notify Shell integration change + */ + private fun notifyShellIntegrationChange() { + try { + val extHostTerminalShellIntegrationProxy = + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostTerminalShellIntegration) + + extHostTerminalShellIntegrationProxy.shellIntegrationChange(instanceId = numericId) + logger.info("✅ Notified exthost Shell integration initialized: (terminal: $extHostTerminalId)") + + notifyEnvironmentVariableChange(extHostTerminalShellIntegrationProxy) + } catch (e: Exception) { + logger.error("❌ Failed to notify exthost Shell integration initialized: (terminal: $extHostTerminalId)", e) + } + } + + /** + * Notify environment variable change + */ + private fun notifyEnvironmentVariableChange(extHostTerminalShellIntegrationProxy: ExtHostTerminalShellIntegrationProxy) { + config.env?.takeIf { it.isNotEmpty() }?.let { env -> + try { + val envKeys = env.keys.toTypedArray() + val envValues = env.values.toTypedArray() + + extHostTerminalShellIntegrationProxy.shellEnvChange( + instanceId = numericId, + shellEnvKeys = envKeys, + shellEnvValues = envValues, + isTrusted = true + ) + + logger.info("✅ Notified exthost environment variable change: ${env.size} variables (terminal: $extHostTerminalId)") + } catch (e: Exception) { + logger.error("❌ Failed to notify environment variable change: (terminal: $extHostTerminalId)", e) + } + } + } + + /** + * Trigger terminal close event + */ + private fun onTerminalClosed() { + logger.info("🔔 Terminal closed event triggered: $extHostTerminalId (numericId: $numericId)") + + try { + notifyTerminalClosed() + callbackManager.executeCloseCallbacks() + + if (!state.isDisposed) { + dispose() + } + } catch (e: Exception) { + logger.error("Failed to handle terminal closed event: $extHostTerminalId", e) + } + } + + /** + * Notify exthost process that terminal is closed + */ + private fun notifyTerminalClosed() { + try { + logger.info("📤 Notify exthost process terminal closed: $extHostTerminalId (numericId: $numericId)") + + val extHostTerminalServiceProxy = + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostTerminalService) + extHostTerminalServiceProxy.acceptTerminalClosed( + id = numericId, + exitCode = null, + exitReason = numericId + ) + + logger.info("✅ Successfully notified exthost process terminal closed: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to notify exthost process terminal closed: $extHostTerminalId", e) + } + } + + override fun dispose() { + if (state.isDisposed) return + + logger.info("🧹 Disposing terminal instance: $extHostTerminalId") + + try { + // 🎯 Mark as disposed first to avoid repeated calls in callbacks + state.markDisposed() + + callbackManager.clear() + scope.cancel() + + // 🎯 Dispose terminalWidget, onTerminalClosed callback will be skipped since state.isDisposed=true + terminalWidget?.let { widget -> + try { + Disposer.dispose(widget) + } catch (e: Exception) { + logger.error("❌ Failed to dispose terminalWidget: $extHostTerminalId", e) + } + } + + terminalShellIntegration.dispose() + cleanupResources() + + logger.info("✅ Terminal instance disposed: $extHostTerminalId") + } catch (e: Exception) { + logger.error("❌ Failed to dispose terminal instance: $extHostTerminalId", e) + } + } + + /** + * Cleanup resources + */ + private fun cleanupResources() { + terminalWidget = null + shellWidget = null + } +} + +/** + * Terminal configuration data class + */ +data class TerminalConfig( + val name: String? = null, + val shellPath: String? = null, + val shellArgs: List? = null, + val cwd: String? = null, + val env: Map? = null, + val useShellEnvironment: Boolean? = null, + val hideFromUser: Boolean? = null, + val isFeatureTerminal: Boolean? = null, + val forceShellIntegration: Boolean? = null, + val initialText: String? = null +) { + companion object { + /** + * Create TerminalConfig from Map + */ + fun fromMap(config: Map): TerminalConfig { + return TerminalConfig( + name = config["name"] as? String, + shellPath = config["shellPath"] as? String, + shellArgs = config["shellArgs"] as? List, + cwd = config["cwd"] as? String, + env = config["env"] as? Map, + useShellEnvironment = config["useShellEnvironment"] as? Boolean, + hideFromUser = config["hideFromUser"] as? Boolean, + isFeatureTerminal = config["isFeatureTerminal"] as? Boolean, + forceShellIntegration = config["forceShellIntegration"] as? Boolean, + initialText = config["initialText"] as? String + ) + } + } + + /** + * Convert to ShellLaunchConfigDto + */ + fun toShellLaunchConfigDto(defaultCwd: String?): ShellLaunchConfigDto { + return ShellLaunchConfigDto( + name = name, + executable = shellPath, + args = shellArgs, + cwd = cwd ?: defaultCwd, + env = env, + useShellEnvironment = useShellEnvironment, + hideFromUser = hideFromUser, + reconnectionProperties = null, + type = null, + isFeatureTerminal = isFeatureTerminal, + tabActions = null, + shellIntegrationEnvironmentReporting = forceShellIntegration + ) + } +} + +/** + * Terminal state manager + */ +private class TerminalState { + @Volatile + private var isInitialized = false + + @Volatile + private var _isDisposed = false + + val isDisposed: Boolean get() = _isDisposed + + fun checkCanInitialize(terminalId: String) { + if (isInitialized || _isDisposed) { + throw IllegalStateException("Terminal instance already initialized or disposed: $terminalId") + } + } + + fun markInitialized() { + isInitialized = true + } + + fun markDisposed() { + _isDisposed = true + } + + fun canOperate(): Boolean { + return isInitialized && !_isDisposed + } +} + +/** + * Terminal callback manager + */ +private class TerminalCallbackManager { + private val logger = Logger.getInstance(TerminalCallbackManager::class.java) + private val terminalCloseCallbacks = mutableListOf<() -> Unit>() + + fun addCloseCallback(callback: () -> Unit) { + terminalCloseCallbacks.add(callback) + } + + fun executeCloseCallbacks() { + terminalCloseCallbacks.forEach { callback -> + try { + callback() + } catch (e: Exception) { + logger.error("Failed to execute terminal close callback", e) + } + } + } + + fun clear() { + terminalCloseCallbacks.clear() + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstanceManager.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstanceManager.kt new file mode 100644 index 0000000000..05e0426656 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalInstanceManager.kt @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger +import java.io.File +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger + +/** + * Terminal instance manager + * Responsible for managing the lifecycle and mapping of all terminal instances + * Avoids circular dependencies between different services + */ +@Service(Service.Level.PROJECT) +class TerminalInstanceManager : Disposable { + private val logger = Logger.getInstance(TerminalInstanceManager::class.java) + + // Terminal instance management + private val terminals = ConcurrentHashMap() + private val terminalsByNumericId = ConcurrentHashMap() + private val nextNumericId = AtomicInteger(1) + + /** + * Allocate a new numeric ID + */ + fun allocateNumericId(): Int { + return nextNumericId.getAndIncrement() + } + + /** + * Register terminal instance + */ + fun registerTerminal(extHostTerminalId: String, terminalInstance: TerminalInstance) { + terminals[extHostTerminalId] = terminalInstance + terminalsByNumericId[terminalInstance.numericId] = terminalInstance + + // 🎯 Add terminal close event listener for automatic cleanup + terminalInstance.addTerminalCloseCallback { + logger.info("🔔 Received terminal close event callback: $extHostTerminalId") + + // Automatically remove terminal instance from manager + unregisterTerminal(extHostTerminalId) + + // Additional cleanup logic can be added here + // e.g., save terminal state, clean up related resources, etc. + } + + logger.info("📝 Registered terminal instance: $extHostTerminalId (numericId: ${terminalInstance.numericId})") + } + + /** + * Unregister terminal instance + */ + fun unregisterTerminal(extHostTerminalId: String): TerminalInstance? { + val terminalInstance = terminals.remove(extHostTerminalId) + if (terminalInstance != null) { + terminalsByNumericId.remove(terminalInstance.numericId) + logger.info("🗑️ Unregistered terminal instance: $extHostTerminalId (numericId: ${terminalInstance.numericId})") + } + return terminalInstance + } + + /** + * Get terminal instance (by string ID) + */ + fun getTerminalInstance(id: String): TerminalInstance? { + return terminals[id] + } + + /** + * Get terminal instance (by numeric ID) + */ + fun getTerminalInstance(numericId: Int): TerminalInstance? { + return terminalsByNumericId[numericId] + } + + /** + * Get all terminal instances + */ + fun getAllTerminals(): Collection { + return terminals.values + } + + /** + * Check if terminal exists + */ + fun containsTerminal(extHostTerminalId: String): Boolean { + return terminals.containsKey(extHostTerminalId) + } + + /** + * Get terminal count + */ + fun getTerminalCount(): Int { + return terminals.size + } + + /** + * Get all terminal IDs + */ + fun getAllTerminalIds(): Set { + return terminals.keys.toSet() + } + + /** + * Get all numeric IDs + */ + fun getAllNumericIds(): Set { + return terminalsByNumericId.keys.toSet() + } + + override fun dispose() { + logger.info("🧹 Disposing terminal instance manager") + + try { + // Dispose all terminal instances + val terminalList = terminals.values.toList() + terminals.clear() + terminalsByNumericId.clear() + + terminalList.forEach { terminal -> + try { + terminal.dispose() + } catch (e: Exception) { + logger.error("Failed to dispose terminal instance: ${terminal.extHostTerminalId}", e) + } + } + + logger.info("✅ Terminal instance manager disposed") + + } catch (e: Exception) { + logger.error("❌ Failed to dispose terminal instance manager", e) + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalShellIntegration.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalShellIntegration.kt new file mode 100644 index 0000000000..a0124eb7f2 --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/TerminalShellIntegration.kt @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.diagnostic.Logger +import ai.kilocode.jetbrains.ipc.proxy.IRPCProtocol +import ai.kilocode.jetbrains.core.ServiceProxyRegistry +import ai.kilocode.jetbrains.util.URI + +/** + * Terminal shell integration manager + * Responsible for handling the lifecycle management of terminal shell command execution and RPC communication with ExtHost + * + * @param extHostTerminalId ExtHost terminal ID + * @param numericId Numeric terminal ID + * @param rpcProtocol RPC protocol instance + */ +class TerminalShellIntegration( + private val extHostTerminalId: String, + private val numericId: Int, + private val rpcProtocol: IRPCProtocol +) { + + companion object { + private const val HIGH_CONFIDENCE = 2 + private const val DEFAULT_EXIT_CODE = 0 + private const val LOG_PREFIX_SETUP = "🔧" + private const val LOG_PREFIX_START = "🚀" + private const val LOG_PREFIX_END = "🏁" + private const val LOG_PREFIX_DATA = "✨" + private const val LOG_PREFIX_CWD = "📁" + private const val LOG_PREFIX_SUCCESS = "✅" + private const val LOG_PREFIX_ERROR = "❌" + private const val LOG_PREFIX_DISPOSE = "🧹" + } + + private val logger = Logger.getInstance(TerminalShellIntegration::class.java) + private var shellIntegrationState: ShellIntegrationOutputState? = null + private var shellEventListener: ShellEventListener? = null + + /** + * Lazy delegate for getting ExtHost terminal shell integration proxy + */ + private val extHostProxy by lazy { + rpcProtocol.getProxy(ServiceProxyRegistry.ExtHostContext.ExtHostTerminalShellIntegration) + } + + /** + * Setup shell integration + * Initialize shell event listener and state manager + */ + fun setupShellIntegration() { + runCatching { + logger.info("$LOG_PREFIX_SETUP Setting up shell integration (terminal: $extHostTerminalId)...") + + initializeShellEventListener() + initializeShellIntegrationState() + + logger.info("$LOG_PREFIX_SUCCESS Shell integration setup complete (terminal: $extHostTerminalId)") + }.onFailure { exception -> + logger.error("$LOG_PREFIX_ERROR Failed to setup shell integration (terminal: $extHostTerminalId)", exception) + } + } + + /** + * Dispose shell integration and release related resources + */ + fun dispose() { + logger.info("$LOG_PREFIX_DISPOSE Disposing shell integration: $extHostTerminalId") + + runCatching { + shellIntegrationState?.apply { + terminate() + dispose() + } + shellEventListener = null + shellIntegrationState = null + + logger.info("$LOG_PREFIX_SUCCESS Shell integration disposed: $extHostTerminalId") + }.onFailure { exception -> + logger.error("$LOG_PREFIX_ERROR Failed to dispose shell integration: $extHostTerminalId", exception) + } + } + + /** + * Append raw output data + * @param data Output data + */ + fun appendRawOutput(data: String) { + shellIntegrationState?.appendRawOutput(data) + } + + /** + * Initialize shell event listener + */ + private fun initializeShellEventListener() { + shellEventListener = TerminalShellEventListener() + } + + /** + * Initialize shell integration state manager + */ + private fun initializeShellIntegrationState() { + shellIntegrationState = ShellIntegrationOutputState().apply { + shellEventListener?.let { addListener(it) } + } + } + + /** + * Helper function to safely execute RPC calls + * @param operation Operation name for logging + * @param action RPC operation + */ + private inline fun safeRpcCall(operation: String, action: () -> Unit) { + runCatching { + action() + logger.debug("$LOG_PREFIX_SUCCESS $operation succeeded (terminal: $extHostTerminalId)") + }.onFailure { exception -> + logger.error("$LOG_PREFIX_ERROR $operation failed (terminal: $extHostTerminalId)", exception) + } + } + + /** + * Inner class for terminal shell event listener + * Handles various shell command execution events + */ + private inner class TerminalShellEventListener : ShellEventListener { + + override fun onShellExecutionStart(commandLine: String, cwd: String) { + logger.info("$LOG_PREFIX_START Command execution started: '$commandLine' in directory '$cwd' (terminal: $extHostTerminalId)") + + safeRpcCall("Notify ExtHost command start") { + extHostProxy.shellExecutionStart( + instanceId = numericId, + commandLineValue = commandLine, + commandLineConfidence = HIGH_CONFIDENCE, + isTrusted = true, + cwd = URI.file(cwd) + ) + } + } + + override fun onShellExecutionEnd(commandLine: String, exitCode: Int?) { + val actualExitCode = exitCode ?: DEFAULT_EXIT_CODE + logger.info("$LOG_PREFIX_END Command execution finished: '$commandLine' (exit code: $actualExitCode) (terminal: $extHostTerminalId)") + + safeRpcCall("Notify ExtHost command end") { + extHostProxy.shellExecutionEnd( + instanceId = numericId, + commandLineValue = commandLine, + commandLineConfidence = HIGH_CONFIDENCE, + isTrusted = true, + exitCode = actualExitCode + ) + } + } + + override fun onShellExecutionData(data: String) { + logger.debug("$LOG_PREFIX_DATA Clean output data: ${data.length} chars (terminal: $extHostTerminalId)") + + safeRpcCall("Send shellExecutionData") { + extHostProxy.shellExecutionData( + instanceId = numericId, + data = data + ) + } + } + + override fun onCwdChange(cwd: String) { + logger.info("$LOG_PREFIX_CWD Working directory changed to: '$cwd' (terminal: $extHostTerminalId)") + + safeRpcCall("Notify ExtHost directory change") { + extHostProxy.cwdChange( + instanceId = numericId, + cwd = URI.file(cwd) + ) + } + } + } +} \ No newline at end of file diff --git a/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/WeCoderTerminalCustomizer.kt b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/WeCoderTerminalCustomizer.kt new file mode 100644 index 0000000000..286a07f2cf --- /dev/null +++ b/jetbrains/plugin/src/main/kotlin/ai/kilocode/jetbrains/terminal/WeCoderTerminalCustomizer.kt @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 Weibo, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package ai.kilocode.jetbrains.terminal + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import org.jetbrains.plugins.terminal.LocalTerminalCustomizer +import java.io.File +import java.nio.file.Paths +import java.util.concurrent.atomic.AtomicBoolean + +class WeCoderTerminalCustomizer : LocalTerminalCustomizer() { + + private val logger = Logger.getInstance(WeCoderTerminalCustomizer::class.java) + + // Mark file copy status + private val filesCopied = AtomicBoolean(false) + + // Get the base directory for shell integration files - use user home directory for cross-platform compatibility + private val shellIntegrationBaseDir: String by lazy { + val userHome = System.getProperty("user.home") + Paths.get(userHome, ".roo-cline-shell-integrations").toString() + } + + init { + // Asynchronously copy shell integration files during class initialization + copyShellIntegrationFiles() + } + + /** + * Asynchronously copy shell integration files to user home directory + */ + private fun copyShellIntegrationFiles() { + if (filesCopied.get()) { + return // Already copied + } + + // Use IDEA's background thread pool to execute asynchronously + ApplicationManager.getApplication().executeOnPooledThread { + if (!filesCopied.compareAndSet(false, true)) { + return@executeOnPooledThread // Prevent duplicate copy + } + + try { + logger.info("🚀 Start async copy of shell integration files to user home...") + + // Define shell integration configs to copy + val shellConfigs = mapOf( + "vscode-zsh" to listOf(".zshrc", ".zshenv"), + "vscode-bash" to listOf("bashrc"), + "vscode-powershell" to listOf("profile.ps1", "diagnose.ps1") + ) + + // Copy integration files for each shell + shellConfigs.forEach { (shellType, files) -> + val sourceDir = "roo-cline-shell-integrations/$shellType" + val targetDir = Paths.get(shellIntegrationBaseDir, shellType).toString() + + // Create target directory + val targetDirFile = File(targetDir) + if (!targetDirFile.exists()) { + targetDirFile.mkdirs() + logger.info("📁 Created $shellType target directory: $targetDir") + } + + // Copy files + files.forEach { fileName -> + val inputStream = javaClass.classLoader.getResourceAsStream("$sourceDir/$fileName") + if (inputStream != null) { + val targetFile = File("$targetDir/$fileName") + targetFile.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + // Set executable permission + targetFile.setExecutable(true, true) + logger.info("✅ Successfully copied $shellType file: $fileName") + } else { + logger.warn("⚠️ Cannot find $shellType source file: $fileName") + } + } + } + + logger.info("✅ Shell integration files async copy complete") + + } catch (e: Exception) { + logger.error("❌ Failed to async copy shell integration files", e) + filesCopied.set(false) // Copy failed, reset state to allow retry + } + } + } + + + override fun customizeCommandAndEnvironment( + project: Project, + workingDirectory: String?, + command: Array, + envs: MutableMap + ): Array { + + // Print debug logs + logger.info("🔧 WeCodeTerminalCustomizer - customize terminal command and environment") + logger.info("📂 Working directory: $workingDirectory") + logger.info("🔨 Command: ${command.joinToString(" ")}") + logger.info("🌍 Environment variables: ${envs.entries.joinToString("\n")}") + + // Inject VSCode shell integration script + return injectVSCodeScript(command, envs) + } + + private fun injectVSCodeScript(command: Array, envs: MutableMap): Array { + val shellName = File(command[0]).name + val scriptPath = getVSCodeScript(shellName) ?: run { + logger.warn("🚫 No integration script found for Shell($shellName)") + return command + } + + logger.info("🔧 Injecting Shell Integration script: $scriptPath") + logger.info("🐚 Shell type: $shellName") + + // Set general injection flag + envs["VSCODE_INJECTION"] = "1" + + return when (shellName) { + "bash", "sh" -> injectBashScript(command, envs, scriptPath) + "zsh" -> injectZshScript(command, envs, scriptPath) + "powershell", "pwsh", "powershell.exe" -> injectPowerShellScript(command, envs, scriptPath) + else -> { + logger.warn("⚠️ Unsupported shell type: $shellName") + command + } + } + } + + /** + * Inject VSCode integration script for Bash/Sh + */ + private fun injectBashScript(command: Array, envs: MutableMap, scriptPath: String): Array { + val rcfileIndex = command.indexOf("--rcfile") + + return if (rcfileIndex != -1 && rcfileIndex + 1 < command.size) { + // If --rcfile parameter already exists, save the original rcfile path + val originalRcfile = command[rcfileIndex + 1] + logger.info("🔧 Detected existing --rcfile parameter: $originalRcfile") + + // Save the original rcfile path to environment variable for script use + envs["ORIGINAL_BASH_RCFILE"] = originalRcfile + + // Replace the existing --rcfile parameter value + val newCommand = command.clone() + newCommand[rcfileIndex + 1] = scriptPath + logger.info("🔧 Replaced --rcfile parameter with: $scriptPath") + newCommand + } else { + // If --rcfile parameter does not exist, add new parameter + logger.info("🔧 Added new --rcfile parameter: $scriptPath") + arrayOf(command[0], "--rcfile", scriptPath) + command.drop(1) + } + } + + /** + * Inject VSCode integration script for Zsh + */ + private fun injectZshScript(command: Array, envs: MutableMap, scriptPath: String): Array { + // Save user's original ZDOTDIR environment variable + val userZdotdir = envs["ZDOTDIR"] + ?: System.getenv("ZDOTDIR") + ?: System.getProperty("user.home") + + envs["USER_ZDOTDIR"] = userZdotdir + envs["ZDOTDIR"] = scriptPath + + logger.info("🔧 Saved original ZDOTDIR: $userZdotdir, set new ZDOTDIR: $scriptPath") + return command + } + + /** + * Inject VSCode integration script for PowerShell + */ + private fun injectPowerShellScript(command: Array, envs: MutableMap, scriptPath: String): Array { + logger.info("🔧 Inject PowerShell script: $scriptPath") + + // Add debug info environment variables + //envs["WECODER_SHELL_INTEGRATION"] = "1" + //envs["WECODER_SCRIPT_PATH"] = scriptPath + + // Set environment variables required for PowerShell shell integration + envs["VSCODE_NONCE"] = generateNonce() + envs["VSCODE_SHELL_ENV_REPORTING"] = "1" + envs["VSCODE_STABLE"] = "1" // Mark as stable version + + logger.info("🔧 Set PowerShell environment variables: VSCODE_NONCE=${envs["VSCODE_NONCE"]}") + + // Find existing -File parameter position + val fileIndex = command.indexOf("-File") + + return if (fileIndex != -1 && fileIndex + 1 < command.size) { + // If -File parameter already exists, save the original script path + val originalScript = command[fileIndex + 1] + logger.info("🔧 Detected existing -File parameter: $originalScript") + + // Save the original script path to environment variable for script use + envs["ORIGINAL_POWERSHELL_SCRIPT"] = originalScript + + // Replace the existing -File parameter value + val newCommand = command.clone() + newCommand[fileIndex + 1] = scriptPath + logger.info("🔧 Replace -File parameter with: $scriptPath") + newCommand + } else { + // If -File parameter does not exist, add parameter in IDEA default format + // Default format: powershell.exe -NoExit -ExecutionPolicy Bypass -File