diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3ec839df4ef..f121b855a0d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -140,6 +140,7 @@ function f(x: number, y: string): void { } - Do not duplicate code. Always look for existing utility functions, helpers, or patterns in the codebase before implementing new functionality. Reuse and extend existing code whenever possible. - You MUST deal with disposables by registering them immediately after creation for later disposal. Use helpers such as `DisposableStore`, `MutableDisposable` or `DisposableMap`. Do NOT register a disposable to the containing class if the object is created within a method that is called repeadedly to avoid leaks. Instead, return a `IDisposable` from such method and let the caller register it. - You MUST NOT use storage keys of another component only to make changes to that component. You MUST come up with proper API to change another component. +- Use `IEditorService` to open editors instead of `IEditorGroupsService.activeGroup.openEditor` to ensure that the editor opening logic is properly followed and to avoid bypassing important features such as `revealIfOpened` or `preserveFocus`. ## Learnings - Minimize the amount of assertions in tests. Prefer one snapshot-style `assert.deepStrictEqual` over multiple precise assertions, as they are much more difficult to understand and to update. diff --git a/.github/skills/agent-sessions-layout/SKILL.md b/.github/skills/agent-sessions-layout/SKILL.md index f818e2dc75e..a76794d9c7d 100644 --- a/.github/skills/agent-sessions-layout/SKILL.md +++ b/.github/skills/agent-sessions-layout/SKILL.md @@ -61,7 +61,6 @@ When proposing or implementing changes, follow these rules from the spec: | `sessions/browser/style.css` | Layout-specific styles | | `sessions/browser/parts/` | Agent session part implementations | | `sessions/browser/parts/titlebarPart.ts` | Titlebar part, MainTitlebarPart, AuxiliaryTitlebarPart, TitleService | -| `sessions/browser/parts/editorModal.ts` | Editor modal overlay | | `sessions/browser/parts/sidebarPart.ts` | Sidebar part (with footer) | | `sessions/browser/parts/chatBarPart.ts` | Chat Bar part | | `sessions/browser/widget/` | Agent sessions chat widget | @@ -76,5 +75,5 @@ After modifying layout code: 1. Verify the build compiles without errors via the `VS Code - Build` task 2. Ensure the grid structure matches the spec's visual representation 3. Confirm part visibility toggling works correctly (show/hide/maximize) -4. Test the editor modal opens/closes properly on editor events +4. Test that editors open in the `ModalEditorPart` overlay and that it closes properly 5. Verify sidebar footer renders with account widget diff --git a/.github/skills/sessions/SKILL.md b/.github/skills/sessions/SKILL.md index 010c3779a72..4889632066b 100644 --- a/.github/skills/sessions/SKILL.md +++ b/.github/skills/sessions/SKILL.md @@ -84,7 +84,6 @@ src/vs/sessions/ │ ├── auxiliaryBarPart.ts # Auxiliary Bar (with run script dropdown) │ ├── panelPart.ts # Panel (terminal, output, etc.) │ ├── projectBarPart.ts # Project bar (folder entries) -│ ├── editorModal.ts # Editor modal overlay │ ├── agentSessionsChatInputPart.ts # Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts # Welcome view (mascot + target buttons + pickers) │ └── media/ # Part CSS files @@ -134,13 +133,13 @@ Use the `agent-sessions-layout` skill for detailed guidance on the layout. Key p | Chat Bar | Visible | Primary chat widget | | Auxiliary Bar | Visible | Changes view, etc. | | Panel | Hidden | Terminal, output | -| Editor | Hidden | Modal overlay, auto-shows on editor open | +| Editor | Hidden | Main part hidden; editors open via `MODAL_GROUP` into `ModalEditorPart` | **Not included:** Activity Bar, Status Bar, Banner. ### 4.3 Editor Modal -Editors appear as modal overlays (80% of workbench, min 400×300, max 1200×900). The modal auto-shows when an editor opens and auto-hides when all editors close. Click backdrop, press Escape, or click X to dismiss. +The main editor part is hidden (`display:none`). All editors open via `MODAL_GROUP` into the standard `ModalEditorPart` overlay (created on-demand by `EditorParts.createModalEditorPart`). The sessions configuration sets `workbench.editor.useModal` to `'on'`, which causes `findGroup()` to redirect all editor opens to the modal. Click backdrop or press Escape to dismiss. ## 5. Chat Widget diff --git a/.github/skills/update-screenshots/SKILL.md b/.github/skills/update-screenshots/SKILL.md new file mode 100644 index 00000000000..f4cac151d61 --- /dev/null +++ b/.github/skills/update-screenshots/SKILL.md @@ -0,0 +1,87 @@ +--- +name: update-screenshots +description: Download screenshot baselines from the latest CI run and commit them. Use when asked to update, accept, or refresh component screenshot baselines from CI, or after the screenshot-test GitHub Action reports differences. This skill should be run as a subagent. +--- + +# Update Component Screenshots from CI + +When asked to update, accept, or refresh screenshot baselines from CI — or when the `Screenshot Tests` GitHub Action has failed with screenshot differences — follow this procedure to download the CI-generated screenshots and commit them as the new baselines. + +## Why CI Screenshots? + +Screenshots captured locally may differ from CI due to platform differences (fonts, rendering, DPI). The CI (Linux, ubuntu-latest) is the source of truth. This skill downloads the CI-produced screenshots and commits them as baselines. + +## Prerequisites + +- The `gh` CLI must be authenticated (`gh auth status`). +- The `Screenshot Tests` GitHub Action must have run and produced a `screenshot-diff` artifact. + +## Procedure + +### 1. Find the latest screenshot artifact + +If the user provides a specific run ID or PR number, use that. Otherwise, find the latest run: + +```bash +# For a specific PR: +gh run list --workflow screenshot-test.yml --branch --limit 5 --json databaseId,status,conclusion,headBranch + +# For the current branch: +gh run list --workflow screenshot-test.yml --branch $(git branch --show-current) --limit 5 --json databaseId,status,conclusion +``` + +Pick the most recent run that has a `screenshot-diff` artifact (runs where screenshots matched won't have one). + +### 2. Download the artifact + +```bash +gh run download --name screenshot-diff --dir .tmp/screenshot-diff +``` + +This downloads: +- `test/componentFixtures/.screenshots/current/` — the CI-captured screenshots +- `test/componentFixtures/.screenshots/report.json` — structured diff report +- `test/componentFixtures/.screenshots/report.md` — human-readable diff report + +### 3. Review the changes + +Show the user what changed by reading the markdown report: + +```bash +cat .tmp/screenshot-diff/test/componentFixtures/.screenshots/report.md +``` + +### 4. Copy CI screenshots to baseline + +```bash +# Remove old baselines and replace with CI screenshots +rm -rf test/componentFixtures/.screenshots/baseline/ +cp -r .tmp/screenshot-diff/test/componentFixtures/.screenshots/current/ test/componentFixtures/.screenshots/baseline/ +``` + +### 5. Clean up + +```bash +rm -rf .tmp/screenshot-diff +``` + +### 6. Stage and commit + +```bash +git add test/componentFixtures/.screenshots/baseline/ +git commit -m "update screenshot baselines from CI" +``` + +### 7. Verify + +Confirm the baselines are updated by listing the files: + +```bash +git diff --stat HEAD~1 +``` + +## Notes + +- If no `screenshot-diff` artifact exists, the screenshots already match the baselines — no update needed. +- The `--filter` option on the CLI can be used to selectively accept only some fixtures if needed. +- After committing updated baselines, the next CI run should pass the screenshot comparison. diff --git a/.github/workflows/screenshot-test.yml b/.github/workflows/screenshot-test.yml new file mode 100644 index 00000000000..e5a91228a14 --- /dev/null +++ b/.github/workflows/screenshot-test.yml @@ -0,0 +1,125 @@ +name: Screenshot Tests + +on: + push: + branches: [main] + pull_request: + branches: + - main + - 'release/*' + +permissions: + contents: read + pull-requests: write + checks: write + +concurrency: + group: screenshots-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + screenshots: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + + - name: Install dependencies + run: npm ci --ignore-scripts + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install build/vite dependencies + run: rm -f package-lock.json && npm install + working-directory: build/vite + + - name: Install Playwright Chromium + run: npx playwright install chromium + + - name: Capture screenshots + run: npx component-explorer screenshot --project ./test/componentFixtures/component-explorer.json + + - name: Compare screenshots + id: compare + run: | + npx component-explorer screenshot:compare \ + --project ./test/componentFixtures \ + --report ./test/componentFixtures/.screenshots/report.json \ + --report-markdown ./test/componentFixtures/.screenshots/report.md + continue-on-error: true + + - name: Upload screenshot report + if: steps.compare.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: screenshot-diff + path: | + test/componentFixtures/.screenshots/current/ + test/componentFixtures/.screenshots/report.json + test/componentFixtures/.screenshots/report.md + + - name: Set check title + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + REPORT="test/componentFixtures/.screenshots/report.json" + if [ -f "$REPORT" ]; then + CHANGED=$(node -e "const r = require('./$REPORT'); console.log(r.summary.added + r.summary.removed + r.summary.changed)") + TITLE="${CHANGED} screenshots changed" + else + TITLE="Screenshots match" + fi + + SHA="${{ github.event.pull_request.head.sha || github.sha }}" + CHECK_RUN_ID=$(gh api "repos/${{ github.repository }}/commits/$SHA/check-runs" \ + --jq '.check_runs[] | select(.name == "screenshots") | .id') + + if [ -n "$CHECK_RUN_ID" ]; then + gh api "repos/${{ github.repository }}/check-runs/$CHECK_RUN_ID" \ + -X PATCH --input - <> $GITHUB_STEP_SUMMARY + else + echo "## Screenshots ✅" >> $GITHUB_STEP_SUMMARY + echo "No visual changes detected." >> $GITHUB_STEP_SUMMARY + fi + + # - name: Post PR comment + # if: github.event_name == 'pull_request' + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # run: | + # COMMENT_MARKER="" + # BODY="$COMMENT_MARKER"$'\n' + # + # if [ -f test/componentFixtures/.screenshots/report.md ]; then + # BODY+=$(cat test/componentFixtures/.screenshots/report.md) + # BODY+=$'\n\n' + # BODY+="📦 [Download the \`screenshot-diff\` artifact](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) to review images." + # else + # BODY+="## Screenshots ✅"$'\n\n' + # BODY+="No visual changes detected." + # fi + # + # # Find existing comment + # EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \ + # --paginate --jq ".[] | select(.body | startswith(\"$COMMENT_MARKER\")) | .id" | head -1) + # + # if [ -n "$EXISTING" ]; then + # gh api "repos/${{ github.repository }}/issues/comments/$EXISTING" -X PATCH -f body="$BODY" + # else + # gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY" + # fi diff --git a/.gitignore b/.gitignore index 3d97a65e027..7359fbeaa22 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ src/vs/base/browser/ui/codicons/codicon/codicon.ttf /out*/ /extensions/**/out/ build/node_modules +build/darwin/.dmgbuild coverage/ test_data/ test-results/ @@ -25,3 +26,5 @@ product.overrides.json .vscode-test vscode-telemetry-docs/ test-output.json +test/componentFixtures/.screenshots/* +!test/componentFixtures/.screenshots/baseline/ diff --git a/.vscode/settings.json b/.vscode/settings.json index f65efbf06ee..5cd09294f01 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -100,10 +100,10 @@ // --- TypeScript --- "typescript.experimental.useTsgo": true, "typescript.tsdk": "node_modules/typescript/lib", - "typescript.preferences.importModuleSpecifier": "relative", - "typescript.preferences.quoteStyle": "single", + "js/ts.preferences.importModuleSpecifier": "relative", + "js/ts.preferences.quoteStyle": "single", "typescript.tsc.autoDetect": "off", - "typescript.preferences.autoImportFileExcludePatterns": [ + "js/ts.preferences.autoImportFileExcludePatterns": [ "@xterm/xterm", "@xterm/headless", "node-pty", @@ -144,6 +144,9 @@ "ts": "warning", "eslint": "warning" }, + "git.worktreeIncludeFiles": [ + "product.overrides.json" + ], // --- GitHub --- "githubPullRequests.experimental.createView": true, "githubPullRequests.assignCreated": "${user}", @@ -199,12 +202,11 @@ "sash" ], // --- Workbench --- - // "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654 "editor.aiStats.enabled": true, // Team selfhosting on ai stats "azureMcp.enabledServices": [ "kusto" // Needed for kusto tool in data.prompt.md ], "azureMcp.serverMode": "all", "azureMcp.readOnly": true, - "debug.breakpointsView.presentation": "tree" + "debug.breakpointsView.presentation": "tree", } diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 88722aecc7a..1921fd456af 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -133,8 +133,6 @@ jobs: - script: | set -e - # Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py - python3 -m pip install pyobjc-framework-Quartz DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg" mkdir -p $DMG_OUT node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index 2854c1f9417..64b91f71401 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -240,8 +240,6 @@ steps: - script: | set -e - # Needed for https://github.com/dmgbuild/dmgbuild/blob/main/src/dmgbuild/badge.py - python3 -m pip install pyobjc-framework-Quartz DMG_OUT="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_dmg" mkdir -p $DMG_OUT node build/darwin/create-dmg.ts $(agent.builddirectory) $DMG_OUT diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index d8fddac7c3d..420fcee4958 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -172,7 +172,7 @@ extends: enabled: true configFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/tsaoptions.json binskim: - analyzeTargetGlob: '+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.exe;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.dll;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.node;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.node;-:file|$(Build.SourcesDirectory)/.build/**/system-setup/VSCodeSetup*.exe;-:file|$(Build.SourcesDirectory)/.build/**/user-setup/VSCodeUserSetup*.exe' + analyzeTargetGlob: '+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.exe;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.dll;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.node;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.exe;-:file|$(Agent.BuildDirectory)/VSCode-*/**/resources/**/*.node;-:file|$(Build.SourcesDirectory)/.build/**/system-setup/VSCodeSetup*.exe;-:file|$(Build.SourcesDirectory)/.build/**/user-setup/VSCodeUserSetup*.exe' codeql: runSourceLanguagesInSourceAnalysis: true compiled: diff --git a/build/darwin/create-dmg.ts b/build/darwin/create-dmg.ts index dcfb8001a8e..82c15affa20 100644 --- a/build/darwin/create-dmg.ts +++ b/build/darwin/create-dmg.ts @@ -10,41 +10,131 @@ import { spawn } from '@malept/cross-spawn-promise'; const root = path.dirname(path.dirname(import.meta.dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); -interface DmgBuildSettings { - title: string; - icon?: string | null; - 'badge-icon'?: string | null; - background?: string; - 'background-color'?: string; - 'icon-size'?: number; - 'text-size'?: number; - format?: string; - window?: { - position?: { x: number; y: number }; - size?: { width: number; height: number }; - }; - contents: Array<{ - path: string; - x: number; - y: number; - type: 'file' | 'link'; - name?: string; - }>; +const DMGBUILD_REPO = 'https://github.com/dmgbuild/dmgbuild.git'; +const DMGBUILD_COMMIT = '75c8a6c7835c5b73dfd4510d92a8f357f93a5fbf'; +const MIN_PYTHON_VERSION = [3, 10]; + +function getDmgBuildPath(): string { + return path.join(import.meta.dirname, '.dmgbuild'); } -function getDmgBuilderPath(): string { - return path.join(import.meta.dirname, '..', 'node_modules', 'dmg-builder'); +function getVenvPath(): string { + return path.join(getDmgBuildPath(), 'venv'); } -function getDmgBuilderVendorPath(): string { - return path.join(getDmgBuilderPath(), 'vendor'); +function getPythonPath(): string { + return path.join(getVenvPath(), 'bin', 'python3'); +} + +async function checkPythonVersion(pythonBin: string): Promise { + try { + const output = await spawn(pythonBin, ['--version']); + const match = output.match(/Python (\d+)\.(\d+)/); + if (match) { + const major = parseInt(match[1], 10); + const minor = parseInt(match[2], 10); + return major > MIN_PYTHON_VERSION[0] || (major === MIN_PYTHON_VERSION[0] && minor >= MIN_PYTHON_VERSION[1]); + } + } catch { + // not available + } + return false; +} + +/** + * Finds a Python binary that meets the minimum version requirement. + * Tries well-known candidates first, and if none are suitable, + * installs Python 3.12 via Homebrew. + */ +async function findSuitablePython(): Promise { + const candidates = [ + 'python3', + 'python3.12', + 'python3.11', + 'python3.10', + // Homebrew paths (Apple Silicon) + '/opt/homebrew/opt/python@3.12/bin/python3', + '/opt/homebrew/opt/python@3.11/bin/python3', + '/opt/homebrew/opt/python@3.10/bin/python3', + // Homebrew paths (Intel) + '/usr/local/opt/python@3.12/bin/python3', + '/usr/local/opt/python@3.11/bin/python3', + '/usr/local/opt/python@3.10/bin/python3', + ]; + + for (const candidate of candidates) { + if (await checkPythonVersion(candidate)) { + console.log(`Found suitable Python: ${candidate}`); + return candidate; + } + } + + console.log(`No Python >= ${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]} found, installing via Homebrew...`); + await spawn('brew', ['install', 'python@3.12'], { + stdio: 'inherit', + env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1', HOMEBREW_NO_INSTALL_CLEANUP: '1' } + }); + + // Use `brew --prefix` to reliably locate the installation + const brewPrefix = (await spawn('brew', ['--prefix', 'python@3.12'])).trim(); + const brewBinDir = path.join(brewPrefix, 'bin'); + console.log(`Homebrew Python prefix: ${brewPrefix}`); + + // Try both python3 and python3.12 (keg-only formulae may only have the versioned name) + for (const name of ['python3', 'python3.12']) { + const fullPath = path.join(brewBinDir, name); + if (await checkPythonVersion(fullPath)) { + console.log(`Using Homebrew Python: ${fullPath}`); + return fullPath; + } + } + + throw new Error(`Could not find Python >= ${MIN_PYTHON_VERSION[0]}.${MIN_PYTHON_VERSION[1]} even after Homebrew install at ${brewPrefix}.`); +} + +async function ensureDmgBuild(): Promise { + const dmgBuildPath = getDmgBuildPath(); + const venvPath = getVenvPath(); + const markerFile = path.join(dmgBuildPath, '.installed'); + if (fs.existsSync(markerFile)) { + console.log('dmgbuild already installed, skipping setup'); + return; + } + + console.log('Setting up dmgbuild from GitHub...'); + if (fs.existsSync(dmgBuildPath)) { + fs.rmSync(dmgBuildPath, { recursive: true }); + } + + console.log(`Cloning dmgbuild from ${DMGBUILD_REPO} at ${DMGBUILD_COMMIT}...`); + await spawn('git', ['clone', DMGBUILD_REPO, dmgBuildPath], { + stdio: 'inherit' + }); + await spawn('git', ['-C', dmgBuildPath, 'checkout', DMGBUILD_COMMIT], { + stdio: 'inherit' + }); + + const pythonBin = await findSuitablePython(); + console.log('Creating Python virtual environment...'); + await spawn(pythonBin, ['-m', 'venv', venvPath], { + stdio: 'inherit' + }); + + console.log('Installing dmgbuild and dependencies into venv...'); + const pipPath = path.join(venvPath, 'bin', 'pip'); + await spawn(pipPath, ['install', dmgBuildPath], { + stdio: 'inherit' + }); + + fs.writeFileSync(markerFile, `Installed at ${new Date().toISOString()}\nCommit: ${DMGBUILD_COMMIT}\n`); + console.log('dmgbuild setup complete'); } async function runDmgBuild(settingsFile: string, volumeName: string, artifactPath: string): Promise { - const vendorDir = getDmgBuilderVendorPath(); - const scriptPath = path.join(vendorDir, 'run_dmgbuild.py'); - await spawn('python3', [scriptPath, '-s', settingsFile, volumeName, artifactPath], { - cwd: vendorDir, + await ensureDmgBuild(); + + const pythonPath = getPythonPath(); + await spawn(pythonPath, ['-m', 'dmgbuild', '-s', settingsFile, volumeName, artifactPath], { stdio: 'inherit' }); } @@ -98,34 +188,17 @@ async function main(buildDir?: string, outDir?: string): Promise { fs.unlinkSync(artifactPath); } - const settings: DmgBuildSettings = { - title, - 'badge-icon': diskIconPath, - background: backgroundPath, - format: 'ULMO', - 'text-size': 12, - window: { - position: { x: 100, y: 400 }, - size: { width: 480, height: 352 } - }, - contents: [ - { - path: appPath, - x: 120, - y: 160, - type: 'file' - }, - { - path: '/Applications', - x: 360, - y: 160, - type: 'link' - } - ] - }; - - const settingsFile = path.join(outDir, '.dmg-settings.json'); - fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2)); + // Copy and process the settings template for dmgbuild + const settingsTemplatePath = path.join(import.meta.dirname, 'dmg-settings.py.template'); + const settingsFile = path.join(outDir, '.dmg-settings.py'); + let settingsContent = fs.readFileSync(settingsTemplatePath, 'utf8'); + settingsContent = settingsContent + .replace('{{VOLUME_NAME}}', JSON.stringify(title)) + .replace('{{BADGE_ICON}}', JSON.stringify(diskIconPath)) + .replace('{{BACKGROUND}}', JSON.stringify(backgroundPath)) + .replace('{{APP_PATH}}', JSON.stringify(appPath)) + .replace('{{APP_NAME}}', JSON.stringify(product.nameLong + '.app')); + fs.writeFileSync(settingsFile, settingsContent); try { await runDmgBuild(settingsFile, dmgName, artifactPath); diff --git a/build/darwin/dmg-settings.py.template b/build/darwin/dmg-settings.py.template new file mode 100644 index 00000000000..4a54a69ab02 --- /dev/null +++ b/build/darwin/dmg-settings.py.template @@ -0,0 +1,38 @@ +# dmgbuild settings template +# Placeholders are replaced at build time + +volume_name = {{VOLUME_NAME}} +format = 'ULMO' +badge_icon = {{BADGE_ICON}} +background = {{BACKGROUND}} + +# Volume size (None = auto-calculate) +size = None + +# Files and symlinks +files = [{{APP_PATH}}] +symlinks = { + 'Applications': '/Applications' +} + +# Window settings +show_status_bar = False +show_tab_view = False +show_toolbar = False +show_pathbar = False +show_sidebar = False +sidebar_width = 180 + +# Window position and size +window_rect = ((100, 400), (480, 352)) + +# Icon view settings +default_view = 'icon-view' +icon_locations = { + {{APP_NAME}}: (120, 160), + 'Applications': (360, 160) +} + +# Text size for icon labels +text_size = 12 +icon_size = 80 diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 31c31f58406..bc34262af5c 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -389,7 +389,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d const isInsiderOrExploration = quality === 'insider' || quality === 'exploration'; const embedded = isInsiderOrExploration - ? (product as typeof product & { embedded?: { nameShort: string; nameLong: string; applicationName: string; dataFolderName: string; darwinBundleIdentifier: string } }).embedded + ? (product as typeof product & { embedded?: { nameShort: string; nameLong: string; applicationName: string; dataFolderName: string; darwinBundleIdentifier: string; urlProtocol: string } }).embedded : undefined; const packageSubJsonStream = isInsiderOrExploration @@ -409,6 +409,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d json.applicationName = embedded.applicationName; json.dataFolderName = embedded.dataFolderName; json.darwinBundleIdentifier = embedded.darwinBundleIdentifier; + json.urlProtocol = embedded.urlProtocol; return json; })) .pipe(rename('product.sub.json')) diff --git a/build/package-lock.json b/build/package-lock.json index 5cf0189d3e6..b78c4c8389a 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -53,7 +53,6 @@ "ansi-colors": "^3.2.3", "byline": "^5.0.0", "debug": "^4.3.2", - "dmg-builder": "26.8.1", "esbuild": "0.27.2", "extract-zip": "^2.0.1", "gulp-merge-json": "^2.1.1", @@ -490,48 +489,6 @@ "node": ">=6.9.0" } }, - "node_modules/@develar/schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/@develar/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@develar/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", @@ -559,136 +516,6 @@ "node": ">= 6" } }, - "node_modules/@electron/fuses": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", - "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.1", - "fs-extra": "^9.0.1", - "minimist": "^1.2.5" - }, - "bin": { - "electron-fuses": "dist/bin.js" - } - }, - "node_modules/@electron/fuses/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@electron/fuses/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@electron/fuses/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@electron/fuses/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@electron/fuses/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/fuses/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/fuses/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/fuses/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/fuses/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/get": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", @@ -710,60 +537,6 @@ "global-agent": "^3.0.0" } }, - "node_modules/@electron/notarize": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", - "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.1", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/notarize/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/notarize/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/notarize/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/osx-sign": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-2.0.0.tgz", @@ -812,143 +585,6 @@ "node": ">=10" } }, - "node_modules/@electron/rebuild": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", - "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "got": "^11.7.0", - "graceful-fs": "^4.2.11", - "node-abi": "^4.2.0", - "node-api-version": "^0.2.1", - "node-gyp": "^11.2.0", - "ora": "^5.1.0", - "read-binary-file-arch": "^1.0.6", - "semver": "^7.3.5", - "tar": "^7.5.6", - "yargs": "^17.0.1" - }, - "bin": { - "electron-rebuild": "lib/cli.js" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/rebuild/node_modules/node-abi": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.26.0.tgz", - "integrity": "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.6.3" - }, - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/@electron/rebuild/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/universal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", - "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@electron/asar": "^3.3.1", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "dir-compare": "^4.2.0", - "fs-extra": "^11.1.1", - "minimatch": "^9.0.3", - "plist": "^3.1.0" - }, - "engines": { - "node": ">=16.4" - } - }, - "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/universal/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -1457,19 +1093,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -1492,61 +1115,6 @@ "node": ">= 12.13.0" } }, - "node_modules/@malept/flatpak-bundler": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", - "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "tmp-promise": "^3.0.2" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1585,67 +1153,6 @@ "node": ">= 8" } }, - "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@secretlint/config-creator": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", @@ -2317,18 +1824,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/plist": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", - "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "xmlbuilder": ">=11.0.1" - } - }, "node_modules/@types/pump": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/pump/-/pump-1.0.1.tgz", @@ -2399,14 +1894,6 @@ "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", "dev": true }, - "node_modules/@types/verror": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", - "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/@types/vinyl": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", @@ -2820,23 +2307,6 @@ "node": ">=10.0.0" } }, - "node_modules/7zip-bin": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", - "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2864,16 +2334,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -2959,283 +2419,6 @@ "node": ">= 8" } }, - "node_modules/app-builder-bin": { - "version": "5.0.0-alpha.12", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", - "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/app-builder-lib": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", - "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@develar/schema-utils": "~2.6.5", - "@electron/asar": "3.4.1", - "@electron/fuses": "^1.8.0", - "@electron/get": "^3.0.0", - "@electron/notarize": "2.5.0", - "@electron/osx-sign": "1.3.3", - "@electron/rebuild": "^4.0.3", - "@electron/universal": "2.0.3", - "@malept/flatpak-bundler": "^0.4.0", - "@types/fs-extra": "9.0.13", - "async-exit-hook": "^2.0.1", - "builder-util": "26.8.1", - "builder-util-runtime": "9.5.1", - "chromium-pickle-js": "^0.2.0", - "ci-info": "4.3.1", - "debug": "^4.3.4", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", - "ejs": "^3.1.8", - "electron-publish": "26.8.1", - "fs-extra": "^10.1.0", - "hosted-git-info": "^4.1.0", - "isbinaryfile": "^5.0.0", - "jiti": "^2.4.2", - "js-yaml": "^4.1.0", - "json5": "^2.2.3", - "lazy-val": "^1.0.5", - "minimatch": "^10.0.3", - "plist": "3.1.0", - "proper-lockfile": "^4.1.2", - "resedit": "^1.7.0", - "semver": "~7.7.3", - "tar": "^7.5.7", - "temp-file": "^3.4.0", - "tiny-async-pool": "1.3.0", - "which": "^5.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "dmg-builder": "26.8.1", - "electron-builder-squirrel-windows": "26.8.1" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", - "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/osx-sign": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", - "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "compare-version": "^0.1.2", - "debug": "^4.3.4", - "fs-extra": "^10.0.0", - "isbinaryfile": "^4.0.8", - "minimist": "^1.2.6", - "plist": "^3.0.5" - }, - "bin": { - "electron-osx-flat": "bin/electron-osx-flat.js", - "electron-osx-sign": "bin/electron-osx-sign.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/@electron/osx-sign/node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/app-builder-lib/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/app-builder-lib/node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/app-builder-lib/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/app-builder-lib/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/app-builder-lib/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/app-builder-lib/node_modules/minimatch": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", - "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/app-builder-lib/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3271,17 +2454,6 @@ "node": ">=0.10.0" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -3301,13 +2473,6 @@ "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", @@ -3323,16 +2488,6 @@ "node": ">= 0.10" } }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3340,16 +2495,6 @@ "dev": true, "license": "MIT" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/azure-devops-node-api": { "version": "12.5.0", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", @@ -3417,6 +2562,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3485,6 +2631,7 @@ "url": "https://feross.org/support" } ], + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3505,186 +2652,6 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builder-util": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", - "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/debug": "^4.1.6", - "7zip-bin": "~5.2.0", - "app-builder-bin": "5.0.0-alpha.12", - "builder-util-runtime": "9.5.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.6", - "debug": "^4.3.4", - "fs-extra": "^10.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "js-yaml": "^4.1.0", - "sanitize-filename": "^1.6.3", - "source-map-support": "^0.5.19", - "stat-mode": "^1.0.0", - "temp-file": "^3.4.0", - "tiny-async-pool": "1.3.0" - } - }, - "node_modules/builder-util-runtime": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", - "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "sax": "^1.2.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/builder-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/builder-util/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/builder-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/builder-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/builder-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/builder-util/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/builder-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/builder-util/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/builder-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/builder-util/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", @@ -3694,128 +2661,6 @@ "node": ">=0.10.0" } }, - "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", - "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3954,220 +2799,6 @@ "dev": true, "optional": true }, - "node_modules/chromium-pickle-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", - "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cli-truncate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cli-truncate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -4284,16 +2915,6 @@ "node": ">=18" } }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4306,17 +2927,6 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "devOptional": true }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.1.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4415,29 +3025,6 @@ "node": ">=4.0.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -4484,6 +3071,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "dev": true, + "optional": true, "engines": { "node": ">=8" } @@ -4505,134 +3093,6 @@ "p-limit": "^3.1.0 " } }, - "node_modules/dmg-builder": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", - "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "app-builder-lib": "26.8.1", - "builder-util": "26.8.1", - "fs-extra": "^10.1.0", - "iconv-lite": "^0.6.2", - "js-yaml": "^4.1.0" - }, - "optionalDependencies": { - "dmg-license": "^1.0.11" - } - }, - "node_modules/dmg-builder/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-builder/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/dmg-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/dmg-license": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", - "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "@types/plist": "^3.0.1", - "@types/verror": "^1.10.3", - "ajv": "^6.10.0", - "crc": "^3.8.0", - "iconv-corefoundation": "^1.1.7", - "plist": "^3.0.4", - "smart-buffer": "^4.0.2", - "verror": "^1.10.0" - }, - "bin": { - "dmg-license": "bin/dmg-license.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dmg-license/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/dmg-license/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -4688,35 +3148,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4777,166 +3208,6 @@ "url": "https://bevry.me/fund" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-publish": { - "version": "26.8.1", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", - "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^9.0.11", - "builder-util": "26.8.1", - "builder-util-runtime": "9.5.1", - "chalk": "^4.1.2", - "form-data": "^4.0.5", - "fs-extra": "^10.1.0", - "lazy-val": "^1.0.5", - "mime": "^2.5.2" - } - }, - "node_modules/electron-publish/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/electron-publish/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/electron-publish/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/electron-publish/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-publish/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-publish/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-publish/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/electron-publish/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-publish/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4944,17 +3215,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -4998,13 +3258,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -5103,16 +3356,6 @@ "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -5155,13 +3398,6 @@ "node": ">=6" } }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -5195,17 +3431,6 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "optional": true - }, "node_modules/fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -5306,57 +3531,6 @@ "pend": "~1.2.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5457,19 +3631,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5499,16 +3660,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5895,45 +4046,6 @@ "node": ">= 14" } }, - "node_modules/iconv-corefoundation": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", - "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "cli-truncate": "^2.1.0", - "node-addon-api": "^1.6.3" - }, - "engines": { - "node": "^8.11.2 || >=10" - } - }, - "node_modules/iconv-corefoundation/node_modules/node-addon-api": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5952,7 +4064,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "optional": true }, "node_modules/ignore": { "version": "7.0.5", @@ -5964,16 +4077,6 @@ "node": ">= 4" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/index-to-position": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", @@ -6011,16 +4114,6 @@ "dev": true, "optional": true }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -6091,16 +4184,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6122,19 +4205,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -6159,19 +4229,6 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "devOptional": true }, - "node_modules/isbinaryfile": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", - "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6221,34 +4278,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6422,13 +4451,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/lazy-val": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6468,99 +4490,6 @@ "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -6582,29 +4511,6 @@ "node": ">=10" } }, - "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -6738,16 +4644,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -6775,6 +4671,7 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", + "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6789,128 +4686,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -6938,16 +4713,6 @@ "dev": true, "optional": true }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-abi": { "version": "3.30.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz", @@ -6990,54 +4755,6 @@ "dev": true, "optional": true }, - "node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/node-api-version/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -7050,45 +4767,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/node-sarif-builder": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.2.0.tgz", @@ -7141,22 +4819,6 @@ "node": ">= 10.0.0" } }, - "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -7279,22 +4941,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", @@ -7312,129 +4958,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -7620,21 +5143,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pe-library": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", - "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -7755,16 +5263,6 @@ "integrity": "sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= sha512-lg++21mreCEOuGWTbO5DnJKAdxfjrdN0S9ysoW9SzdSJvbkWpkaDdpG/cdsPCsEnoLUwmd9m3WcZhngW7yKA2g==", "dev": true }, - "node_modules/proc-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7780,39 +5278,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7830,16 +5295,6 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -7960,19 +5415,6 @@ "node": ">=0.8" } }, - "node_modules/read-binary-file-arch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", - "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "bin": { - "read-binary-file-arch": "cli.js" - } - }, "node_modules/read-pkg": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", @@ -8060,16 +5502,6 @@ "node": ">= 0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8080,24 +5512,6 @@ "node": ">=0.10.0" } }, - "node_modules/resedit": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", - "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pe-library": "^0.4.1" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -8116,37 +5530,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -8206,23 +5589,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "devOptional": true }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, - "license": "WTFPL OR ISC", - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -8516,47 +5882,6 @@ "dev": true, "license": "MIT" }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8566,17 +5891,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -8620,29 +5934,6 @@ "dev": true, "optional": true }, - "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/stat-mode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", - "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -8965,23 +6256,6 @@ "node": ">=8" } }, - "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -9013,75 +6287,6 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/temp-file": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", - "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-exit-hook": "^2.0.1", - "fs-extra": "^10.0.0" - } - }, - "node_modules/temp-file/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/temp-file/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/terminal-link": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", @@ -9168,56 +6373,6 @@ "node": ">=0.10.0" } }, - "node_modules/tiny-async-pool": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", - "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^5.5.0" - } - }, - "node_modules/tiny-async-pool/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tmp": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", @@ -9228,16 +6383,6 @@ "node": ">=14.14" } }, - "node_modules/tmp-promise": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", - "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tmp": "^0.2.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9333,16 +6478,6 @@ "node": "^18 || ^20 || >= 21" } }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -9432,32 +6567,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", @@ -9473,29 +6582,12 @@ "node": ">= 4.0.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9522,22 +6614,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/version-range": { "version": "4.15.0", "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", @@ -9690,16 +6766,6 @@ "node": ">= 10.0.0" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9901,51 +6967,12 @@ "node": ">=0.4" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/build/package.json b/build/package.json index 0b18b8daff8..785f04f3b22 100644 --- a/build/package.json +++ b/build/package.json @@ -47,7 +47,6 @@ "ansi-colors": "^3.2.3", "byline": "^5.0.0", "debug": "^4.3.2", - "dmg-builder": "26.8.1", "esbuild": "0.27.2", "extract-zip": "^2.0.1", "gulp-merge-json": "^2.1.1", diff --git a/build/vite/package-lock.json b/build/vite/package-lock.json index 4db5338149d..476a7ae626f 100644 --- a/build/vite/package-lock.json +++ b/build/vite/package-lock.json @@ -8,63 +8,13 @@ "name": "@vscode/sample-source", "version": "0.0.0", "devDependencies": { - "@vscode/component-explorer": "next", - "@vscode/component-explorer-vite-plugin": "next", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-vite-plugin": "^0.1.1-10", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", + "rollup": "*", "vite": "npm:rolldown-vite@latest" } }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, "node_modules/@oxc-project/runtime": { "version": "0.101.0", "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz", @@ -85,210 +35,6 @@ "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz", - "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz", - "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz", - "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz", - "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz", - "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz", - "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz", - "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz", - "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz", - "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz", - "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.0-beta.53", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz", @@ -313,351 +59,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", @@ -670,8 +71,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.57.1", @@ -685,42 +85,29 @@ "optional": true, "os": [ "win32" - ], - "peer": true - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + ] }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@vscode/component-explorer": { - "version": "0.1.1-2", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-2.tgz", - "integrity": "sha512-2VMoXLnDBk+hKrhw+iGUsEjnCd1YiiZqe+1LdQIKdk16zqYRtJ5iO6yDxZ4cKy3Wphd+qLDUWmZSULNtKioMrQ==", + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-10.tgz", + "integrity": "sha512-Nokjk2DB1hgKeUL1FW5dHfXySgj17BgxcsiyzcG6etdFIbMpzv85nMQxrW/88aklgmJPrRVefMRHFYSds/F3/g==", "dev": true, - "peerDependencies": { + "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@vscode/component-explorer-vite-plugin": { - "version": "0.1.1-2", - "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-2.tgz", - "integrity": "sha512-iYSp8shDZEJJrjMWGneWyjFbFyED5Og74c9h5XBmVPZBDN4INfOTmPlC+HYTv/CL5+NFxpl91CdtacCmqz2EXw==", + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-10.tgz", + "integrity": "sha512-1F2Ier7lpFPvYzWxyNCBy3qYzSwRyTw6k3pm+l6DBMMNT+OTnCZ3+awa7wtijZXMc4O1WooxswjrjBu++Oqftg==", "dev": true, "dependencies": { "tinyglobby": "^0.2.0" @@ -768,28 +155,12 @@ } } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lightningcss": { "version": "1.31.1", @@ -821,216 +192,6 @@ "lightningcss-win32-x64-msvc": "1.31.1" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", - "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", - "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", - "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", - "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", - "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", - "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", - "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", - "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", - "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.31.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", - "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", @@ -1058,7 +219,6 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1140,7 +300,6 @@ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -1154,7 +313,6 @@ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -1201,7 +359,6 @@ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -1247,7 +404,6 @@ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -1279,14 +435,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, "node_modules/vite": { "name": "rolldown-vite", "version": "7.3.1", diff --git a/build/vite/package.json b/build/vite/package.json index 9800e54af40..b4268fceca1 100644 --- a/build/vite/package.json +++ b/build/vite/package.json @@ -9,14 +9,10 @@ "preview": "vite preview" }, "devDependencies": { + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-vite-plugin": "^0.1.1-10", "@vscode/rollup-plugin-esm-url": "^1.0.1-1", - "vite": "npm:rolldown-vite@latest", - "@vscode/component-explorer": "next", - "@vscode/component-explorer-vite-plugin": "next" - }, - "overrides": { - "@vscode/component-explorer-vite-plugin": { - "vite": "$vite" - } + "rollup": "*", + "vite": "npm:rolldown-vite@latest" } } diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts index 6cdde88076a..d6d8931fa31 100644 --- a/build/vite/vite.config.ts +++ b/build/vite/vite.config.ts @@ -170,13 +170,13 @@ export default defineConfig({ createHotClassSupport(), componentExplorer({ logLevel: 'verbose', - include: 'build/vite/**/*.fixture.ts', + include: join(__dirname, '../../src/**/*.fixture.ts'), }), ], customLogger: logger, resolve: { alias: { - '~@vscode/codicons': '/node_modules/@vscode/codicons', + '~@vscode/codicons': join(__dirname, '../../node_modules/@vscode/codicons'), } }, esbuild: { @@ -198,7 +198,6 @@ export default defineConfig({ server: { cors: true, port: 5199, - origin: 'http://localhost:5199', fs: { allow: [ // To allow loading from sources, not needed when loading monaco-editor from npm package diff --git a/extensions/debug-auto-launch/.vscodeignore b/extensions/debug-auto-launch/.vscodeignore index 1ebf0088b62..0628555db00 100644 --- a/extensions/debug-auto-launch/.vscodeignore +++ b/extensions/debug-auto-launch/.vscodeignore @@ -1,5 +1,5 @@ src/** tsconfig*.json out/** -extension.webpack.config.js +esbuild*.mts package-lock.json diff --git a/extensions/git-base/extension-browser.webpack.config.js b/extensions/debug-auto-launch/esbuild.mts similarity index 51% rename from extensions/git-base/extension-browser.webpack.config.js rename to extensions/debug-auto-launch/esbuild.mts index fcdf954744c..2b75ca703da 100644 --- a/extensions/git-base/extension-browser.webpack.config.js +++ b/extensions/debug-auto-launch/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - output: { - filename: 'extension.js' - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-server-ready/.vscodeignore b/extensions/debug-server-ready/.vscodeignore index 04b88eb4a45..7b79c7b47b4 100644 --- a/extensions/debug-server-ready/.vscodeignore +++ b/extensions/debug-server-ready/.vscodeignore @@ -1,6 +1,6 @@ src/** tsconfig*.json out/** -extension.webpack.config.js +esbuild*.mts package-lock.json .vscode diff --git a/extensions/git-base/extension.webpack.config.js b/extensions/debug-server-ready/esbuild.mts similarity index 51% rename from extensions/git-base/extension.webpack.config.js rename to extensions/debug-server-ready/esbuild.mts index 0bea7c7e821..2b75ca703da 100644 --- a/extensions/git-base/extension.webpack.config.js +++ b/extensions/debug-server-ready/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - output: { - filename: 'extension.js' - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/git-base/.vscodeignore b/extensions/git-base/.vscodeignore index 835918d9403..44bb9f7ee78 100644 --- a/extensions/git-base/.vscodeignore +++ b/extensions/git-base/.vscodeignore @@ -1,7 +1,6 @@ src/** build/** cgmanifest.json -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild*.mts tsconfig*.json diff --git a/extensions/git-base/esbuild.browser.mts b/extensions/git-base/esbuild.browser.mts new file mode 100644 index 00000000000..bf525ffa1ef --- /dev/null +++ b/extensions/git-base/esbuild.browser.mts @@ -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. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-auto-launch/extension.webpack.config.js b/extensions/git-base/esbuild.mts similarity index 51% rename from extensions/debug-auto-launch/extension.webpack.config.js rename to extensions/git-base/esbuild.mts index 0c857b362f5..2b75ca703da 100644 --- a/extensions/debug-auto-launch/extension.webpack.config.js +++ b/extensions/git-base/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts', +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/git-base/tsconfig.browser.json b/extensions/git-base/tsconfig.browser.json new file mode 100644 index 00000000000..dbacbb22fdf --- /dev/null +++ b/extensions/git-base/tsconfig.browser.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ] +} diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index 8805685e222..6823ef2cb7a 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -3,8 +3,6 @@ src/** notebook-src/** out/** tsconfig*.json -extension.webpack.config.js -extension-browser.webpack.config.js +esbuild*.mts package-lock.json .gitignore -esbuild.* diff --git a/extensions/ipynb/esbuild.browser.mts b/extensions/ipynb/esbuild.browser.mts new file mode 100644 index 00000000000..6b08f26043b --- /dev/null +++ b/extensions/ipynb/esbuild.browser.mts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'ipynbMain.browser': path.join(srcDir, 'ipynbMain.browser.ts'), + 'notebookSerializerWorker': path.join(srcDir, 'notebookSerializerWorker.web.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/ipynb/esbuild.mts b/extensions/ipynb/esbuild.mts new file mode 100644 index 00000000000..3aebefcf4df --- /dev/null +++ b/extensions/ipynb/esbuild.mts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'ipynbMain.node': path.join(srcDir, 'ipynbMain.node.ts'), + 'notebookSerializerWorker': path.join(srcDir, 'notebookSerializerWorker.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/ipynb/extension-browser.webpack.config.js b/extensions/ipynb/extension-browser.webpack.config.js deleted file mode 100644 index 08a0edc8ad0..00000000000 --- a/extensions/ipynb/extension-browser.webpack.config.js +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -import path from 'path'; - -const mainConfig = withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/ipynbMain.browser.ts' - }, - output: { - filename: 'ipynbMain.browser.js', - path: path.join(import.meta.dirname, 'dist', 'browser') - } -}); - - -const workerConfig = withBrowserDefaults({ - context: import.meta.dirname, - entry: { - notebookSerializerWorker: './src/notebookSerializerWorker.web.ts', - }, - output: { - filename: 'notebookSerializerWorker.js', - path: path.join(import.meta.dirname, 'dist', 'browser'), - libraryTarget: 'var', - library: 'serverExportVar' - }, -}); - -export default [mainConfig, workerConfig]; diff --git a/extensions/ipynb/extension.webpack.config.js b/extensions/ipynb/extension.webpack.config.js deleted file mode 100644 index 2bfea01dd91..00000000000 --- a/extensions/ipynb/extension.webpack.config.js +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; -import path from 'path'; - -export default withDefaults({ - context: import.meta.dirname, - entry: { - ['ipynbMain.node']: './src/ipynbMain.node.ts', - notebookSerializerWorker: './src/notebookSerializerWorker.ts', - }, - output: { - path: path.resolve(import.meta.dirname, 'dist'), - filename: '[name].js' - }, - plugins: [ - ...nodePlugins(import.meta.dirname), // add plugins, don't replace inherited - ] -}); diff --git a/extensions/ipynb/tsconfig.browser.json b/extensions/ipynb/tsconfig.browser.json new file mode 100644 index 00000000000..27cbc984bb2 --- /dev/null +++ b/extensions/ipynb/tsconfig.browser.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/ipynbMain.browser.ts", + "./src/notebookSerializerWorker.web.ts" + ] +} diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index f1ee36118f2..838a94e6396 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -156,8 +156,12 @@ "watch": "npm run build-preview && gulp watch-extension:media-preview", "vscode:prepublish": "npm run build-ext", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:media-preview ./tsconfig.json", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json index ba67e03fdce..fed905d2651 100644 --- a/extensions/mermaid-chat-features/package.json +++ b/extensions/mermaid-chat-features/package.json @@ -118,8 +118,12 @@ "vscode:prepublish": "npm run build-ext && npm run build-chat-webview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:mermaid-chat-features", "build-chat-webview": "node ./esbuild.webview.mts", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "compile-web": "npm-run-all2 -lp bundle-web typecheck-web", + "bundle-web": "node ./esbuild.browser.mts", + "typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit", + "watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web", + "watch-bundle-web": "node ./esbuild.browser.mts --watch", + "watch-typecheck-web": "tsgo --project ./tsconfig.browser.json --noEmit --watch" }, "devDependencies": { "@types/node": "^22.18.10", diff --git a/extensions/microsoft-authentication/.vscodeignore b/extensions/microsoft-authentication/.vscodeignore index 3e683c7e56d..e61623be63b 100644 --- a/extensions/microsoft-authentication/.vscodeignore +++ b/extensions/microsoft-authentication/.vscodeignore @@ -2,7 +2,7 @@ .vscode-test/** out/test/** out/** -extension.webpack.config.js +esbuild*.mts package-lock.json src/** .gitignore diff --git a/extensions/microsoft-authentication/esbuild.mts b/extensions/microsoft-authentication/esbuild.mts new file mode 100644 index 00000000000..ef8f793c26e --- /dev/null +++ b/extensions/microsoft-authentication/esbuild.mts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'node:fs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +const isWindows = process.platform === 'win32'; +const isMacOS = process.platform === 'darwin'; +const isLinux = !isWindows && !isMacOS; + +const windowsArches = ['x64']; +const linuxArches = ['x64']; + +let platformFolder: string; +switch (process.platform) { + case 'win32': platformFolder = 'windows'; break; + case 'darwin': platformFolder = 'macos'; break; + case 'linux': platformFolder = 'linux'; break; + default: throw new Error(`Unsupported platform: ${process.platform}`); +} + +const arch = process.env.VSCODE_ARCH || process.arch; + +/** + * Copy native MSAL runtime binaries to the output directory. + */ +async function copyNativeMsalFiles(outDir: string): Promise { + if ( + !(isWindows && windowsArches.includes(arch)) && + !isMacOS && + !(isLinux && linuxArches.includes(arch)) + ) { + return; + } + + const msalRuntimeDir = path.join(import.meta.dirname, 'node_modules', '@azure', 'msal-node-runtime', 'dist', platformFolder, arch); + try { + const files = await fs.promises.readdir(msalRuntimeDir); + for (const file of files) { + if (/^(lib)?msal.*\.(node|dll|dylib|so)$/.test(file)) { + await fs.promises.copyFile(path.join(msalRuntimeDir, file), path.join(outDir, file)); + } + } + } catch { + // Skip if directory doesn't exist (unsupported platform/arch) + } +} + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + external: ['vscode', './msal-node-runtime'], + alias: { + 'keytar': path.resolve(import.meta.dirname, 'packageMocks', 'keytar', 'index.js'), + }, + }, +}, process.argv, copyNativeMsalFiles); diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js deleted file mode 100644 index a46d5a527df..00000000000 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; -import CopyWebpackPlugin from 'copy-webpack-plugin'; -import path from 'path'; - -const isWindows = process.platform === 'win32'; -const isMacOS = process.platform === 'darwin'; -const isLinux = !isWindows && !isMacOS; - -const windowsArches = ['x64']; -const linuxArches = ['x64']; - -let platformFolder; -switch (process.platform) { - case 'win32': - platformFolder = 'windows'; - break; - case 'darwin': - platformFolder = 'macos'; - break; - case 'linux': - platformFolder = 'linux'; - break; - default: - throw new Error(`Unsupported platform: ${process.platform}`); -} - -const arch = process.env.VSCODE_ARCH || process.arch; -console.log(`Building Microsoft Authentication Extension for ${process.platform} (${arch})`); - -const plugins = [...nodePlugins(import.meta.dirname)]; -if ( - (isWindows && windowsArches.includes(arch)) || - isMacOS || - (isLinux && linuxArches.includes(arch)) -) { - plugins.push(new CopyWebpackPlugin({ - patterns: [ - { - // The native files we need to ship with the extension - from: `**/dist/${platformFolder}/${arch}/(lib|)msal*.(node|dll|dylib|so)`, - to: '[name][ext]' - } - ] - })); -} - -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts' - }, - externals: { - // The @azure/msal-node-runtime package requires this native node module (.node). - // It is currently only included on Windows, but the package handles unsupported platforms - // gracefully. - './msal-node-runtime': 'commonjs ./msal-node-runtime' - }, - resolve: { - alias: { - 'keytar': path.resolve(import.meta.dirname, 'packageMocks', 'keytar', 'index.js') - } - }, - plugins -}); diff --git a/extensions/npm/package-lock.json b/extensions/npm/package-lock.json index c266824737e..694e98b5e12 100644 --- a/extensions/npm/package-lock.json +++ b/extensions/npm/package-lock.json @@ -12,7 +12,7 @@ "find-up": "^5.0.0", "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^3.2.0", - "minimatch": "^10.2.1", + "minimatch": "^5.1.6", "request-light": "^0.7.0", "vscode-uri": "^3.0.8", "which": "^4.0.0", @@ -58,24 +58,17 @@ } }, "node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c= sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==" }, "node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -216,18 +209,14 @@ } }, "node_modules/minimatch": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", - "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", - "license": "BlueOak-1.0.0", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/p-limit": { diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 434604f2467..bba6a23b8ac 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -30,7 +30,7 @@ "find-up": "^5.0.0", "find-yarn-workspace-root": "^2.0.0", "jsonc-parser": "^3.2.0", - "minimatch": "^10.2.1", + "minimatch": "^5.1.6", "request-light": "^0.7.0", "which": "^4.0.0", "which-pm": "^2.1.1", diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 7cc9120df11..ba833705cb4 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -10,7 +10,7 @@ import { } from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; -import { minimatch } from 'minimatch'; +import minimatch from 'minimatch'; import { Utils } from 'vscode-uri'; import { findPreferredPM } from './preferred-pm'; import { readScripts } from './readScripts'; diff --git a/extensions/theme-2026/themes/styles.css b/extensions/theme-2026/themes/styles.css index af89d698f9c..c1bc21f2121 100644 --- a/extensions/theme-2026/themes/styles.css +++ b/extensions/theme-2026/themes/styles.css @@ -492,10 +492,10 @@ color: var(--vscode-icon-foreground) !important; } -/* Chat input toolbar icons should use proper foreground color, not the muted icon.foreground */ +/* Chat input toolbar icons should follow icon foreground token */ .monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon, .monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon { - color: var(--vscode-foreground) !important; + color: var(--vscode-icon-foreground) !important; } /* Buttons */ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index f7c55c96d9e..50e325eac8c 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1977,7 +1977,11 @@ "jsonl": "_json", "postcss": "_css", "django-html": "_html_3", - "blade": "_php" + "blade": "_php", + "prompt": "_markdown", + "instructions": "_markdown", + "chatagent": "_markdown", + "skill": "_markdown" }, "light": { "file": "_default_light", @@ -2299,7 +2303,11 @@ "jsonl": "_json_light", "postcss": "_css_light", "django-html": "_html_3_light", - "blade": "_php_light" + "blade": "_php_light", + "prompt": "_markdown_light", + "instructions": "_markdown_light", + "chatagent": "_markdown_light", + "skill": "_markdown_light" }, "fileNames": { "mix": "_hex_light", @@ -2403,4 +2411,4 @@ } }, "version": "https://github.com/jesseweed/seti-ui/commit/2d6c5e68b4ded73c92dac291845ee44e1182d511" -} \ No newline at end of file +} diff --git a/extensions/vscode-test-resolver/.vscodeignore b/extensions/vscode-test-resolver/.vscodeignore index 43e7067afbd..ec136872c17 100644 --- a/extensions/vscode-test-resolver/.vscodeignore +++ b/extensions/vscode-test-resolver/.vscodeignore @@ -4,3 +4,4 @@ typings/** **/*.map .gitignore tsconfig*.json +esbuild*.mts diff --git a/extensions/vscode-test-resolver/esbuild.browser.mts b/extensions/vscode-test-resolver/esbuild.browser.mts new file mode 100644 index 00000000000..29f504d6987 --- /dev/null +++ b/extensions/vscode-test-resolver/esbuild.browser.mts @@ -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. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist', 'browser'); + +run({ + platform: 'browser', + entryPoints: { + 'testResolverMain': path.join(srcDir, 'extension.browser.ts'), + }, + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/debug-server-ready/extension.webpack.config.js b/extensions/vscode-test-resolver/esbuild.mts similarity index 51% rename from extensions/debug-server-ready/extension.webpack.config.js rename to extensions/vscode-test-resolver/esbuild.mts index 0c857b362f5..2b75ca703da 100644 --- a/extensions/debug-server-ready/extension.webpack.config.js +++ b/extensions/vscode-test-resolver/esbuild.mts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check -import withDefaults from '../shared.webpack.config.mjs'; +import * as path from 'node:path'; +import { run } from '../esbuild-extension-common.mts'; -export default withDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.ts', +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'dist'); + +run({ + platform: 'node', + entryPoints: { + 'extension': path.join(srcDir, 'extension.ts'), }, - resolve: { - mainFields: ['module', 'main'] - } -}); + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/vscode-test-resolver/extension-browser.webpack.config.js b/extensions/vscode-test-resolver/extension-browser.webpack.config.js deleted file mode 100644 index 9aeb636997b..00000000000 --- a/extensions/vscode-test-resolver/extension-browser.webpack.config.js +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; - -export default withBrowserDefaults({ - context: import.meta.dirname, - entry: { - extension: './src/extension.browser.ts' - }, - output: { - filename: 'testResolverMain.js' - } -}); diff --git a/extensions/vscode-test-resolver/tsconfig.browser.json b/extensions/vscode-test-resolver/tsconfig.browser.json new file mode 100644 index 00000000000..790349e7fec --- /dev/null +++ b/extensions/vscode-test-resolver/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": {}, + "exclude": [ + "./src/test/**" + ], + "files": [ + "./src/extension.browser.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index 32c79495802..33c4fa7383b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,16 +30,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -84,6 +84,8 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20260130", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-cli": "^0.1.1-6", "@vscode/gulp-electron": "https://github.com/microsoft/vscode-gulp-electron.git#405e3df0e4e9c37fcf549cbe6f5cef8d5ba5ddff", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", @@ -1144,6 +1146,19 @@ "xtend": "~4.0.1" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1491,6 +1506,89 @@ "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2966,6 +3064,45 @@ "integrity": "sha512-5MfjQ+LBXnzLB/+nfpB8EpvHPdUkoW57cFcrIAHz52L/sBjwOxZER3+K2+nwb+/ejAiPmogTBDoJP/NM85uBtQ==", "license": "CC-BY-4.0" }, + "node_modules/@vscode/component-explorer": { + "version": "0.1.1-10", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-10.tgz", + "integrity": "sha512-Nokjk2DB1hgKeUL1FW5dHfXySgj17BgxcsiyzcG6etdFIbMpzv85nMQxrW/88aklgmJPrRVefMRHFYSds/F3/g==", + "dev": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@vscode/component-explorer-cli": { + "version": "0.1.1-6", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-cli/-/component-explorer-cli-0.1.1-6.tgz", + "integrity": "sha512-OnypYKeBH8ZZh6++2NvVo9lPXFvHpIik6Y/KAa/UVMp4hI58KlQ0zEOWszvwR1i6mESn+BRWERFbQbNlKLec5g==", + "dev": true, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.26.0", + "clipanion": "^4.0.0-rc.4", + "express": "^5.0.0", + "zod": "^4.3.6" + }, + "bin": { + "component-explorer": "dist/index.js" + }, + "peerDependencies": { + "playwright": ">=1.40.0", + "vite": ">=5.0.0" + } + }, + "node_modules/@vscode/component-explorer-cli/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@vscode/deviceid": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@vscode/deviceid/-/deviceid-0.1.4.tgz", @@ -3910,30 +4047,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -3943,7 +4080,7 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures/node_modules/lru-cache": { @@ -3965,63 +4102,63 @@ "license": "ISC" }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz", - "integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.167.tgz", + "integrity": "sha512-8TokXIwL8UeHhR4mAlUurzrqku5xaDXsikNi0HWpTcPCtZPdntxW36OaHxJmmpuHc8CecdaJehSuhApeW2TuZw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" @@ -4833,6 +4970,31 @@ "readable-stream": "^3.4.0" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -5093,6 +5255,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5394,6 +5573,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ], + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5765,6 +5960,16 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cookies": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", @@ -5871,6 +6076,24 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6067,9 +6290,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7128,6 +7351,16 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -7162,6 +7395,29 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -7312,6 +7568,154 @@ "node": ">=0.10.0" } }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-rate-limit/node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -7500,6 +7904,23 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -7608,6 +8029,28 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-parent-dir": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.1.tgz", @@ -7959,6 +8402,16 @@ "node": ">= 18" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -10191,6 +10644,16 @@ "node": ">=0.10.0" } }, + "node_modules/hono": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", + "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -10405,6 +10868,23 @@ "node": ">=0.8.0" } }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -10556,6 +11036,16 @@ "node": ">= 12" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -11136,6 +11626,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-base64": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", @@ -11265,6 +11765,13 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -11815,6 +12322,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -12215,6 +12735,19 @@ "node": ">= 0.10.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-options": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", @@ -13183,6 +13716,19 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -13889,6 +14435,16 @@ "node": ">=0.10.0" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -14270,6 +14826,20 @@ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -14361,6 +14931,22 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -14397,6 +14983,32 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -14425,6 +15037,33 @@ "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", "dev": true }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/read-package-json-fast": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", @@ -15027,6 +15666,30 @@ "node": ">=8.0" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -15073,12 +15736,29 @@ "ret": "~0.1.10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -15164,6 +15844,70 @@ "node": ">= 0.10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -15203,6 +15947,26 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -15340,6 +16104,82 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -17004,6 +17844,16 @@ "node": "*" } }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ] + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -17255,6 +18105,16 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -18276,6 +19136,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/package.json b/package.json index fb6ac1043c8..d4dda3bbce1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.110.0", - "distro": "85914d5a600261a53306190177be48aa8f0cdfb4", + "distro": "bd187e4508a244500eb533c56e5cccb6801a699c", "author": { "name": "Microsoft Corporation" }, @@ -73,7 +73,9 @@ "extensions-ci": "npm run gulp extensions-ci", "extensions-ci-pr": "npm run gulp extensions-ci-pr", "perf": "node scripts/code-perf.js", - "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)" + "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)", + "install-local-component-explorer": "npm install ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-0.1.0.tgz ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-cli-0.1.0.tgz --no-save && cd build/vite && npm install ../../../vscode-packages/js-component-explorer/dist/vscode-component-explorer-vite-plugin-0.1.0.tgz --no-save", + "install-latest-component-explorer": "npm install @vscode/component-explorer@next @vscode/component-explorer-cli@next && cd build/vite && npm install @vscode/component-explorer-vite-plugin@next" }, "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.23", @@ -96,16 +98,16 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -150,6 +152,8 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.45.0", "@typescript/native-preview": "^7.0.0-dev.20260130", + "@vscode/component-explorer": "^0.1.1-10", + "@vscode/component-explorer-cli": "^0.1.1-6", "@vscode/gulp-electron": "https://github.com/microsoft/vscode-gulp-electron.git#405e3df0e4e9c37fcf549cbe6f5cef8d5ba5ddff", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", diff --git a/product.json b/product.json index fe7d07a079b..af053a48660 100644 --- a/product.json +++ b/product.json @@ -116,6 +116,7 @@ "name": "Apple" } }, + "providerExtensionId": "vscode.github-authentication", "providerUriSetting": "github-enterprise.uri", "providerScopes": [ [ diff --git a/remote/package-lock.json b/remote/package-lock.json index d58a4306534..625b7143211 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -22,16 +22,16 @@ "@vscode/vscode-languagedetection": "1.0.23", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -578,30 +578,30 @@ "license": "MIT" }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -611,67 +611,67 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/headless": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.165.tgz", - "integrity": "sha512-GjAqUhEiY7gb12+yIItptgMKUwHMa7o39HpezD7sfNjYLjmvWQcB02jqUdVMsvjjAKTe2YJMXp3RkApeXdMRVg==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.1.0-beta.167.tgz", + "integrity": "sha512-8TokXIwL8UeHhR4mAlUurzrqku5xaDXsikNi0HWpTcPCtZPdntxW36OaHxJmmpuHc8CecdaJehSuhApeW2TuZw==", "license": "MIT", "workspaces": [ "addons/*" ] }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/package.json b/remote/package.json index 0784e097baf..ecc25ae5590 100644 --- a/remote/package.json +++ b/remote/package.json @@ -17,16 +17,16 @@ "@vscode/vscode-languagedetection": "1.0.23", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/headless": "^6.1.0-beta.165", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/headless": "^6.1.0-beta.167", + "@xterm/xterm": "^6.1.0-beta.167", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 93e41b43970..8ee7ddeb9bd 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -14,15 +14,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.23", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/xterm": "^6.1.0-beta.167", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", @@ -100,30 +100,30 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.165.tgz", - "integrity": "sha512-48GUTZg7sKB7tQvtC7FcH22GxxO0cIUVM4hw068Oi3cJnxDLLPQDicPv70fFG7zysGxxEKE7A39GMtHhwFI75Q==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.3.0-beta.167.tgz", + "integrity": "sha512-+JSjagAk6okCaGVYFwkKl8qIBfy+W+h7p/qULIi9cC8QyeswOLaE4GOqY5yuGNQYU+zMlrpgR1ttyp0o6y9LHg==", "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-image": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.165.tgz", - "integrity": "sha512-DwYvKRgytc1OYoJVwA/doOTT92K8asgvnt3FzsHt5D+XgniwdvM5nwjxv95p6UXv0kEOxQWFy3sNJl/4g/5pew==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.10.0-beta.167.tgz", + "integrity": "sha512-Bxi2oTaX7YM1gup0OSv02n9+tA3P1Ozlu5zyB/ZwSVkepB9FOxCODWD0l3DhWyLGMBqQ+OY/COw5SRxrKyvkNg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-ligatures": { - "version": "0.11.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.165.tgz", - "integrity": "sha512-3nuPBH4ZrGYF+yj/tBB/+YaLRnn8qqbR9J9OcvM6aeDfboEeaFAYIpmdqjh+2Rl2JFTIgZoiS3dKLWaUUpk0Tw==", + "version": "0.11.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.11.0-beta.167.tgz", + "integrity": "sha512-d+9ANnoz6D4J06CjronVolcG+J0jqUWQXbzciRqQkHq0or5k8PYuIj2DuuyBx/0rOaN7JYN347KQ9iylk+++xA==", "license": "MIT", "dependencies": { "lru-cache": "^6.0.0", @@ -133,58 +133,58 @@ "node": ">8.0.0" }, "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-progress": { - "version": "0.3.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.165.tgz", - "integrity": "sha512-Jl+dhHkFBUafrXCECI/EepcGV1GYuU1X/0oXkPYu/VYfbmkjQSAidmfBAEyS+4+AUK5Lkf6yLdb1N13tZVexyg==", + "version": "0.3.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-progress/-/addon-progress-0.3.0-beta.167.tgz", + "integrity": "sha512-8eeaWnp0pnjYaKtOLsXVCE0hTFXS0A2kZCciWp52l6CbNGQsnky4VNWJXKaJrGbS+RHGxT6qWgcB+Mx5ETzZfg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-search": { - "version": "0.17.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.165.tgz", - "integrity": "sha512-3KjonTDJl/8M6jI5nTJITVT+Z528d/5CgqRmn6IV+sDgRfr3W84RZNDsaxXsLoc0GDsxQIB74/FmnNykUQ5Yew==", + "version": "0.17.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.17.0-beta.167.tgz", + "integrity": "sha512-1K6POdu0iCdjtW0Bs2z3IGWpMU4gJypbYxGnecbGnsH86rNRGwAKS0bKwWlHAiUQLlOxSGxTiNbazbrDln03FQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.15.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.165.tgz", - "integrity": "sha512-NjXE+of4NJagrtHlzePBuWQ8a9pBFhhmQuvOhPj9W3CSi+VanuMoM/oRaT1TbR3efHk2JdCsKVDJScEzY8kdjw==", + "version": "0.15.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.15.0-beta.167.tgz", + "integrity": "sha512-7EK/PN7QaUZcNE+bHmt7ELSNK3OBR2UZEuqNkE/0ooha7KqqI9mxZQG53Yn6wYcmRip34OFW9YF49kbTCkFuBg==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.10.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.165.tgz", - "integrity": "sha512-a6myeixOXDYeuOj0GK+/LWXbXXWanFVMvQRUMgC7wmUNGgSZiyJ8NPWzhAq6Vib4jSQ02pd+ux4ZtWs5kyvFLg==", + "version": "0.10.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.10.0-beta.167.tgz", + "integrity": "sha512-uOJCfsMhML8GTesUKqCC4CH2cPH9yCIFnixiwgpcE5eLVrLszXW3tny25S/bu6EM+rfvE4nwIvLNTMrQYYnMFA==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.20.0-beta.164", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.164.tgz", - "integrity": "sha512-wXTi281yTWY1iAmRh21N6AhcEMopTjIm4xsdDdNmS5LbhxNuhVKNNIGKm5Zhd/G9fpn/vrfC4yZ6KA0lI/ZAxg==", + "version": "0.20.0-beta.166", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.20.0-beta.166.tgz", + "integrity": "sha512-SZmz7HDeSMc4O0++x14ma/UWbK/0Ea8AikHw6V5ex/shjrjwbik7Uf2n8FfG2zMYNgBakvCy/SbwDPtQN+IbRQ==", "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^6.1.0-beta.165" + "@xterm/xterm": "^6.1.0-beta.167" } }, "node_modules/@xterm/xterm": { - "version": "6.1.0-beta.165", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.165.tgz", - "integrity": "sha512-OUszO4HSmGPEw3EhboyIcNLQKJQKCDsYHv9kYFcaiK3biuNjGP0VAPVUJOLbf3V9fa1GLUUq+t985blqvTApoA==", + "version": "6.1.0-beta.167", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.167.tgz", + "integrity": "sha512-OOG2gcH9OhEjY+KW3X2s30e1KzaRlynhkF9/oKfb2PNUJBYUdXeww4YAugrz7+nLP8KxCeOdSJrq7VvRzyZrwA==", "license": "MIT", "workspaces": [ "addons/*" diff --git a/remote/web/package.json b/remote/web/package.json index eb80319e29b..37b14b0114f 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -9,15 +9,15 @@ "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.23", - "@xterm/addon-clipboard": "^0.3.0-beta.165", - "@xterm/addon-image": "^0.10.0-beta.165", - "@xterm/addon-ligatures": "^0.11.0-beta.165", - "@xterm/addon-progress": "^0.3.0-beta.165", - "@xterm/addon-search": "^0.17.0-beta.165", - "@xterm/addon-serialize": "^0.15.0-beta.165", - "@xterm/addon-unicode11": "^0.10.0-beta.165", - "@xterm/addon-webgl": "^0.20.0-beta.164", - "@xterm/xterm": "^6.1.0-beta.165", + "@xterm/addon-clipboard": "^0.3.0-beta.167", + "@xterm/addon-image": "^0.10.0-beta.167", + "@xterm/addon-ligatures": "^0.11.0-beta.167", + "@xterm/addon-progress": "^0.3.0-beta.167", + "@xterm/addon-search": "^0.17.0-beta.167", + "@xterm/addon-serialize": "^0.15.0-beta.167", + "@xterm/addon-unicode11": "^0.10.0-beta.167", + "@xterm/addon-webgl": "^0.20.0-beta.166", + "@xterm/xterm": "^6.1.0-beta.167", "jschardet": "3.1.4", "katex": "^0.16.22", "tas-client": "0.3.1", diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index a369fb81871..254bb04f46c 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -8,6 +8,7 @@ import { getProgressAccessibilitySignalScheduler } from './progressAccessibility import { RunOnceScheduler } from '../../../common/async.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../common/lifecycle.js'; import { isNumber } from '../../../common/types.js'; +import { localize } from '../../../../nls.js'; import './progressbar.css'; const CSS_DONE = 'done'; @@ -15,8 +16,10 @@ const CSS_ACTIVE = 'active'; const CSS_INFINITE = 'infinite'; const CSS_INFINITE_LONG_RUNNING = 'infinite-long-running'; const CSS_DISCRETE = 'discrete'; +const NLS_PROGRESS_LABEL = localize('progress', "Progress"); export interface IProgressBarOptions extends IProgressBarStyles { + ariaLabel?: string; } export interface IProgressBarStyles { @@ -68,6 +71,7 @@ export class ProgressBar extends Disposable { this.element.classList.add('monaco-progress-container'); this.element.setAttribute('role', 'progressbar'); this.element.setAttribute('aria-valuemin', '0'); + this.element.setAttribute('aria-label', options?.ariaLabel && options.ariaLabel.trim() ? options.ariaLabel : NLS_PROGRESS_LABEL); container.appendChild(this.element); this.bit = document.createElement('div'); diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index 14f5f4b5fcc..c81e62f98f7 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -576,7 +576,7 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) { [1, ScanCode.Insert, 'Insert', KeyCode.Insert, 'Insert', 45, 'VK_INSERT', empty, empty], [1, ScanCode.Home, 'Home', KeyCode.Home, 'Home', 36, 'VK_HOME', empty, empty], [1, ScanCode.PageUp, 'PageUp', KeyCode.PageUp, 'PageUp', 33, 'VK_PRIOR', empty, empty], - [1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Delete', 46, 'VK_DELETE', empty, empty], + [1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Del', 46, 'VK_DELETE', 'Delete', empty], [1, ScanCode.End, 'End', KeyCode.End, 'End', 35, 'VK_END', empty, empty], [1, ScanCode.PageDown, 'PageDown', KeyCode.PageDown, 'PageDown', 34, 'VK_NEXT', empty, empty], [1, ScanCode.ArrowRight, 'ArrowRight', KeyCode.RightArrow, 'RightArrow', 39, 'VK_RIGHT', 'Right', empty], @@ -808,6 +808,8 @@ export namespace KeyCodeUtils { return 'Left'; case KeyCode.RightArrow: return 'Right'; + case KeyCode.Delete: + return 'Delete'; } return uiMap.keyCodeToStr(keyCode); diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 2602572ba07..8325d6789f0 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -360,6 +360,7 @@ export interface IDefaultChatAgent { apple: { id: string; name: string }; }; + readonly providerExtensionId: string; readonly providerUriSetting: string; readonly providerScopes: string[][]; diff --git a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts index 0247519b550..45473f77c4d 100644 --- a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts +++ b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts @@ -8,6 +8,7 @@ import { Disposable, markAsSingleton } from '../../../base/common/lifecycle.js'; import { RGBA8 } from '../core/misc/rgba.js'; import { TokenizationRegistry } from '../languages.js'; import { ColorId } from '../encodedTokenAttributes.js'; +import { BugIndicatingError, onUnexpectedError } from '../../../base/common/errors.js'; export class MinimapTokensColorTracker extends Disposable { private static _INSTANCE: MinimapTokensColorTracker | null = null; @@ -57,7 +58,12 @@ export class MinimapTokensColorTracker extends Disposable { // background color (basically invisible) colorId = ColorId.DefaultBackground; } - return this._colors[colorId]; + let color = this._colors[colorId]; + if (!color) { + onUnexpectedError(new BugIndicatingError(`Missing color for colorId ${colorId}`)); + color = RGBA8.Empty; + } + return color; } public backgroundIsLight(): boolean { diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index b3d1ed909f9..58c13c19311 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -336,6 +336,11 @@ export interface IActionListOptions { */ readonly showFilter?: boolean; + /** + * Placeholder text for the filter input. + */ + readonly filterPlaceholder?: string; + /** * Section IDs that should be collapsed by default. */ @@ -480,7 +485,7 @@ export class ActionList extends Disposable { this._filterInput = document.createElement('input'); this._filterInput.type = 'text'; this._filterInput.className = 'action-list-filter-input'; - this._filterInput.placeholder = localize('actionList.filter.placeholder', "Search..."); + this._filterInput.placeholder = this._options?.filterPlaceholder ?? localize('actionList.filter.placeholder', "Search..."); this._filterInput.setAttribute('aria-label', localize('actionList.filter.ariaLabel', "Filter items")); this._filterContainer.appendChild(this._filterInput); @@ -488,33 +493,6 @@ export class ActionList extends Disposable { this._filterText = this._filterInput!.value; this._applyFilter(); })); - - // Keyboard navigation from filter input - this._register(dom.addDisposableListener(this._filterInput, 'keydown', (e: KeyboardEvent) => { - if (e.key === 'ArrowUp') { - e.preventDefault(); - this._list.domFocus(); - const lastIndex = this._list.length - 1; - if (lastIndex >= 0) { - this._list.focusLast(undefined, this.focusCondition); - } - } else if (e.key === 'ArrowDown') { - e.preventDefault(); - this._list.domFocus(); - this.focusNext(); - } else if (e.key === 'Enter') { - e.preventDefault(); - this.acceptSelected(); - } else if (e.key === 'Escape') { - if (this._filterText) { - e.preventDefault(); - e.stopPropagation(); - this._filterInput!.value = ''; - this._filterText = ''; - this._applyFilter(); - } - } - })); } this._applyFilter(); @@ -546,10 +524,10 @@ export class ActionList extends Disposable { } else { this._collapsedSections.add(section); } - this._applyFilter(true); + this._applyFilter(); } - private _applyFilter(reposition?: boolean): void { + private _applyFilter(): void { const filterLower = this._filterText.toLowerCase(); const isFiltering = filterLower.length > 0; const visible: IActionListItem[] = []; @@ -647,9 +625,7 @@ export class ActionList extends Disposable { } } // Reposition the context view so the widget grows in the correct direction - if (reposition) { - this._contextViewService.layout(); - } + this._contextViewService.layout(); } } @@ -708,6 +684,16 @@ export class ActionList extends Disposable { this._contextViewService.hideContextView(); } + clearFilter(): boolean { + if (this._filterInput && this._filterText) { + this._filterInput.value = ''; + this._filterText = ''; + this._applyFilter(); + return true; + } + return false; + } + private hasDynamicHeight(): boolean { if (this._options?.showFilter) { return true; @@ -867,17 +853,41 @@ export class ActionList extends Disposable { } focusPrevious() { + if (this._filterInput && dom.isActiveElement(this._filterInput)) { + this._list.domFocus(); + this._list.focusLast(undefined, this.focusCondition); + return; + } + const previousFocus = this._list.getFocus(); this._list.focusPrevious(1, true, undefined, this.focusCondition); const focused = this._list.getFocus(); if (focused.length > 0) { + // If focus wrapped (was at first focusable, now at last), move to filter instead + if (this._filterInput && previousFocus.length > 0 && focused[0] > previousFocus[0]) { + this._list.setFocus([]); + this._filterInput.focus(); + return; + } this._list.reveal(focused[0]); } } focusNext() { + if (this._filterInput && dom.isActiveElement(this._filterInput)) { + this._list.domFocus(); + this._list.focusFirst(undefined, this.focusCondition); + return; + } + const previousFocus = this._list.getFocus(); this._list.focusNext(1, true, undefined, this.focusCondition); const focused = this._list.getFocus(); if (focused.length > 0) { + // If focus wrapped (was at last focusable, now at first), move to filter instead + if (this._filterInput && previousFocus.length > 0 && focused[0] < previousFocus[0]) { + this._list.setFocus([]); + this._filterInput.focus(); + return; + } this._list.reveal(focused[0]); } } diff --git a/src/vs/platform/actionWidget/browser/actionWidget.ts b/src/vs/platform/actionWidget/browser/actionWidget.ts index 3e43f12b195..58792a384ff 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -103,6 +103,10 @@ class ActionWidgetService extends Disposable implements IActionWidgetService { return this._list?.value?.toggleFocusedSection() ?? false; } + clearFilter(): boolean { + return this._list?.value?.clearFilter() ?? false; + } + hide(didCancel?: boolean) { this._list.value?.hide(didCancel); this._list.clear(); @@ -220,6 +224,29 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'clearFilterCodeActionWidget', + title: localize2('clearFilterCodeActionWidget.title', "Clear action widget filter"), + precondition: ContextKeyExpr.and(ActionWidgetContextKeys.Visible, ActionWidgetContextKeys.FilterFocused), + keybinding: { + weight: weight + 1, + primary: KeyCode.Escape, + } + }); + } + + run(accessor: ServicesAccessor): void { + const widgetService = accessor.get(IActionWidgetService); + if (widgetService instanceof ActionWidgetService) { + if (!widgetService.clearFilter()) { + widgetService.hide(true); + } + } + } +}); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/platform/mcp/common/mcpGateway.ts b/src/vs/platform/mcp/common/mcpGateway.ts index e8d2194d37a..27fd5426922 100644 --- a/src/vs/platform/mcp/common/mcpGateway.ts +++ b/src/vs/platform/mcp/common/mcpGateway.ts @@ -13,10 +13,29 @@ export const IMcpGatewayService = createDecorator('IMcpGatew export const McpGatewayChannelName = 'mcpGateway'; export const McpGatewayToolBrokerChannelName = 'mcpGatewayToolBroker'; +export interface IGatewayCallToolResult { + result: MCP.CallToolResult; + serverIndex: number; +} + +export interface IGatewayServerResources { + serverIndex: number; + resources: readonly MCP.Resource[]; +} + +export interface IGatewayServerResourceTemplates { + serverIndex: number; + resourceTemplates: readonly MCP.ResourceTemplate[]; +} + export interface IMcpGatewayToolInvoker { readonly onDidChangeTools: Event; + readonly onDidChangeResources: Event; listTools(): Promise; - callTool(name: string, args: Record): Promise; + callTool(name: string, args: Record): Promise; + listResources(): Promise; + readResource(serverIndex: number, uri: string): Promise; + listResourceTemplates(): Promise; } /** diff --git a/src/vs/platform/mcp/node/mcpGatewayChannel.ts b/src/vs/platform/mcp/node/mcpGatewayChannel.ts index d8a976f3308..0b0ce1edb0a 100644 --- a/src/vs/platform/mcp/node/mcpGatewayChannel.ts +++ b/src/vs/platform/mcp/node/mcpGatewayChannel.ts @@ -6,7 +6,7 @@ import { Event } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { IPCServer, IServerChannel } from '../../../base/parts/ipc/common/ipc.js'; -import { IMcpGatewayService, McpGatewayToolBrokerChannelName } from '../common/mcpGateway.js'; +import { IGatewayCallToolResult, IGatewayServerResources, IGatewayServerResourceTemplates, IMcpGatewayService, McpGatewayToolBrokerChannelName } from '../common/mcpGateway.js'; import { MCP } from '../common/modelContextProtocol.js'; /** @@ -35,8 +35,12 @@ export class McpGatewayChannel extends Disposable implements IServerCh const brokerChannel = ipcChannelForContext(this._ipcServer, ctx); const result = await this.mcpGatewayService.createGateway(ctx, { onDidChangeTools: brokerChannel.listen('onDidChangeTools'), + onDidChangeResources: brokerChannel.listen('onDidChangeResources'), listTools: () => brokerChannel.call('listTools'), - callTool: (name, callArgs) => brokerChannel.call('callTool', { name, args: callArgs }), + callTool: (name, callArgs) => brokerChannel.call('callTool', { name, args: callArgs }), + listResources: () => brokerChannel.call('listResources'), + readResource: (serverIndex, uri) => brokerChannel.call('readResource', { serverIndex, uri }), + listResourceTemplates: () => brokerChannel.call('listResourceTemplates'), }); return result as T; } diff --git a/src/vs/platform/mcp/node/mcpGatewaySession.ts b/src/vs/platform/mcp/node/mcpGatewaySession.ts index 691453ec059..836d6571e3b 100644 --- a/src/vs/platform/mcp/node/mcpGatewaySession.ts +++ b/src/vs/platform/mcp/node/mcpGatewaySession.ts @@ -19,6 +19,56 @@ const MCP_INVALID_REQUEST = -32600; const MCP_METHOD_NOT_FOUND = -32601; const MCP_INVALID_PARAMS = -32602; +const GATEWAY_URI_AUTHORITY_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/?#]*)(.*)/; + +/** + * Encodes a resource URI for the gateway by appending `-{serverIndex}` to the authority. + * This namespaces resources from different MCP servers served through the same gateway. + */ +export function encodeGatewayResourceUri(uri: string, serverIndex: number): string { + const match = uri.match(GATEWAY_URI_AUTHORITY_RE); + if (!match) { + return uri; + } + const [, prefix, authority, rest] = match; + return `${prefix}${authority}-${serverIndex}${rest}`; +} + +/** + * Decodes a gateway-encoded resource URI, extracting the server index and original URI. + */ +export function decodeGatewayResourceUri(uri: string): { serverIndex: number; originalUri: string } { + const match = uri.match(GATEWAY_URI_AUTHORITY_RE); + if (!match) { + throw new JsonRpcError(MCP_INVALID_PARAMS, `Invalid resource URI: ${uri}`); + } + const [, prefix, authority, rest] = match; + const suffixMatch = authority.match(/^(.*)-([0-9]+)$/); + if (!suffixMatch) { + throw new JsonRpcError(MCP_INVALID_PARAMS, `Invalid gateway resource URI (no server index): ${uri}`); + } + const [, originalAuthority, indexStr] = suffixMatch; + return { + serverIndex: parseInt(indexStr, 10), + originalUri: `${prefix}${originalAuthority}${rest}`, + }; +} + +function encodeResourceUrisInContent(content: MCP.ContentBlock[], serverIndex: number): MCP.ContentBlock[] { + return content.map(block => { + if (block.type === 'resource_link') { + return { ...block, uri: encodeGatewayResourceUri(block.uri, serverIndex) }; + } + if (block.type === 'resource') { + return { + ...block, + resource: { ...block.resource, uri: encodeGatewayResourceUri(block.resource.uri, serverIndex) }, + }; + } + return block; + }); +} + export class McpGatewaySession extends Disposable { private readonly _rpc: JsonRpcProtocol; private readonly _sseClients = new Set(); @@ -50,6 +100,14 @@ export class McpGatewaySession extends Disposable { this._rpc.sendNotification({ method: 'notifications/tools/list_changed' }); })); + + this._register(this._toolInvoker.onDidChangeResources(() => { + if (!this._isInitialized) { + return; + } + + this._rpc.sendNotification({ method: 'notifications/resources/list_changed' }); + })); } public attachSseClient(_req: http.IncomingMessage, res: http.ServerResponse): void { @@ -148,6 +206,12 @@ export class McpGatewaySession extends Disposable { return this._handleListTools(); case 'tools/call': return this._handleCallTool(request); + case 'resources/list': + return this._handleListResources(); + case 'resources/read': + return this._handleReadResource(request); + case 'resources/templates/list': + return this._handleListResourceTemplates(); default: throw new JsonRpcError(MCP_METHOD_NOT_FOUND, `Method not found: ${request.method}`); } @@ -157,6 +221,7 @@ export class McpGatewaySession extends Disposable { if (notification.method === 'notifications/initialized') { this._isInitialized = true; this._rpc.sendNotification({ method: 'notifications/tools/list_changed' }); + this._rpc.sendNotification({ method: 'notifications/resources/list_changed' }); } } @@ -167,6 +232,9 @@ export class McpGatewaySession extends Disposable { tools: { listChanged: true, }, + resources: { + listChanged: true, + }, }, serverInfo: { name: 'VS Code MCP Gateway', @@ -175,7 +243,7 @@ export class McpGatewaySession extends Disposable { }; } - private _handleCallTool(request: IJsonRpcRequest): unknown { + private async _handleCallTool(request: IJsonRpcRequest): Promise { const params = typeof request.params === 'object' && request.params ? request.params as Record : undefined; if (!params || typeof params.name !== 'string') { throw new JsonRpcError(MCP_INVALID_PARAMS, 'Missing tool call params'); @@ -189,16 +257,71 @@ export class McpGatewaySession extends Disposable { ? params.arguments as Record : {}; - return this._toolInvoker.callTool(params.name, argumentsValue).catch(error => { + try { + const { result, serverIndex } = await this._toolInvoker.callTool(params.name, argumentsValue); + return { + ...result, + content: encodeResourceUrisInContent(result.content, serverIndex), + }; + } catch (error) { this._logService.error('[McpGatewayService] Tool call invocation failed', error); throw new JsonRpcError(MCP_INVALID_PARAMS, String(error)); - }); + } } private _handleListTools(): unknown { return this._toolInvoker.listTools() .then(tools => ({ tools })); } + + private async _handleListResources(): Promise { + const serverResults = await this._toolInvoker.listResources(); + const allResources: MCP.Resource[] = []; + for (const { serverIndex, resources } of serverResults) { + for (const resource of resources) { + allResources.push({ + ...resource, + uri: encodeGatewayResourceUri(resource.uri, serverIndex), + }); + } + } + return { resources: allResources }; + } + + private async _handleReadResource(request: IJsonRpcRequest): Promise { + const params = typeof request.params === 'object' && request.params ? request.params as Record : undefined; + if (!params || typeof params.uri !== 'string') { + throw new JsonRpcError(MCP_INVALID_PARAMS, 'Missing resource URI'); + } + + const { serverIndex, originalUri } = decodeGatewayResourceUri(params.uri); + try { + const result = await this._toolInvoker.readResource(serverIndex, originalUri); + return { + contents: result.contents.map(content => ({ + ...content, + uri: encodeGatewayResourceUri(content.uri, serverIndex), + })), + }; + } catch (error) { + this._logService.error('[McpGatewayService] Resource read failed', error); + throw new JsonRpcError(MCP_INVALID_PARAMS, String(error)); + } + } + + private async _handleListResourceTemplates(): Promise { + const serverResults = await this._toolInvoker.listResourceTemplates(); + const allTemplates: MCP.ResourceTemplate[] = []; + for (const { serverIndex, resourceTemplates } of serverResults) { + for (const template of resourceTemplates) { + allTemplates.push({ + ...template, + uriTemplate: encodeGatewayResourceUri(template.uriTemplate, serverIndex), + }); + } + } + return { resourceTemplates: allTemplates }; + } } export function isInitializeMessage(message: JsonRpcMessage | JsonRpcMessage[]): boolean { diff --git a/src/vs/platform/mcp/test/node/mcpGatewaySession.test.ts b/src/vs/platform/mcp/test/node/mcpGatewaySession.test.ts index 945b2877ca3..98712bb9681 100644 --- a/src/vs/platform/mcp/test/node/mcpGatewaySession.test.ts +++ b/src/vs/platform/mcp/test/node/mcpGatewaySession.test.ts @@ -11,7 +11,7 @@ import { IJsonRpcErrorResponse, IJsonRpcSuccessResponse } from '../../../../base import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { NullLogService } from '../../../log/common/log.js'; import { MCP } from '../../common/modelContextProtocol.js'; -import { McpGatewaySession } from '../../node/mcpGatewaySession.js'; +import { decodeGatewayResourceUri, encodeGatewayResourceUri, McpGatewaySession } from '../../node/mcpGatewaySession.js'; class TestServerResponse extends EventEmitter { public statusCode: number | undefined; @@ -48,6 +48,7 @@ suite('McpGatewaySession', () => { function createInvoker() { const onDidChangeTools = new Emitter(); + const onDidChangeResources = new Emitter(); const tools: readonly MCP.Tool[] = [{ name: 'test_tool', description: 'Test tool', @@ -59,20 +60,35 @@ suite('McpGatewaySession', () => { } }]; + const resources: readonly MCP.Resource[] = [{ + uri: 'file:///test/resource.txt', + name: 'resource.txt', + }]; + return { onDidChangeTools, + onDidChangeResources, invoker: { onDidChangeTools: onDidChangeTools.event, + onDidChangeResources: onDidChangeResources.event, listTools: async () => tools, - callTool: async (_name: string, args: Record): Promise => ({ - content: [{ type: 'text', text: `Hello, ${typeof args.name === 'string' ? args.name : 'World'}!` }] - }) + callTool: async (_name: string, args: Record) => ({ + result: { + content: [{ type: 'text' as const, text: `Hello, ${typeof args.name === 'string' ? args.name : 'World'}!` }] + }, + serverIndex: 0, + }), + listResources: async () => [{ serverIndex: 0, resources }], + readResource: async (_serverIndex: number, _uri: string) => ({ + contents: [{ uri: 'file:///test/resource.txt', text: 'hello world', mimeType: 'text/plain' }], + }), + listResourceTemplates: async () => [{ serverIndex: 0, resourceTemplates: [{ uriTemplate: 'file:///test/{name}', name: 'Test Template' }] }], } }; } test('returns initialize result', async () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); const session = new McpGatewaySession('session-1', new NullLogService(), () => { }, invoker); const responses = await session.handleIncoming({ @@ -93,10 +109,11 @@ suite('McpGatewaySession', () => { assert.strictEqual((response.result as { protocolVersion: string }).protocolVersion, '2025-11-25'); session.dispose(); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); }); test('rejects non-initialize requests before initialized notification', async () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); const session = new McpGatewaySession('session-2', new NullLogService(), () => { }, invoker); const responses = await session.handleIncoming({ @@ -112,10 +129,11 @@ suite('McpGatewaySession', () => { assert.strictEqual(response.error.code, -32600); session.dispose(); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); }); test('serves tools/list and tools/call after initialized notification', async () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); const session = new McpGatewaySession('session-3', new NullLogService(), () => { }, invoker); await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' }); @@ -145,10 +163,11 @@ suite('McpGatewaySession', () => { assert.strictEqual(text, 'Hello, VS Code!'); session.dispose(); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); }); test('broadcasts notifications to attached SSE clients', async () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); const session = new McpGatewaySession('session-4', new NullLogService(), () => { }, invoker); const response = new TestServerResponse(); @@ -161,12 +180,14 @@ suite('McpGatewaySession', () => { assert.ok(response.writes.some(chunk => chunk.includes(': connected'))); assert.ok(response.writes.some(chunk => chunk.includes('event: message'))); assert.ok(response.writes.some(chunk => chunk.includes('notifications/tools/list_changed'))); + assert.ok(response.writes.some(chunk => chunk.includes('notifications/resources/list_changed'))); session.dispose(); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); }); test('emits list changed on tool invoker changes', async () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); const session = new McpGatewaySession('session-5', new NullLogService(), () => { }, invoker); const response = new TestServerResponse(); @@ -181,10 +202,11 @@ suite('McpGatewaySession', () => { assert.ok(response.writes.slice(writesBefore).some(chunk => chunk.includes('notifications/tools/list_changed'))); session.dispose(); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); }); test('disposes attached SSE clients and callback', () => { - const { invoker, onDidChangeTools } = createInvoker(); + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); let disposed = false; const session = new McpGatewaySession('session-6', new NullLogService(), () => { disposed = true; @@ -197,5 +219,145 @@ suite('McpGatewaySession', () => { assert.strictEqual(response.writableEnded, true); assert.strictEqual(disposed, true); onDidChangeTools.dispose(); + onDidChangeResources.dispose(); + }); + + test('emits resources list changed on resource invoker changes', async () => { + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); + const session = new McpGatewaySession('session-7', new NullLogService(), () => { }, invoker); + const response = new TestServerResponse(); + + session.attachSseClient({} as http.IncomingMessage, response as unknown as http.ServerResponse); + await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' }); + await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' }); + + const writesBefore = response.writes.length; + onDidChangeResources.fire(); + + assert.ok(response.writes.length > writesBefore); + assert.ok(response.writes.slice(writesBefore).some(chunk => chunk.includes('notifications/resources/list_changed'))); + session.dispose(); + onDidChangeTools.dispose(); + onDidChangeResources.dispose(); + }); + + test('serves resources/list with encoded URIs', async () => { + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); + const session = new McpGatewaySession('session-8', new NullLogService(), () => { }, invoker); + + await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' }); + await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' }); + + const responses = await session.handleIncoming({ jsonrpc: '2.0', id: 2, method: 'resources/list' }); + const response = responses[0] as IJsonRpcSuccessResponse; + const resources = (response.result as { resources: Array<{ uri: string; name: string }> }).resources; + assert.strictEqual(resources.length, 1); + assert.strictEqual(resources[0].uri, 'file://-0/test/resource.txt'); + assert.strictEqual(resources[0].name, 'resource.txt'); + session.dispose(); + onDidChangeTools.dispose(); + onDidChangeResources.dispose(); + }); + + test('serves resources/read with URI decoding and re-encoding', async () => { + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); + const session = new McpGatewaySession('session-9', new NullLogService(), () => { }, invoker); + + await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' }); + await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' }); + + const responses = await session.handleIncoming({ + jsonrpc: '2.0', + id: 2, + method: 'resources/read', + params: { uri: 'file://-0/test/resource.txt' }, + }); + const response = responses[0] as IJsonRpcSuccessResponse; + const contents = (response.result as { contents: Array<{ uri: string; text: string }> }).contents; + assert.strictEqual(contents.length, 1); + assert.strictEqual(contents[0].uri, 'file://-0/test/resource.txt'); + assert.strictEqual(contents[0].text, 'hello world'); + session.dispose(); + onDidChangeTools.dispose(); + onDidChangeResources.dispose(); + }); + + test('serves resources/templates/list with encoded URI templates', async () => { + const { invoker, onDidChangeTools, onDidChangeResources } = createInvoker(); + const session = new McpGatewaySession('session-10', new NullLogService(), () => { }, invoker); + + await session.handleIncoming({ jsonrpc: '2.0', id: 1, method: 'initialize' }); + await session.handleIncoming({ jsonrpc: '2.0', method: 'notifications/initialized' }); + + const responses = await session.handleIncoming({ jsonrpc: '2.0', id: 2, method: 'resources/templates/list' }); + const response = responses[0] as IJsonRpcSuccessResponse; + const templates = (response.result as { resourceTemplates: Array<{ uriTemplate: string; name: string }> }).resourceTemplates; + assert.strictEqual(templates.length, 1); + assert.strictEqual(templates[0].uriTemplate, 'file://-0/test/{name}'); + assert.strictEqual(templates[0].name, 'Test Template'); + session.dispose(); + onDidChangeTools.dispose(); + onDidChangeResources.dispose(); + }); +}); + +suite('Gateway Resource URI encoding', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('encodes and decodes URI with authority', () => { + const encoded = encodeGatewayResourceUri('https://example.com/resource', 3); + assert.strictEqual(encoded, 'https://example.com-3/resource'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 3); + assert.strictEqual(decoded.originalUri, 'https://example.com/resource'); + }); + + test('encodes and decodes URI with empty authority', () => { + const encoded = encodeGatewayResourceUri('file:///path/to/file', 0); + assert.strictEqual(encoded, 'file://-0/path/to/file'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 0); + assert.strictEqual(decoded.originalUri, 'file:///path/to/file'); + }); + + test('encodes and decodes URI with authority containing hyphens', () => { + const encoded = encodeGatewayResourceUri('https://my-server.example.com/res', 12); + assert.strictEqual(encoded, 'https://my-server.example.com-12/res'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 12); + assert.strictEqual(decoded.originalUri, 'https://my-server.example.com/res'); + }); + + test('encodes and decodes URI with port', () => { + const encoded = encodeGatewayResourceUri('http://localhost:8080/api', 5); + assert.strictEqual(encoded, 'http://localhost:8080-5/api'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 5); + assert.strictEqual(decoded.originalUri, 'http://localhost:8080/api'); + }); + + test('encodes and decodes URI with query and fragment', () => { + const encoded = encodeGatewayResourceUri('https://example.com/resource?q=1#section', 2); + assert.strictEqual(encoded, 'https://example.com-2/resource?q=1#section'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 2); + assert.strictEqual(decoded.originalUri, 'https://example.com/resource?q=1#section'); + }); + + test('encodes and decodes custom scheme URIs', () => { + const encoded = encodeGatewayResourceUri('custom://myhost/path', 7); + assert.strictEqual(encoded, 'custom://myhost-7/path'); + const decoded = decodeGatewayResourceUri(encoded); + assert.strictEqual(decoded.serverIndex, 7); + assert.strictEqual(decoded.originalUri, 'custom://myhost/path'); + }); + + test('returns URI unchanged if no scheme match', () => { + const encoded = encodeGatewayResourceUri('not-a-uri', 1); + assert.strictEqual(encoded, 'not-a-uri'); + }); + + test('throws on decode of URI without server index suffix', () => { + assert.throws(() => decodeGatewayResourceUri('https://example.com/resource')); }); }); diff --git a/src/vs/sessions/LAYOUT.md b/src/vs/sessions/LAYOUT.md index 1448a8a3dca..7cd6cc059ba 100644 --- a/src/vs/sessions/LAYOUT.md +++ b/src/vs/sessions/LAYOUT.md @@ -27,20 +27,9 @@ The Agent Sessions Workbench (`Workbench` in `sessions/browser/workbench.ts`) pr │ │ Panel │ └─────────┴───────────────────────────────────────────────────────┘ - ┌───────────────────────────────────────┐ - │ ╔═══════════════════════════╗ │ - │ ║ Editor Modal Overlay ║ │ - │ ║ ┌─────────────────────┐ ║ │ - │ ║ │ [header] [X] │ ║ │ - │ ║ ├─────────────────────┤ ║ │ - │ ║ │ │ ║ │ - │ ║ │ Editor Part │ ║ │ - │ ║ │ │ ║ │ - │ ║ │ │ ║ │ - │ ║ └─────────────────────┘ ║ │ - │ ╚═══════════════════════════╝ │ - └───────────────────────────────────────┘ - (shown when editors are open) +Editors open via MODAL_GROUP into the standard ModalEditorPart overlay +(created on-demand by EditorParts.createModalEditorPart). The main +editor part exists but is hidden (display:none) for future use. ``` ### 2.2 Parts @@ -52,7 +41,7 @@ The Agent Sessions Workbench (`Workbench` in `sessions/browser/workbench.ts`) pr | Titlebar | `Parts.TITLEBAR_PART` | Top of right section | Always visible | — | | Sidebar | `Parts.SIDEBAR_PART` | Left, spans full height from top to bottom | Visible | `ViewContainerLocation.Sidebar` | | Chat Bar | `Parts.CHATBAR_PART` | Top-right section, takes remaining width | Visible | `ViewContainerLocation.ChatBar` | -| Editor | `Parts.EDITOR_PART` | **Modal overlay** (not in grid) | Hidden | — | +| Editor | `Parts.EDITOR_PART` | Hidden main part (not in grid); editors open via `MODAL_GROUP` into `ModalEditorPart` overlay | Hidden | — | | Auxiliary Bar | `Parts.AUXILIARYBAR_PART` | Top-right section, right side | Visible | `ViewContainerLocation.AuxiliaryBar` | | Panel | `Parts.PANEL_PART` | Below Chat Bar and Auxiliary Bar (right section only) | Hidden | `ViewContainerLocation.Panel` | @@ -180,87 +169,37 @@ This structure places the sidebar at the root level spanning the full window hei | Sidebar | 300px width | | Auxiliary Bar | 300px width | | Chat Bar | Remaining space | -| Editor Modal | 80% of workbench (min 400x300, max 1200x900), calculated in TypeScript | | Panel | 300px height | | Titlebar | Determined by `minimumHeight` (~30px) | ### 4.3 Editor Modal -The Editor part is rendered as a **modal overlay** rather than being part of the grid. This provides a focused editing experience that hovers above the main workbench layout. +The main editor part is created but hidden (`display:none`). It exists for future use but is not currently visible. All editors are forced to open in the `ModalEditorPart` overlay via the standard `createModalEditorPart()` mechanism. -#### Modal Structure +#### How It Works -``` -EditorModal -├── Overlay (semi-transparent backdrop) -├── Container (centered dialog) -│ ├── Header (32px, contains close button) -│ └── Content (editor part fills remaining space) -``` +The sessions configuration sets `workbench.editor.useModal` to `'on'` (in `contrib/configuration/browser/configuration.contribution.ts`). This causes `findGroup()` in `editorGroupFinder.ts` to redirect all editor opens (that do not specify an explicit preferred group) to `createModalEditorPart()`, which creates the standard workbench `ModalEditorPart` overlay on-demand. + +When the setting is `'on'`: +- All editors without an explicit preferred group open in the modal editor part +- The modal is not auto-closed when editors open without explicit `MODAL_GROUP` as preferred group #### Behavior | Trigger | Action | |---------|--------| -| Editor opens (`onWillOpenEditor`) | Modal shows automatically | -| All editors close | Modal hides automatically | +| Any editor opens (no explicit group) | `ModalEditorPart` overlay created/reused automatically | +| All editors closed in modal | Modal closes and is disposed | | Click backdrop | Close all editors, hide modal | -| Click close button (X) | Close all editors, hide modal | -| Press Escape key | Close all editors, hide modal | +| Press Escape | Close all editors, hide modal | -#### Modal Sizing +#### Configuration -Modal dimensions are calculated in TypeScript rather than CSS. The `EditorModal.layout()` method receives workbench dimensions and computes the modal size with constraints: +The setting `workbench.editor.useModal` is an enum with three values: +- `'off'`: Editors never open in a modal overlay +- `'default'`: Certain editors (e.g. Settings, Keyboard Shortcuts) may open in a modal overlay when requested via `MODAL_GROUP` +- `'on'`: All editors open in a modal overlay (used by sessions window) -| Property | Value | Constant | -|----------|-------|----------| -| Size Percentage | 80% of workbench | `MODAL_SIZE_PERCENTAGE = 0.8` | -| Max Width | 1200px | `MODAL_MAX_WIDTH = 1200` | -| Max Height | 900px | `MODAL_MAX_HEIGHT = 900` | -| Min Width | 400px | `MODAL_MIN_WIDTH = 400` | -| Min Height | 300px | `MODAL_MIN_HEIGHT = 300` | -| Header Height | 32px | `MODAL_HEADER_HEIGHT = 32` | - -The calculation: -```typescript -modalWidth = min(MODAL_MAX_WIDTH, max(MODAL_MIN_WIDTH, workbenchWidth * MODAL_SIZE_PERCENTAGE)) -modalHeight = min(MODAL_MAX_HEIGHT, max(MODAL_MIN_HEIGHT, workbenchHeight * MODAL_SIZE_PERCENTAGE)) -contentHeight = modalHeight - MODAL_HEADER_HEIGHT -``` - -#### CSS Classes - -| Class | Applied To | Notes | -|-------|------------|-------| -| `editor-modal-overlay` | Overlay container | Positioned absolute, full size | -| `editor-modal-overlay.visible` | When modal is shown | Enables pointer events | -| `editor-modal-backdrop` | Semi-transparent backdrop | Clicking closes modal | -| `editor-modal-container` | Centered modal dialog | Width/height set in TypeScript | -| `editor-modal-header` | Header with close button | Fixed 32px height | -| `editor-modal-content` | Editor content area | Width/height set in TypeScript | -| `editor-modal-visible` | Added to `mainContainer` when modal is visible | — | - -#### Implementation - -The modal is implemented in `EditorModal` class (`parts/editorModal.ts`): - -```typescript -class EditorModal extends Disposable { - // Events - readonly onDidChangeVisibility: Event; - - // State - get visible(): boolean; - - // Methods - show(): void; // Show modal using stored dimensions - hide(): void; // Hide modal - close(): void; // Close all editors, then hide - layout(workbenchWidth: number, workbenchHeight: number): void; // Store dimensions, re-layout if visible -} -``` - -The `Workbench.layout()` passes the workbench dimensions to `EditorModal.layout()`, which calculates and applies the modal size with min/max constraints. Dimensions are stored so that `show()` can use them when the modal becomes visible. --- @@ -302,9 +241,9 @@ setPartHidden(hidden: boolean, part: Parts): void - Showing a part restores the last active pane composite - **Panel Part:** - If the panel is maximized when hiding, it exits maximized state first -- **Editor Part Auto-Visibility:** - - Automatically shows when an editor is about to open (`onWillOpenEditor`) - - Automatically hides when the last editor closes (`onDidCloseEditor` + all groups empty) +- **Editor Part:** + - The main editor part is always hidden (`display:none`); `setEditorHidden()` is a no-op + - All editors open via `MODAL_GROUP` into the `ModalEditorPart` overlay, which manages its own lifecycle ### 6.2 Part Sizing @@ -386,11 +325,10 @@ Applied to `mainContainer` based on part visibility: | Class | Applied When | |-------|--------------| | `nosidebar` | Sidebar is hidden | -| `nomaineditorarea` | Editor modal is hidden | +| `nomaineditorarea` | Editor part is hidden (always applied — main editor part is permanently hidden) | | `noauxiliarybar` | Auxiliary bar is hidden | | `nochatbar` | Chat bar is hidden | | `nopanel` | Panel is hidden | -| `editor-modal-visible` | Editor modal is visible | ### 8.2 Window State Classes @@ -424,7 +362,6 @@ The Agent Sessions workbench uses specialized part implementations that extend t | Chat Bar | `ChatBarPart` | `AbstractPaneCompositePart` | `sessions/browser/parts/chatBarPart.ts` | | Titlebar | `TitlebarPart` / `MainTitlebarPart` | `Part` | `sessions/browser/parts/titlebarPart.ts` | | Project Bar | `ProjectBarPart` | `Part` | `sessions/browser/parts/projectBarPart.ts` | -| Editor Modal | `EditorModal` | `Disposable` | `sessions/browser/parts/editorModal.ts` | ### 9.2 Key Differences from Standard Parts @@ -555,7 +492,7 @@ src/vs/sessions/ │ ├── menus.ts # Agent sessions menu IDs (Menus export) │ ├── layoutActions.ts # Layout actions (toggle sidebar, secondary sidebar, panel) │ ├── paneCompositePartService.ts # AgenticPaneCompositePartService -│ ├── style.css # Layout-specific styles (including editor modal) +│ ├── style.css # Layout-specific styles │ ├── widget/ # Agent sessions chat widget │ │ ├── AGENTS_CHAT_WIDGET.md # Chat widget architecture documentation │ │ ├── agentSessionsChatWidget.ts # Main chat widget wrapper @@ -570,7 +507,6 @@ src/vs/sessions/ │ ├── panelPart.ts # Agent session panel │ ├── chatBarPart.ts # Chat Bar part implementation │ ├── projectBarPart.ts # Project bar part (folder entries, icon customization) -│ ├── editorModal.ts # Editor modal overlay implementation │ ├── parts.ts # AgenticParts enum │ ├── agentSessionsChatInputPart.ts # Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts # Chat welcome part @@ -635,8 +571,8 @@ When modifying the Agent Sessions layout: 1. `constructor()` — Register error handlers 2. `startup()` — Initialize services and layout 3. `initServices()` — Set up service collection (including `TitleService`), register singleton services, set lifecycle to `Ready` -4. `initLayout()` — Get services, register layout listeners, register editor open/close listeners -5. `renderWorkbench()` — Create DOM, create parts, create editor modal, set up notifications +4. `initLayout()` — Get services, register layout listeners +5. `renderWorkbench()` — Create DOM, create parts, create hidden editor part, set up notifications 6. `createWorkbenchLayout()` — Build the grid structure 7. `createWorkbenchManagement()` — (No-op in agent sessions layout) 8. `layout()` — Perform initial layout @@ -704,6 +640,7 @@ interface IPartVisibilityState { | Date | Change | |------|--------| +| 2026-02-20 | Replaced custom `EditorModal` with standard `ModalEditorPart` via `MODAL_GROUP`; main editor part created but hidden; changed `workbench.editor.useModal` from boolean to enum (`off`/`default`/`on`); sessions config uses `on`; removed `editorModal.ts` and editor modal CSS | | 2026-02-17 | Added `-webkit-app-region: drag` to sidebar title area so it can be used to drag the window; interactive children (actions, composite bar, labels) marked `no-drag`; CSS rules scoped to `.agent-sessions-workbench` in `parts/media/sidebarPart.css` | | 2026-02-13 | Documentation sync: Updated all file names, class names, and references to match current implementation. `AgenticWorkbench` → `Workbench`, `AgenticSidebarPart` → `SidebarPart`, `AgenticAuxiliaryBarPart` → `AuxiliaryBarPart`, `AgenticPanelPart` → `PanelPart`, `agenticWorkbench.ts` → `workbench.ts`, `agenticWorkbenchMenus.ts` → `menus.ts`, `agenticLayoutActions.ts` → `layoutActions.ts`, `AgenticTitleBarWidget` → `SessionsTitleBarWidget`, `AgenticTitleBarContribution` → `SessionsTitleBarContribution`. Removed references to deleted files (`sidebarRevealButton.ts`, `floatingToolbar.ts`, `agentic.contributions.ts`, `agenticTitleBarWidget.ts`). Updated pane composite architecture from `SyncDescriptor`-based to `AgenticPaneCompositePartService`. Moved account widget docs from titlebar to sidebar footer. Added documentation for sidebar footer, project bar, traffic light spacer, card appearance styling, widget directory, and new contrib structure (`accountMenu/`, `chat/`, `configuration/`, `sessions/`). Updated titlebar actions to reflect Run Script split button and Open submenu. Removed Toggle Maximize panel action (no longer registered). Updated contributions section with all current contributions and their locations. | | 2026-02-13 | Changed grid structure: sidebar now spans full window height at root level (HORIZONTAL root orientation); Titlebar moved inside right section; Grid is now `Sidebar \| [Titlebar / TopRight / Panel]` instead of `Titlebar / [Sidebar \| RightSection]`; Panel maximize now excludes both titlebar and sidebar; Floating toolbar positioning no longer depends on titlebar height | diff --git a/src/vs/sessions/README.md b/src/vs/sessions/README.md index 002781a49ce..d03cecc3dd1 100644 --- a/src/vs/sessions/README.md +++ b/src/vs/sessions/README.md @@ -65,7 +65,6 @@ src/vs/sessions/ │ ├── panelPart.ts ← Panel part │ ├── chatBarPart.ts ← Chat bar part │ ├── projectBarPart.ts ← Project bar part (folder entries) -│ ├── editorModal.ts ← Editor modal overlay │ ├── parts.ts ← AgenticParts enum │ ├── agentSessionsChatInputPart.ts ← Chat input part adapter │ ├── agentSessionsChatWelcomePart.ts ← Chat welcome part diff --git a/src/vs/sessions/browser/layoutActions.ts b/src/vs/sessions/browser/layoutActions.ts index d16de091c62..7d1a013328a 100644 --- a/src/vs/sessions/browser/layoutActions.ts +++ b/src/vs/sessions/browser/layoutActions.ts @@ -13,7 +13,7 @@ import { Menus } from './menus.js'; import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js'; import { registerIcon } from '../../platform/theme/common/iconRegistry.js'; -import { AuxiliaryBarVisibleContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js'; +import { AuxiliaryBarVisibleContext, IsAuxiliaryWindowContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js'; import { IWorkbenchLayoutService, Parts } from '../../workbench/services/layout/browser/layoutService.js'; // Register Icons @@ -52,7 +52,8 @@ class ToggleSidebarVisibilityAction extends Action2 { { id: Menus.TitleBarLeft, group: 'navigation', - order: 0 + order: 0, + when: IsAuxiliaryWindowContext.toNegated() } ] }); @@ -97,7 +98,8 @@ class ToggleSecondarySidebarVisibilityAction extends Action2 { { id: Menus.TitleBarRight, group: 'navigation', - order: 10 + order: 10, + when: IsAuxiliaryWindowContext.toNegated() } ] }); @@ -132,7 +134,8 @@ class TogglePanelVisibilityAction extends Action2 { { id: Menus.PanelTitle, group: 'navigation', - order: 2 + order: 2, + when: IsAuxiliaryWindowContext.toNegated() } ] }); diff --git a/src/vs/sessions/browser/parts/editorModal.ts b/src/vs/sessions/browser/parts/editorModal.ts deleted file mode 100644 index 6b30966f562..00000000000 --- a/src/vs/sessions/browser/parts/editorModal.ts +++ /dev/null @@ -1,194 +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 { $ } from '../../../base/browser/dom.js'; -import { mainWindow } from '../../../base/browser/window.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; -import { Emitter, Event } from '../../../base/common/event.js'; -import { Codicon } from '../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { Part } from '../../../workbench/browser/part.js'; -import { Parts } from '../../../workbench/services/layout/browser/layoutService.js'; -import { IEditorGroupsService } from '../../../workbench/services/editor/common/editorGroupsService.js'; -import { mark } from '../../../base/common/performance.js'; - -const MODAL_HEADER_HEIGHT = 32; -const MODAL_SIZE_PERCENTAGE = 0.8; -const MODAL_MIN_WIDTH = 400; -const MODAL_MAX_WIDTH = 1200; -const MODAL_MIN_HEIGHT = 300; -const MODAL_MAX_HEIGHT = 900; - -export class EditorModal extends Disposable { - - private readonly _onDidChangeVisibility = this._register(new Emitter()); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - - private readonly overlay: HTMLElement; - private readonly container: HTMLElement; - private readonly content: HTMLElement; - - private _visible = false; - get visible(): boolean { return this._visible; } - - private _workbenchWidth = 0; - private _workbenchHeight = 0; - - constructor( - private readonly parentContainer: HTMLElement, - private readonly editorPart: Part, - private readonly editorGroupService: IEditorGroupsService - ) { - super(); - - // Create modal structure - this.overlay = this.createOverlay(); - this.container = this.createContainer(); - this.content = this.createContent(); - - // Assemble the modal - this.container.appendChild(this.content); - this.overlay.appendChild(this.container); - - // Create and add editor part to modal content - this.createEditorPart(); - - // Register keyboard handler - this.registerKeyboardHandler(); - - // Add to parent - this.parentContainer.appendChild(this.overlay); - } - - private createOverlay(): HTMLElement { - const overlay = $('div.editor-modal-overlay'); - - // Create backdrop (clicking closes the modal) - const backdrop = $('div.editor-modal-backdrop'); - backdrop.addEventListener('click', () => this.close()); - overlay.appendChild(backdrop); - - return overlay; - } - - private createContainer(): HTMLElement { - const container = $('div.editor-modal-container'); - container.setAttribute('role', 'dialog'); - container.setAttribute('aria-modal', 'true'); - - // Create header with close button - const header = $('div.editor-modal-header'); - const closeButton = $('button.editor-modal-close-button'); - closeButton.setAttribute('aria-label', 'Close'); - closeButton.title = 'Close (Escape)'; - const closeIcon = $('span'); - closeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.close)); - closeButton.appendChild(closeIcon); - closeButton.addEventListener('click', () => this.close()); - header.appendChild(closeButton); - container.appendChild(header); - - return container; - } - - private createContent(): HTMLElement { - return $('div.editor-modal-content'); - } - - private createEditorPart(): void { - const editorPartContainer = document.createElement('div'); - editorPartContainer.classList.add('part', 'editor'); - editorPartContainer.id = Parts.EDITOR_PART; - editorPartContainer.setAttribute('role', 'main'); - - mark('code/willCreatePart/workbench.parts.editor'); - this.editorPart.create(editorPartContainer, { restorePreviousState: false }); - mark('code/didCreatePart/workbench.parts.editor'); - - this.content.appendChild(editorPartContainer); - } - - private registerKeyboardHandler(): void { - mainWindow.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && this._visible) { - this.close(); - } - }); - } - - show(): void { - if (this._visible) { - return; - } - - this._visible = true; - this.overlay.classList.add('visible'); - - this.doLayout(); - - this._onDidChangeVisibility.fire(true); - } - - hide(): void { - if (!this._visible) { - return; - } - - this._visible = false; - this.overlay.classList.remove('visible'); - - this._onDidChangeVisibility.fire(false); - } - - close(): void { - if (!this._visible) { - return; - } - - // Close all editors in all groups - for (const group of this.editorGroupService.groups) { - group.closeAllEditors(); - } - - // Hide the modal - this.hide(); - } - - layout(workbenchWidth: number, workbenchHeight: number): void { - this._workbenchWidth = workbenchWidth; - this._workbenchHeight = workbenchHeight; - - if (this._visible) { - this.doLayout(); - } - } - - private doLayout(): void { - // Calculate modal dimensions based on workbench size with constraints - const modalWidth = Math.floor( - Math.min(MODAL_MAX_WIDTH, Math.max(MODAL_MIN_WIDTH, this._workbenchWidth * MODAL_SIZE_PERCENTAGE)) - ); - const modalHeight = Math.floor( - Math.min(MODAL_MAX_HEIGHT, Math.max(MODAL_MIN_HEIGHT, this._workbenchHeight * MODAL_SIZE_PERCENTAGE)) - ); - - // Set the modal container dimensions - this.container.style.width = `${modalWidth}px`; - this.container.style.height = `${modalHeight}px`; - - // Calculate content dimensions (subtract header height) - const contentWidth = modalWidth; - const contentHeight = modalHeight - MODAL_HEADER_HEIGHT; - - if (contentWidth > 0 && contentHeight > 0) { - // Explicitly size the content area - this.content.style.width = `${contentWidth}px`; - this.content.style.height = `${contentHeight}px`; - - // Layout the editor part - this.editorPart.layout(contentWidth, contentHeight, 0, 0); - } - } -} diff --git a/src/vs/sessions/browser/parts/titlebarPart.ts b/src/vs/sessions/browser/parts/titlebarPart.ts index f8839f0db5b..37716ece0bf 100644 --- a/src/vs/sessions/browser/parts/titlebarPart.ts +++ b/src/vs/sessions/browser/parts/titlebarPart.ts @@ -348,7 +348,6 @@ export class AuxiliaryTitlebarPart extends TitlebarPart implements IAuxiliaryTit constructor( readonly container: HTMLElement, - editorGroupsContainer: IEditorGroupsContainer, private readonly mainTitlebar: TitlebarPart, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @@ -418,8 +417,8 @@ export class TitleService extends MultiWindowParts implements ITit return titlebarPart; } - protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): TitlebarPart & IAuxiliaryTitlebarPart { - return instantiationService.createInstance(AuxiliaryTitlebarPart, container, editorGroupsContainer, this.mainPart); + protected doCreateAuxiliaryTitlebarPart(container: HTMLElement, _editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): TitlebarPart & IAuxiliaryTitlebarPart { + return instantiationService.createInstance(AuxiliaryTitlebarPart, container, this.mainPart); } //#endregion diff --git a/src/vs/sessions/browser/style.css b/src/vs/sessions/browser/style.css index aa371216552..ac055f6948e 100644 --- a/src/vs/sessions/browser/style.css +++ b/src/vs/sessions/browser/style.css @@ -48,112 +48,6 @@ background-color: var(--vscode-sideBar-background); } -/* Editor Modal Overlay */ -.agent-sessions-workbench .editor-modal-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - visibility: hidden; -} - -.agent-sessions-workbench .editor-modal-overlay.visible { - pointer-events: auto; - visibility: visible; -} - -/* Modal Backdrop */ -.agent-sessions-workbench .editor-modal-backdrop { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.4); - opacity: 0; - transition: opacity 0.15s ease-out; -} - -.agent-sessions-workbench .editor-modal-overlay.visible .editor-modal-backdrop { - opacity: 1; -} - -/* Modal Container */ -.agent-sessions-workbench .editor-modal-container { - position: relative; - display: flex; - flex-direction: column; - /* Width and height are set dynamically in TypeScript */ - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-editorWidget-border, var(--vscode-contrastBorder)); - border-radius: 8px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); - overflow: hidden; - transform: scale(0.95); - opacity: 0; - transition: transform 0.15s ease-out, opacity 0.15s ease-out; -} - -.agent-sessions-workbench .editor-modal-overlay.visible .editor-modal-container { - transform: scale(1); - opacity: 1; -} - -/* Modal Header with close button */ -.agent-sessions-workbench .editor-modal-header { - display: flex; - align-items: center; - justify-content: flex-end; - height: 32px; - min-height: 32px; - padding: 0 8px; - background-color: var(--vscode-editorGroupHeader-tabsBackground); - border-bottom: 1px solid var(--vscode-editorGroupHeader-tabsBorder, transparent); -} - -.agent-sessions-workbench .editor-modal-close-button { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--vscode-icon-foreground); - cursor: pointer; - border-radius: 4px; -} - -.agent-sessions-workbench .editor-modal-close-button:hover { - background-color: var(--vscode-toolbar-hoverBackground); -} - -.agent-sessions-workbench .editor-modal-close-button:active { - background-color: var(--vscode-toolbar-activeBackground); -} - -/* Editor Content Area */ -.agent-sessions-workbench .editor-modal-content { - flex: 1; - overflow: hidden; - position: relative; - min-height: 0; /* Allow flexbox shrinking */ -} - -.agent-sessions-workbench .editor-modal-content > .part.editor { - position: absolute !important; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - /* ---- Chat Input ---- */ .agent-sessions-workbench .interactive-session .chat-input-container { diff --git a/src/vs/sessions/browser/workbench.ts b/src/vs/sessions/browser/workbench.ts index 3ba1a8bb2cc..657ad48a44a 100644 --- a/src/vs/sessions/browser/workbench.ts +++ b/src/vs/sessions/browser/workbench.ts @@ -59,7 +59,6 @@ import { registerNotificationCommands } from '../../workbench/browser/parts/noti import { NotificationsToasts } from '../../workbench/browser/parts/notifications/notificationsToasts.js'; import { IMarkdownRendererService } from '../../platform/markdown/browser/markdownRenderer.js'; import { EditorMarkdownCodeBlockRenderer } from '../../editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js'; -import { EditorModal } from './parts/editorModal.js'; import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; import { TitleService } from './parts/titlebarPart.js'; @@ -83,8 +82,7 @@ enum LayoutClasses { AUXILIARYBAR_HIDDEN = 'noauxiliarybar', CHATBAR_HIDDEN = 'nochatbar', FULLSCREEN = 'fullscreen', - MAXIMIZED = 'maximized', - EDITOR_MODAL_VISIBLE = 'editor-modal-visible' + MAXIMIZED = 'maximized' } //#endregion @@ -230,8 +228,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { private panelPartView!: ISerializableView; private auxiliaryBarPartView!: ISerializableView; - // Editor modal - private editorModal!: EditorModal; private chatBarPartView!: ISerializableView; private readonly partVisibility: IPartVisibilityState = { @@ -545,8 +541,8 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { mark(`code/didCreatePart/${id}`); } - // Create Editor Part in modal - this.createEditorModal(); + // Create Editor Part (hidden — all editors open via MODAL_GROUP) + this.createHiddenEditorPart(); // Notification Handlers this.createNotificationsHandlers(instantiationService, notificationService); @@ -592,13 +588,18 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { return part; } - private createEditorModal(): void { - const editorPart = this.getPart(Parts.EDITOR_PART); - this.editorModal = this._register(new EditorModal( - this.mainContainer, - editorPart, - this.editorGroupService - )); + private createHiddenEditorPart(): void { + const editorPartContainer = document.createElement('div'); + editorPartContainer.classList.add('part', 'editor'); + editorPartContainer.id = Parts.EDITOR_PART; + editorPartContainer.setAttribute('role', 'main'); + editorPartContainer.style.display = 'none'; + + mark('code/willCreatePart/workbench.parts.editor'); + this.getPart(Parts.EDITOR_PART).create(editorPartContainer, { restorePreviousState: false }); + mark('code/didCreatePart/workbench.parts.editor'); + + this.mainContainer.appendChild(editorPartContainer); } private restore(lifecycleService: ILifecycleService): void { @@ -880,9 +881,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { // Layout the grid widget this.workbenchGrid.layout(this._mainContainerDimension.width, this._mainContainerDimension.height); - // Layout the editor modal with workbench dimensions - this.editorModal.layout(this._mainContainerDimension.width, this._mainContainerDimension.height); - // Emit as event this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension); } @@ -1106,14 +1104,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService { this.partVisibility.editor = !hidden; this.mainContainer.classList.toggle(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN, hidden); - this.mainContainer.classList.toggle(LayoutClasses.EDITOR_MODAL_VISIBLE, !hidden); - - // Show/hide modal - if (hidden) { - this.editorModal.hide(); - } else { - this.editorModal.show(); - } } private setPanelHidden(hidden: boolean): void { diff --git a/src/vs/sessions/contrib/accountMenu/browser/account.contribution.ts b/src/vs/sessions/contrib/accountMenu/browser/account.contribution.ts index 9b4701fe419..cbff23edc4c 100644 --- a/src/vs/sessions/contrib/accountMenu/browser/account.contribution.ts +++ b/src/vs/sessions/contrib/accountMenu/browser/account.contribution.ts @@ -307,7 +307,6 @@ class AccountWidgetContribution extends Disposable implements IWorkbenchContribu when: ContextKeyExpr.or( CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready), CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload), - CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates), CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading), CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded), CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating), diff --git a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedback.contribution.ts b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedback.contribution.ts index 82acbdb1c5b..6bb6704f602 100644 --- a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedback.contribution.ts +++ b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedback.contribution.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import './agentFeedbackEditorInputContribution.js'; -import './agentFeedbackGlyphMarginContribution.js'; +import './agentFeedbackLineDecorationContribution.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { AgentFeedbackService, IAgentFeedbackService } from './agentFeedbackService.js'; import { AgentFeedbackAttachmentContribution } from './agentFeedbackAttachment.js'; +import { AgentFeedbackAttachmentWidget } from './agentFeedbackAttachmentWidget.js'; import { AgentFeedbackEditorOverlay } from './agentFeedbackEditorOverlay.js'; import { registerAgentFeedbackEditorActions } from './agentFeedbackEditorActions.js'; +import { IChatAttachmentWidgetRegistry } from '../../../../workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.js'; +import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; registerWorkbenchContribution2(AgentFeedbackEditorOverlay.ID, AgentFeedbackEditorOverlay, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(AgentFeedbackAttachmentContribution.ID, AgentFeedbackAttachmentContribution, WorkbenchPhase.AfterRestored); @@ -18,3 +21,14 @@ registerWorkbenchContribution2(AgentFeedbackAttachmentContribution.ID, AgentFeed registerAgentFeedbackEditorActions(); registerSingleton(IAgentFeedbackService, AgentFeedbackService, InstantiationType.Delayed); + +// Register the custom attachment widget for agentFeedback attachments +class AgentFeedbackAttachmentWidgetContribution { + static readonly ID = 'workbench.contrib.agentFeedbackAttachmentWidgetFactory'; + constructor(@IChatAttachmentWidgetRegistry registry: IChatAttachmentWidgetRegistry) { + registry.registerFactory('agentFeedback', (instantiationService, attachment, options, container) => { + return instantiationService.createInstance(AgentFeedbackAttachmentWidget, attachment as IAgentFeedbackVariableEntry, options, container); + }); + } +} +registerWorkbenchContribution2(AgentFeedbackAttachmentWidgetContribution.ID, AgentFeedbackAttachmentWidgetContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts index 720eb60b9e2..e45f96e488d 100644 --- a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts +++ b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts @@ -14,7 +14,7 @@ import { IAgentFeedbackService, IAgentFeedback } from './agentFeedbackService.js import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js'; import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; -const ATTACHMENT_ID_PREFIX = 'agentFeedback:'; +export const ATTACHMENT_ID_PREFIX = 'agentFeedback:'; /** * Keeps the "N feedback items" attachment in the chat input in sync with the diff --git a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackGlyphMarginContribution.ts b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackGlyphMarginContribution.ts index 6436e692fc2..bc7805d4b17 100644 --- a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackGlyphMarginContribution.ts +++ b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackGlyphMarginContribution.ts @@ -8,7 +8,7 @@ import { Disposable } from '../../../../base/common/lifecycle.js'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; -import { GlyphMarginLane, IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -20,19 +20,15 @@ import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browse import { getSessionForResource } from './agentFeedbackEditorUtils.js'; import { Selection } from '../../../../editor/common/core/selection.js'; -const GLYPH_MARGIN_LANE = GlyphMarginLane.Left; - const feedbackGlyphDecoration = ModelDecorationOptions.register({ description: 'agent-feedback-glyph', - glyphMarginClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-glyph`, - glyphMargin: { position: GLYPH_MARGIN_LANE }, + linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-glyph`, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, }); const addFeedbackHintDecoration = ModelDecorationOptions.register({ description: 'agent-feedback-add-hint', - glyphMarginClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`, - glyphMargin: { position: GLYPH_MARGIN_LANE }, + linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, }); @@ -117,9 +113,10 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements return; } + const isLineDecoration = e.target.type === MouseTargetType.GUTTER_LINE_DECORATIONS && !e.target.detail.isAfterLines; + const isContentArea = e.target.type === MouseTargetType.CONTENT_TEXT || e.target.type === MouseTargetType.CONTENT_EMPTY; if (e.target.position - && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN - && !e.target.detail.isAfterLines + && (isLineDecoration || isContentArea) && !this._feedbackLines.has(e.target.position.lineNumber) ) { this._updateHintDecoration(e.target.position.lineNumber); @@ -150,7 +147,7 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements private _onMouseDown(e: IEditorMouseEvent): void { if (!e.target.position - || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN + || e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS || e.target.detail.isAfterLines || !this._sessionResource ) { @@ -174,9 +171,9 @@ export class AgentFeedbackGlyphMarginContribution extends Disposable implements const endColumn = model.getLineLastNonWhitespaceColumn(lineNumber); if (startColumn === 0 || endColumn === 0) { // Empty line - select the whole line range - this._editor.setSelection(new Selection(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber))); + this._editor.setSelection(new Selection(lineNumber, model.getLineMaxColumn(lineNumber), lineNumber, 1)); } else { - this._editor.setSelection(new Selection(lineNumber, startColumn, lineNumber, endColumn)); + this._editor.setSelection(new Selection(lineNumber, endColumn, lineNumber, startColumn)); } this._editor.focus(); } diff --git a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackLineDecorationContribution.ts b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackLineDecorationContribution.ts new file mode 100644 index 00000000000..63f29606303 --- /dev/null +++ b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackLineDecorationContribution.ts @@ -0,0 +1,188 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/agentFeedbackLineDecoration.css'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; +import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; +import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; +import { IModelDeltaDecoration, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IAgentFeedbackService } from './agentFeedbackService.js'; +import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js'; +import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; +import { getSessionForResource } from './agentFeedbackEditorUtils.js'; +import { Selection } from '../../../../editor/common/core/selection.js'; + +const feedbackLineDecoration = ModelDecorationOptions.register({ + description: 'agent-feedback-line-decoration', + linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.comment)} agent-feedback-line-decoration`, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, +}); + +const addFeedbackHintDecoration = ModelDecorationOptions.register({ + description: 'agent-feedback-add-hint', + linesDecorationsClassName: `${ThemeIcon.asClassName(Codicon.add)} agent-feedback-add-hint`, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, +}); + +export class AgentFeedbackLineDecorationContribution extends Disposable implements IEditorContribution { + + static readonly ID = 'agentFeedback.lineDecorationContribution'; + + private readonly _feedbackDecorations; + + private _hintDecorationId: string | null = null; + private _hintLine = -1; + private _sessionResource: URI | undefined; + private _feedbackLines = new Set(); + + constructor( + private readonly _editor: ICodeEditor, + @IAgentFeedbackService private readonly _agentFeedbackService: IAgentFeedbackService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService, + @IAgentSessionsService private readonly _agentSessionsService: IAgentSessionsService, + ) { + super(); + + this._feedbackDecorations = this._editor.createDecorationsCollection(); + + this._store.add(this._agentFeedbackService.onDidChangeFeedback(() => this._updateFeedbackDecorations())); + this._store.add(this._editor.onDidChangeModel(() => this._onModelChanged())); + this._store.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onMouseMove(e))); + this._store.add(this._editor.onMouseLeave(() => this._updateHintDecoration(-1))); + this._store.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onMouseDown(e))); + + this._resolveSession(); + this._updateFeedbackDecorations(); + } + + private _onModelChanged(): void { + this._updateHintDecoration(-1); + this._resolveSession(); + this._updateFeedbackDecorations(); + } + + private _resolveSession(): void { + const model = this._editor.getModel(); + if (!model) { + this._sessionResource = undefined; + return; + } + this._sessionResource = getSessionForResource(model.uri, this._chatEditingService, this._agentSessionsService); + } + + private _updateFeedbackDecorations(): void { + if (!this._sessionResource) { + this._feedbackDecorations.clear(); + this._feedbackLines.clear(); + return; + } + + const feedbackItems = this._agentFeedbackService.getFeedback(this._sessionResource); + const decorations: IModelDeltaDecoration[] = []; + const lines = new Set(); + + for (const item of feedbackItems) { + const model = this._editor.getModel(); + if (!model || item.resourceUri.toString() !== model.uri.toString()) { + continue; + } + + const line = item.range.startLineNumber; + lines.add(line); + decorations.push({ + range: new Range(line, 1, line, 1), + options: feedbackLineDecoration, + }); + } + + this._feedbackLines = lines; + this._feedbackDecorations.set(decorations); + } + + private _onMouseMove(e: IEditorMouseEvent): void { + if (!this._sessionResource) { + this._updateHintDecoration(-1); + return; + } + + const isLineDecoration = e.target.type === MouseTargetType.GUTTER_LINE_DECORATIONS && !e.target.detail.isAfterLines; + const isContentArea = e.target.type === MouseTargetType.CONTENT_TEXT || e.target.type === MouseTargetType.CONTENT_EMPTY; + if (e.target.position + && (isLineDecoration || isContentArea) + && !this._feedbackLines.has(e.target.position.lineNumber) + ) { + this._updateHintDecoration(e.target.position.lineNumber); + } else { + this._updateHintDecoration(-1); + } + } + + private _updateHintDecoration(line: number): void { + if (line === this._hintLine) { + return; + } + + this._hintLine = line; + this._editor.changeDecorations(accessor => { + if (this._hintDecorationId) { + accessor.removeDecoration(this._hintDecorationId); + this._hintDecorationId = null; + } + if (line !== -1) { + this._hintDecorationId = accessor.addDecoration( + new Range(line, 1, line, 1), + addFeedbackHintDecoration, + ); + } + }); + } + + private _onMouseDown(e: IEditorMouseEvent): void { + if (!e.target.position + || e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS + || e.target.detail.isAfterLines + || !this._sessionResource + ) { + return; + } + + const lineNumber = e.target.position.lineNumber; + + // Lines with existing feedback - do nothing + if (this._feedbackLines.has(lineNumber)) { + return; + } + + // Select the line content and focus the editor + const model = this._editor.getModel(); + if (!model) { + return; + } + + const startColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); + const endColumn = model.getLineLastNonWhitespaceColumn(lineNumber); + if (startColumn === 0 || endColumn === 0) { + // Empty line - select the whole line range + this._editor.setSelection(new Selection(lineNumber, model.getLineMaxColumn(lineNumber), lineNumber, 1)); + } else { + this._editor.setSelection(new Selection(lineNumber, endColumn, lineNumber, startColumn)); + } + this._editor.focus(); + } + + override dispose(): void { + this._feedbackDecorations.clear(); + this._updateHintDecoration(-1); + super.dispose(); + } +} + +registerEditorContribution(AgentFeedbackLineDecorationContribution.ID, AgentFeedbackLineDecorationContribution, EditorContributionInstantiation.Eventually); diff --git a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackService.ts b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackService.ts index 701495fc188..4813a045baf 100644 --- a/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackService.ts +++ b/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackService.ts @@ -13,7 +13,6 @@ import { isEqual } from '../../../../base/common/resources.js'; import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js'; import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; import { agentSessionContainsResource, editingEntriesContainResource } from '../../../../workbench/contrib/chat/browser/sessionResourceMatching.js'; -import { IChatWidget, IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js'; // --- Types -------------------------------------------------------------------- @@ -83,8 +82,6 @@ export interface IAgentFeedbackService { // --- Implementation ----------------------------------------------------------- -const AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX = 'agentFeedback:'; - export class AgentFeedbackService extends Disposable implements IAgentFeedbackService { declare readonly _serviceBrand: undefined; @@ -103,41 +100,8 @@ export class AgentFeedbackService extends Disposable implements IAgentFeedbackSe constructor( @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IAgentSessionsService private readonly _agentSessionsService: IAgentSessionsService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, ) { super(); - - this._registerChatWidgetListeners(); - } - - private _registerChatWidgetListeners(): void { - for (const widget of this._chatWidgetService.getAllWidgets()) { - this._registerWidgetListeners(widget); - } - - this._store.add(this._chatWidgetService.onDidAddWidget(widget => { - this._registerWidgetListeners(widget); - })); - } - - private _registerWidgetListeners(widget: IChatWidget): void { - this._store.add(widget.attachmentModel.onDidChange(e => { - for (const deletedId of e.deleted) { - if (!deletedId.startsWith(AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX)) { - continue; - } - - const sessionResourceString = deletedId.slice(AGENT_FEEDBACK_ATTACHMENT_ID_PREFIX.length); - if (!sessionResourceString) { - continue; - } - - const sessionResource = URI.parse(sessionResourceString); - if (this.getFeedback(sessionResource).length > 0) { - this.clearFeedback(sessionResource); - } - } - })); } addFeedback(sessionResource: URI, resourceUri: URI, range: IRange, text: string): IAgentFeedback { diff --git a/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackGlyphMargin.css b/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackGlyphMargin.css index 16aac443e48..33bf495578f 100644 --- a/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackGlyphMargin.css +++ b/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackGlyphMargin.css @@ -3,24 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-glyph, -.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint { +.monaco-editor .agent-feedback-glyph, +.monaco-editor .agent-feedback-add-hint { border-radius: 3px; display: flex !important; align-items: center; justify-content: center; } -.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-glyph { - background-color: var(--vscode-editorGutter-commentGlyphForeground, var(--vscode-icon-foreground)); - color: var(--vscode-editor-background); +.monaco-editor .agent-feedback-glyph { + background-color: var(--vscode-toolbar-hoverBackground); } -.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint { +.monaco-editor .agent-feedback-add-hint { background-color: var(--vscode-toolbar-hoverBackground); opacity: 0.7; } -.monaco-editor .glyph-margin-widgets .cgmr.agent-feedback-add-hint:hover { +.monaco-editor .agent-feedback-add-hint:hover { opacity: 1; } diff --git a/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackLineDecoration.css b/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackLineDecoration.css new file mode 100644 index 00000000000..890791c5973 --- /dev/null +++ b/src/vs/sessions/contrib/agentFeedback/browser/media/agentFeedbackLineDecoration.css @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .agent-feedback-line-decoration, +.monaco-editor .agent-feedback-add-hint { + border-radius: 3px; + display: flex !important; + align-items: center; + justify-content: center; +} + +.monaco-editor .agent-feedback-line-decoration { + background-color: var(--vscode-toolbar-hoverBackground); +} + +.monaco-editor .agent-feedback-add-hint { + background-color: var(--vscode-toolbar-hoverBackground); + opacity: 0.7; +} + +.monaco-editor .agent-feedback-add-hint:hover { + opacity: 1; +} diff --git a/src/vs/sessions/contrib/aiCustomizationManagement/browser/aiCustomizationManagementEditor.ts b/src/vs/sessions/contrib/aiCustomizationManagement/browser/aiCustomizationManagementEditor.ts index caaf09e7ca2..7d2e3de1fac 100644 --- a/src/vs/sessions/contrib/aiCustomizationManagement/browser/aiCustomizationManagementEditor.ts +++ b/src/vs/sessions/contrib/aiCustomizationManagement/browser/aiCustomizationManagementEditor.ts @@ -247,7 +247,7 @@ export class AICustomizationManagementEditor extends EditorPane { layout: (width, _, height) => { this.sidebarContainer.style.width = `${width}px`; if (height !== undefined) { - const listHeight = height - 24; + const listHeight = height - 8; this.sectionsList.layout(listHeight, width); } }, diff --git a/src/vs/sessions/contrib/aiCustomizationManagement/browser/media/aiCustomizationManagement.css b/src/vs/sessions/contrib/aiCustomizationManagement/browser/media/aiCustomizationManagement.css index 4954a2a3fd9..a307cfa98df 100644 --- a/src/vs/sessions/contrib/aiCustomizationManagement/browser/media/aiCustomizationManagement.css +++ b/src/vs/sessions/contrib/aiCustomizationManagement/browser/media/aiCustomizationManagement.css @@ -9,7 +9,6 @@ flex-direction: column; height: 100%; overflow: hidden; - border-top: 1px solid var(--vscode-panel-border); } /* Sidebar */ @@ -17,12 +16,12 @@ background-color: var(--vscode-sideBar-background); height: 100%; overflow: hidden; - border-right: 1px solid var(--vscode-panel-border); } .ai-customization-management-editor .sidebar-content { height: 100%; - padding: 12px 0 12px 4px; + padding: 4px; + box-sizing: border-box; display: flex; flex-direction: column; } @@ -41,7 +40,6 @@ padding: 4px 8px; gap: 10px; cursor: pointer; - margin: 1px 6px; border-radius: 4px; transition: background-color 0.1s ease, opacity 0.1s ease; } diff --git a/src/vs/sessions/contrib/changesView/browser/changesView.ts b/src/vs/sessions/contrib/changesView/browser/changesView.ts index a5e7e3e70a8..973e8e2ba1b 100644 --- a/src/vs/sessions/contrib/changesView/browser/changesView.ts +++ b/src/vs/sessions/contrib/changesView/browser/changesView.ts @@ -52,7 +52,7 @@ import { chatEditingWidgetFileStateContextKey, hasAppliedChatEditsContextKey, ha import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js'; import { createFileIconThemableTreeContainerScope } from '../../../../workbench/contrib/files/browser/views/explorerView.js'; import { IActivityService, NumberBadge } from '../../../../workbench/services/activity/common/activity.js'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js'; +import { IEditorService, MODAL_GROUP, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js'; import { IExtensionService } from '../../../../workbench/services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; @@ -656,39 +656,53 @@ export class ChangesViewPane extends ViewPane { if (this.tree) { const tree = this.tree; - this.renderDisposables.add(tree.onDidOpen(async (e) => { - if (!e.element) { - return; - } + const openFileItem = (item: IChangesFileItem, items: IChangesFileItem[], sideBySide: boolean) => { + const { uri: modifiedFileUri, originalUri, isDeletion } = item; + const currentIndex = items.indexOf(item); - // Ignore folder elements - only open files - if (!isChangesFileItem(e.element)) { - return; - } + const navigation = { + total: items.length, + current: currentIndex, + navigate: (index: number) => { + const target = items[index]; + if (target) { + openFileItem(target, items, false); + } + } + }; - const { uri: modifiedFileUri, originalUri, isDeletion } = e.element; + const group = sideBySide ? SIDE_GROUP : MODAL_GROUP; if (isDeletion && originalUri) { - await this.editorService.openEditor({ + this.editorService.openEditor({ resource: originalUri, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); return; } if (originalUri) { - await this.editorService.openEditor({ + this.editorService.openEditor({ original: { resource: originalUri }, modified: { resource: modifiedFileUri }, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); return; } - await this.editorService.openEditor({ + this.editorService.openEditor({ resource: modifiedFileUri, - options: e.editorOptions - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + options: { modal: { navigation } } + }, group); + }; + + this.renderDisposables.add(tree.onDidOpen((e) => { + if (!e.element || !isChangesFileItem(e.element)) { + return; + } + + const items = combinedEntriesObs.get(); + openFileItem(e.element, items, e.sideBySide); })); } diff --git a/src/vs/sessions/contrib/chat/browser/branchPicker.ts b/src/vs/sessions/contrib/chat/browser/branchPicker.ts new file mode 100644 index 00000000000..f00e7f42abd --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/branchPicker.ts @@ -0,0 +1,198 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { localize } from '../../../../nls.js'; +import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; +import { ActionListItemKind, IActionListDelegate, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js'; +import { IGitRepository } from '../../../../workbench/contrib/git/common/gitService.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { INewSession } from './newSession.js'; + +const COPILOT_WORKTREE_PATTERN = 'copilot-worktree-'; +const FILTER_THRESHOLD = 10; + +interface IBranchItem { + readonly name: string; +} + +/** + * A self-contained widget for selecting a git branch. + * Uses `IGitRepository.getRefs` to list local branches. + * Copilot worktree branches are shown in a collapsible section; + * other branches are listed without a section header. + * Writes the selected branch to the new session object. + */ +export class BranchPicker extends Disposable { + + private _selectedBranch: string | undefined; + private _newSession: INewSession | undefined; + private _branches: string[] = []; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _slotElement: HTMLElement | undefined; + private _triggerElement: HTMLElement | undefined; + + get selectedBranch(): string | undefined { + return this._selectedBranch; + } + + constructor( + @IActionWidgetService private readonly actionWidgetService: IActionWidgetService, + ) { + super(); + } + + /** + * Sets the new session that this picker writes to. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + + /** + * Sets the git repository and loads its branches. + * When undefined, the picker is shown disabled. + */ + async setRepository(repository: IGitRepository | undefined): Promise { + this._branches = []; + this._selectedBranch = undefined; + + if (!repository) { + this._newSession?.setBranch(undefined); + this._updateTriggerLabel(); + return; + } + + const refs = await repository.getRefs({ pattern: 'refs/heads' }); + this._branches = refs + .map(ref => ref.name) + .filter((name): name is string => !!name) + .filter(name => !name.includes(COPILOT_WORKTREE_PATTERN)); + + // Select active branch, main, master, or the first branch by default + const defaultBranch = this._branches.find(b => b === repository.state.get().HEAD?.name) + ?? this._branches.find(b => b === 'main') + ?? this._branches.find(b => b === 'master') + ?? this._branches[0]; + if (defaultBranch) { + this._selectBranch(defaultBranch); + } + + this._updateTriggerLabel(); + } + + /** + * Renders the branch picker trigger into the given container. + */ + render(container: HTMLElement): void { + this._renderDisposables.clear(); + + const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + this._slotElement = slot; + this._renderDisposables.add({ dispose: () => slot.remove() }); + + const trigger = dom.append(slot, dom.$('a.action-label')); + trigger.tabIndex = 0; + trigger.role = 'button'; + this._triggerElement = trigger; + this._updateTriggerLabel(); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.CLICK, (e) => { + dom.EventHelper.stop(e, true); + this.showPicker(); + })); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.KEY_DOWN, (e) => { + if (e.key === 'Enter' || e.key === ' ') { + dom.EventHelper.stop(e, true); + this.showPicker(); + } + })); + } + + /** + * Shows or hides the picker. + */ + setVisible(visible: boolean): void { + if (this._slotElement) { + this._slotElement.style.display = visible ? '' : 'none'; + } + } + + /** + * Shows the branch picker dropdown anchored to the trigger element. + */ + showPicker(): void { + if (!this._triggerElement || this.actionWidgetService.isVisible || this._branches.length === 0) { + return; + } + + const items = this._buildItems(); + const triggerElement = this._triggerElement; + const delegate: IActionListDelegate = { + onSelect: (item) => { + this.actionWidgetService.hide(); + this._selectBranch(item.name); + }, + onHide: () => { triggerElement.focus(); }, + }; + + const totalActions = items.filter(i => i.kind === ActionListItemKind.Action).length; + + this.actionWidgetService.show( + 'branchPicker', + false, + items, + delegate, + this._triggerElement, + undefined, + [], + { + getAriaLabel: (item) => item.label ?? '', + getWidgetAriaLabel: () => localize('branchPicker.ariaLabel', "Branch Picker"), + }, + totalActions > FILTER_THRESHOLD ? { showFilter: true, filterPlaceholder: localize('branchPicker.filter', "Filter branches...") } : undefined, + ); + } + + private _buildItems(): IActionListItem[] { + return this._branches.map(branch => ({ + kind: ActionListItemKind.Action, + label: branch, + group: { title: '', icon: this._selectedBranch === branch ? Codicon.check : Codicon.blank }, + item: { name: branch }, + })); + } + + private _selectBranch(branch: string): void { + if (this._selectedBranch !== branch) { + this._selectedBranch = branch; + this._newSession?.setBranch(branch); + this._onDidChange.fire(branch); + this._updateTriggerLabel(); + } + } + + private _updateTriggerLabel(): void { + if (!this._triggerElement) { + return; + } + dom.clearNode(this._triggerElement); + const isDisabled = this._branches.length === 0; + const label = this._selectedBranch ?? localize('branchPicker.select', "Branch"); + dom.append(this._triggerElement, renderIcon(Codicon.gitBranch)); + const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); + labelSpan.textContent = label; + dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); + this._slotElement?.classList.toggle('disabled', isDisabled); + } +} diff --git a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts index 8fa8db33b1c..d337a84b2db 100644 --- a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts @@ -36,6 +36,7 @@ import { NewChatViewPane, SessionsViewId } from './newChatViewPane.js'; import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; +import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; export class OpenSessionWorktreeInVSCodeAction extends Action2 { static readonly ID = 'chat.openSessionWorktreeInVSCode'; @@ -156,6 +157,7 @@ MenuRegistry.appendMenuItem(Menus.TitleBarRight, { icon: Codicon.folderOpened, group: 'navigation', order: 9, + when: IsAuxiliaryWindowContext.toNegated() }); diff --git a/src/vs/sessions/contrib/chat/browser/folderPicker.ts b/src/vs/sessions/contrib/chat/browser/folderPicker.ts new file mode 100644 index 00000000000..dddc95aed2b --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/folderPicker.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { basename, isEqual } from '../../../../base/common/resources.js'; +import { URI } from '../../../../base/common/uri.js'; +import { localize } from '../../../../nls.js'; +import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; +import { ActionListItemKind, IActionListDelegate, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js'; +import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IWorkspacesService, isRecentFolder } from '../../../../platform/workspaces/common/workspaces.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { INewSession } from './newSession.js'; + +const STORAGE_KEY_LAST_FOLDER = 'agentSessions.lastPickedFolder'; +const STORAGE_KEY_RECENT_FOLDERS = 'agentSessions.recentlyPickedFolders'; +const MAX_RECENT_FOLDERS = 10; +const FILTER_THRESHOLD = 10; + +interface IFolderItem { + readonly uri: URI; + readonly label: string; +} + +/** + * A folder picker that uses the action widget dropdown to show a list of + * recently selected and recently opened folders. Remembers the last selected + * folder and recently picked folders in storage. Enables a filter input when + * there are more than 10 items. + */ +export class FolderPicker extends Disposable { + + private readonly _onDidSelectFolder = this._register(new Emitter()); + readonly onDidSelectFolder: Event = this._onDidSelectFolder.event; + + private _selectedFolderUri: URI | undefined; + private _recentlyPickedFolders: URI[] = []; + private _cachedRecentFolders: { uri: URI; label?: string }[] = []; + private _newSession: INewSession | undefined; + + private _triggerElement: HTMLElement | undefined; + private readonly _renderDisposables = this._register(new DisposableStore()); + + get selectedFolderUri(): URI | undefined { + return this._selectedFolderUri; + } + + /** + * Sets the pending session that this picker writes to. + * When the user selects a folder, it calls `setRepoUri` on the session. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + + constructor( + @IActionWidgetService private readonly actionWidgetService: IActionWidgetService, + @IStorageService private readonly storageService: IStorageService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + ) { + super(); + + // Restore last picked folder + const lastFolder = this.storageService.get(STORAGE_KEY_LAST_FOLDER, StorageScope.PROFILE); + if (lastFolder) { + try { this._selectedFolderUri = URI.parse(lastFolder); } catch { /* ignore */ } + } + + // Restore recently picked folders + try { + const stored = this.storageService.get(STORAGE_KEY_RECENT_FOLDERS, StorageScope.PROFILE); + if (stored) { + this._recentlyPickedFolders = JSON.parse(stored).map((s: string) => URI.parse(s)); + } + } catch { /* ignore */ } + + // Pre-fetch recently opened folders + this.workspacesService.getRecentlyOpened().then(recent => { + this._cachedRecentFolders = recent.workspaces + .filter(isRecentFolder) + .slice(0, MAX_RECENT_FOLDERS) + .map(r => ({ uri: r.folderUri, label: r.label })); + }).catch(() => { /* ignore */ }); + } + + /** + * Renders the folder picker trigger button into the given container. + * Returns the container element. + */ + render(container: HTMLElement): HTMLElement { + this._renderDisposables.clear(); + + const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + this._renderDisposables.add({ dispose: () => slot.remove() }); + + const trigger = dom.append(slot, dom.$('a.action-label')); + trigger.tabIndex = 0; + trigger.role = 'button'; + this._triggerElement = trigger; + + this._updateTriggerLabel(trigger); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.CLICK, (e) => { + dom.EventHelper.stop(e, true); + this.showPicker(); + })); + + this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.KEY_DOWN, (e) => { + if (e.key === 'Enter' || e.key === ' ') { + dom.EventHelper.stop(e, true); + this.showPicker(); + } + })); + + return slot; + } + + /** + * Shows the folder picker dropdown anchored to the trigger element. + */ + showPicker(): void { + if (!this._triggerElement || this.actionWidgetService.isVisible) { + return; + } + + const currentFolderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + const items = this._buildItems(currentFolderUri); + const showFilter = items.filter(i => i.kind === ActionListItemKind.Action).length > FILTER_THRESHOLD; + + const triggerElement = this._triggerElement; + const delegate: IActionListDelegate = { + onSelect: (item) => { + this.actionWidgetService.hide(); + if (item.uri.scheme === 'command' && item.uri.path === 'browse') { + this._browseForFolder(); + } else { + this._selectFolder(item.uri); + } + }, + onHide: () => { triggerElement.focus(); }, + }; + + this.actionWidgetService.show( + 'folderPicker', + false, + items, + delegate, + this._triggerElement, + undefined, + [], + { + getAriaLabel: (item) => item.label ?? '', + getWidgetAriaLabel: () => localize('folderPicker.ariaLabel', "Folder Picker"), + }, + showFilter ? { showFilter: true, filterPlaceholder: localize('folderPicker.filter', "Filter folders...") } : undefined, + ); + } + + /** + * Programmatically set the selected folder. + */ + setSelectedFolder(folderUri: URI): void { + this._selectFolder(folderUri); + } + + /** + * Clears the selected folder. + */ + clearSelection(): void { + this._selectedFolderUri = undefined; + this._updateTriggerLabel(this._triggerElement); + } + + private _selectFolder(folderUri: URI): void { + this._selectedFolderUri = folderUri; + this._addToRecentlyPickedFolders(folderUri); + this.storageService.store(STORAGE_KEY_LAST_FOLDER, folderUri.toString(), StorageScope.PROFILE, StorageTarget.MACHINE); + this._updateTriggerLabel(this._triggerElement); + this._newSession?.setRepoUri(folderUri); + this._onDidSelectFolder.fire(folderUri); + } + + private async _browseForFolder(): Promise { + try { + const selected = await this.fileDialogService.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + title: localize('selectFolder', "Select Folder"), + }); + if (selected?.[0]) { + this._selectFolder(selected[0]); + } + } catch { + // dialog was cancelled or failed — nothing to do + } + } + + private _addToRecentlyPickedFolders(folderUri: URI): void { + this._recentlyPickedFolders = [folderUri, ...this._recentlyPickedFolders.filter(f => !isEqual(f, folderUri))].slice(0, MAX_RECENT_FOLDERS); + this.storageService.store(STORAGE_KEY_RECENT_FOLDERS, JSON.stringify(this._recentlyPickedFolders.map(f => f.toString())), StorageScope.PROFILE, StorageTarget.MACHINE); + } + + private _buildItems(currentFolderUri: URI | undefined): IActionListItem[] { + const seenUris = new Set(); + if (currentFolderUri) { + seenUris.add(currentFolderUri.toString()); + } + + const items: IActionListItem[] = []; + + // Currently selected folder (shown first, checked) + if (currentFolderUri) { + items.push({ + kind: ActionListItemKind.Action, + label: basename(currentFolderUri), + group: { title: '', icon: Codicon.check }, + item: { uri: currentFolderUri, label: basename(currentFolderUri) }, + }); + } + + // Combine recently picked folders and recently opened folders + const allFolders: { uri: URI; label?: string }[] = [ + ...this._recentlyPickedFolders.map(uri => ({ uri })), + ...this._cachedRecentFolders, + ]; + for (const folder of allFolders) { + const key = folder.uri.toString(); + if (seenUris.has(key)) { + continue; + } + seenUris.add(key); + const label = folder.label || basename(folder.uri); + items.push({ + kind: ActionListItemKind.Action, + label, + group: { title: '', icon: Codicon.blank }, + item: { uri: folder.uri, label }, + }); + } + + // Separator + Browse... + if (items.length > 0) { + items.push({ + kind: ActionListItemKind.Separator, + label: '', + }); + } + items.push({ + kind: ActionListItemKind.Action, + label: localize('browseFolder', "Browse..."), + group: { title: '', icon: Codicon.folderOpened }, + item: { uri: URI.from({ scheme: 'command', path: 'browse' }), label: localize('browseFolder', "Browse...") }, + }); + + return items; + } + + private _updateTriggerLabel(trigger: HTMLElement | undefined): void { + if (!trigger) { + return; + } + + dom.clearNode(trigger); + const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + const label = folderUri ? basename(folderUri) : localize('pickFolder', "Pick Folder"); + + dom.append(trigger, renderIcon(Codicon.folder)); + const labelSpan = dom.append(trigger, dom.$('span.sessions-chat-dropdown-label')); + labelSpan.textContent = label; + dom.append(trigger, renderIcon(Codicon.chevronDown)); + } +} diff --git a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css index 75d90b5f97d..a6987292c9b 100644 --- a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css +++ b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css @@ -298,6 +298,17 @@ color: var(--vscode-foreground); } +.sessions-chat-picker-slot.disabled .action-label { + opacity: 0.5; + cursor: default; + pointer-events: none; +} + +.sessions-chat-picker-slot.disabled .action-label:hover { + background-color: transparent; + color: var(--vscode-descriptionForeground); +} + .sessions-chat-picker-slot .action-label .codicon { font-size: 14px; flex-shrink: 0; diff --git a/src/vs/sessions/contrib/chat/browser/newChatContextAttachments.ts b/src/vs/sessions/contrib/chat/browser/newChatContextAttachments.ts index ca42f1b4515..25ae3a3f71e 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatContextAttachments.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatContextAttachments.ts @@ -7,27 +7,27 @@ import * as dom from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Emitter } from '../../../../base/common/event.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { isObject } from '../../../../base/common/types.js'; import { localize } from '../../../../nls.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IQuickInputService, IQuickPickItem, IQuickPickItemWithResource } from '../../../../platform/quickinput/common/quickInput.js'; -import { AnythingQuickAccessProviderRunOptions } from '../../../../platform/quickinput/common/quickAccess.js'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { basename } from '../../../../base/common/resources.js'; +import { Schemas } from '../../../../base/common/network.js'; -import { AnythingQuickAccessProvider } from '../../../../workbench/contrib/search/browser/anythingQuickAccess.js'; import { IChatRequestVariableEntry, OmittedState } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; -import { isSupportedChatFileScheme } from '../../../../workbench/contrib/chat/common/constants.js'; import { resizeImage } from '../../../../workbench/contrib/chat/browser/chatImageUtils.js'; import { imageToHash, isImage } from '../../../../workbench/contrib/chat/browser/widget/input/editor/chatPasteProviders.js'; import { getPathForFile } from '../../../../platform/dnd/browser/dnd.js'; +import { getExcludes, ISearchConfiguration, ISearchService, QueryType } from '../../../../workbench/services/search/common/search.js'; /** * Manages context attachments for the sessions new-chat widget. @@ -36,6 +36,7 @@ import { getPathForFile } from '../../../../platform/dnd/browser/dnd.js'; * - File picker via quick access ("Files and Open Folders...") * - Image from Clipboard * - Drag and drop files + * - Paste images from clipboard (Ctrl/Cmd+V) */ export class NewChatContextAttachments extends Disposable { @@ -51,12 +52,14 @@ export class NewChatContextAttachments extends Disposable { } constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITextModelService private readonly textModelService: ITextModelService, @IFileService private readonly fileService: IFileService, @IClipboardService private readonly clipboardService: IClipboardService, @IFileDialogService private readonly fileDialogService: IFileDialogService, + @ILabelService private readonly labelService: ILabelService, + @ISearchService private readonly searchService: ISearchService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); } @@ -168,50 +171,241 @@ export class NewChatContextAttachments extends Disposable { })); } + // --- Paste --- + + registerPasteHandler(element: HTMLElement): void { + const supportedMimeTypes = [ + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/bmp', + 'image/gif', + 'image/tiff' + ]; + + this._register(dom.addDisposableListener(element, dom.EventType.PASTE, async (e: ClipboardEvent) => { + const items = e.clipboardData?.items; + if (!items) { + return; + } + + // Check synchronously for image data before any async work + // so preventDefault stops the editor from inserting text. + let imageFile: File | undefined; + for (const item of Array.from(items)) { + if (!item.type.startsWith('image/') || !supportedMimeTypes.includes(item.type)) { + continue; + } + const file = item.getAsFile(); + if (file) { + imageFile = file; + break; + } + } + + if (!imageFile) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + + const arrayBuffer = await imageFile.arrayBuffer(); + const data = new Uint8Array(arrayBuffer); + if (!isImage(data)) { + return; + } + + const resizedData = await resizeImage(data, imageFile.type); + const displayName = this._getUniqueImageName(); + + this._addAttachments({ + id: await imageToHash(resizedData), + name: displayName, + fullName: displayName, + value: resizedData, + kind: 'image', + }); + }, true)); + } + // --- Picker --- - showPicker(): void { - // Build addition picks for the quick access - const additionPicks: IQuickPickItem[] = []; + showPicker(folderUri?: URI): void { + const picker = this.quickInputService.createQuickPick({ useSeparators: true }); + const disposables = new DisposableStore(); + picker.placeholder = localize('chatContext.attach.placeholder', "Attach as context..."); + picker.matchOnDescription = true; + picker.sortByLabel = false; - // "Files and Open Folders..." pick - opens a file dialog - additionPicks.push({ - label: localize('filesAndFolders', "Files and Open Folders..."), - iconClass: ThemeIcon.asClassName(Codicon.file), - id: 'sessions.filesAndFolders', - }); - - // "Image from Clipboard" pick - additionPicks.push({ - label: localize('imageFromClipboard', "Image from Clipboard"), - iconClass: ThemeIcon.asClassName(Codicon.fileMedia), - id: 'sessions.imageFromClipboard', - }); - - const providerOptions: AnythingQuickAccessProviderRunOptions = { - filter: (pick) => { - if (_isQuickPickItemWithResource(pick) && pick.resource) { - return this.instantiationService.invokeFunction(accessor => isSupportedChatFileScheme(accessor, pick.resource!.scheme)); - } - return true; + const staticPicks: (IQuickPickItem | IQuickPickSeparator)[] = [ + { + label: localize('files', "Files..."), + iconClass: ThemeIcon.asClassName(Codicon.file), + id: 'sessions.filesAndFolders', }, - additionPicks, - handleAccept: async (item: IQuickPickItem) => { - if (item.id === 'sessions.filesAndFolders') { - await this._handleFileDialog(); - } else if (item.id === 'sessions.imageFromClipboard') { - await this._handleClipboardImage(); - } else { - await this._handleFilePick(item as IQuickPickItemWithResource); + { + label: localize('imageFromClipboard', "Image from Clipboard"), + iconClass: ThemeIcon.asClassName(Codicon.fileMedia), + id: 'sessions.imageFromClipboard', + }, + ]; + + picker.items = staticPicks; + picker.show(); + + if (folderUri) { + let searchCts: CancellationTokenSource | undefined; + let debounceTimer: ReturnType | undefined; + + const runSearch = (filePattern?: string) => { + searchCts?.dispose(true); + searchCts = new CancellationTokenSource(); + const token = searchCts.token; + + picker.busy = true; + this._collectFilePicks(folderUri, filePattern, token).then(filePicks => { + if (token.isCancellationRequested) { + return; + } + picker.busy = false; + if (filePicks.length > 0) { + picker.items = [ + ...staticPicks, + { type: 'separator', label: basename(folderUri) }, + ...filePicks, + ]; + } else { + picker.items = staticPicks; + } + }); + }; + + // Initial search (no filter) + runSearch(); + + // Re-search on user input with debounce + disposables.add(picker.onDidChangeValue(value => { + if (debounceTimer) { + clearTimeout(debounceTimer); } + debounceTimer = setTimeout(() => runSearch(value || undefined), 200); + })); + + disposables.add({ dispose: () => { searchCts?.dispose(true); if (debounceTimer) { clearTimeout(debounceTimer); } } }); + } + + disposables.add(picker.onDidAccept(async () => { + const [selected] = picker.selectedItems; + if (!selected) { + picker.hide(); + return; + } + + picker.hide(); + + if (selected.id === 'sessions.filesAndFolders') { + await this._handleFileDialog(); + } else if (selected.id === 'sessions.imageFromClipboard') { + await this._handleClipboardImage(); + } else if (selected.id) { + await this._attachFileUri(URI.parse(selected.id), selected.label); + } + })); + + disposables.add(picker.onDidHide(() => { + picker.dispose(); + disposables.dispose(); + })); + } + + private async _collectFilePicks(rootUri: URI, filePattern?: string, token?: CancellationToken): Promise { + const maxFiles = 200; + + // For local file:// URIs, use the search service which respects .gitignore and excludes + if (rootUri.scheme === Schemas.file || rootUri.scheme === Schemas.vscodeRemote) { + return this._collectFilePicksViaSearch(rootUri, maxFiles, filePattern, token); + } + + // For virtual filesystems (e.g. github-remote-file://), walk the tree via IFileService + return this._collectFilePicksViaFileService(rootUri, maxFiles, filePattern); + } + + private async _collectFilePicksViaSearch(rootUri: URI, maxFiles: number, filePattern?: string, token?: CancellationToken): Promise { + const excludePattern = getExcludes(this.configurationService.getValue({ resource: rootUri })); + + try { + const searchResult = await this.searchService.fileSearch({ + folderQueries: [{ + folder: rootUri, + disregardIgnoreFiles: false, + }], + type: QueryType.File, + filePattern: filePattern || '', + excludePattern, + sortByScore: true, + maxResults: maxFiles, + }, token); + + return searchResult.results.map(result => ({ + label: basename(result.resource), + description: this.labelService.getUriLabel(result.resource, { relative: true }), + iconClass: ThemeIcon.asClassName(Codicon.file), + id: result.resource.toString(), + } satisfies IQuickPickItem)); + } catch { + return []; + } + } + + private async _collectFilePicksViaFileService(rootUri: URI, maxFiles: number, filePattern?: string): Promise { + const picks: IQuickPickItem[] = []; + const patternLower = filePattern?.toLowerCase(); + const maxDepth = 10; + + const collect = async (uri: URI, depth: number): Promise => { + if (picks.length >= maxFiles || depth > maxDepth) { + return; + } + + try { + const stat = await this.fileService.resolve(uri); + if (!stat.children) { + return; + } + + const children = stat.children.slice().sort((a, b) => { + if (a.isDirectory !== b.isDirectory) { + return a.isDirectory ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + + for (const child of children) { + if (picks.length >= maxFiles) { + break; + } + if (child.isDirectory) { + await collect(child.resource, depth + 1); + } else { + if (patternLower && !child.name.toLowerCase().includes(patternLower)) { + continue; + } + picks.push({ + label: child.name, + description: this.labelService.getUriLabel(child.resource, { relative: true }), + iconClass: ThemeIcon.asClassName(Codicon.file), + id: child.resource.toString(), + }); + } + } + } catch { + // ignore errors for individual directories } }; - this.quickInputService.quickAccess.show('', { - enabledProviderPrefixes: [AnythingQuickAccessProvider.PREFIX], - placeholder: localize('chatContext.attach.placeholder', "Attach as context..."), - providerOptions, - }); + await collect(rootUri, 0); + return picks; } private async _handleFileDialog(): Promise { @@ -230,13 +424,6 @@ export class NewChatContextAttachments extends Disposable { } } - private async _handleFilePick(pick: IQuickPickItemWithResource): Promise { - if (!pick.resource) { - return; - } - await this._attachFileUri(pick.resource, pick.label); - } - private async _attachFileUri(uri: URI, name: string): Promise { if (/\.(png|jpg|jpeg|bmp|gif|tiff)$/i.test(uri.path)) { const readFile = await this.fileService.readFile(uri); @@ -274,10 +461,12 @@ export class NewChatContextAttachments extends Disposable { return; } + const displayName = this._getUniqueImageName(); + this._addAttachments({ id: await imageToHash(imageData), - name: localize('pastedImage', "Pasted Image"), - fullName: localize('pastedImage', "Pasted Image"), + name: displayName, + fullName: displayName, value: imageData, kind: 'image', }); @@ -285,6 +474,15 @@ export class NewChatContextAttachments extends Disposable { // --- State management --- + private _getUniqueImageName(): string { + const baseName = localize('pastedImage', "Pasted Image"); + let name = baseName; + for (let i = 2; this._attachedContext.some(a => a.name === name); i++) { + name = `${baseName} ${i}`; + } + return name; + } + private _addAttachments(...entries: IChatRequestVariableEntry[]): void { for (const entry of entries) { if (!this._attachedContext.some(e => e.id === entry.id)) { @@ -310,9 +508,3 @@ export class NewChatContextAttachments extends Disposable { this._onDidChangeContext.fire(); } } - -function _isQuickPickItemWithResource(obj: unknown): obj is IQuickPickItemWithResource { - return ( - isObject(obj) - && URI.isUri((obj as IQuickPickItemWithResource).resource)); -} diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index eae4d116d1b..431e7e953d2 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -7,14 +7,11 @@ import './media/chatWidget.css'; import './media/chatWelcomePart.css'; import * as dom from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { Separator, toAction } from '../../../../base/common/actions.js'; -import { Radio } from '../../../../base/browser/ui/radio/radio.js'; -import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; +import { toAction } from '../../../../base/common/actions.js'; +import { Emitter } from '../../../../base/common/event.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { IObservable, observableValue } from '../../../../base/common/observable.js'; +import { observableValue } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; @@ -32,150 +29,39 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { basename, isEqual } from '../../../../base/common/resources.js'; import { localize } from '../../../../nls.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; import { ChatSessionPosition, getResourceForNewChatSession } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.js'; import { ChatSessionPickerActionItem, IChatSessionPickerDelegate } from '../../../../workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.js'; import { SearchableOptionPickerActionItem } from '../../../../workbench/contrib/chat/browser/chatSessions/searchableOptionPickerActionItem.js'; -import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js'; -import { IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js'; import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js'; import { IModelPickerDelegate } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem.js'; import { EnhancedModelPickerActionItem } from '../../../../workbench/contrib/chat/browser/widget/input/modelPickerActionItem2.js'; import { IChatInputPickerOptions } from '../../../../workbench/contrib/chat/browser/widget/input/chatInputPickerActionItem.js'; -import { WorkspaceFolderCountContext } from '../../../../workbench/common/contextkeys.js'; import { IViewDescriptorService } from '../../../../workbench/common/views.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js'; -import { IWorkspacesService, IRecentFolder, isRecentFolder } from '../../../../platform/workspaces/common/workspaces.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js'; import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; import { getSimpleEditorOptions } from '../../../../workbench/contrib/codeEditor/browser/simpleEditorOptions.js'; -import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; import { isString } from '../../../../base/common/types.js'; import { NewChatContextAttachments } from './newChatContextAttachments.js'; - -// #region --- Target Config --- - -/** - * A dropdown menu action item that shows an icon, a text label, and a chevron. - */ -class LabeledDropdownMenuActionViewItem extends DropdownMenuActionViewItem { - protected override renderLabel(element: HTMLElement): null { - // Render icon as a separate codicon element - const classNames = typeof this.options.classNames === 'string' - ? this.options.classNames.split(/\s+/g).filter(s => !!s) - : (this.options.classNames ?? []); - if (classNames.length > 0) { - const icon = dom.append(element, dom.$('span')); - icon.classList.add('codicon', ...classNames); - } - - // Add text label (not affected by codicon font) - const label = dom.append(element, dom.$('span.sessions-chat-dropdown-label')); - label.textContent = this._action.label; - - // Add chevron - dom.append(element, renderIcon(Codicon.chevronDown)); - - return null; - } -} - -/** - * Tracks which agent session targets are available and which is selected. - * Targets are fixed at construction time; only the selection changes. - */ -export interface ITargetConfig { - readonly allowedTargets: IObservable>; - readonly selectedTarget: IObservable; - readonly onDidChangeSelectedTarget: Event; - readonly onDidChangeAllowedTargets: Event>; - setSelectedTarget(target: AgentSessionProviders): void; -} - -export interface ITargetConfigOptions { - allowedTargets: AgentSessionProviders[]; - defaultTarget?: AgentSessionProviders; -} - -class TargetConfig extends Disposable implements ITargetConfig { - - private readonly _allowedTargets = observableValue>('allowedTargets', new Set()); - readonly allowedTargets: IObservable> = this._allowedTargets; - - private readonly _selectedTarget = observableValue('selectedTarget', undefined); - readonly selectedTarget: IObservable = this._selectedTarget; - - private readonly _onDidChangeSelectedTarget = this._register(new Emitter()); - readonly onDidChangeSelectedTarget = this._onDidChangeSelectedTarget.event; - - private readonly _onDidChangeAllowedTargets = this._register(new Emitter>()); - readonly onDidChangeAllowedTargets = this._onDidChangeAllowedTargets.event; - - constructor(options: ITargetConfigOptions) { - super(); - const initialSet = new Set(options.allowedTargets); - this._allowedTargets.set(initialSet, undefined); - const defaultTarget = options.defaultTarget && initialSet.has(options.defaultTarget) - ? options.defaultTarget - : initialSet.values().next().value; - this._selectedTarget.set(defaultTarget, undefined); - } - - setSelectedTarget(target: AgentSessionProviders): void { - const allowed = this._allowedTargets.get(); - if (!allowed.has(target)) { - throw new Error(`Target "${target}" is not in the allowed set`); - } - if (this._selectedTarget.get() !== target) { - this._selectedTarget.set(target, undefined); - this._onDidChangeSelectedTarget.fire(target); - } - } - - setAllowedTargets(targets: AgentSessionProviders[]): void { - const newSet = new Set(targets); - this._allowedTargets.set(newSet, undefined); - this._onDidChangeAllowedTargets.fire(newSet); - - // If the currently selected target is no longer allowed, switch to the first allowed target - const current = this._selectedTarget.get(); - if (current && !newSet.has(current)) { - const fallback = newSet.values().next().value; - this._selectedTarget.set(fallback, undefined); - this._onDidChangeSelectedTarget.fire(fallback); - } - } -} - -// #endregion +import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js'; +import { FolderPicker } from './folderPicker.js'; +import { IGitService } from '../../../../workbench/contrib/git/common/gitService.js'; +import { IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js'; +import { BranchPicker } from './branchPicker.js'; +import { INewSession } from './newSession.js'; // #region --- Chat Welcome Widget --- -/** - * Data passed to the `onSendRequest` callback when the user submits a query. - */ -export interface INewChatSendRequestData { - readonly resource: URI; - readonly target: AgentSessionProviders; - readonly query: string; - readonly sendOptions: IChatSendRequestOptions; - readonly selectedOptions: ReadonlyMap; - readonly folderUri?: URI; - readonly attachedContext?: IChatRequestVariableEntry[]; -} - /** * Options for creating a `NewChatWidget`. */ -export interface INewChatWidgetOptions { - readonly targetConfig: ITargetConfigOptions; - readonly onSendRequest?: (data: INewChatSendRequestData) => void; +interface INewChatWidgetOptions { + readonly allowedTargets: AgentSessionProviders[]; + readonly defaultTarget: AgentSessionProviders; readonly sessionPosition?: ChatSessionPosition; } @@ -188,29 +74,27 @@ export interface INewChatWidgetOptions { */ class NewChatWidget extends Disposable { - private readonly _targetConfig: TargetConfig; + private readonly _targetPicker: SessionTargetPicker; + private readonly _isolationModePicker: IsolationModePicker; + private readonly _branchPicker: BranchPicker; private readonly _options: INewChatWidgetOptions; // Input private _editor!: CodeEditorWidget; private readonly _currentLanguageModel = observableValue('currentLanguageModel', undefined); private readonly _modelPickerDisposable = this._register(new MutableDisposable()); - private _pendingSessionResource: URI | undefined; + + // Pending session + private readonly _newSession = this._register(new MutableDisposable()); + private readonly _newSessionListener = this._register(new MutableDisposable()); // Welcome part - private readonly _welcomeContentDisposables = this._register(new DisposableStore()); private _pickersContainer: HTMLElement | undefined; - private _targetDropdownContainer: HTMLElement | undefined; private _extensionPickersLeftContainer: HTMLElement | undefined; private _extensionPickersRightContainer: HTMLElement | undefined; private _inputSlot: HTMLElement | undefined; - private _localModeContainer: HTMLElement | undefined; - private _localModeDropdownContainer: HTMLElement | undefined; - private _localModePickersContainer: HTMLElement | undefined; - private _localMode: 'workspace' | 'worktree' = 'worktree'; - private _selectedFolderUri: URI | undefined; - private _recentlyPickedFolders: URI[] = []; - private _cachedRecentFolders: IRecentFolder[] = []; + private readonly _folderPicker: FolderPicker; + private _folderPickerContainer: HTMLElement | undefined; private readonly _pickerWidgets = new Map(); private readonly _pickerWidgetDisposables = this._register(new DisposableStore()); private readonly _optionEmitters = new Map>(); @@ -229,71 +113,30 @@ class NewChatWidget extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @ILanguageModelsService private readonly languageModelsService: ILanguageModelsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextMenuService contextMenuService: IContextMenuService, @ILogService private readonly logService: ILogService, @IHoverService _hoverService: IHoverService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @IStorageService private readonly storageService: IStorageService, @ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService, + @IGitService private readonly gitService: IGitService, ) { super(); this._contextAttachments = this._register(this.instantiationService.createInstance(NewChatContextAttachments)); - this._targetConfig = this._register(new TargetConfig(options.targetConfig)); + this._folderPicker = this._register(this.instantiationService.createInstance(FolderPicker)); + this._targetPicker = this._register(new SessionTargetPicker(options.allowedTargets, options.defaultTarget)); + this._isolationModePicker = this._register(this.instantiationService.createInstance(IsolationModePicker)); + this._branchPicker = this._register(this.instantiationService.createInstance(BranchPicker)); this._options = options; - // Restore last picked folder and recently picked folders - const lastFolder = this.storageService.get('agentSessions.lastPickedFolder', StorageScope.PROFILE); - if (lastFolder) { - try { this._selectedFolderUri = URI.parse(lastFolder); } catch { /* ignore */ } - } - try { - const stored = this.storageService.get('agentSessions.recentlyPickedFolders', StorageScope.PROFILE); - if (stored) { - this._recentlyPickedFolders = JSON.parse(stored).map((s: string) => URI.parse(s)); - } - } catch { /* ignore */ } - - // Pre-fetch recently opened folders - this.workspacesService.getRecentlyOpened().then(recent => { - this._cachedRecentFolders = recent.workspaces.filter(isRecentFolder).slice(0, 10); - }).catch(error => { - this.logService.error('Failed to fetch recently opened workspaces for agent sessions', error); - }); - - // When target changes, regenerate pending resource - this._register(this._targetConfig.onDidChangeSelectedTarget(() => { - this._generatePendingSessionResource(); - this._notifyFolderSelection(); - this._renderExtensionPickers(true); - this._renderLocalModePicker(); + // When target changes, create new session + this._register(this._targetPicker.onDidChangeTarget((target) => { + this._createNewSession(); + const isLocal = target === AgentSessionProviders.Background; + this._isolationModePicker.setVisible(isLocal); + this._branchPicker.setVisible(isLocal); })); - this._register(this._targetConfig.onDidChangeAllowedTargets(() => { - if (this._targetDropdownContainer) { - dom.clearNode(this._targetDropdownContainer); - this._renderTargetDropdown(this._targetDropdownContainer); - } - this._renderExtensionPickers(true); - })); - - // Listen for option group changes to re-render pickers - this._register(this.chatSessionsService.onDidChangeOptionGroups(() => this._renderExtensionPickers())); - - // React to chat session option changes - this._register(this.chatSessionsService.onDidChangeSessionOptions((e: URI | undefined) => { - if (this._pendingSessionResource && isEqual(this._pendingSessionResource, e)) { - this._syncOptionsFromSession(this._pendingSessionResource); - this._renderExtensionPickers(); - } - })); - - const workspaceFolderCountKey = new Set([WorkspaceFolderCountContext.key]); this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(workspaceFolderCountKey)) { - this._renderExtensionPickers(true); - } if (this._whenClauseKeys.size > 0 && e.affectsSome(this._whenClauseKeys)) { this._renderExtensionPickers(true); } @@ -319,6 +162,7 @@ class NewChatWidget extends Disposable { // Input area inside the input slot const inputArea = dom.$('.sessions-chat-input-area'); this._contextAttachments.registerDropTarget(inputArea); + this._contextAttachments.registerPasteHandler(inputArea); // Attachments row (plus button + pills) inside input area, above editor const attachRow = dom.append(inputArea, dom.$('.sessions-chat-attach-row')); @@ -330,11 +174,17 @@ class NewChatWidget extends Disposable { this._createBottomToolbar(inputArea); this._inputSlot.appendChild(inputArea); - // Local mode picker (below the input, shown when Local is selected) - this._localModeContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); - this._localModeDropdownContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-left')); - dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-spacer')); - this._localModePickersContainer = dom.append(this._localModeContainer, dom.$('.sessions-chat-local-mode-right')); + // Isolation mode and branch pickers (below the input, shown when Local target is selected) + const isolationContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); + this._isolationModePicker.render(isolationContainer); + dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-spacer')); + const branchContainer = dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-right')); + this._branchPicker.render(branchContainer); + + // Set initial visibility based on default target + const isLocal = this._targetPicker.selectedTarget === AgentSessionProviders.Background; + this._isolationModePicker.setVisible(isLocal); + this._branchPicker.setVisible(isLocal); // Render target buttons & extension pickers this._renderOptionGroupPickers(); @@ -342,49 +192,75 @@ class NewChatWidget extends Disposable { // Initialize model picker this._initDefaultModel(); - // Generate pending resource for option changes - this._generatePendingSessionResource(); - - // Render local mode picker - this._renderLocalModePicker(); + // Create initial session + this._createNewSession(); // Reveal welcomeElement.classList.add('revealed'); } - private _getEffectiveTarget(): AgentSessionProviders | undefined { - const target = this._targetConfig.selectedTarget.get(); - if (target === AgentSessionProviders.Local && this._localMode === 'worktree') { - return AgentSessionProviders.Background; - } - return target; - } - - private readonly _pendingSessionResources = new Map(); - - private _generatePendingSessionResource(): void { - const target = this._getEffectiveTarget(); - if (!target || target === AgentSessionProviders.Local) { - this._pendingSessionResource = undefined; - return; - } - - // Reuse existing pending resource for the same target type - const existing = this._pendingSessionResources.get(target); - if (existing) { - this._pendingSessionResource = existing; - return; - } - - this._pendingSessionResource = getResourceForNewChatSession({ + private async _createNewSession(): Promise { + const target = this._targetPicker.selectedTarget; + const defaultRepoUri = this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + const resource = getResourceForNewChatSession({ type: target, position: this._options.sessionPosition ?? ChatSessionPosition.Sidebar, displayName: '', }); - this._pendingSessionResources.set(target, this._pendingSessionResource); - this.sessionsManagementService.createNewPendingSession(this._pendingSessionResource,) - .catch((err) => this.logService.trace('Failed to create pending session:', err)); + try { + const session = await this.sessionsManagementService.createNewSessionForTarget(target, resource, defaultRepoUri); + this._setNewSession(session); + } catch (e) { + this.logService.error('Failed to create new session:', e); + } + } + + private _setNewSession(session: INewSession): void { + this._newSession.value = session; + + // Wire pickers to the new session + this._folderPicker.setNewSession(session); + this._isolationModePicker.setNewSession(session); + this._branchPicker.setNewSession(session); + + // Set the current model on the session + const currentModel = this._currentLanguageModel.get(); + if (currentModel) { + session.setModelId(currentModel.identifier); + } + + // Open repository for the session's repoUri + if (session.repoUri) { + this._openRepository(session.repoUri); + } + + // Render extension pickers for the new session + this._renderExtensionPickers(true); + + // Listen for session changes + this._newSessionListener.value = session.onDidChange((changeType) => { + if (changeType === 'repoUri' && session.repoUri) { + this._openRepository(session.repoUri); + } + if (changeType === 'isolationMode') { + this._branchPicker.setVisible(session.isolationMode === 'worktree'); + } + if (changeType === 'options') { + this._syncOptionsFromSession(session.resource); + this._renderExtensionPickers(); + } + }); + } + + private _openRepository(folderUri: URI): void { + this.gitService.openRepository(folderUri).then(repository => { + this._isolationModePicker.setRepository(repository); + this._branchPicker.setRepository(repository); + }).catch(() => { + this._isolationModePicker.setRepository(undefined); + this._branchPicker.setRepository(undefined); + }); } // --- Editor --- @@ -441,7 +317,37 @@ class NewChatWidget extends Disposable { attachButton.title = localize('addContext', "Add Context..."); attachButton.ariaLabel = localize('addContext', "Add Context..."); dom.append(attachButton, renderIcon(Codicon.add)); - this._register(dom.addDisposableListener(attachButton, dom.EventType.CLICK, () => this._contextAttachments.showPicker())); + this._register(dom.addDisposableListener(attachButton, dom.EventType.CLICK, () => { + this._contextAttachments.showPicker(this._getContextFolderUri()); + })); + } + + /** + * Returns the folder URI for the context picker based on the current target. + * Local targets use the workspace folder; cloud targets construct a github-remote-file:// URI. + */ + private _getContextFolderUri(): URI | undefined { + const target = this._targetPicker.selectedTarget; + + if (target === AgentSessionProviders.Background) { + return this._folderPicker.selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; + } + + // For cloud targets, look for a repository option in the selected options + for (const [groupId, option] of this._selectedOptions) { + if (isRepoOrFolderGroup({ id: groupId, name: groupId, items: [] })) { + const nwo = option.id; // e.g. "owner/repo" + if (nwo && nwo.includes('/')) { + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${nwo}/HEAD`, + }); + } + } + } + + return undefined; } private _createBottomToolbar(container: HTMLElement): void { @@ -467,10 +373,11 @@ class NewChatWidget extends Disposable { currentModel: this._currentLanguageModel, setModel: (model: ILanguageModelChatMetadataAndIdentifier) => { this._currentLanguageModel.set(model, undefined); + this._newSession.value?.setModelId(model.identifier); }, getModels: () => this._getAvailableModels(), canManageModels: () => true, - showCuratedModels: () => this._localMode === 'workspace', + showCuratedModels: () => false, }; const pickerOptions: IChatInputPickerOptions = { @@ -516,9 +423,6 @@ class NewChatWidget extends Disposable { if (!model.metadata.isUserSelectable) { return false; } - if (model.metadata.targetChatSessionType === AgentSessionProviders.Background) { - return false; - } return true; } @@ -529,166 +433,46 @@ class NewChatWidget extends Disposable { return; } - this._disposePickerWidgets(); + this._clearExtensionPickers(); dom.clearNode(this._pickersContainer); const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers')); // Left half: target switcher (right-justified within its half) const leftHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-left-half')); - this._targetDropdownContainer = dom.append(leftHalf, dom.$('.sessions-chat-dropdown-wrapper')); - this._renderTargetDropdown(this._targetDropdownContainer); + const targetDropdownContainer = dom.append(leftHalf, dom.$('.sessions-chat-dropdown-wrapper')); + this._targetPicker.render(targetDropdownContainer); // Right half: separator + pickers (left-justified within its half) const rightHalf = dom.append(pickersRow, dom.$('.sessions-chat-pickers-right-half')); this._extensionPickersLeftContainer = dom.append(rightHalf, dom.$('.sessions-chat-pickers-left-separator')); this._extensionPickersRightContainer = dom.append(rightHalf, dom.$('.sessions-chat-extension-pickers-right')); + // Folder picker (rendered once, shown/hidden based on target) + this._folderPickerContainer = this._folderPicker.render(rightHalf); + this._folderPickerContainer.style.display = 'none'; + this._renderExtensionPickers(); } - private _renderTargetDropdown(container: HTMLElement): void { - const allowed = this._targetConfig.allowedTargets.get(); - if (allowed.size === 0) { - return; - } - - const activeType = this._targetConfig.selectedTarget.get() ?? AgentSessionProviders.Local; - const targets = [AgentSessionProviders.Local, AgentSessionProviders.Cloud].filter(t => allowed.has(t)); - const activeIndex = targets.indexOf(activeType); - - const radio = new Radio({ - items: targets.map(target => ({ - text: getAgentSessionProviderName(target), - isActive: target === activeType, - })), - }); - this._welcomeContentDisposables.add(radio); - container.appendChild(radio.domNode); - - if (activeIndex >= 0) { - radio.setActiveItem(activeIndex); - } - - this._welcomeContentDisposables.add(radio.onDidSelect(index => { - this._targetConfig.setSelectedTarget(targets[index]); - })); - } - - // --- Local mode picker (Workspace / Worktree) --- - - private readonly _localModeDisposables = this._register(new DisposableStore()); - - private _renderLocalModePicker(): void { - if (!this._localModeContainer || !this._localModeDropdownContainer || !this._localModePickersContainer) { - return; - } - - this._localModeDisposables.clear(); - dom.clearNode(this._localModeDropdownContainer); - dom.clearNode(this._localModePickersContainer); - - const selectedTarget = this._targetConfig.selectedTarget.get(); - if (selectedTarget !== AgentSessionProviders.Local) { - this._localModeContainer.style.visibility = 'hidden'; - return; - } - - this._localModeContainer.style.visibility = ''; - - // Dropdown button for Workspace / Worktree - const modeLabel = this._localMode === 'workspace' - ? localize('localMode.workspace', "Workspace") - : localize('localMode.worktree', "Worktree"); - const modeIcon = this._localMode === 'workspace' ? Codicon.folder : Codicon.worktree; - - const modeAction = toAction({ id: 'localMode', label: modeLabel, run: () => { } }); - const modeDropdown = this._localModeDisposables.add(new LabeledDropdownMenuActionViewItem( - modeAction, - { - getActions: () => [ - toAction({ - id: 'localMode.workspace', - label: localize('localMode.workspace', "Workspace"), - checked: this._localMode === 'workspace', - run: () => this._setLocalMode('workspace'), - }), - toAction({ - id: 'localMode.worktree', - label: localize('localMode.worktree', "Worktree"), - checked: this._localMode === 'worktree', - run: () => this._setLocalMode('worktree'), - }), - ], - }, - this.contextMenuService, - { classNames: [...ThemeIcon.asClassNameArray(modeIcon)] } - )); - const modeSlot = dom.append(this._localModeDropdownContainer, dom.$('.sessions-chat-picker-slot')); - modeDropdown.render(modeSlot); - - // Render pickers in the right side - this._renderLocalModePickers(); - } - - private _setLocalMode(mode: 'workspace' | 'worktree'): void { - if (this._localMode !== mode) { - this._localMode = mode; - this._generatePendingSessionResource(); - this._notifyFolderSelection(); - this._renderLocalModePicker(); - } - } - - private _notifyFolderSelection(): void { - this._selectedOptions.clear(); - if (!this._pendingSessionResource) { - return; - } - const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; - if (folderUri) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: 'repository', value: folderUri.fsPath }] - ).catch((err) => this.logService.error('Failed to notify extension of folder selection:', err)); - } - } - - private _addToRecentlyPickedFolders(folderUri: URI): void { - this._recentlyPickedFolders = [folderUri, ...this._recentlyPickedFolders.filter(f => !isEqual(f, folderUri))].slice(0, 10); - this.storageService.store('agentSessions.recentlyPickedFolders', JSON.stringify(this._recentlyPickedFolders.map(f => f.toString())), StorageScope.PROFILE, StorageTarget.MACHINE); - } - - private _renderLocalModePickers(): void { - if (!this._localModePickersContainer) { - return; - } - dom.clearNode(this._localModePickersContainer); - - if (this._localMode === 'worktree') { - // Worktree mode: render extension pickers for Background provider - this._renderExtensionPickersInContainer(this._localModePickersContainer, AgentSessionProviders.Background); - } - } - - // --- Welcome: Extension option pickers --- + // --- Welcome: Extension option pickers (Cloud target only) --- private _renderExtensionPickers(force?: boolean): void { if (!this._extensionPickersRightContainer) { return; } - const activeSessionType = this._getEffectiveTarget(); - if (!activeSessionType) { - this._clearExtensionPickers(); - return; - } + const activeSessionType = this._targetPicker.selectedTarget; - // For Local target, show folder picker in top row and handle bottom row - if (this._targetConfig.selectedTarget.get() === AgentSessionProviders.Local) { + // Extension pickers are only shown for Cloud target + if (activeSessionType === AgentSessionProviders.Background) { this._clearExtensionPickers(); - this._renderLocalFolderPickerInTopRow(); - this._renderLocalModePicker(); + if (this._folderPickerContainer) { + this._folderPickerContainer.style.display = ''; + } + if (this._extensionPickersLeftContainer) { + this._extensionPickersLeftContainer.style.display = 'block'; + } return; } @@ -724,16 +508,6 @@ class NewChatWidget extends Disposable { return; } - visibleGroups.sort((a, b) => { - // Repo/folder pickers first, then others - const aRepo = isRepoOrFolderGroup(a) ? 0 : 1; - const bRepo = isRepoOrFolderGroup(b) ? 0 : 1; - if (aRepo !== bRepo) { - return aRepo - bRepo; - } - return (a.when ? 1 : 0) - (b.when ? 1 : 0); - }); - if (!force && this._pickerWidgets.size === visibleGroups.length) { const allMatch = visibleGroups.every(g => this._pickerWidgets.has(g.id)); if (allMatch) { @@ -743,7 +517,6 @@ class NewChatWidget extends Disposable { this._clearExtensionPickers(); - // Show the separator between target switcher and extension pickers if (this._extensionPickersLeftContainer) { this._extensionPickersLeftContainer.style.display = 'block'; } @@ -765,12 +538,7 @@ class NewChatWidget extends Disposable { this._updateOptionContextKey(optionGroup.id, option.id); emitter.fire(option); - if (this._pendingSessionResource) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: optionGroup.id, value: option }] - ).catch((err) => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); - } + this._newSession.value?.setOption(optionGroup.id, option); this._renderExtensionPickers(true); }, @@ -778,7 +546,7 @@ class NewChatWidget extends Disposable { const groups = this.chatSessionsService.getOptionGroupsForSessionType(activeSessionType); return groups?.find((g: { id: string }) => g.id === optionGroup.id); }, - getSessionResource: () => this._pendingSessionResource, + getSessionResource: () => this._newSession.value?.resource, }; const action = toAction({ id: optionGroup.id, label: optionGroup.name, run: () => { } }); @@ -790,161 +558,7 @@ class NewChatWidget extends Disposable { this._pickerWidgetDisposables.add(widget); this._pickerWidgets.set(optionGroup.id, widget); - // All pickers go to the right - const targetContainer = this._extensionPickersRightContainer!; - - const slot = dom.append(targetContainer, dom.$('.sessions-chat-picker-slot')); - widget.render(slot); - } - } - - private _renderLocalFolderPickerInTopRow(): void { - if (!this._extensionPickersRightContainer) { - return; - } - - // Show the separator - if (this._extensionPickersLeftContainer) { - this._extensionPickersLeftContainer.style.display = 'block'; - } - - this._renderLocalFolderPickerInContainer(this._extensionPickersRightContainer, this._pickerWidgetDisposables); - } - - private _renderLocalFolderPickerInContainer(container: HTMLElement, disposables: DisposableStore): void { - const currentFolderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; - const folderName = currentFolderUri ? basename(currentFolderUri) : localize('pickFolder', "Pick Folder"); - - const switchFolder = async (folderUri: URI) => { - this._selectedFolderUri = folderUri; - this._addToRecentlyPickedFolders(folderUri); - this.storageService.store('agentSessions.lastPickedFolder', folderUri.toString(), StorageScope.PROFILE, StorageTarget.MACHINE); - this._notifyFolderSelection(); - this._renderExtensionPickers(true); - }; - - const folderAction = toAction({ id: 'folderPicker', label: folderName, run: () => { } }); - const folderDropdown = disposables.add(new LabeledDropdownMenuActionViewItem( - folderAction, - { - getActions: () => this._getFolderPickerActions(currentFolderUri, switchFolder), - }, - this.contextMenuService, - { classNames: [...ThemeIcon.asClassNameArray(Codicon.folder)] } - )); - const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); - folderDropdown.render(slot); - } - - private _getFolderPickerActions(currentFolderUri: URI | undefined, switchFolder: (uri: URI) => Promise): (ReturnType | Separator)[] { - const seenUris = new Set(); - if (currentFolderUri) { - seenUris.add(currentFolderUri.toString()); - } - - const actions: (ReturnType | Separator)[] = []; - - // Combine recently picked folders and recently opened folders (picked first, then opened) - const allFolders: { uri: URI; label?: string }[] = [ - ...this._recentlyPickedFolders.map(uri => ({ uri })), - ...this._cachedRecentFolders.map(r => ({ uri: r.folderUri, label: r.label })), - ]; - for (const folder of allFolders) { - const key = folder.uri.toString(); - if (seenUris.has(key)) { - continue; - } - seenUris.add(key); - actions.push(toAction({ - id: key, - label: folder.label || basename(folder.uri), - run: () => switchFolder(folder.uri), - })); - } - - actions.push(new Separator()); - actions.push(toAction({ - id: 'browse', - label: localize('browseFolder', "Browse..."), - run: async () => { - const selected = await this.fileDialogService.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - title: localize('selectFolder', "Select Folder"), - }); - if (selected?.[0]) { - await switchFolder(selected[0]); - } - }, - })); - - return actions; - } - - private _renderExtensionPickersInContainer(container: HTMLElement, sessionType: AgentSessionProviders): void { - const optionGroups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType); - if (!optionGroups || optionGroups.length === 0) { - return; - } - - const visibleGroups: IChatSessionProviderOptionGroup[] = []; - for (const group of optionGroups) { - if (isModelOptionGroup(group)) { - continue; - } - if (group.id === 'repository') { - continue; - } - const hasItems = group.items.length > 0 || (group.commands || []).length > 0 || !!group.searchable; - const passesWhenClause = this._evaluateOptionGroupVisibility(group); - if (hasItems && passesWhenClause) { - visibleGroups.push(group); - } - } - - for (const optionGroup of visibleGroups) { - const initialItem = this._getDefaultOptionForGroup(optionGroup); - const initialState = { group: optionGroup, item: initialItem }; - - if (initialItem) { - this._updateOptionContextKey(optionGroup.id, initialItem.id); - } - - const emitter = this._getOrCreateOptionEmitter(optionGroup.id); - const itemDelegate: IChatSessionPickerDelegate = { - getCurrentOption: () => this._selectedOptions.get(optionGroup.id) ?? this._getDefaultOptionForGroup(optionGroup), - onDidChangeOption: emitter.event, - setOption: (option: IChatSessionProviderOptionItem) => { - this._selectedOptions.set(optionGroup.id, option); - this._updateOptionContextKey(optionGroup.id, option.id); - emitter.fire(option); - - if (this._pendingSessionResource) { - this.chatSessionsService.notifySessionOptionsChange( - this._pendingSessionResource, - [{ optionId: optionGroup.id, value: option }] - ).catch((err) => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); - } - - this._renderLocalModePickers(); - }, - getOptionGroup: () => { - const groups = this.chatSessionsService.getOptionGroupsForSessionType(sessionType); - return groups?.find((g: { id: string }) => g.id === optionGroup.id); - }, - getSessionResource: () => this._pendingSessionResource, - }; - - const action = toAction({ id: optionGroup.id, label: optionGroup.name, run: () => { } }); - const widget = this.instantiationService.createInstance( - optionGroup.searchable ? SearchableOptionPickerActionItem : ChatSessionPickerActionItem, - action, initialState, itemDelegate - ); - - this._localModeDisposables.add(widget); - - const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + const slot = dom.append(this._extensionPickersRightContainer!, dom.$('.sessions-chat-picker-slot')); widget.render(slot); } } @@ -963,8 +577,8 @@ class NewChatWidget extends Disposable { return selectedOption; } - if (this._pendingSessionResource) { - const sessionOption = this.chatSessionsService.getSessionOption(this._pendingSessionResource, optionGroup.id); + if (this._newSession.value) { + const sessionOption = this.chatSessionsService.getSessionOption(this._newSession.value.resource, optionGroup.id); if (!isString(sessionOption)) { return sessionOption; } @@ -974,7 +588,7 @@ class NewChatWidget extends Disposable { } private _syncOptionsFromSession(sessionResource: URI): void { - const activeSessionType = this._getEffectiveTarget(); + const activeSessionType = this._targetPicker.selectedTarget; if (!activeSessionType) { return; } @@ -1025,16 +639,13 @@ class NewChatWidget extends Disposable { return emitter; } - private _disposePickerWidgets(): void { - this._pickerWidgetDisposables.clear(); - this._pickerWidgets.clear(); - this._optionEmitters.clear(); - } - private _clearExtensionPickers(): void { this._pickerWidgetDisposables.clear(); this._pickerWidgets.clear(); this._optionEmitters.clear(); + if (this._folderPickerContainer) { + this._folderPickerContainer.style.display = 'none'; + } if (this._extensionPickersLeftContainer) { this._extensionPickersLeftContainer.style.display = 'none'; } @@ -1047,50 +658,23 @@ class NewChatWidget extends Disposable { private _send(): void { const query = this._editor.getModel()?.getValue().trim(); - if (!query) { + const session = this._newSession.value; + if (!query || !session) { return; } - const target = this._getEffectiveTarget(); - if (!target) { - this.logService.warn('ChatWelcomeWidget: No target selected, cannot create session'); - return; - } + session.setQuery(query); + session.setAttachedContext( + this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined + ); - const position = this._options.sessionPosition ?? ChatSessionPosition.Sidebar; - const resource = this._pendingSessionResource - ?? getResourceForNewChatSession({ type: target, position, displayName: '' }); - - const contribution = target !== AgentSessionProviders.Local - ? this.chatSessionsService.getChatSessionContribution(target) - : undefined; - - const sendOptions: IChatSendRequestOptions = { - location: ChatAgentLocation.Chat, - userSelectedModelId: this._currentLanguageModel.get()?.identifier, - modeInfo: { - kind: ChatModeKind.Agent, - isBuiltin: true, - modeInstructions: undefined, - modeId: 'agent', - applyCodeBlockSuggestionId: undefined, - }, - agentIdSilent: contribution?.type, - attachedContext: this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined, - }; - - const folderUri = this._selectedFolderUri ?? this.workspaceContextService.getWorkspace().folders[0]?.uri; - - this._options.onSendRequest?.({ - resource, - target, - query, - sendOptions, - selectedOptions: new Map(this._selectedOptions), - folderUri, - attachedContext: this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined, - }); + this.sessionsManagementService.sendRequestForNewSession( + session.resource + ).catch(e => this.logService.error('Failed to send request:', e)); + // Clear sent session so a fresh one is created next time + this._newSession.clear(); + this._newSessionListener.clear(); this._contextAttachments.clear(); } @@ -1100,16 +684,12 @@ class NewChatWidget extends Disposable { this._editor?.layout(); } - setVisible(_visible: boolean): void { - // no-op - } - focusInput(): void { this._editor?.focus(); } updateAllowedTargets(targets: AgentSessionProviders[]): void { - this._targetConfig.setAllowedTargets(targets); + this._targetPicker.updateAllowedTargets(targets); } } @@ -1137,9 +717,7 @@ export class NewChatViewPane extends ViewPane { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, - @ISessionsManagementService private readonly activeSessionService: ISessionsManagementService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); } @@ -1150,15 +728,8 @@ export class NewChatViewPane extends ViewPane { this._widget = this._register(this.instantiationService.createInstance( NewChatWidget, { - targetConfig: { - allowedTargets: this.computeAllowedTargets(), - defaultTarget: AgentSessionProviders.Local, - }, - onSendRequest: (data) => { - this.activeSessionService.sendRequestForNewSession( - data.resource, data.query, data.sendOptions, data.selectedOptions, data.folderUri - ).catch(e => this.logService.error('NewChatViewPane: Failed to open session and send request', e)); - }, + allowedTargets: this.computeAllowedTargets(), + defaultTarget: AgentSessionProviders.Background, } satisfies INewChatWidgetOptions, )); @@ -1171,7 +742,7 @@ export class NewChatViewPane extends ViewPane { } private computeAllowedTargets(): AgentSessionProviders[] { - const targets: AgentSessionProviders[] = [AgentSessionProviders.Local, AgentSessionProviders.Cloud]; + const targets: AgentSessionProviders[] = [AgentSessionProviders.Background, AgentSessionProviders.Cloud]; return targets; } @@ -1187,7 +758,6 @@ export class NewChatViewPane extends ViewPane { override setVisible(visible: boolean): void { super.setVisible(visible); - this._widget?.setVisible(visible); if (visible) { this._widget?.focusInput(); } @@ -1220,20 +790,3 @@ function isRepoOrFolderGroup(group: IChatSessionProviderOptionGroup): boolean { nameLower === 'repository' || nameLower === 'repositories' || nameLower === 'folder' || nameLower === 'folders'; } - -function getAgentSessionProviderName(provider: AgentSessionProviders): string { - switch (provider) { - case AgentSessionProviders.Local: - return localize('chat.session.providerLabel.local', "Local"); - case AgentSessionProviders.Background: - return localize('chat.session.providerLabel.background', "Worktree"); - case AgentSessionProviders.Cloud: - return localize('chat.session.providerLabel.cloud', "Cloud"); - case AgentSessionProviders.Claude: - return 'Claude'; - case AgentSessionProviders.Codex: - return 'Codex'; - case AgentSessionProviders.Growth: - return 'Growth'; - } -} diff --git a/src/vs/sessions/contrib/chat/browser/newSession.ts b/src/vs/sessions/contrib/chat/browser/newSession.ts new file mode 100644 index 00000000000..266a2c2e7f9 --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/newSession.ts @@ -0,0 +1,221 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; +import { IsolationMode } from './sessionTargetPicker.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { IActiveSessionItem } from '../../sessions/browser/sessionsManagementService.js'; + +import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; + +export type NewSessionChangeType = 'repoUri' | 'isolationMode' | 'branch' | 'options'; + +/** + * A new session represents a session being configured before the first + * request is sent. It holds the user's selections (repoUri, isolationMode) + * and fires a single event when any property changes. + */ +export interface INewSession extends IDisposable { + readonly resource: URI; + readonly target: AgentSessionProviders; + readonly activeSessionItem: IActiveSessionItem; + readonly repoUri: URI | undefined; + readonly isolationMode: IsolationMode; + readonly branch: string | undefined; + readonly modelId: string | undefined; + readonly query: string | undefined; + readonly attachedContext: IChatRequestVariableEntry[] | undefined; + readonly selectedOptions: ReadonlyMap; + readonly onDidChange: Event; + setRepoUri(uri: URI): void; + setIsolationMode(mode: IsolationMode): void; + setBranch(branch: string | undefined): void; + setModelId(modelId: string | undefined): void; + setQuery(query: string): void; + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void; + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void; +} + +const REPOSITORY_OPTION_ID = 'repository'; +const BRANCH_OPTION_ID = 'branch'; +const ISOLATION_OPTION_ID = 'isolation'; + +/** + * Local new session for Background agent sessions. + * Fires `onDidChange` for both `repoUri` and `isolationMode` changes. + * Notifies the extension service with session options for each property change. + */ +export class LocalNewSession extends Disposable implements INewSession { + + private _repoUri: URI | undefined; + private _isolationMode: IsolationMode = 'worktree'; + private _branch: string | undefined; + private _modelId: string | undefined; + private _query: string | undefined; + private _attachedContext: IChatRequestVariableEntry[] | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + readonly target = AgentSessionProviders.Background; + readonly selectedOptions = new Map(); + + get resource(): URI { return this.activeSessionItem.resource; } + get repoUri(): URI | undefined { return this._repoUri; } + get isolationMode(): IsolationMode { return this._isolationMode; } + get branch(): string | undefined { return this._branch; } + get modelId(): string | undefined { return this._modelId; } + get query(): string | undefined { return this._query; } + get attachedContext(): IChatRequestVariableEntry[] | undefined { return this._attachedContext; } + + constructor( + readonly activeSessionItem: IActiveSessionItem, + defaultRepoUri: URI | undefined, + private readonly chatSessionsService: IChatSessionsService, + private readonly logService: ILogService, + ) { + super(); + if (defaultRepoUri) { + this._repoUri = defaultRepoUri; + this.setOption(REPOSITORY_OPTION_ID, defaultRepoUri.fsPath); + } + } + + setRepoUri(uri: URI): void { + this._repoUri = uri; + this._isolationMode = 'workspace'; + this._branch = undefined; + this._onDidChange.fire('repoUri'); + this.setOption(REPOSITORY_OPTION_ID, uri.fsPath); + } + + setIsolationMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; + this._onDidChange.fire('isolationMode'); + this.setOption(ISOLATION_OPTION_ID, mode); + } + } + + setBranch(branch: string | undefined): void { + if (this._branch !== branch) { + this._branch = branch; + this._onDidChange.fire('branch'); + this.setOption(BRANCH_OPTION_ID, branch ?? ''); + } + } + + setModelId(modelId: string | undefined): void { + this._modelId = modelId; + } + + setQuery(query: string): void { + this._query = query; + } + + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void { + this._attachedContext = context; + } + + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void { + if (typeof value === 'string') { + this.selectedOptions.set(optionId, { id: value, name: value }); + } else { + this.selectedOptions.set(optionId, value); + } + this.chatSessionsService.notifySessionOptionsChange( + this.resource, + [{ optionId, value }] + ).catch((err) => this.logService.error(`Failed to notify session option ${optionId} change:`, err)); + } +} + +/** + * Remote new session for Cloud agent sessions. + * Fires `onDidChange` and notifies the extension service when `repoUri` changes. + * Ignores `isolationMode` (not relevant for cloud). + */ +export class RemoteNewSession extends Disposable implements INewSession { + + private _repoUri: URI | undefined; + private _isolationMode: IsolationMode = 'worktree'; + private _modelId: string | undefined; + private _query: string | undefined; + private _attachedContext: IChatRequestVariableEntry[] | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + readonly selectedOptions = new Map(); + + get resource(): URI { return this.activeSessionItem.resource; } + get repoUri(): URI | undefined { return this._repoUri; } + get isolationMode(): IsolationMode { return this._isolationMode; } + get branch(): string | undefined { return undefined; } + get modelId(): string | undefined { return this._modelId; } + get query(): string | undefined { return this._query; } + get attachedContext(): IChatRequestVariableEntry[] | undefined { return this._attachedContext; } + + constructor( + readonly activeSessionItem: IActiveSessionItem, + readonly target: AgentSessionProviders, + private readonly chatSessionsService: IChatSessionsService, + private readonly logService: ILogService, + ) { + super(); + + // Listen for extension-driven option group and session option changes + this._register(this.chatSessionsService.onDidChangeOptionGroups(() => { + this._onDidChange.fire('options'); + })); + this._register(this.chatSessionsService.onDidChangeSessionOptions((e: URI | undefined) => { + if (isEqual(this.resource, e)) { + this._onDidChange.fire('options'); + } + })); + } + + setRepoUri(uri: URI): void { + this._repoUri = uri; + this._onDidChange.fire('repoUri'); + this.setOption('repository', uri.fsPath); + } + + setIsolationMode(_mode: IsolationMode): void { + // No-op for remote sessions — isolation mode is not relevant + } + + setBranch(_branch: string | undefined): void { + // No-op for remote sessions — branch is not relevant + } + + setModelId(modelId: string | undefined): void { + this._modelId = modelId; + } + + setQuery(query: string): void { + this._query = query; + } + + setAttachedContext(context: IChatRequestVariableEntry[] | undefined): void { + this._attachedContext = context; + } + + setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void { + if (typeof value !== 'string') { + this.selectedOptions.set(optionId, value); + } + this._onDidChange.fire('options'); + this.chatSessionsService.notifySessionOptionsChange( + this.resource, + [{ optionId, value }] + ).catch((err) => this.logService.error(`Failed to notify extension of ${optionId} change:`, err)); + } +} diff --git a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts index dfa11fd710d..d196c18f303 100644 --- a/src/vs/sessions/contrib/chat/browser/runScriptAction.ts +++ b/src/vs/sessions/contrib/chat/browser/runScriptAction.ts @@ -17,6 +17,7 @@ import { ITerminalService } from '../../../../workbench/contrib/terminal/browser import { Menus } from '../../../browser/menus.js'; import { ISessionsConfigurationService, ISessionScript } from './sessionsConfigurationService.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; +import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; // Menu IDs - exported for use in auxiliary bar part @@ -190,4 +191,5 @@ MenuRegistry.appendMenuItem(Menus.TitleBarRight, { icon: Codicon.play, group: 'navigation', order: 8, + when: IsAuxiliaryWindowContext.toNegated() }); diff --git a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts new file mode 100644 index 00000000000..1d762632f9f --- /dev/null +++ b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { toAction } from '../../../../base/common/actions.js'; +import { Radio } from '../../../../base/browser/ui/radio/radio.js'; +import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { localize } from '../../../../nls.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { IGitRepository } from '../../../../workbench/contrib/git/common/gitService.js'; +import { INewSession } from './newSession.js'; + +/** + * A dropdown menu action item that shows an icon, a text label, and a chevron. + */ +class LabeledDropdownMenuActionViewItem extends DropdownMenuActionViewItem { + protected override renderLabel(element: HTMLElement): null { + const classNames = typeof this.options.classNames === 'string' + ? this.options.classNames.split(/\s+/g).filter(s => !!s) + : (this.options.classNames ?? []); + if (classNames.length > 0) { + const icon = dom.append(element, dom.$('span')); + icon.classList.add('codicon', ...classNames); + } + + const label = dom.append(element, dom.$('span.sessions-chat-dropdown-label')); + label.textContent = this._action.label; + + dom.append(element, renderIcon(Codicon.chevronDown)); + + return null; + } +} + +// #region --- Session Target Picker --- + +/** + * A self-contained widget for selecting the session target (Local vs Cloud). + * Encapsulates state, events, and rendering. Can be placed anywhere in the view. + */ +export class SessionTargetPicker extends Disposable { + + private _selectedTarget: AgentSessionProviders; + private _allowedTargets: AgentSessionProviders[]; + + private readonly _onDidChangeTarget = this._register(new Emitter()); + readonly onDidChangeTarget: Event = this._onDidChangeTarget.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _container: HTMLElement | undefined; + + get selectedTarget(): AgentSessionProviders { + return this._selectedTarget; + } + + constructor( + allowedTargets: AgentSessionProviders[], + defaultTarget: AgentSessionProviders, + ) { + super(); + this._allowedTargets = allowedTargets; + this._selectedTarget = allowedTargets.includes(defaultTarget) + ? defaultTarget + : allowedTargets[0]; + } + + /** + * Renders the target radio (Local / Cloud) into the given container. + */ + render(container: HTMLElement): void { + this._container = container; + this._renderRadio(); + } + + updateAllowedTargets(targets: AgentSessionProviders[]): void { + if (targets.length === 0) { + return; + } + this._allowedTargets = targets; + if (!targets.includes(this._selectedTarget)) { + this._selectedTarget = targets[0]; + this._onDidChangeTarget.fire(this._selectedTarget); + } + if (this._container) { + this._renderRadio(); + } + } + + private _renderRadio(): void { + if (!this._container) { + return; + } + + this._renderDisposables.clear(); + dom.clearNode(this._container); + + if (this._allowedTargets.length === 0) { + return; + } + + const targets = [AgentSessionProviders.Background, AgentSessionProviders.Cloud].filter(t => this._allowedTargets.includes(t)); + const activeIndex = targets.indexOf(this._selectedTarget); + + const radio = new Radio({ + items: targets.map(target => ({ + text: getTargetLabel(target), + isActive: target === this._selectedTarget, + })), + }); + this._renderDisposables.add(radio); + this._container.appendChild(radio.domNode); + + if (activeIndex >= 0) { + radio.setActiveItem(activeIndex); + } + + this._renderDisposables.add(radio.onDidSelect(index => { + const target = targets[index]; + if (this._selectedTarget !== target) { + this._selectedTarget = target; + this._onDidChangeTarget.fire(target); + } + })); + } +} + +function getTargetLabel(provider: AgentSessionProviders): string { + switch (provider) { + case AgentSessionProviders.Local: + case AgentSessionProviders.Background: + return localize('chat.session.providerLabel.local', "Local"); + case AgentSessionProviders.Cloud: + return localize('chat.session.providerLabel.cloud', "Cloud"); + case AgentSessionProviders.Claude: + return 'Claude'; + case AgentSessionProviders.Codex: + return 'Codex'; + case AgentSessionProviders.Growth: + return 'Growth'; + } +} + +// #endregion + +// #region --- Isolation Mode Picker --- + +export type IsolationMode = 'worktree' | 'workspace'; + +/** + * A self-contained widget for selecting the isolation mode (Worktree vs Folder). + * Encapsulates state, events, and rendering. Can be placed anywhere in the view. + */ +export class IsolationModePicker extends Disposable { + + private _isolationMode: IsolationMode = 'worktree'; + private _newSession: INewSession | undefined; + private _repository: IGitRepository | undefined; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _container: HTMLElement | undefined; + private _dropdownContainer: HTMLElement | undefined; + + get isolationMode(): IsolationMode { + return this._isolationMode; + } + + constructor( + @IContextMenuService private readonly contextMenuService: IContextMenuService, + ) { + super(); + } + + /** + * Sets the pending session that this picker writes to. + */ + setNewSession(session: INewSession | undefined): void { + this._newSession = session; + } + + /** + * Sets the git repository. When undefined, worktree option is hidden + * and isolation mode falls back to 'workspace'. + */ + setRepository(repository: IGitRepository | undefined): void { + this._repository = repository; + if (repository) { + this._setMode('worktree'); + } else if (this._isolationMode === 'worktree') { + this._setMode('workspace'); + } + this._renderDropdown(); + } + + /** + * Renders the isolation mode dropdown into the given container. + */ + render(container: HTMLElement): void { + this._container = container; + this._dropdownContainer = dom.append(container, dom.$('.sessions-chat-local-mode-left')); + this._renderDropdown(); + } + + /** + * Shows or hides the picker. + */ + setVisible(visible: boolean): void { + if (this._container) { + this._container.style.visibility = visible ? '' : 'hidden'; + } + } + + private _renderDropdown(): void { + if (!this._dropdownContainer) { + return; + } + + this._renderDisposables.clear(); + dom.clearNode(this._dropdownContainer); + + const modeLabel = this._isolationMode === 'worktree' + ? localize('isolationMode.worktree', "Worktree") + : localize('isolationMode.folder', "Folder"); + const modeIcon = this._isolationMode === 'worktree' ? Codicon.worktree : Codicon.folder; + const isDisabled = !this._repository; + + const modeAction = toAction({ id: 'isolationMode', label: modeLabel, run: () => { } }); + const modeDropdown = this._renderDisposables.add(new LabeledDropdownMenuActionViewItem( + modeAction, + { + getActions: () => isDisabled ? [] : [ + toAction({ + id: 'isolationMode.worktree', + label: localize('isolationMode.worktree', "Worktree"), + checked: this._isolationMode === 'worktree', + run: () => this._setMode('worktree'), + }), + toAction({ + id: 'isolationMode.folder', + label: localize('isolationMode.folder', "Folder"), + checked: this._isolationMode === 'workspace', + run: () => this._setMode('workspace'), + }), + ], + }, + this.contextMenuService, + { classNames: [...ThemeIcon.asClassNameArray(modeIcon)] } + )); + const modeSlot = dom.append(this._dropdownContainer, dom.$('.sessions-chat-picker-slot')); + modeDropdown.render(modeSlot); + modeSlot.classList.toggle('disabled', isDisabled); + } + + private _setMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; + this._newSession?.setIsolationMode(mode); + this._onDidChange.fire(mode); + this._renderDropdown(); + } + } +} + +// #endregion diff --git a/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts b/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts index 57de7b183e9..73601b49ce1 100644 --- a/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts +++ b/src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts @@ -36,7 +36,8 @@ Registry.as(Extensions.Configuration).registerDefaultCon 'workbench.startupEditor': 'none', 'workbench.tips.enabled': false, 'workbench.layoutControl.type': 'toggles', - 'workbench.editor.allowOpenInModalEditor': false, + 'workbench.editor.useModal': 'on', + 'workbench.editor.labelFormat': 'short', 'window.menuStyle': 'custom', 'window.dialogStyle': 'custom', diff --git a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts new file mode 100644 index 00000000000..1d67b91d1b4 --- /dev/null +++ b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; +import { GitHubFileSystemProvider, GITHUB_REMOTE_FILE_SCHEME } from './githubFileSystemProvider.js'; + +// --- View registration is currently disabled in favor of the "Add Context" picker. +// The Files view will be re-enabled once we finalize the sessions auxiliary bar layout. + +// --- Session Repo FileSystem Provider Registration + +class GitHubFileSystemProviderContribution extends Disposable { + + static readonly ID = 'workbench.contrib.githubFileSystemProvider'; + + constructor( + @IFileService fileService: IFileService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + const provider = this._register(instantiationService.createInstance(GitHubFileSystemProvider)); + this._register(fileService.registerProvider(GITHUB_REMOTE_FILE_SCHEME, provider)); + } +} + +registerWorkbenchContribution2( + GitHubFileSystemProviderContribution.ID, + GitHubFileSystemProviderContribution, + WorkbenchPhase.AfterRestored +); diff --git a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts new file mode 100644 index 00000000000..0ef75870432 --- /dev/null +++ b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts @@ -0,0 +1,577 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/fileTreeView.css'; +import * as dom from '../../../../base/browser/dom.js'; +import { IAsyncDataSource, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; +import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; +import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, derived, IObservable, observableFromEvent } from '../../../../base/common/observable.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { URI } from '../../../../base/common/uri.js'; +import { localize } from '../../../../nls.js'; +import { FileKind, IFileService, IFileStat } from '../../../../platform/files/common/files.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { WorkbenchCompressibleAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { IResourceLabel, ResourceLabels } from '../../../../workbench/browser/labels.js'; +import { IViewPaneOptions, ViewPane } from '../../../../workbench/browser/parts/views/viewPane.js'; +import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/viewPaneContainer.js'; +import { IViewDescriptorService } from '../../../../workbench/common/views.js'; +import { createFileIconThemableTreeContainerScope } from '../../../../workbench/contrib/files/browser/views/explorerView.js'; +import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; +import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js'; +import { isIChatSessionFileChange2 } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../workbench/services/editor/common/editorService.js'; +import { IExtensionService } from '../../../../workbench/services/extensions/common/extensions.js'; +import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js'; +import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { ISessionsManagementService, IActiveSessionItem } from '../../sessions/browser/sessionsManagementService.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from './githubFileSystemProvider.js'; +import { basename } from '../../../../base/common/path.js'; +import { isEqual } from '../../../../base/common/resources.js'; + +const $ = dom.$; + +// --- Constants + +export const FILE_TREE_VIEW_CONTAINER_ID = 'workbench.view.agentSessions.fileTreeContainer'; +export const FILE_TREE_VIEW_ID = 'workbench.view.agentSessions.fileTree'; + +// --- Tree Item + +interface IFileTreeItem { + readonly uri: URI; + readonly name: string; + readonly isDirectory: boolean; +} + +// --- Data Source + +class FileTreeDataSource implements IAsyncDataSource { + + constructor( + private readonly fileService: IFileService, + private readonly logService: ILogService, + ) { } + + hasChildren(element: URI | IFileTreeItem): boolean { + if (URI.isUri(element)) { + return true; // root + } + return element.isDirectory; + } + + async getChildren(element: URI | IFileTreeItem): Promise { + const uri = URI.isUri(element) ? element : element.uri; + + try { + const stat = await this.fileService.resolve(uri); + if (!stat.children) { + return []; + } + + return stat.children + .map((child: IFileStat): IFileTreeItem => ({ + uri: child.resource, + name: child.name, + isDirectory: child.isDirectory, + })) + .sort((a, b) => { + // Directories first, then alphabetical + if (a.isDirectory !== b.isDirectory) { + return a.isDirectory ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + } catch (e) { + this.logService.warn(`[FileTreeView] Error fetching children for ${uri.toString()}:`, e); + return []; + } + } +} + +// --- Delegate + +class FileTreeDelegate implements IListVirtualDelegate { + getHeight(): number { + return 22; + } + + getTemplateId(): string { + return FileTreeRenderer.TEMPLATE_ID; + } +} + +// --- Renderer + +interface IFileTreeTemplate { + readonly label: IResourceLabel; + readonly templateDisposables: DisposableStore; +} + +class FileTreeRenderer implements ICompressibleTreeRenderer { + static readonly TEMPLATE_ID = 'fileTreeRenderer'; + readonly templateId = FileTreeRenderer.TEMPLATE_ID; + + constructor( + private readonly labels: ResourceLabels, + @ILabelService private readonly labelService: ILabelService, + ) { } + + renderTemplate(container: HTMLElement): IFileTreeTemplate { + const templateDisposables = new DisposableStore(); + const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true, supportIcons: true })); + return { label, templateDisposables }; + } + + renderElement(node: ITreeNode, _index: number, templateData: IFileTreeTemplate): void { + const element = node.element; + templateData.label.element.style.display = 'flex'; + templateData.label.setFile(element.uri, { + fileKind: element.isDirectory ? FileKind.FOLDER : FileKind.FILE, + hidePath: true, + }); + } + + renderCompressedElements(node: ITreeNode, void>, _index: number, templateData: IFileTreeTemplate): void { + const compressed = node.element; + const lastElement = compressed.elements[compressed.elements.length - 1]; + + templateData.label.element.style.display = 'flex'; + + const label = compressed.elements.map(e => e.name); + templateData.label.setResource({ resource: lastElement.uri, name: label }, { + fileKind: lastElement.isDirectory ? FileKind.FOLDER : FileKind.FILE, + separator: this.labelService.getSeparator(lastElement.uri.scheme), + }); + } + + disposeTemplate(templateData: IFileTreeTemplate): void { + templateData.templateDisposables.dispose(); + } +} + +// --- Compression Delegate + +class FileTreeCompressionDelegate { + isIncompressible(element: IFileTreeItem): boolean { + return !element.isDirectory; + } +} + +// --- View Pane + +export class FileTreeViewPane extends ViewPane { + + private bodyContainer: HTMLElement | undefined; + private welcomeContainer: HTMLElement | undefined; + private treeContainer: HTMLElement | undefined; + + private tree: WorkbenchCompressibleAsyncDataTree | undefined; + + private readonly renderDisposables = this._register(new DisposableStore()); + private readonly treeInputDisposable = this._register(new MutableDisposable()); + + private currentBodyHeight = 0; + private currentBodyWidth = 0; + + /** + * Observable that tracks the root URI for the file tree. + * - For background sessions: the worktree or repository local path + * - For cloud sessions: a github-remote-file:// URI derived from the session's repository metadata + * - For local sessions: the workspace folder + */ + private readonly treeRootUri: IObservable; + + constructor( + options: IViewPaneOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IFileService private readonly fileService: IFileService, + @IEditorService private readonly editorService: IEditorService, + @ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService, + @IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService, + @ILogService private readonly logService: ILogService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService); + + // Track active session changes AND session model updates (metadata/changes can arrive later) + const sessionsChangedSignal = observableFromEvent( + this, + this.agentSessionsService.model.onDidChangeSessions, + () => ({}), + ); + + this.treeRootUri = derived(reader => { + const activeSession = this.sessionManagementService.activeSession.read(reader); + sessionsChangedSignal.read(reader); // re-evaluate when sessions data updates + return this.resolveTreeRoot(activeSession); + }); + } + + /** + * Determines the root URI for the file tree based on the active session type. + * Tries multiple data sources: IActiveSessionItem fields, agent session model metadata, + * and file change URIs as a last resort. + */ + private resolveTreeRoot(activeSession: IActiveSessionItem | undefined): URI | undefined { + if (!activeSession) { + return undefined; + } + + const sessionType = getChatSessionType(activeSession.resource); + + // 1. Try the direct worktree/repository fields from IActiveSessionItem + if (activeSession.worktree) { + this.logService.info(`[FileTreeView] Using worktree: ${activeSession.worktree.toString()}`); + return activeSession.worktree; + } + if (activeSession.repository && activeSession.repository.scheme === 'file') { + this.logService.info(`[FileTreeView] Using repository: ${activeSession.repository.toString()}`); + return activeSession.repository; + } + + // 2. Query the agent session model directly for metadata + const agentSession = this.agentSessionsService.getSession(activeSession.resource); + if (agentSession?.metadata) { + const metadata = agentSession.metadata; + + // Background sessions: local paths (try multiple known metadata keys) + const workingDir = metadata.workingDirectoryPath as string | undefined; + if (workingDir) { + this.logService.info(`[FileTreeView] Using metadata.workingDirectoryPath: ${workingDir}`); + return URI.file(workingDir); + } + const worktreePath = metadata.worktreePath as string | undefined; + if (worktreePath) { + this.logService.info(`[FileTreeView] Using metadata.worktreePath: ${worktreePath}`); + return URI.file(worktreePath); + } + const repositoryPath = metadata.repositoryPath as string | undefined; + if (repositoryPath) { + this.logService.info(`[FileTreeView] Using metadata.repositoryPath: ${repositoryPath}`); + return URI.file(repositoryPath); + } + + // Cloud sessions: GitHub repo info in metadata + const repoUri = this.extractRepoUriFromMetadata(metadata); + if (repoUri) { + return repoUri; + } + } + + // 3. For cloud/remote sessions: try to infer repo from file change URIs + if (sessionType === AgentSessionProviders.Cloud || sessionType === AgentSessionProviders.Codex) { + const repoUri = this.inferRepoFromChanges(activeSession.resource); + if (repoUri) { + this.logService.info(`[FileTreeView] Inferred repo from changes: ${repoUri.toString()}`); + return repoUri; + } + } + + // 4. Try to parse the repository URI as a GitHub URL + if (activeSession.repository) { + const repoStr = activeSession.repository.toString(); + const parsed = this.parseGitHubUrl(repoStr); + if (parsed) { + this.logService.info(`[FileTreeView] Parsed repository URI as GitHub: ${parsed.owner}/${parsed.repo}`); + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${parsed.owner}/${parsed.repo}/HEAD`, + }); + } + } + + this.logService.trace(`[FileTreeView] No tree root resolved for session ${activeSession.resource.toString()} (type: ${sessionType})`); + return undefined; + } + + /** + * Extracts a github-remote-file:// URI from session metadata, trying various known fields. + */ + private extractRepoUriFromMetadata(metadata: { readonly [key: string]: unknown }): URI | undefined { + // repositoryNwo: "owner/repo" + const repositoryNwo = metadata.repositoryNwo as string | undefined; + if (repositoryNwo && repositoryNwo.includes('/')) { + this.logService.info(`[FileTreeView] Using metadata.repositoryNwo: ${repositoryNwo}`); + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${repositoryNwo}/HEAD`, + }); + } + + // repositoryUrl: "https://github.com/owner/repo" + const repositoryUrl = metadata.repositoryUrl as string | undefined; + if (repositoryUrl) { + const parsed = this.parseGitHubUrl(repositoryUrl); + if (parsed) { + this.logService.info(`[FileTreeView] Using metadata.repositoryUrl: ${repositoryUrl}`); + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${parsed.owner}/${parsed.repo}/HEAD`, + }); + } + } + + // repository: could be "owner/repo" or a URL + const repository = metadata.repository as string | undefined; + if (repository) { + if (repository.includes('/') && !repository.includes(':')) { + // Looks like "owner/repo" + this.logService.info(`[FileTreeView] Using metadata.repository as nwo: ${repository}`); + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${repository}/HEAD`, + }); + } + const parsed = this.parseGitHubUrl(repository); + if (parsed) { + this.logService.info(`[FileTreeView] Using metadata.repository as URL: ${repository}`); + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${parsed.owner}/${parsed.repo}/HEAD`, + }); + } + } + + return undefined; + } + + /** + * Attempts to infer the repository from the session's file change URIs. + * Cloud sessions have changes with URIs that reveal the repository. + */ + private inferRepoFromChanges(sessionResource: URI): URI | undefined { + const agentSession = this.agentSessionsService.getSession(sessionResource); + if (!agentSession?.changes || !(agentSession.changes instanceof Array)) { + return undefined; + } + + for (const change of agentSession.changes) { + const fileUri = isIChatSessionFileChange2(change) + ? (change.modifiedUri ?? change.uri) + : change.modifiedUri; + + if (!fileUri) { + continue; + } + + const parsed = this.parseRepoFromFileUri(fileUri); + if (parsed) { + return URI.from({ + scheme: GITHUB_REMOTE_FILE_SCHEME, + authority: 'github', + path: `/${parsed.owner}/${parsed.repo}/${parsed.ref}`, + }); + } + } + + return undefined; + } + + /** + * Tries to extract GitHub owner/repo from a file change URI. + * Handles various URI formats used by cloud sessions. + */ + private parseRepoFromFileUri(uri: URI): { owner: string; repo: string; ref: string } | undefined { + // Pattern: vscode-vfs://github/{owner}/{repo}/... + if (uri.authority === 'github' || uri.authority?.startsWith('github')) { + const parts = uri.path.split('/').filter(Boolean); + if (parts.length >= 2) { + return { owner: parts[0], repo: parts[1], ref: 'HEAD' }; + } + } + + // Pattern: github://{owner}/{repo}/... or github1s://{owner}/{repo}/... + if (uri.scheme === 'github' || uri.scheme === 'github1s') { + const parts = uri.authority ? uri.authority.split('/') : uri.path.split('/').filter(Boolean); + if (parts.length >= 2) { + return { owner: parts[0], repo: parts[1], ref: 'HEAD' }; + } + } + + // Pattern: https://github.com/{owner}/{repo}/... + return this.parseGitHubUrl(uri.toString()); + } + + private parseGitHubUrl(url: string): { owner: string; repo: string; ref: string } | undefined { + const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url) + || /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i.exec(url); + return match ? { owner: match[1], repo: match[2], ref: 'HEAD' } : undefined; + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + this.bodyContainer = dom.append(container, $('.file-tree-view-body')); + + // Welcome message for empty state + this.welcomeContainer = dom.append(this.bodyContainer, $('.file-tree-welcome')); + const welcomeIcon = dom.append(this.welcomeContainer, $('.file-tree-welcome-icon')); + welcomeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.repoClone)); + const welcomeMessage = dom.append(this.welcomeContainer, $('.file-tree-welcome-message')); + welcomeMessage.textContent = localize('fileTreeView.noRepository', "No repository available for this session."); + + // Tree container + this.treeContainer = dom.append(this.bodyContainer, $('.file-tree-container.show-file-icons')); + this._register(createFileIconThemableTreeContainerScope(this.treeContainer, this.themeService)); + + this._register(this.onDidChangeBodyVisibility(visible => { + if (visible) { + this.onVisible(); + } else { + this.renderDisposables.clear(); + } + })); + + if (this.isBodyVisible()) { + this.onVisible(); + } + } + + private onVisible(): void { + this.renderDisposables.clear(); + this.logService.info('[FileTreeView] onVisible called'); + + // Create tree if needed + if (!this.tree && this.treeContainer) { + const resourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility })); + const dataSource = new FileTreeDataSource(this.fileService, this.logService); + + this.tree = this.instantiationService.createInstance( + WorkbenchCompressibleAsyncDataTree, + 'FileTreeView', + this.treeContainer, + new FileTreeDelegate(), + new FileTreeCompressionDelegate(), + [this.instantiationService.createInstance(FileTreeRenderer, resourceLabels)], + dataSource, + { + accessibilityProvider: { + getAriaLabel: (element: IFileTreeItem) => element.name, + getWidgetAriaLabel: () => localize('fileTreeView', "File Tree") + }, + identityProvider: { + getId: (element: IFileTreeItem) => element.uri.toString() + }, + compressionEnabled: true, + collapseByDefault: (_e: IFileTreeItem) => true, + } + ); + } + + // Handle tree open events (open files in editor) + if (this.tree) { + this.renderDisposables.add(this.tree.onDidOpen(async (e) => { + if (!e.element || e.element.isDirectory) { + return; + } + + await this.editorService.openEditor({ + resource: e.element.uri, + options: e.editorOptions, + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + })); + } + + // React to active session changes + let lastRootUri: URI | undefined; + this.renderDisposables.add(autorun(reader => { + const rootUri = this.treeRootUri.read(reader); + const hasRoot = !!rootUri; + + dom.setVisibility(hasRoot, this.treeContainer!); + dom.setVisibility(!hasRoot, this.welcomeContainer!); + + if (this.tree && rootUri && !isEqual(rootUri, lastRootUri)) { + lastRootUri = rootUri; + this.updateTitle(basename(rootUri.path) || rootUri.toString()); + this.treeInputDisposable.clear(); + this.tree.setInput(rootUri).then(() => { + this.layoutTree(); + }); + } else if (!rootUri && lastRootUri) { + lastRootUri = undefined; + } + })); + } + + private layoutTree(): void { + if (!this.tree) { + return; + } + this.tree.layout(this.currentBodyHeight, this.currentBodyWidth); + } + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + this.currentBodyHeight = height; + this.currentBodyWidth = width; + this.layoutTree(); + } + + override focus(): void { + super.focus(); + this.tree?.domFocus(); + } + + override dispose(): void { + this.tree?.dispose(); + this.tree = undefined; + super.dispose(); + } +} + +// --- View Pane Container + +export class FileTreeViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @ILogService logService: ILogService, + ) { + super(FILE_TREE_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService); + } + + override create(parent: HTMLElement): void { + super.create(parent); + parent.classList.add('file-tree-viewlet'); + } +} diff --git a/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts b/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts new file mode 100644 index 00000000000..289911e3995 --- /dev/null +++ b/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts @@ -0,0 +1,293 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +import { FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, createFileSystemProviderError, IFileChange } from '../../../../platform/files/common/files.js'; +import { IRequestService, asJson } from '../../../../platform/request/common/request.js'; +import { IAuthenticationService } from '../../../../workbench/services/authentication/common/authentication.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; + +export const GITHUB_REMOTE_FILE_SCHEME = 'github-remote-file'; + +/** + * GitHub REST API response for the Trees endpoint. + * GET /repos/{owner}/{repo}/git/trees/{tree_sha}?recursive=1 + */ +interface IGitHubTreeResponse { + readonly sha: string; + readonly url: string; + readonly truncated: boolean; + readonly tree: readonly IGitHubTreeEntry[]; +} + +interface IGitHubTreeEntry { + readonly path: string; + readonly mode: string; + readonly type: 'blob' | 'tree'; + readonly sha: string; + readonly size?: number; + readonly url: string; +} + +interface ITreeCacheEntry { + /** Map from path → entry metadata */ + readonly entries: Map; + readonly fetchedAt: number; +} + +/** + * A readonly virtual filesystem provider backed by the GitHub REST API. + * + * URI format: github-remote-file://github/{owner}/{repo}/{ref}/{path...} + * + * For example: github-remote-file://github/microsoft/vscode/main/src/vs/base/common/uri.ts + * + * This provider fetches the full recursive tree from the GitHub Trees API on first + * access and caches it. Individual file contents are fetched on demand via the + * Blobs API. + */ +export class GitHubFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { + + private readonly _onDidChangeCapabilities = this._register(new Emitter()); + readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; + + readonly capabilities: FileSystemProviderCapabilities = + FileSystemProviderCapabilities.Readonly | + FileSystemProviderCapabilities.FileReadWrite | + FileSystemProviderCapabilities.PathCaseSensitive; + + private readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; + + /** Cache keyed by "owner/repo/ref" */ + private readonly treeCache = new Map(); + + /** Cache TTL - 5 minutes */ + private static readonly CACHE_TTL_MS = 5 * 60 * 1000; + + constructor( + @IRequestService private readonly requestService: IRequestService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @ILogService private readonly logService: ILogService, + ) { + super(); + } + + // --- URI parsing + + /** + * Parse a github-remote-file URI into its components. + * Format: github-remote-file://github/{owner}/{repo}/{ref}/{path...} + */ + private parseUri(resource: URI): { owner: string; repo: string; ref: string; path: string } { + // authority = "github" + // path = /{owner}/{repo}/{ref}/{rest...} + const parts = resource.path.split('/').filter(Boolean); + if (parts.length < 3) { + throw createFileSystemProviderError('Invalid github-remote-file URI: expected /{owner}/{repo}/{ref}/...', FileSystemProviderErrorCode.FileNotFound); + } + + const owner = parts[0]; + const repo = parts[1]; + const ref = parts[2]; + const path = parts.slice(3).join('/'); + + return { owner, repo, ref, path }; + } + + private getCacheKey(owner: string, repo: string, ref: string): string { + return `${owner}/${repo}/${ref}`; + } + + // --- GitHub API + + private async getAuthToken(): Promise { + const sessions = await this.authenticationService.getSessions('github', ['repo']); + if (sessions.length > 0) { + return sessions[0].accessToken; + } + + // Try to create a session if none exists + const session = await this.authenticationService.createSession('github', ['repo']); + return session.accessToken; + } + + private async fetchTree(owner: string, repo: string, ref: string): Promise { + const cacheKey = this.getCacheKey(owner, repo, ref); + const cached = this.treeCache.get(cacheKey); + if (cached && (Date.now() - cached.fetchedAt) < GitHubFileSystemProvider.CACHE_TTL_MS) { + return cached; + } + + this.logService.info(`[SessionRepoFS] Fetching tree for ${owner}/${repo}@${ref}`); + const token = await this.getAuthToken(); + + const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(ref)}?recursive=1`; + const response = await this.requestService.request({ + type: 'GET', + url, + headers: { + 'Authorization': `token ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode-SessionRepoFS', + }, + }, CancellationToken.None); + + const data = await asJson(response); + if (!data) { + throw createFileSystemProviderError(`Failed to fetch tree for ${owner}/${repo}@${ref}`, FileSystemProviderErrorCode.Unavailable); + } + + const entries = new Map(); + + // Add root directory entry + entries.set('', { type: FileType.Directory, size: 0, sha: data.sha }); + + // Track directories implicitly from paths + const dirs = new Set(); + + for (const entry of data.tree) { + const fileType = entry.type === 'tree' ? FileType.Directory : FileType.File; + entries.set(entry.path, { type: fileType, size: entry.size ?? 0, sha: entry.sha }); + + if (fileType === FileType.Directory) { + dirs.add(entry.path); + } + + // Ensure parent directories are tracked + const pathParts = entry.path.split('/'); + for (let i = 1; i < pathParts.length; i++) { + const parentPath = pathParts.slice(0, i).join('/'); + if (!dirs.has(parentPath)) { + dirs.add(parentPath); + if (!entries.has(parentPath)) { + entries.set(parentPath, { type: FileType.Directory, size: 0, sha: '' }); + } + } + } + } + + const cacheEntry: ITreeCacheEntry = { entries, fetchedAt: Date.now() }; + this.treeCache.set(cacheKey, cacheEntry); + return cacheEntry; + } + + // --- IFileSystemProvider + + async stat(resource: URI): Promise { + const { owner, repo, ref, path } = this.parseUri(resource); + const tree = await this.fetchTree(owner, repo, ref); + const entry = tree.entries.get(path); + + if (!entry) { + throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound); + } + + return { + type: entry.type, + ctime: 0, + mtime: 0, + size: entry.size, + }; + } + + async readdir(resource: URI): Promise<[string, FileType][]> { + const { owner, repo, ref, path } = this.parseUri(resource); + const tree = await this.fetchTree(owner, repo, ref); + + const prefix = path ? path + '/' : ''; + const result: [string, FileType][] = []; + + for (const [entryPath, entry] of tree.entries) { + if (!entryPath.startsWith(prefix)) { + continue; + } + + const relativePath = entryPath.slice(prefix.length); + // Only include direct children (no nested paths) + if (relativePath && !relativePath.includes('/')) { + result.push([relativePath, entry.type]); + } + } + + return result; + } + + async readFile(resource: URI): Promise { + const { owner, repo, ref, path } = this.parseUri(resource); + const tree = await this.fetchTree(owner, repo, ref); + const entry = tree.entries.get(path); + + if (!entry || entry.type === FileType.Directory) { + throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound); + } + + const token = await this.getAuthToken(); + + // Fetch file content via the Blobs API + const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/blobs/${encodeURIComponent(entry.sha)}`; + const response = await this.requestService.request({ + type: 'GET', + url, + headers: { + 'Authorization': `token ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode-SessionRepoFS', + }, + }, CancellationToken.None); + + const data = await asJson<{ content: string; encoding: string }>(response); + if (!data) { + throw createFileSystemProviderError(`Failed to read file ${path}`, FileSystemProviderErrorCode.Unavailable); + } + + if (data.encoding === 'base64') { + const binaryString = atob(data.content.replace(/\n/g, '')); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + + return new TextEncoder().encode(data.content); + } + + // --- Readonly stubs + + watch(): IDisposable { + return Disposable.None; + } + + async writeFile(_resource: URI, _content: Uint8Array, _opts: IFileWriteOptions): Promise { + throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions); + } + + async mkdir(_resource: URI): Promise { + throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions); + } + + async delete(_resource: URI, _opts: IFileDeleteOptions): Promise { + throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions); + } + + async rename(_from: URI, _to: URI, _opts: IFileOverwriteOptions): Promise { + throw createFileSystemProviderError('Operation not supported', FileSystemProviderErrorCode.NoPermissions); + } + + // --- Cache management + + invalidateCache(owner: string, repo: string, ref: string): void { + this.treeCache.delete(this.getCacheKey(owner, repo, ref)); + } + + override dispose(): void { + this.treeCache.clear(); + super.dispose(); + } +} diff --git a/src/vs/sessions/contrib/fileTreeView/browser/media/fileTreeView.css b/src/vs/sessions/contrib/fileTreeView/browser/media/fileTreeView.css new file mode 100644 index 00000000000..3affb3068f9 --- /dev/null +++ b/src/vs/sessions/contrib/fileTreeView/browser/media/fileTreeView.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +.file-tree-view-body { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.file-tree-view-body .file-tree-welcome { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 16px; + gap: 8px; + color: var(--vscode-descriptionForeground); +} + +.file-tree-view-body .file-tree-welcome-icon { + font-size: 24px; + opacity: 0.6; +} + +.file-tree-view-body .file-tree-welcome-message { + font-size: 12px; + text-align: center; +} + +.file-tree-view-body .file-tree-container { + flex: 1; + overflow: hidden; +} diff --git a/src/vs/sessions/contrib/sessions/browser/media/customizationsToolbar.css b/src/vs/sessions/contrib/sessions/browser/media/customizationsToolbar.css index d671775dbd5..8a4298cf909 100644 --- a/src/vs/sessions/contrib/sessions/browser/media/customizationsToolbar.css +++ b/src/vs/sessions/contrib/sessions/browser/media/customizationsToolbar.css @@ -124,10 +124,11 @@ .ai-customization-toolbar .ai-customization-toolbar-content { max-height: 500px; overflow: hidden; - transition: max-height 0.2s ease-out; + transition: max-height 0.2s ease-out, display 0s linear 0.2s; } .ai-customization-toolbar.collapsed .ai-customization-toolbar-content { max-height: 0; + display: none; } } diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 8ac2ffd7a0c..d3a7e961119 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -16,15 +16,15 @@ import { ChatViewId, ChatViewPaneTarget, IChatWidgetService } from '../../../../ import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.js'; import { IChatSessionItem, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; import { IChatService, IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js'; -import { ChatAgentLocation } from '../../../../workbench/contrib/chat/common/constants.js'; +import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js'; import { IAgentSession, isAgentSession } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js'; import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; import { LocalChatSessionUri } from '../../../../workbench/contrib/chat/common/model/chatUri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { IWorkspaceEditingService } from '../../../../workbench/services/workspaces/common/workspaceEditing.js'; import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; +import { INewSession, LocalNewSession, RemoteNewSession } from '../../chat/browser/newSession.js'; export const IsNewChatSessionContext = new RawContextKey('isNewChatSession', true); @@ -81,10 +81,16 @@ export interface ISessionsManagementService { createNewPendingSession(pendingSessionResource: URI): Promise; /** - * Open a new session, apply options, and send the initial request. - * This is the main entry point for the new-chat welcome widget. + * Create a pending session object for the given target type. + * Local sessions collect options locally; remote sessions notify the extension. */ - sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap, folderUri?: URI): Promise; + createNewSessionForTarget(target: AgentSessionProviders, sessionResource: URI, defaultRepoUri?: URI): Promise; + + /** + * Open a new session, apply options, and send the initial request. + * Looks up the session by resource URI and builds send options from it. + */ + sendRequestForNewSession(sessionResource: URI): Promise; /** * Commit files in a worktree and refresh the agent sessions model @@ -102,6 +108,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa private readonly _activeSession = observableValue(this, undefined); readonly activeSession: IObservable = this._activeSession; + private readonly _newSessions = new Map(); private lastSelectedSession: URI | undefined; private readonly isNewChatSessionContext: IContextKey; @@ -115,7 +122,6 @@ export class SessionsManagementService extends Disposable implements ISessionsMa @ILogService private readonly logService: ILogService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, @IViewsService private readonly viewsService: IViewsService, @ICommandService private readonly commandService: ICommandService, ) { @@ -255,6 +261,19 @@ export class SessionsManagementService extends Disposable implements ISessionsMa return activeSessionItem; } + async createNewSessionForTarget(target: AgentSessionProviders, sessionResource: URI, defaultRepoUri?: URI): Promise { + const activeSessionItem = await this.createNewPendingSession(sessionResource); + + let newSession: INewSession; + if (target === AgentSessionProviders.Background || target === AgentSessionProviders.Local) { + newSession = new LocalNewSession(activeSessionItem, defaultRepoUri, this.chatSessionsService, this.logService); + } else { + newSession = new RemoteNewSession(activeSessionItem, target, this.chatSessionsService, this.logService); + } + this._newSessions.set(newSession.resource.toString(), newSession); + return newSession; + } + /** * Open an existing agent session - set it as active and reveal it. */ @@ -307,39 +326,45 @@ export class SessionsManagementService extends Disposable implements ISessionsMa this._activeSession.set(activeSessionItem, undefined); } - async sendRequestForNewSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap, folderUri?: URI): Promise { - if (LocalChatSessionUri.isLocalSession(sessionResource)) { - await this.sendLocalSession(sessionResource, query, sendOptions, folderUri); - } else { - await this.sendCustomSession(sessionResource, query, sendOptions, selectedOptions); - } - } - - /** - * Local sessions run directly through the ChatWidget. - * Set the workspace folder, open a fresh chat view, and submit via acceptInput. - */ - private async sendLocalSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, folderUri?: URI): Promise { - if (folderUri) { - await this.workspaceEditingService.updateFolders(0, this.workspaceContextService.getWorkspace().folders.length, [{ uri: folderUri }]); + async sendRequestForNewSession(sessionResource: URI): Promise { + const session = this._newSessions.get(sessionResource.toString()); + if (!session) { + this.logService.error(`[SessionsManagementService] No new session found for resource: ${sessionResource.toString()}`); + return; } - await this.openSession(sessionResource); - - const widget = this.chatWidgetService.lastFocusedWidget; - if (widget) { - if (sendOptions.attachedContext?.length) { - widget.attachmentModel.addContext(...sendOptions.attachedContext); - } - widget.setInput(query); - widget.acceptInput(query); + const query = session.query; + if (!query) { + this.logService.error('[SessionsManagementService] No query set on session'); + return; } + + const contribution = this.chatSessionsService.getChatSessionContribution(session.target); + const sendOptions: IChatSendRequestOptions = { + location: ChatAgentLocation.Chat, + userSelectedModelId: session.modelId, + modeInfo: { + kind: ChatModeKind.Agent, + isBuiltin: true, + modeInstructions: undefined, + modeId: 'agent', + applyCodeBlockSuggestionId: undefined, + }, + agentIdSilent: contribution?.type, + attachedContext: session.attachedContext, + }; + + await this.sendCustomSession(sessionResource, query, sendOptions, session.selectedOptions); + + // Clean up the session after sending + this._newSessions.delete(sessionResource.toString()); + session.dispose(); } /** * Custom sessions (worktree, cloud, etc.) go through the chat service. - * Apply selected options, send the request, then wait for the extension - * to create an agent session so it appears in the sidebar. + * Options have already been applied via setOption during session configuration. + * Send the request, then wait for the extension to create an agent session. */ private async sendCustomSession(sessionResource: URI, query: string, sendOptions: IChatSendRequestOptions, selectedOptions?: ReadonlyMap): Promise { // 1. Open the session - loads the model and shows the ChatViewPane diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsViewPane.ts b/src/vs/sessions/contrib/sessions/browser/sessionsViewPane.ts index a1e45eb3d0c..d9860c68197 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsViewPane.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsViewPane.ts @@ -9,6 +9,7 @@ import './media/sessionsViewPane.css'; import * as DOM from '../../../../base/browser/dom.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { MutableDisposable } from '../../../../base/common/lifecycle.js'; import { autorun } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -221,6 +222,7 @@ export class AgenticSessionsViewPane extends ViewPane { updateHeaderTotalCount(); // Toggle collapse on header click + const transitionListener = this._register(new MutableDisposable()); const toggleCollapse = () => { const collapsed = container.classList.toggle('collapsed'); header.classList.toggle('collapsed', collapsed); @@ -230,14 +232,13 @@ export class AgenticSessionsViewPane extends ViewPane { chevron.classList.add(...ThemeIcon.asClassNameArray(collapsed ? Codicon.chevronRight : Codicon.chevronDown)); // Re-layout after the transition so sessions control gets the right height - const onTransitionEnd = () => { - toolbarContainer.removeEventListener('transitionend', onTransitionEnd); + transitionListener.value = DOM.addDisposableListener(toolbarContainer, 'transitionend', () => { + transitionListener.clear(); if (this.viewPaneContainer) { const { offsetHeight, offsetWidth } = this.viewPaneContainer; this.layoutBody(offsetHeight, offsetWidth); } - }; - toolbarContainer.addEventListener('transitionend', onTransitionEnd); + }); }; this._register(headerButton.onDidClick(() => toggleCollapse())); diff --git a/src/vs/sessions/electron-browser/parts/titlebarPart.ts b/src/vs/sessions/electron-browser/parts/titlebarPart.ts index a222e9dacfa..f1313c72b0a 100644 --- a/src/vs/sessions/electron-browser/parts/titlebarPart.ts +++ b/src/vs/sessions/electron-browser/parts/titlebarPart.ts @@ -106,7 +106,6 @@ class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements IAuxilia constructor( readonly container: HTMLElement, - editorGroupsContainer: IEditorGroupsContainer, private readonly mainTitlebar: TitlebarPart, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @@ -133,7 +132,7 @@ export class NativeTitleService extends TitleService { return this.instantiationService.createInstance(MainNativeTitlebarPart); } - protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart { - return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart); + protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, _editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart { + return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, this.mainPart); } } diff --git a/src/vs/sessions/electron-browser/sessions.ts b/src/vs/sessions/electron-browser/sessions.ts index b7d33fa75da..ce01931aa2b 100644 --- a/src/vs/sessions/electron-browser/sessions.ts +++ b/src/vs/sessions/electron-browser/sessions.ts @@ -78,7 +78,7 @@ try { let workbenchUrl: string; if (!!safeProcess.env['VSCODE_DEV'] && globalThis._VSCODE_USE_RELATIVE_IMPORTS) { - workbenchUrl = './workbench.desktop.main.js'; // for dev purposes only + workbenchUrl = './sessions.desktop.main.js'; // for dev purposes only } else { workbenchUrl = new URL(`vs/sessions/sessions.desktop.main.js`, baseUrl).href; } diff --git a/src/vs/sessions/sessions.desktop.main.ts b/src/vs/sessions/sessions.desktop.main.ts index 81e6f1b534a..14ea745fd17 100644 --- a/src/vs/sessions/sessions.desktop.main.ts +++ b/src/vs/sessions/sessions.desktop.main.ts @@ -196,6 +196,7 @@ import './contrib/chat/browser/chat.contribution.js'; import './contrib/sessions/browser/sessions.contribution.js'; import './contrib/sessions/browser/customizationsToolbar.contribution.js'; import './contrib/changesView/browser/changesView.contribution.js'; +import './contrib/fileTreeView/browser/fileTreeView.contribution.js'; // view registration disabled; filesystem provider still needed import './contrib/configuration/browser/configuration.contribution.js'; //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts index 30a68e7b3c5..6ed0a6d0acd 100644 --- a/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadGitExtensionService.ts @@ -3,33 +3,139 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Sequencer } from '../../../base/common/async.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { Disposable } from '../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../base/common/map.js'; import { URI } from '../../../base/common/uri.js'; -import { IGitExtensionService, IGitService } from '../../contrib/git/common/gitService.js'; +import { GitRepository } from '../../contrib/git/browser/gitService.js'; +import { IGitExtensionDelegate, IGitService, GitRef, GitRefQuery, GitRefType, GitRepositoryState, GitBranch, IGitRepository } from '../../contrib/git/common/gitService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; -import { ExtHostContext, ExtHostGitExtensionShape, MainContext, MainThreadGitExtensionShape } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostGitExtensionShape, GitRefTypeDto, GitRepositoryStateDto, MainContext, MainThreadGitExtensionShape } from '../common/extHost.protocol.js'; + +function toGitRefType(type: GitRefTypeDto): GitRefType { + switch (type) { + case GitRefTypeDto.Head: return GitRefType.Head; + case GitRefTypeDto.RemoteHead: return GitRefType.RemoteHead; + case GitRefTypeDto.Tag: return GitRefType.Tag; + default: throw new Error(`Unknown GitRefType: ${type}`); + } +} + +function toGitRepositoryState(dto: GitRepositoryStateDto | undefined): GitRepositoryState { + return { + HEAD: dto?.HEAD ? { + type: toGitRefType(dto.HEAD.type), + name: dto.HEAD.name, + commit: dto.HEAD.commit, + remote: dto.HEAD.remote, + upstream: dto.HEAD.upstream, + ahead: dto.HEAD.ahead, + behind: dto.HEAD.behind, + } satisfies GitBranch : undefined, + }; +} @extHostNamedCustomer(MainContext.MainThreadGitExtension) -export class MainThreadGitExtensionService extends Disposable implements MainThreadGitExtensionShape, IGitExtensionService { +export class MainThreadGitExtensionService extends Disposable implements MainThreadGitExtensionShape, IGitExtensionDelegate { private readonly _proxy: ExtHostGitExtensionShape; + private readonly _openRepositorySequencer = new Sequencer(); + + private _repositoryHandles = new ResourceMap(); + private _repositories = new Map(); + + get repositories(): Iterable { + return this._repositories.values(); + } constructor( extHostContext: IExtHostContext, - @IGitService private readonly gitService: IGitService, + @IGitService private readonly gitService: IGitService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostGitExtension); - gitService.setDelegate(this); + this._initializeDelegate(); } - async openRepository(uri: URI): Promise { - const result = await this._proxy.$openRepository(uri); - return result ? URI.revive(result) : undefined; + private async _initializeDelegate(): Promise { + // Check whether the vscode.git extension is available in the extension host + // process before setting the delegate. The delegate should only be set once, + // for the extension host process that runs the vscode.git extension + const isExtensionAvailable = await this._proxy.$isGitExtensionAvailable(); + + if (isExtensionAvailable && !this._store.isDisposed) { + this._register(this.gitService.setDelegate(this)); + } } - override dispose(): void { - this.gitService.clearDelegate(); - super.dispose(); + private _getRepositoryByUri(uri: URI): IGitRepository | undefined { + const handle = this._repositoryHandles.get(uri); + return handle !== undefined ? this._repositories.get(handle) : undefined; + } + + async openRepository(uri: URI): Promise { + return this._openRepositorySequencer.queue(async () => { + // Check if we already have a repository for the given URI + const existingRepository = this._getRepositoryByUri(uri); + if (existingRepository) { + return existingRepository; + } + + // Open the repository + const result = await this._proxy.$openRepository(uri); + if (!result) { + return undefined; + } + + const repositoryRootUri = URI.revive(result.rootUri); + + // Check if we already have a repository for the given root + const existingRepositoryForRoot = this._getRepositoryByUri(repositoryRootUri); + if (existingRepositoryForRoot) { + return existingRepositoryForRoot; + } + + // Create a new repository and store it in the maps + const state = toGitRepositoryState(result.state); + const repository = new GitRepository(repositoryRootUri, state, this); + + this._repositories.set(result.handle, repository); + this._repositoryHandles.set(repositoryRootUri, result.handle); + + return repository; + }); + } + + async getRefs(root: URI, query: GitRefQuery, token?: CancellationToken): Promise { + const handle = this._repositoryHandles.get(root); + if (handle === undefined) { + return []; + } + + const result = await this._proxy.$getRefs(handle, query, token); + + if (token?.isCancellationRequested) { + return []; + } + + return result.map(ref => ({ + ...ref, + type: toGitRefType(ref.type) + } satisfies GitRef)); + } + + async $onDidChangeRepository(handle: number): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return; + } + + const state = await this._proxy.$getRepositoryState(handle); + if (!state) { + return; + } + + // Update the repository state + repository.updateState(toGitRepositoryState(state)); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5ddc2106e84..a0bf5e90b7c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -119,6 +119,7 @@ export interface IMainContext extends IRPCProtocol { // --- main thread export interface MainThreadGitExtensionShape extends IDisposable { + $onDidChangeRepository(handle: number): Promise; } export interface MainThreadClipboardShape extends IDisposable { @@ -3457,8 +3458,51 @@ export interface ExtHostChatSessionsShape { $provideHandleOptionsChange(providerHandle: number, sessionResource: UriComponents, updates: ReadonlyArray, token: CancellationToken): Promise; } +export interface GitRefQueryDto { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + +export enum GitRefTypeDto { + Head, + RemoteHead, + Tag +} + +export interface GitRefDto { + readonly id: string; + readonly name: string; + readonly type: GitRefTypeDto; + readonly revision: string; +} + +export interface GitRepositoryStateDto { + readonly HEAD?: GitBranchDto; +} + +export interface GitBranchDto { + readonly name?: string; + readonly commit?: string; + readonly type: GitRefTypeDto; + readonly remote?: string; + readonly upstream?: GitUpstreamRefDto; + readonly ahead?: number; + readonly behind?: number; +} + +export interface GitUpstreamRefDto { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + export interface ExtHostGitExtensionShape { - $openRepository(root: UriComponents): Promise; + $isGitExtensionAvailable(): Promise; + $openRepository(root: UriComponents): Promise<{ handle: number; rootUri: UriComponents; state: GitRepositoryStateDto } | undefined>; + $getRefs(handle: number, query: GitRefQueryDto, token?: CancellationToken): Promise; + $getRepositoryState(handle: number): Promise; } // --- proxy identifiers diff --git a/src/vs/workbench/api/common/extHostGitExtensionService.ts b/src/vs/workbench/api/common/extHostGitExtensionService.ts index 47276d420b8..8029798f00f 100644 --- a/src/vs/workbench/api/common/extHostGitExtensionService.ts +++ b/src/vs/workbench/api/common/extHostGitExtensionService.ts @@ -4,18 +4,94 @@ *--------------------------------------------------------------------------------------------*/ import type * as vscode from 'vscode'; +import { Event } from '../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; +import { observableFromEvent, waitForState } from '../../../base/common/observable.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { IExtHostExtensionService } from './extHostExtensionService.js'; import { IExtHostRpcService } from './extHostRpcService.js'; -import { ExtHostGitExtensionShape } from './extHost.protocol.js'; +import { ExtHostGitExtensionShape, GitBranchDto, GitRefDto, GitRefQueryDto, GitRefTypeDto, GitRepositoryStateDto, GitUpstreamRefDto, MainContext, MainThreadGitExtensionShape } from './extHost.protocol.js'; +import { ResourceMap } from '../../../base/common/map.js'; const GIT_EXTENSION_ID = 'vscode.git'; +function toGitRefTypeDto(type: GitRefType): GitRefTypeDto { + switch (type) { + case GitRefType.Head: return GitRefTypeDto.Head; + case GitRefType.RemoteHead: return GitRefTypeDto.RemoteHead; + case GitRefType.Tag: return GitRefTypeDto.Tag; + default: throw new Error(`Unknown GitRefType: ${type}`); + } +} + +function toGitBranchDto(branch: Branch): GitBranchDto { + return { + name: branch.name, + commit: branch.commit, + type: toGitRefTypeDto(branch.type), + remote: branch.remote, + upstream: branch.upstream ? toGitUpstreamRefDto(branch.upstream) : undefined, + ahead: branch.ahead, + behind: branch.behind, + }; +} + +function toGitUpstreamRefDto(upstream: UpstreamRef): GitUpstreamRefDto { + return { + remote: upstream.remote, + name: upstream.name, + commit: upstream.commit, + }; +} + +interface Repository { + readonly rootUri: vscode.Uri; + readonly state: RepositoryState; + + getRefs(query: GitRefQuery, token?: vscode.CancellationToken): Promise; +} + +interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly onDidChange: Event; +} + +interface Branch extends GitRef { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +interface UpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + +interface GitRef { + type: GitRefType; + name?: string; + commit?: string; + remote?: string; +} + +const enum GitRefType { + Head, + RemoteHead, + Tag +} + +interface GitRefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + interface GitExtensionAPI { - openRepository(root: vscode.Uri): Promise<{ readonly rootUri: vscode.Uri } | null>; + openRepository(root: vscode.Uri): Promise; } interface GitExtension { @@ -31,29 +107,124 @@ export const IExtHostGitExtensionService = createDecorator(); + private readonly _repositoryByUri = new ResourceMap(); + private readonly _disposables = this._register(new DisposableStore()); constructor( - @IExtHostRpcService _extHostRpc: IExtHostRpcService, + @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, ) { super(); + + this._proxy = extHostRpc.getProxy(MainContext.MainThreadGitExtension); } - // --- Called by the main thread via RPC (ExtHostGitShape) --- + async $isGitExtensionAvailable(): Promise { + const registry = await this._extHostExtensionService.getExtensionRegistry(); + return !!registry.getExtensionDescription(GIT_EXTENSION_ID); + } - async $openRepository(uri: UriComponents): Promise { + async $openRepository(uri: UriComponents): Promise<{ handle: number; rootUri: UriComponents; state: GitRepositoryStateDto } | undefined> { const api = await this._ensureGitApi(); if (!api) { return undefined; } const repository = await api.openRepository(URI.revive(uri)); - return repository?.rootUri; + if (!repository) { + return undefined; + } + + const existingHandle = this._repositoryByUri.get(repository.rootUri); + if (existingHandle !== undefined) { + return { + handle: existingHandle, + rootUri: repository.rootUri, + state: { + HEAD: repository.state.HEAD ? toGitBranchDto(repository.state.HEAD) : undefined + } + }; + } + + // Ensure that the repository state is initialized + const repositoryStateObs = observableFromEvent(this, + repository.state.onDidChange, () => repository.state); + await waitForState(repositoryStateObs, state => !!state.HEAD); + + const repositoryState = repositoryStateObs.get(); + + // Store the repository and its handle in the maps + const handle = ExtHostGitExtensionService._handlePool++; + + this._repositories.set(handle, repository); + this._repositoryByUri.set(repository.rootUri, handle); + + // Subscribe to repository state changes + this._disposables.add(repository.state.onDidChange(() => { + this._proxy.$onDidChangeRepository(handle); + })); + + return { + handle, + rootUri: repository.rootUri, + state: { + HEAD: repositoryState.HEAD + ? toGitBranchDto(repositoryState.HEAD) + : undefined + } + }; } - // --- Private helpers --- + async $getRefs(handle: number, query: GitRefQueryDto, token?: vscode.CancellationToken): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return []; + } + + try { + const refs = await repository.getRefs(query, token); + const result: (GitRefDto | undefined)[] = refs.map(ref => { + if (!ref.name || !ref.commit) { + return undefined; + } + + const id = ref.type === GitRefType.Head + ? `refs/heads/${ref.name}` + : ref.type === GitRefType.RemoteHead + ? `refs/remotes/${ref.remote}/${ref.name}` + : `refs/tags/${ref.name}`; + + return { + id, + name: ref.name, + type: toGitRefTypeDto(ref.type), + revision: ref.commit + } satisfies GitRefDto; + }); + + return result.filter(ref => !!ref); + } catch { + return []; + } + } + + async $getRepositoryState(handle: number): Promise { + const repository = this._repositories.get(handle); + if (!repository) { + return undefined; + } + + const state = repository.state; + return { HEAD: state.HEAD ? toGitBranchDto(state.HEAD) : undefined }; + } private async _ensureGitApi(): Promise { if (this._gitApi) { diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 5fd906f1d85..961340c6548 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from './editorQuickAccess.js'; import { SideBySideEditor } from './sideBySideEditor.js'; import { TextDiffEditor } from './textDiffEditor.js'; -import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from '../../../common/contextkeys.js'; +import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext, IsSessionsWindowContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from '../../../common/contextkeys.js'; import { CloseDirection, EditorInputCapabilities, EditorsOrder, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, isEditorInputWithOptionsAndGroup } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -1422,7 +1422,8 @@ function registerModalEditorCommands(): void { menu: { id: MenuId.ModalEditorTitle, group: 'navigation', - order: 0 + order: 0, + when: IsSessionsWindowContext.negate() } }); } diff --git a/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css b/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css index 44c00388467..6be74735630 100644 --- a/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css +++ b/src/vs/workbench/browser/parts/editor/media/modalEditorPart.css @@ -55,7 +55,7 @@ align-items: center; height: 32px; min-height: 32px; - padding: 0 8px 0 16px; + padding: 0 8px 0 10px; color: var(--vscode-titleBar-activeForeground); background-color: var(--vscode-titleBar-activeBackground); border-bottom: 1px solid var(--vscode-titleBar-border, transparent); @@ -67,8 +67,17 @@ font-weight: 500; color: var(--vscode-titleBar-activeForeground); overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + + .monaco-icon-label { + color: inherit; + height: 32px; + line-height: 32px; + + &::before, + & > .monaco-icon-label-iconpath { + height: 32px; + } + } } /* Modal Editor Navigation */ diff --git a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts index bdbd911fe28..1f6e93ba311 100644 --- a/src/vs/workbench/browser/parts/editor/modalEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor/modalEditorPart.ts @@ -25,7 +25,8 @@ import { EditorPart } from './editorPart.js'; import { GroupDirection, GroupsOrder, IModalEditorPart, GroupActivationReason } from '../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { EditorPartModalContext, EditorPartModalMaximizedContext, EditorPartModalNavigationContext } from '../../../common/contextkeys.js'; -import { Verbosity } from '../../../common/editor.js'; +import { EditorResourceAccessor, SideBySideEditor, Verbosity } from '../../../common/editor.js'; +import { ResourceLabel } from '../../labels.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; @@ -123,7 +124,7 @@ export class ModalEditorPart { const headerElement = editorPartContainer.appendChild($('.modal-editor-header')); // Title element - const titleElement = append(headerElement, $('div.modal-editor-title')); + const titleElement = append(headerElement, $('div.modal-editor-title.show-file-icons')); titleElement.id = titleId; titleElement.textContent = ''; @@ -194,15 +195,30 @@ export class ModalEditorPart { menuOptions: { shouldForwardArgs: true } })); - disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, (() => { - - // Update title when active editor changes + // Create label + const label = disposables.add(scopedInstantiationService.createInstance(ResourceLabel, titleElement, {})); + disposables.add(Event.runAndSubscribe(modalEditorService.onDidActiveEditorChange, () => { const activeEditor = editorPart.activeGroup.activeEditor; - titleElement.textContent = activeEditor?.getTitle(Verbosity.MEDIUM) ?? ''; + if (activeEditor) { + const { labelFormat } = editorPart.partOptions; + + label.element.setResource( + { + resource: EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.BOTH }), + name: activeEditor.getName(), + description: activeEditor.getDescription(labelFormat === 'short' ? Verbosity.SHORT : labelFormat === 'long' ? Verbosity.LONG : Verbosity.MEDIUM) || '' + }, + { + icon: activeEditor.getIcon(), + extraClasses: activeEditor.getLabelExtraClasses(), + } + ); + } else { + label.element.clear(); + } - // Notify editor part that active editor changed editorPart.notifyActiveEditorChanged(); - }))); + })); // Handle double-click on header to toggle maximize disposables.add(addDisposableListener(headerElement, EventType.DBLCLICK, e => { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b1c5637a750..a14964c0ef4 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -349,10 +349,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, such as when forcing an editor to open in a specific group or to the side of the currently active group."), 'default': false }, - 'workbench.editor.allowOpenInModalEditor': { - 'type': 'boolean', - 'description': localize('allowOpenInModalEditor', "Controls whether editors can be opened in a modal overlay. When enabled, certain editors such as Settings and Keyboard Shortcuts may open in a centered modal overlay instead of as a regular editor tab."), - 'default': product.quality !== 'stable', // TODO@bpasero figure out the default for stable + 'workbench.editor.useModal': { + 'type': 'string', + 'enum': ['off', 'default', 'on'], + 'enumDescriptions': [ + localize('useModal.off', "Editors never open in a modal overlay."), + localize('useModal.default', "Certain editors such as Settings and Keyboard Shortcuts may open in a centered modal overlay."), + localize('useModal.on', "All editors open in a centered modal overlay."), + ], + 'description': localize('useModal', "Controls whether editors open in a modal overlay."), + 'default': product.quality !== 'stable' ? 'default' : 'off', tags: ['experimental'], experiment: { mode: 'auto' diff --git a/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css b/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css index 0a1ea3099ab..e040c1a1741 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css +++ b/src/vs/workbench/contrib/browserView/electron-browser/media/browser.css @@ -27,6 +27,7 @@ .actions-container { gap: 4px; + margin-right: 4px; } } diff --git a/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts index a10346c9bb3..26917675200 100644 --- a/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/accessibility/chatAccessibilityService.ts @@ -19,7 +19,7 @@ import { IHostService } from '../../../../services/host/browser/host.js'; import { AccessibilityVoiceSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { ElicitationState, IChatElicitationRequest, IChatService } from '../../common/chatService/chatService.js'; import { IChatResponseViewModel } from '../../common/model/chatViewModel.js'; -import { ChatConfiguration } from '../../common/constants.js'; +import { ChatConfiguration, ChatNotificationMode } from '../../common/constants.js'; import { IChatAccessibilityService, IChatWidgetService } from '../chat.js'; import { ChatWidget } from '../widget/chatWidget.js'; import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; @@ -91,7 +91,8 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi } private async _showOSNotification(widget: ChatWidget, container: HTMLElement, responseContent: string): Promise { - if (!this._configurationService.getValue(ChatConfiguration.NotifyWindowOnResponseReceived)) { + const mode = this._configurationService.getValue(ChatConfiguration.NotifyWindowOnResponseReceived); + if (mode === ChatNotificationMode.Off) { return; } @@ -100,7 +101,8 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi return; } - if (targetWindow.document.hasFocus()) { + const isFocused = targetWindow.document.hasFocus(); + if (mode !== ChatNotificationMode.Always && isFocused) { return; } @@ -109,7 +111,10 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi return; } - await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + // Focus window in notify mode (flash taskbar/dock) if not already focused + if (!isFocused) { + await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + } // Dispose any previous unhandled notifications to avoid replacement/coalescing. this.toasts.clearAndDisposeAll(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts index 99bad32ce49..bc789fbed6d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityActions.ts @@ -31,7 +31,7 @@ class AnnounceChatConfirmationAction extends Action2 { precondition: ChatContextKeys.enabled, f1: true, keybinding: { - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.ExternalExtension + 1, primary: KeyMod.CtrlCmd | KeyCode.KeyA | KeyMod.Shift, when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ChatContextKeys.Editing.hasQuestionCarousel.negate()) } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 88e50c6a498..5843e1ee006 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -893,7 +893,7 @@ export function registerChatActions() { f1: true, precondition: ChatContextKeys.inChatSession, keybinding: [{ - weight: KeybindingWeight.WorkbenchContrib, + weight: KeybindingWeight.ExternalExtension + 1, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA, when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.Editing.hasQuestionCarousel), }] diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 5542c4cad88..45fe3049777 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -465,15 +465,16 @@ export class AttachContextAction extends Action2 { super({ id: 'workbench.action.chat.attachContext', title: localize2('workbench.action.chat.attachContext.label.2', "Add Context..."), - icon: Codicon.attach, + icon: Codicon.add, category: CHAT_CATEGORY, keybinding: { when: ContextKeyExpr.and(ChatContextKeys.inChatInput, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)), primary: KeyMod.CtrlCmd | KeyCode.Slash, weight: KeybindingWeight.EditorContrib }, - menu: { + menu: [{ when: ContextKeyExpr.and( + ChatContextKeys.inQuickChat.negate(), ContextKeyExpr.or( ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditorInline), CTX_INLINE_CHAT_V2_ENABLED) @@ -483,10 +484,21 @@ export class AttachContextAction extends Action2 { ChatContextKeys.agentSupportsAttachments ) ), - id: MenuId.ChatInputAttachmentToolbar, + id: MenuId.ChatInput, group: 'navigation', - order: 3 - }, + order: 101 + }, { + when: ContextKeyExpr.and( + ChatContextKeys.inQuickChat, + ContextKeyExpr.or( + ChatContextKeys.lockedToCodingAgent.negate(), + ChatContextKeys.agentSupportsAttachments + ) + ), + id: MenuId.ChatExecute, + group: 'navigation', + order: -1 + }], }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 1fac629812d..a9e08c350c5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -181,7 +181,11 @@ abstract class SubmitAction extends Action2 { } } -const requestInProgressOrPendingToolCall = ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.Editing.hasToolConfirmation); +const requestInProgressOrPendingToolCall = ContextKeyExpr.or( + ChatContextKeys.requestInProgress, + ChatContextKeys.Editing.hasToolConfirmation, + ChatContextKeys.Editing.hasQuestionCarousel, +); const whenNotInProgress = ChatContextKeys.requestInProgress.negate(); export class ChatSubmitAction extends SubmitAction { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 3743ead31ab..9dbb758b406 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -112,10 +112,6 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('div.agent-session-main-col', [ h('div.agent-session-title-row', [ h('div.agent-session-title@title'), - h('div.agent-session-status@statusContainer', [ - h('span.agent-session-status-provider-icon@statusProviderIcon'), - h('span.agent-session-status-time@statusTime') - ]), h('div.agent-session-title-toolbar@titleToolbar'), ]), h('div.agent-session-details-row', [ @@ -124,9 +120,15 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre h('span.agent-session-diff-added@addedSpan'), h('span.agent-session-diff-removed@removedSpan') ]), - h('div.agent-session-badge@badge'), - h('span.agent-session-separator@separator'), h('div.agent-session-description@description'), + h('div.agent-session-details-right', [ + h('div.agent-session-badge@badge'), + h('span.agent-session-separator@separator'), + h('div.agent-session-status@statusContainer', [ + h('span.agent-session-status-provider-icon@statusProviderIcon'), + h('span.agent-session-status-time@statusTime') + ]), + ]), ]) ]) ] @@ -219,9 +221,8 @@ export class AgentSessionRenderer extends Disposable implements ICompressibleTre this.renderDescription(session, template, hasBadge); } - // Separator (dot between badge and description) - const hasDescription = template.description.textContent !== ''; - template.separator.classList.toggle('has-separator', hasBadge && hasDescription); + // Separator (dot between badge and timestamp) + template.separator.classList.toggle('has-separator', hasBadge); // Status this.renderStatus(session, template); @@ -508,7 +509,7 @@ export class AgentSessionSectionRenderer implements ICompressibleTreeRenderer { - static readonly ITEM_HEIGHT = 48; + static readonly ITEM_HEIGHT = 54; static readonly SECTION_HEIGHT = 26; getHeight(element: AgentSessionListItem): number { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css index b9863aae7dc..0432fdba3eb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/media/agentsessionsviewer.css @@ -44,10 +44,6 @@ color: unset; } - .monaco-list-row.selected .agent-session-status { - color: unset; - } - .monaco-list:not(:focus) .monaco-list-row.selected .agent-session-details-row { color: var(--vscode-descriptionForeground); } @@ -66,7 +62,7 @@ } } - /* On hover or keyboard focus: show toolbar, hide status */ + /* On hover or keyboard focus: show toolbar */ .monaco-list-row:hover, .monaco-list-row.focused { @@ -74,15 +70,15 @@ display: block; } - .agent-session-status { - display: none; + .agent-session-title { + margin-right: 8px; } } .agent-session-item { display: flex; flex-direction: row; - padding: 6px 6px; + padding: 8px 6px; &.archived { color: var(--vscode-descriptionForeground); @@ -143,7 +139,7 @@ .agent-session-title-row { line-height: 17px; - padding-bottom: 4px; + padding-bottom: 6px; } .agent-session-details-row { @@ -227,21 +223,25 @@ flex: 1; overflow: hidden; white-space: nowrap; - margin-right: 16px; - mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); - -webkit-mask-image: linear-gradient(to right, black calc(100% - 32px), transparent); } .agent-session-title { font-size: 13px; } + .agent-session-details-right { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + white-space: nowrap; + flex-shrink: 0; + } + .agent-session-status { display: flex; align-items: center; font-variant-numeric: tabular-nums; - font-size: 12px; - color: var(--vscode-descriptionForeground); white-space: nowrap; .agent-session-status-provider-icon { diff --git a/src/vs/workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.ts b/src/vs/workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.ts new file mode 100644 index 00000000000..9cf993aa7bb --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as event from '../../../../../base/common/event.js'; +import { IDisposable } from '../../../../../base/common/lifecycle.js'; +import { createDecorator, IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IChatRequestVariableEntry } from '../../common/attachments/chatVariableEntries.js'; + +/** + * Interface for a contributed attachment widget instance. + */ +export interface IChatAttachmentWidgetInstance extends IDisposable { + readonly element: HTMLElement; + readonly onDidDelete: event.Event; + readonly onDidOpen: event.Event; +} + +/** + * Factory function type for creating attachment widgets. + * Receives the instantiation service so it can create DI-injected widget instances. + */ +export type ChatAttachmentWidgetFactory = ( + instantiationService: IInstantiationService, + attachment: IChatRequestVariableEntry, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, +) => IChatAttachmentWidgetInstance; + +export const IChatAttachmentWidgetRegistry = createDecorator('chatAttachmentWidgetRegistry'); + +export interface IChatAttachmentWidgetRegistry { + readonly _serviceBrand: undefined; + + /** + * Register a widget factory for a specific attachment kind. + */ + registerFactory(kind: string, factory: ChatAttachmentWidgetFactory): IDisposable; + + /** + * Try to create a widget for the given attachment using a registered factory. + * Returns undefined if no factory is registered for the attachment's kind. + */ + createWidget( + instantiationService: IInstantiationService, + attachment: IChatRequestVariableEntry, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + ): IChatAttachmentWidgetInstance | undefined; +} + +export class ChatAttachmentWidgetRegistry implements IChatAttachmentWidgetRegistry { + + declare readonly _serviceBrand: undefined; + + private readonly _factories = new Map(); + + registerFactory(kind: string, factory: ChatAttachmentWidgetFactory): IDisposable { + this._factories.set(kind, factory); + return { + dispose: () => { + if (this._factories.get(kind) === factory) { + this._factories.delete(kind); + } + } + }; + } + + createWidget( + instantiationService: IInstantiationService, + attachment: IChatRequestVariableEntry, + options: { shouldFocusClearButton: boolean; supportsDeletion: boolean }, + container: HTMLElement, + ): IChatAttachmentWidgetInstance | undefined { + const factory = this._factories.get(attachment.kind); + if (!factory) { + return undefined; + } + return factory(instantiationService, attachment, options, container); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 7209af49aee..54e5db4fda3 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -43,7 +43,7 @@ import { ChatTodoListService, IChatTodoListService } from '../common/tools/chatT import { ChatTransferService, IChatTransferService } from '../common/model/chatTransferService.js'; import { IChatVariablesService } from '../common/attachments/chatVariables.js'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from '../common/widget/chatWidgetHistoryService.js'; -import { AgentsControlClickBehavior, ChatConfiguration } from '../common/constants.js'; +import { AgentsControlClickBehavior, ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; import { ILanguageModelsService, LanguageModelsService } from '../common/languageModels.js'; import { ILanguageModelStatsService, LanguageModelStatsService } from '../common/languageModelStats.js'; @@ -51,7 +51,7 @@ import { ILanguageModelToolsConfirmationService } from '../common/tools/language import { ILanguageModelToolsService } from '../common/tools/languageModelToolsService.js'; import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js'; import { PromptsConfig } from '../common/promptSyntax/config/config.js'; -import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, CLAUDE_RULES_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS } from '../common/promptSyntax/config/promptFileLocations.js'; +import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS } from '../common/promptSyntax/config/promptFileLocations.js'; import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js'; import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL } from '../common/promptSyntax/promptTypes.js'; import { hookFileSchema, HOOK_SCHEMA_URI, HOOK_FILE_GLOB } from '../common/promptSyntax/hookSchema.js'; @@ -96,6 +96,7 @@ import { ChatAccessibilityService } from './accessibility/chatAccessibilityServi import './attachments/chatAttachmentModel.js'; import './widget/input/chatStatusWidget.js'; import { ChatAttachmentResolveService, IChatAttachmentResolveService } from './attachments/chatAttachmentResolveService.js'; +import { ChatAttachmentWidgetRegistry, IChatAttachmentWidgetRegistry } from './attachments/chatAttachmentWidgetRegistry.js'; import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './widget/chatContentParts/chatMarkdownAnchorService.js'; import { ChatContextPickService, IChatContextPickService } from './attachments/chatContextPickService.js'; import { ChatInputBoxContentProvider } from './widget/input/editor/chatEditorInputContentProvider.js'; @@ -331,10 +332,16 @@ configurationRegistry.registerConfiguration({ default: { } }, - 'chat.notifyWindowOnConfirmation': { - type: 'boolean', - description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should present the user with an OS notification when a confirmation or question needs input while the window is not in focus. This includes a window badge as well as notification toast."), - default: true, + [ChatConfiguration.NotifyWindowOnConfirmation]: { + type: 'string', + enum: ['off', 'windowNotFocused', 'always'], + enumDescriptions: [ + nls.localize('chat.notifyWindowOnConfirmation.off', "Never show OS notifications for confirmations."), + nls.localize('chat.notifyWindowOnConfirmation.windowNotFocused', "Show OS notifications for confirmations when the window is not focused."), + nls.localize('chat.notifyWindowOnConfirmation.always', "Always show OS notifications for confirmations, even when the window is focused."), + ], + description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether a chat session should present the user with an OS notification when a confirmation or question needs input. This includes a window badge as well as notification toast."), + default: 'windowNotFocused', }, [ChatConfiguration.AutoReply]: { default: false, @@ -479,9 +486,15 @@ configurationRegistry.registerConfiguration({ description: nls.localize('chat.contextUsage.enabled', "Show the context window usage indicator in the chat input."), }, [ChatConfiguration.NotifyWindowOnResponseReceived]: { - type: 'boolean', - default: true, - description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should present the user with an OS notification when a response is received while the window is not in focus. This includes a window badge as well as notification toast."), + type: 'string', + enum: ['off', 'windowNotFocused', 'always'], + enumDescriptions: [ + nls.localize('chat.notifyWindowOnResponseReceived.off', "Never show OS notifications for responses."), + nls.localize('chat.notifyWindowOnResponseReceived.windowNotFocused', "Show OS notifications for responses when the window is not focused."), + nls.localize('chat.notifyWindowOnResponseReceived.always', "Always show OS notifications for responses, even when the window is focused."), + ], + default: 'windowNotFocused', + description: nls.localize('chat.notifyWindowOnResponseReceived', "Controls whether a chat session should present the user with an OS notification when a response is received. This includes a window badge as well as notification toast."), }, 'chat.checkpoints.enabled': { type: 'boolean', @@ -747,8 +760,7 @@ configurationRegistry.registerConfiguration({ INSTRUCTIONS_DOCUMENTATION_URL, ), default: { - [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true, - [CLAUDE_RULES_SOURCE_FOLDER]: true, + ...DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS.map((folder) => ({ [folder.path]: true })).reduce((acc, curr) => ({ ...acc, ...curr }), {}), }, additionalProperties: { type: 'boolean' }, propertyNames: { @@ -759,8 +771,7 @@ configurationRegistry.registerConfiguration({ tags: ['prompts', 'reusable prompts', 'prompt snippets', 'instructions'], examples: [ { - [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true, - [CLAUDE_RULES_SOURCE_FOLDER]: true, + [DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS[0].path]: true, }, { [INSTRUCTIONS_DEFAULT_SOURCE_FOLDER]: true, @@ -1226,6 +1237,28 @@ Registry.as(Extensions.ConfigurationMigration). return { value }; } }, + { + key: ChatConfiguration.NotifyWindowOnConfirmation, + migrateFn: (value: unknown) => { + if (value === true) { + return { value: ChatNotificationMode.WindowNotFocused }; + } else if (value === false) { + return { value: ChatNotificationMode.Off }; + } + return []; + } + }, + { + key: ChatConfiguration.NotifyWindowOnResponseReceived, + migrateFn: (value: unknown) => { + if (value === true) { + return { value: ChatNotificationMode.WindowNotFocused }; + } else if (value === false) { + return { value: ChatNotificationMode.Off }; + } + return []; + } + }, ]); class ChatResolverContribution extends Disposable { @@ -1554,6 +1587,7 @@ registerSingleton(IPromptsService, PromptsService, InstantiationType.Delayed); registerSingleton(IChatContextPickService, ChatContextPickService, InstantiationType.Delayed); registerSingleton(IChatModeService, ChatModeService, InstantiationType.Delayed); registerSingleton(IChatAttachmentResolveService, ChatAttachmentResolveService, InstantiationType.Delayed); +registerSingleton(IChatAttachmentWidgetRegistry, ChatAttachmentWidgetRegistry, InstantiationType.Delayed); registerSingleton(IChatTodoListService, ChatTodoListService, InstantiationType.Delayed); registerSingleton(IChatOutputRendererService, ChatOutputRendererService, InstantiationType.Delayed); registerSingleton(IChatLayoutService, ChatLayoutService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts index aab01ee1d49..ada489b06ac 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts @@ -576,8 +576,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ if (chatOptions) { const resource = URI.revive(chatOptions.resource); const ref = await chatService.loadSessionForResource(resource, ChatAgentLocation.Chat, CancellationToken.None); - await chatService.sendRequest(resource, chatOptions.prompt, { agentIdSilent: type, attachedContext: chatOptions.attachedContext }); - ref?.dispose(); + try { + const result = await chatService.sendRequest(resource, chatOptions.prompt, { agentIdSilent: type, attachedContext: chatOptions.attachedContext }); + if (result.kind === 'queued') { + await result.deferred; + } else if (result.kind === 'sent') { + await result.data.responseCompletePromise; + } + } finally { + ref?.dispose(); + } } } }), diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetup.ts index 5a059de225b..868420e051d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetup.ts @@ -4,11 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; import product from '../../../../../platform/product/common/product.js'; +import { localize } from '../../../../../nls.js'; +import { EnablementState } from '../../../../services/extensionManagement/common/extensionManagement.js'; +import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; const defaultChat = { completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '', chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '', + providerExtensionId: product.defaultChatAgent?.providerExtensionId ?? '', }; export type InstallChatClassification = { @@ -59,3 +65,44 @@ export function refreshTokens(commandService: ICommandService): void { commandService.executeCommand(defaultChat.completionsRefreshTokenCommand); commandService.executeCommand(defaultChat.chatRefreshTokenCommand); } + +/** + * Ensures the authentication provider extension is enabled. + * If the extension is found locally but disabled, it will be + * re-enabled and running extensions will be updated. + * + * @returns `true` if the extension was re-enabled, `false` otherwise. + */ +export async function maybeEnableAuthExtension( + extensionsWorkbenchService: IExtensionsWorkbenchService, + logService: ILogService +): Promise { + if (!defaultChat.providerExtensionId) { + return false; + } + + const providerExtension = extensionsWorkbenchService.local.find( + e => ExtensionIdentifier.equals(e.identifier.id, defaultChat.providerExtensionId) + ); + + if (!providerExtension) { + return false; + } + + if ( + providerExtension.enablementState === EnablementState.DisabledGlobally || + providerExtension.enablementState === EnablementState.DisabledWorkspace + ) { + logService.info(`[chat setup] auth provider extension '${defaultChat.providerExtensionId}' is disabled, re-enabling it`); + try { + await extensionsWorkbenchService.setEnablement([providerExtension], EnablementState.EnabledGlobally); + await extensionsWorkbenchService.updateRunningExtensions(localize('enableAuthExtension', "Enabling GitHub Authentication")); + return true; + } catch (error) { + logService.error(`[chat setup] failed to re-enable auth provider extension '${defaultChat.providerExtensionId}'`, error); + return false; + } + } + + return false; +} diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupController.ts b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupController.ts index 0379543f996..eb5a4f427b9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupController.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupController.ts @@ -28,7 +28,7 @@ import { IExtensionsWorkbenchService } from '../../../extensions/common/extensio import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, isProUser } from '../../../../services/chat/common/chatEntitlementService.js'; import { CHAT_OPEN_ACTION_ID } from '../actions/chatActions.js'; import { ChatViewId, ChatViewContainerId } from '../chat.js'; -import { ChatSetupAnonymous, ChatSetupStep, ChatSetupResultValue, InstallChatEvent, InstallChatClassification, refreshTokens } from './chatSetup.js'; +import { ChatSetupAnonymous, ChatSetupStep, ChatSetupResultValue, InstallChatEvent, InstallChatClassification, refreshTokens, maybeEnableAuthExtension } from './chatSetup.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js'; @@ -153,6 +153,11 @@ export class ChatSetupController extends Disposable { } private async signIn(options: IChatSetupControllerOptions): Promise<{ defaultAccount: IDefaultAccount | undefined; entitlement: ChatEntitlement | undefined }> { + const authExtensionReEnabled = await maybeEnableAuthExtension(this.extensionsWorkbenchService, this.logService); + if (authExtensionReEnabled) { + refreshTokens(this.commandService); + } + let entitlements; let defaultAccount; try { diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts index 14541ee5f4b..1ff128a1f67 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts @@ -47,13 +47,14 @@ import { ACTION_START as INLINE_CHAT_START } from '../../../inlineChat/common/in import { IPosition } from '../../../../../editor/common/core/position.js'; import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; import { ChatSetupController } from './chatSetupController.js'; -import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult } from './chatSetup.js'; +import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult, maybeEnableAuthExtension, refreshTokens } from './chatSetup.js'; import { ChatSetup } from './chatSetupRunner.js'; import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js'; import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js'; import { IHostService } from '../../../../services/host/browser/host.js'; import { IOutputService } from '../../../../services/output/common/output.js'; +import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -197,6 +198,8 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation { @IViewsService private readonly viewsService: IViewsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IOutputService private readonly outputService: IOutputService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ICommandService private readonly commandService: ICommandService, ) { super(); @@ -315,6 +318,15 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation { } private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise { + + // Ensure auth extension is enabled before waiting for chat readiness. + // This must run before the readiness event listeners are set up because + // updateRunningExtensions restarts all extension hosts. + const authExtensionReEnabled = await maybeEnableAuthExtension(this.extensionsWorkbenchService, this.logService); + if (authExtensionReEnabled) { + refreshTokens(this.commandService); + } + const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource); const modeInfo = widget?.input.currentModeInfo; diff --git a/src/vs/workbench/contrib/chat/browser/chatTipService.ts b/src/vs/workbench/contrib/chat/browser/chatTipService.ts index a6635f79a2e..8ffdf7c86a7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatTipService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatTipService.ts @@ -24,6 +24,21 @@ import { localChatSessionType } from '../common/chatSessionsService.js'; import { IChatService } from '../common/chatService/chatService.js'; import { CreateSlashCommandsUsageTracker } from './createSlashCommandsUsageTracker.js'; import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; + +type ChatTipEvent = { + tipId: string; + action: string; + commandId?: string; +}; + +type ChatTipClassification = { + tipId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the tip.' }; + action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action performed on the tip (shown, dismissed, navigateNext, navigatePrevious, hidden, disabled, commandClicked).' }; + commandId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The command ID that was clicked, if applicable.' }; + owner: 'meganrogge'; + comment: 'Tracks user interactions with chat tips to understand which tips resonate and which are dismissed.'; +}; export const IChatTipService = createDecorator('chatTipService'); @@ -583,6 +598,7 @@ export class ChatTipService extends Disposable implements IChatTipService { private readonly _tracker: TipEligibilityTracker; private readonly _createSlashCommandsUsageTracker: CreateSlashCommandsUsageTracker; private _yoloModeEverEnabled: boolean; + private readonly _tipCommandListener = this._register(new MutableDisposable()); constructor( @IProductService private readonly _productService: IProductService, @@ -592,6 +608,8 @@ export class ChatTipService extends Disposable implements IChatTipService { @IInstantiationService instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @IChatEntitlementService chatEntitlementService: IChatEntitlementService, + @ICommandService private readonly _commandService: ICommandService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { super(); this._tracker = this._register(instantiationService.createInstance(TipEligibilityTracker, TIP_CATALOG)); @@ -630,6 +648,7 @@ export class ChatTipService extends Disposable implements IChatTipService { dismissTip(): void { if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'dismissed'); const dismissed = new Set(this._getDismissedTipIds()); dismissed.add(this._shownTip.id); this._storageService.store(ChatTipService._DISMISSED_TIP_KEY, JSON.stringify([...dismissed]), StorageScope.APPLICATION, StorageTarget.MACHINE); @@ -676,12 +695,18 @@ export class ChatTipService extends Disposable implements IChatTipService { } hideTip(): void { + if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'hidden'); + } this._shownTip = undefined; this._tipRequestId = undefined; this._onDidHideTip.fire(); } async disableTips(): Promise { + if (this._shownTip) { + this._logTipTelemetry(this._shownTip.id, 'disabled'); + } this._shownTip = undefined; this._tipRequestId = undefined; await this._configurationService.updateValue('chat.tips.enabled', false, ConfigurationTarget.APPLICATION); @@ -787,6 +812,9 @@ export class ChatTipService extends Disposable implements IChatTipService { this._tipRequestId = sourceId; this._shownTip = selectedTip; + this._logTipTelemetry(selectedTip.id, 'shown'); + this._trackTipCommandClicks(selectedTip); + return this._createTip(selectedTip); } @@ -820,9 +848,12 @@ export class ChatTipService extends Disposable implements IChatTipService { const idx = ((currentIndex + direction * i) % TIP_CATALOG.length + TIP_CATALOG.length) % TIP_CATALOG.length; const candidate = TIP_CATALOG[idx]; if (!dismissedIds.has(candidate.id) && this._isEligible(candidate, contextKeyService)) { + this._logTipTelemetry(this._shownTip.id, direction === 1 ? 'navigateNext' : 'navigatePrevious'); this._shownTip = candidate; this._tipRequestId = 'welcome'; this._storageService.store(ChatTipService._LAST_TIP_ID_KEY, candidate.id, StorageScope.APPLICATION, StorageTarget.USER); + this._logTipTelemetry(candidate.id, 'shown'); + this._trackTipCommandClicks(candidate); const tip = this._createTip(candidate); this._onDidNavigateTip.fire(tip); return tip; @@ -932,6 +963,27 @@ export class ChatTipService extends Disposable implements IChatTipService { }; } + private _logTipTelemetry(tipId: string, action: string, commandId?: string): void { + this._telemetryService.publicLog2('chatTip', { + tipId, + action, + commandId, + }); + } + + private _trackTipCommandClicks(tip: ITipDefinition): void { + this._tipCommandListener.clear(); + if (!tip.enabledCommands?.length) { + return; + } + const enabledCommandSet = new Set(tip.enabledCommands); + this._tipCommandListener.value = this._commandService.onDidExecuteCommand(e => { + if (enabledCommandSet.has(e.commandId) && this._shownTip?.id === tip.id) { + this._logTipTelemetry(tip.id, 'commandClicked', e.commandId); + } + }); + } + private _readApplicationWithProfileFallback(key: string): string | undefined { const applicationValue = this._storageService.get(key, StorageScope.APPLICATION); if (applicationValue) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts index 2b4857787cf..abece4b0a87 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWindowNotifier.ts @@ -17,6 +17,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IHostService } from '../../../services/host/browser/host.js'; import { IChatModel, IChatRequestNeedsInputInfo } from '../common/model/chatModel.js'; import { IChatService } from '../common/chatService/chatService.js'; +import { ChatConfiguration, ChatNotificationMode } from '../common/constants.js'; import { IChatWidgetService } from './chat.js'; import { AcceptToolConfirmationActionId, IToolConfirmationActionContext } from './actions/chatToolActions.js'; @@ -71,7 +72,8 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu private async _notifyIfNeeded(sessionResource: URI, info: IChatRequestNeedsInputInfo): Promise { // Check configuration - if (!this._configurationService.getValue('chat.notifyWindowOnConfirmation')) { + const mode = this._configurationService.getValue(ChatConfiguration.NotifyWindowOnConfirmation); + if (mode === ChatNotificationMode.Off) { return; } @@ -79,16 +81,18 @@ export class ChatWindowNotifier extends Disposable implements IWorkbenchContribu const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource); const targetWindow = widget ? dom.getWindow(widget.domNode) : mainWindow; - // Only notify if window doesn't have focus - if (targetWindow.document.hasFocus()) { + const isFocused = targetWindow.document.hasFocus(); + if (mode !== ChatNotificationMode.Always && isFocused) { return; } // Clear any existing notification for this session this._clearNotification(sessionResource); - // Focus window in notify mode (flash taskbar/dock) - await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + // Focus window in notify mode (flash taskbar/dock) if not already focused + if (!isFocused) { + await this._hostService.focus(targetWindow, { mode: FocusMode.Notify }); + } // Create OS notification const notificationTitle = info.title ? localize('chatTitle', "Chat: {0}", info.title) : localize('chat.untitledChat', "Untitled Chat"); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts index 538f5d4ac2d..ac323f512e4 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/promptToolsCodeLensProvider.ts @@ -86,7 +86,7 @@ class PromptToolsCodeLensProvider extends Disposable implements CodeLensProvider command: { title: localize('configure-tools.capitalized.ellipsis', "Configure Tools..."), id: this.cmdId, - arguments: [model, toolsAttr.range, toolsAttr.value.type === 'scalar', selectedTools, target] + arguments: [model, toolsAttr.value.range, toolsAttr.value.type === 'scalar', selectedTools, target] } }; return { lenses: [codeLens] }; diff --git a/src/vs/workbench/contrib/chat/browser/tools/languageModelToolsConfirmationService.ts b/src/vs/workbench/contrib/chat/browser/tools/languageModelToolsConfirmationService.ts index bb23081defd..50a3495c4f8 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/languageModelToolsConfirmationService.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/languageModelToolsConfirmationService.ts @@ -267,6 +267,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements label: localize('allowSession', 'Allow in this Session'), detail: localize('allowSessionTooltip', 'Allow this tool to run in this session without confirmation.'), divider: !!actions.length, + scope: 'session', select: async () => { this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'session'); return true; @@ -275,6 +276,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowWorkspace', 'Allow in this Workspace'), detail: localize('allowWorkspaceTooltip', 'Allow this tool to run in this workspace without confirmation.'), + scope: 'workspace', select: async () => { this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'workspace'); return true; @@ -283,6 +285,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowGlobally', 'Always Allow'), detail: localize('allowGloballyTooltip', 'Always allow this tool to run without confirmation.'), + scope: 'profile', select: async () => { this._preExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'profile'); return true; @@ -298,6 +301,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements label: localize('allowServerSession', 'Allow Tools from {0} in this Session', serverLabel), detail: localize('allowServerSessionTooltip', 'Allow all tools from this server to run in this session without confirmation.'), divider: true, + scope: 'session', select: async () => { this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'session'); return true; @@ -306,6 +310,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowServerWorkspace', 'Allow Tools from {0} in this Workspace', serverLabel), detail: localize('allowServerWorkspaceTooltip', 'Allow all tools from this server to run in this workspace without confirmation.'), + scope: 'workspace', select: async () => { this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'workspace'); return true; @@ -314,6 +319,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowServerGlobally', 'Always Allow Tools from {0}', serverLabel), detail: localize('allowServerGloballyTooltip', 'Always allow all tools from this server to run without confirmation.'), + scope: 'profile', select: async () => { this._preExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'profile'); return true; @@ -345,6 +351,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements label: localize('allowSessionPost', 'Allow Without Review in this Session'), detail: localize('allowSessionPostTooltip', 'Allow results from this tool to be sent without confirmation in this session.'), divider: !!actions.length, + scope: 'session', select: async () => { this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'session'); return true; @@ -353,6 +360,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowWorkspacePost', 'Allow Without Review in this Workspace'), detail: localize('allowWorkspacePostTooltip', 'Allow results from this tool to be sent without confirmation in this workspace.'), + scope: 'workspace', select: async () => { this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'workspace'); return true; @@ -361,6 +369,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowGloballyPost', 'Always Allow Without Review'), detail: localize('allowGloballyPostTooltip', 'Always allow results from this tool to be sent without confirmation.'), + scope: 'profile', select: async () => { this._postExecutionToolConfirmStore.setAutoConfirmation(ref.toolId, 'profile'); return true; @@ -376,6 +385,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements label: localize('allowServerSessionPost', 'Allow Tools from {0} Without Review in this Session', serverLabel), detail: localize('allowServerSessionPostTooltip', 'Allow results from all tools from this server to be sent without confirmation in this session.'), divider: true, + scope: 'session', select: async () => { this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'session'); return true; @@ -384,6 +394,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowServerWorkspacePost', 'Allow Tools from {0} Without Review in this Workspace', serverLabel), detail: localize('allowServerWorkspacePostTooltip', 'Allow results from all tools from this server to be sent without confirmation in this workspace.'), + scope: 'workspace', select: async () => { this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'workspace'); return true; @@ -392,6 +403,7 @@ export class LanguageModelToolsConfirmationService extends Disposable implements { label: localize('allowServerGloballyPost', 'Always Allow Tools from {0} Without Review', serverLabel), detail: localize('allowServerGloballyPostTooltip', 'Always allow results from all tools from this server to be sent without confirmation.'), + scope: 'profile', select: async () => { this._postExecutionServerConfirmStore.setAutoConfirmation(definitionId, 'profile'); return true; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatQuestionCarouselPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatQuestionCarouselPart.ts index d1284a8d1e6..20aa1bdcd56 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatQuestionCarouselPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatQuestionCarouselPart.ts @@ -1054,20 +1054,10 @@ export class ChatQuestionCarouselPart extends Disposable implements IChatContent const freeformTextarea = this._freeformTextareas.get(question.id); const freeformValue = freeformTextarea?.value !== '' ? freeformTextarea?.value : undefined; - // Only include defaults if nothing selected AND no freeform input - let finalSelectedValues = selectedValues; - if (selectedValues.length === 0 && !freeformValue && question.defaultValue !== undefined) { - const defaultIds = Array.isArray(question.defaultValue) - ? question.defaultValue - : [question.defaultValue]; - const defaultValues = question.options - ?.filter(opt => defaultIds.includes(opt.id)) - .map(opt => opt.value); - finalSelectedValues = defaultValues?.filter(v => v !== undefined) || []; - } - - if (freeformValue || finalSelectedValues.length > 0) { - return { selectedValues: finalSelectedValues, freeformValue }; + // Return whatever was selected - defaults are applied at render time when + // checkboxes are initially checked, so empty selection means user unchecked all + if (freeformValue || selectedValues.length > 0) { + return { selectedValues, freeformValue }; } return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css index 55370438906..3a2d4c3cef9 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css @@ -233,6 +233,10 @@ .rendered-markdown > p { margin: 0; + + & + p { + margin-top: 5px; + } } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts index b54e72a3215..e736777479d 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts @@ -27,7 +27,11 @@ export interface IToolConfirmationConfig { subtitle?: string; } -type AbstractToolPrimaryAction = IChatConfirmationButton<(() => void)> | Separator; +export interface IAbstractToolPrimaryAction extends IChatConfirmationButton<(() => void)> { + scope?: 'session' | 'workspace' | 'profile'; +} + +type AbstractToolPrimaryAction = IAbstractToolPrimaryAction | Separator; /** * Base class for a tool confirmation. @@ -75,14 +79,32 @@ export abstract class AbstractToolConfirmationSubPart extends BaseChatToolInvoca const skipTooltip = keybindingService.appendKeybinding(config.skipLabel, config.skipActionId); const additionalActions = this.additionalPrimaryActions(); + + // find session scoped action + const sessionAction = additionalActions.find( + (action): action is IAbstractToolPrimaryAction => 'scope' in action && action.scope === 'session' + ); + + // regular allow action + const allowAction: IAbstractToolPrimaryAction = { + label: config.allowLabel, + tooltip: allowTooltip, + data: () => { this.confirmWith(toolInvocation, { type: ToolConfirmKind.UserAction }); }, + }; + + const primaryAction = sessionAction ?? allowAction; + + // rebuild additional list with allow action + const moreActions = sessionAction + ? [allowAction, ...additionalActions.filter(a => a !== sessionAction)] + : additionalActions; + buttons = [ { - label: config.allowLabel, - tooltip: allowTooltip, - data: () => { - this.confirmWith(toolInvocation, { type: ToolConfirmKind.UserAction }); - }, - moreActions: additionalActions.length > 0 ? additionalActions : undefined, + label: primaryAction.label, + tooltip: primaryAction.tooltip, + data: primaryAction.data, + moreActions: moreActions.length > 0 ? moreActions : undefined, }, { label: localize('skip', "Skip"), diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index f30c20e399b..2386b799b6c 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -392,6 +392,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS const tooltip = this.keybindingService.appendKeybinding(tooltipDetail, actionId); return { label, tooltip }; }; + return [ { ...getLabelAndTooltip(localize('tool.allow', "Allow"), AcceptToolConfirmationActionId), diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts index ffecd3be045..9ebf247ad94 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts @@ -104,6 +104,7 @@ export class ToolConfirmationSubPart extends AbstractToolConfirmationSubPart { actions.push({ label: action.label, tooltip: action.detail, + scope: action.scope, data: async () => { const shouldConfirm = await action.select(); if (shouldConfirm) { diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts index dfe354e2514..4805392c6db 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatToolPostExecuteConfirmationPart.ts @@ -87,6 +87,7 @@ export class ChatToolPostExecuteConfirmationPart extends AbstractToolConfirmatio actions.push({ label: action.label, tooltip: action.detail, + scope: action.scope, data: async () => { const shouldConfirm = await action.select(); if (shouldConfirm) { diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts index aac466afd05..62dc18fafa5 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts @@ -1216,6 +1216,15 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const responses = this.viewModel?.getItems().filter(isResponseVM); - const lastResponse = responses?.[responses.length - 1]; - this.chatAccessibilityService.acceptResponse(this, this.container, lastResponse, this.viewModel?.sessionResource, options?.isVoiceInput); - if (lastResponse?.result?.nextQuestion) { - const { prompt, participant, command } = lastResponse.result.nextQuestion; - const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); - if (question) { - this.input.setValue(question, false); + sent.data.responseCreatedPromise.then(() => { + // Only start accessibility progress once a real request/response model exists. + this.chatAccessibilityService.acceptRequest(submittedSessionResource); + sent.data.responseCompletePromise.then(() => { + const responses = this.viewModel?.getItems().filter(isResponseVM); + const lastResponse = responses?.[responses.length - 1]; + this.chatAccessibilityService.acceptResponse(this, this.container, lastResponse, submittedSessionResource, options?.isVoiceInput); + if (lastResponse?.result?.nextQuestion) { + const { prompt, participant, command } = lastResponse.result.nextQuestion; + const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); + if (question) { + this.input.setValue(question, false); + } } - } + }); }); return sent.data.responseCreatedPromise; diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index ed750c0c582..32a286920ce 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -13,7 +13,6 @@ import * as aria from '../../../../../../base/browser/ui/aria/aria.js'; import { ButtonWithIcon } from '../../../../../../base/browser/ui/button/button.js'; import { createInstantHoverDelegate } from '../../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; -import { renderLabelWithIcons } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../../../base/common/actions.js'; import { equals as arraysEqual } from '../../../../../../base/common/arrays.js'; import { DeferredPromise, RunOnceScheduler } from '../../../../../../base/common/async.js'; @@ -32,15 +31,13 @@ import { autorun, derived, derivedOpts, IObservable, ISettableObservable, observ import { isMacintosh } from '../../../../../../base/common/platform.js'; import { isEqual } from '../../../../../../base/common/resources.js'; import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js'; -import { assertType } from '../../../../../../base/common/types.js'; import { URI } from '../../../../../../base/common/uri.js'; import { IEditorConstructionOptions } from '../../../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../../../editor/browser/editorExtensions.js'; import { CodeEditorWidget } from '../../../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; -import { EditorLayoutInfo, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; +import { EditorLayoutInfo, EditorOption, EditorOptions, IEditorOptions } from '../../../../../../editor/common/config/editorOptions.js'; import { IDimension } from '../../../../../../editor/common/core/2d/dimension.js'; import { IPosition } from '../../../../../../editor/common/core/position.js'; -import { IRange, Range } from '../../../../../../editor/common/core/range.js'; import { isLocation } from '../../../../../../editor/common/languages.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; @@ -83,7 +80,7 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd import { InlineChatConfigKeys } from '../../../../inlineChat/common/inlineChat.js'; import { IChatViewTitleActionContext } from '../../../common/actions/chatActions.js'; import { ChatContextKeys } from '../../../common/actions/chatContextKeys.js'; -import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isStringVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; +import { ChatRequestVariableSet, IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry } from '../../../common/attachments/chatVariableEntries.js'; import { ChatMode, IChatMode, IChatModeService } from '../../../common/chatModes.js'; import { IChatFollowup, IChatQuestionCarousel, IChatService, IChatSessionContext } from '../../../common/chatService/chatService.js'; import { agentOptionId, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService, isIChatSessionFileChange2, localChatSessionType } from '../../../common/chatSessionsService.js'; @@ -101,9 +98,9 @@ import { ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionCon import { AgentSessionProviders, getAgentSessionProvider } from '../../agentSessions/agentSessions.js'; import { IAgentSessionsService } from '../../agentSessions/agentSessionsService.js'; import { ChatAttachmentModel } from '../../attachments/chatAttachmentModel.js'; +import { IChatAttachmentWidgetRegistry } from '../../attachments/chatAttachmentWidgetRegistry.js'; import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../../attachments/chatAttachmentWidgets.js'; import { ChatImplicitContexts } from '../../attachments/chatImplicitContext.js'; -import { ImplicitContextAttachmentWidget } from '../../attachments/implicitContextAttachment.js'; import { IChatWidget, IChatWidgetViewModelChangeEvent, ISessionTypePickerDelegate, isIChatResourceViewContext, isIChatViewViewContext, IWorkspacePickerDelegate } from '../../chat.js'; import { ChatEditingShowChangesAction, ViewAllSessionChangesAction, ViewPreviousEditsAction } from '../../chatEditing/chatEditingActions.js'; import { resizeImage } from '../../chatImageUtils.js'; @@ -132,6 +129,7 @@ import { EnhancedModelPickerActionItem } from './modelPickerActionItem2.js'; const $ = dom.$; const INPUT_EDITOR_MAX_HEIGHT = 250; +const INPUT_EDITOR_MIN_VISIBLE_LINES = 2; const CachedLanguageModelsKey = 'chat.cachedLanguageModels.v2'; export interface IChatInputStyles { @@ -234,8 +232,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidClickOverlay = this._register(new Emitter()); readonly onDidClickOverlay: Event = this._onDidClickOverlay.event; - private readonly _implicitContextWidget: MutableDisposable = this._register(new MutableDisposable()); - private readonly _attachmentModel: ChatAttachmentModel; private _widget?: IChatWidget; public get attachmentModel(): ChatAttachmentModel { @@ -331,8 +327,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private executeToolbar!: MenuWorkbenchToolBar; private inputActionsToolbar!: MenuWorkbenchToolBar; - private addFilesToolbar: MenuWorkbenchToolBar | undefined; - private addFilesButton: AddFilesButton | undefined; + get inputEditor() { return this._inputEditor; @@ -513,6 +508,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IChatAttachmentWidgetRegistry private readonly _chatAttachmentWidgetRegistry: IChatAttachmentWidgetRegistry, ) { super(); @@ -1074,7 +1070,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge models.sort((a, b) => a.metadata.name.localeCompare(b.metadata.name)); const sessionType = this.getCurrentSessionType(); - if (sessionType) { + if (sessionType && sessionType !== AgentSessionProviders.Local) { // Session has a specific chat session type - show only models that target // this session type, if any such models exist. return models.filter(entry => entry.metadata?.targetChatSessionType === sessionType && entry.metadata?.isUserSelectable); @@ -1942,13 +1938,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'), dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ dom.h('.chat-input-container@inputContainer', [ - dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), dom.h('.chat-editor-container@editorContainer'), - dom.h('.chat-input-toolbars@inputToolbars'), + dom.h('.chat-input-toolbars@inputToolbars', [ + dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), + ]), ]), ]), dom.h('.chat-attachments-container@attachmentsContainer', [ - dom.h('.chat-attachment-toolbar@attachmentToolbar'), dom.h('.chat-attached-context@attachedContextContainer'), ]), dom.h('.interactive-input-followups@followupsContainer'), @@ -1964,13 +1960,13 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.h('.chat-getting-started-tip-container@chatGettingStartedTipContainer'), dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [ dom.h('.chat-input-container@inputContainer', [ - dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), dom.h('.chat-attachments-container@attachmentsContainer', [ - dom.h('.chat-attachment-toolbar@attachmentToolbar'), dom.h('.chat-attached-context@attachedContextContainer'), ]), dom.h('.chat-editor-container@editorContainer'), - dom.h('.chat-input-toolbars@inputToolbars'), + dom.h('.chat-input-toolbars@inputToolbars', [ + dom.h('.chat-context-usage-container@contextUsageWidgetContainer'), + ]), ]), ]), ]); @@ -1993,7 +1989,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.attachmentsContainer = elements.attachmentsContainer; this.attachedContextContainer = elements.attachedContextContainer; const toolbarsContainer = elements.inputToolbars; - const attachmentToolbarContainer = elements.attachmentToolbar; this.chatEditingSessionWidgetContainer = elements.chatEditingSessionWidgetContainer; this.chatInputTodoListWidgetContainer = elements.chatInputTodoListWidgetContainer; this.chatGettingStartedTipContainer = elements.chatGettingStartedTipContainer; @@ -2002,7 +1997,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.chatInputWidgetsContainer = elements.chatInputWidgetsContainer; this.contextUsageWidgetContainer = elements.contextUsageWidgetContainer; - // Context usage widget + // Context usage widget — will be positioned in the toolbar after toolbars are created this.contextUsageWidget = this._register(this.instantiationService.createInstance(ChatContextUsageWidget)); this.contextUsageWidgetContainer.appendChild(this.contextUsageWidget.domNode); @@ -2335,23 +2330,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderAttachedContext(); })); - this.addFilesToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, attachmentToolbarContainer, MenuId.ChatInputAttachmentToolbar, { - telemetrySource: this.options.menus.telemetrySource, - label: true, - menuOptions: { shouldForwardArgs: true, renderShortTitle: true }, - hiddenItemStrategy: HiddenItemStrategy.NoHide, - hoverDelegate, - actionViewItemProvider: (action, options) => { - if (action.id === 'workbench.action.chat.attachContext') { - const viewItem = this.instantiationService.createInstance(AddFilesButton, this._attachmentModel, action, options); - viewItem.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts); - this.addFilesButton = viewItem; - return this.addFilesButton; - } - return undefined; - } - })); - this.addFilesToolbar.context = { widget, placeholder: localize('chatAttachFiles', 'Search for files and context to add to your request') }; this.renderAttachedContext(); const inputResizeObserver = this._register(new dom.DisposableResizeObserver(() => { @@ -2398,15 +2376,14 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); const attachments = [...this.attachmentModel.attachments.entries()]; - const hasAttachments = Boolean(attachments.length) || Boolean(this.implicitContext?.hasValue); - dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments || (this.addFilesToolbar && !this.addFilesToolbar.isEmpty())), this.attachmentsContainer); + const hasAttachments = Boolean(attachments.length); + dom.setVisibility(Boolean(this.options.renderInputToolbarBelowInput || hasAttachments), this.attachmentsContainer); dom.setVisibility(hasAttachments, this.attachedContextContainer); if (!attachments.length) { this._indexOfLastAttachedContextDeletedWithKeyboard = -1; this._indexOfLastOpenedContext = -1; } - const isSuggestedEnabled = this.configurationService.getValue('chat.implicitContext.suggestedContext'); for (const [index, attachment] of attachments) { const resource = URI.isUri(attachment.value) ? attachment.value : isLocation(attachment.value) ? attachment.value.uri : undefined; @@ -2441,7 +2418,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } else if (isSCMHistoryItemChangeRangeVariableEntry(attachment)) { attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemChangeRangeAttachmentWidget, attachment, lm, options, container, this._contextResourceLabels); } else { - attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, lm, options, container, this._contextResourceLabels); + attachmentWidget = this._chatAttachmentWidgetRegistry.createWidget(this.instantiationService, attachment, options, container) + ?? this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, lm, options, container, this._contextResourceLabels); } if (shouldFocusClearButton) { @@ -2462,54 +2440,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); } - if (isSuggestedEnabled && this.implicitContext?.hasValue) { - this._implicitContextWidget.value = this.instantiationService.createInstance(ImplicitContextAttachmentWidget, () => this._widget, (targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined) => this.isAttachmentAlreadyAttached(targetUri, targetRange, targetHandle, attachments.map(([, a]) => a)), this.implicitContext, this._contextResourceLabels, this._attachmentModel, container); - } else { - this._implicitContextWidget.clear(); - } - - this.addFilesButton?.setShowLabel(this._attachmentModel.size === 0 && !this._implicitContextWidget.value?.hasRenderedContexts); - this._indexOfLastOpenedContext = -1; } - private isAttachmentAlreadyAttached(targetUri: URI | undefined, targetRange: IRange | undefined, targetHandle: number | undefined, attachments: IChatRequestVariableEntry[]): boolean { - return attachments.some((attachment) => { - let uri: URI | undefined; - let range: IRange | undefined; - let handle: number | undefined; - - if (URI.isUri(attachment.value)) { - uri = attachment.value; - } else if (isLocation(attachment.value)) { - uri = attachment.value.uri; - range = attachment.value.range; - } else if (isStringVariableEntry(attachment)) { - uri = attachment.uri; - handle = attachment.handle; - } - - if ((handle !== undefined && targetHandle === undefined) || (handle === undefined && targetHandle !== undefined)) { - return false; - } - - if (handle !== undefined && targetHandle !== undefined && handle !== targetHandle) { - return false; - } - - if (!uri || !isEqual(uri, targetUri)) { - return false; - } - - // check if the exact range is already attached - if (targetRange) { - return range && Range.equalsRange(range, targetRange); - } - - return true; - }); - } - private handleAttachmentDeletion(e: KeyboardEvent | unknown, index: number, attachment: IChatRequestVariableEntry) { // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) if (dom.isKeyboardEvent(e)) { @@ -2552,20 +2485,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - // eslint-disable-next-line no-restricted-syntax - const toolbar = this.addFilesToolbar?.getElement().querySelector('.action-label'); - if (!toolbar) { - return; - } - // eslint-disable-next-line no-restricted-syntax const attachments = Array.from(this.attachedContextContainer.querySelectorAll('.chat-attached-context-attachment')); if (!attachments.length) { return; } - attachments.unshift(toolbar); - const activeElement = dom.getWindow(this.attachmentsContainer).document.activeElement; const currentIndex = attachments.findIndex(attachment => attachment === activeElement); let newIndex = currentIndex; @@ -3026,7 +2951,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth; - const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight); + const lineHeight = this._inputEditor.getOption(EditorOption.lineHeight); + const { top, bottom } = this._inputEditor.getOption(EditorOption.padding); + const inputEditorMinHeight = this.options.renderStyle === 'compact' ? 0 : (lineHeight * INPUT_EDITOR_MIN_VISIBLE_LINES) + top + bottom; + const inputEditorHeight = Math.max(inputEditorMinHeight, Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight)); const newDimension = { width: newEditorWidth, height: inputEditorHeight }; if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) { // This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler @@ -3060,7 +2988,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth(); const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4; const inputToolbarPadding = this.inputActionsToolbar.getItemsLength() ? (this.inputActionsToolbar.getItemsLength() - 1) * 4 : 0; - return executeToolbarWidth + executeToolbarPadding + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding); + const contextUsageWidth = dom.getTotalWidth(this.contextUsageWidgetContainer); + return executeToolbarWidth + executeToolbarPadding + contextUsageWidth + (this.options.renderInputToolbarBelowInput ? 0 : inputToolbarWidth + inputToolbarPadding); }; return { @@ -3149,37 +3078,3 @@ class ChatSessionPickersContainerActionItem extends ActionViewItem { super.dispose(); } } - -class AddFilesButton extends ActionViewItem { - private showLabel: boolean | undefined; - - constructor(context: unknown, action: IAction, options: IActionViewItemOptions) { - super(context, action, { - ...options, - icon: false, - label: true, - keybindingNotRenderedWithLabel: true, - }); - } - - public setShowLabel(show: boolean): void { - this.showLabel = show; - this.updateLabel(); - } - - override render(container: HTMLElement): void { - container.classList.add('chat-attachment-button'); - super.render(container); - this.updateLabel(); - } - - protected override updateLabel(): void { - if (!this.label) { - return; - } - assertType(this.label); - this.label.classList.toggle('has-label', this.showLabel); - const message = this.showLabel ? `$(attach) ${this.action.label}` : `$(attach)`; - dom.reset(this.label, ...renderLabelWithIcons(message)); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts index 71fcfc6db1a..bd38dd8e787 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts @@ -47,7 +47,7 @@ function getUpdateHoverContent(updateState: StateType): MarkdownString { hoverContent.appendMarkdown(localize('chat.modelPicker.restartUpdateHover', "This model requires a newer version of VS Code. [Restart to Update](command:update.restartToUpdate) to access it.")); break; default: - hoverContent.appendMarkdown(localize('chat.modelPicker.checkUpdateHover', "This model requires a newer version of VS Code. [Check for Updates](command:update.checkForUpdate) to access it.")); + hoverContent.appendMarkdown(localize('chat.modelPicker.checkUpdateHover', "This model requires a newer version of VS Code. [Update VS Code](command:update.checkForUpdate) to access it.")); break; } return hoverContent; @@ -124,14 +124,14 @@ export function buildModelPickerItems( selectedModelId: string | undefined, recentModelIds: string[], controlModels: IStringDictionary, - isProUser: boolean, currentVSCodeVersion: string, updateStateType: StateType, onSelect: (model: ILanguageModelChatMetadataAndIdentifier) => void, - upgradePlanUrl: string | undefined, + manageSettingsUrl: string | undefined, commandService: ICommandService, chatEntitlementService: IChatEntitlementService, ): IActionListItem[] { + const isPro = isProUser(chatEntitlementService.entitlement); const items: IActionListItem[] = []; let otherModels: ILanguageModelChatMetadataAndIdentifier[] = []; if (models.length === 0) { @@ -165,7 +165,7 @@ export function buildModelPickerItems( const resolveModel = (id: string) => allModelsMap.get(id) ?? modelsByMetadataId.get(id); const getUnavailableReason = (entry: IModelControlEntry): 'upgrade' | 'update' | 'admin' => { - if (!isProUser) { + if (!isPro) { return 'upgrade'; } if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { @@ -184,7 +184,7 @@ export function buildModelPickerItems( // --- 2. Promoted section (selected + recently used + featured) --- type PromotedItem = | { kind: 'available'; model: ILanguageModelChatMetadataAndIdentifier } - | { kind: 'unavailable'; entry: IModelControlEntry; reason: 'upgrade' | 'update' | 'admin' }; + | { kind: 'unavailable'; id: string; entry: IModelControlEntry; reason: 'upgrade' | 'update' | 'admin' }; const promotedItems: PromotedItem[] = []; @@ -198,7 +198,7 @@ export function buildModelPickerItems( markPlaced(model.identifier, model.metadata.id); const entry = controlModels[model.metadata.id]; if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { - promotedItems.push({ kind: 'unavailable', entry, reason: 'update' }); + promotedItems.push({ kind: 'unavailable', id: model.metadata.id, entry, reason: 'update' }); } else { promotedItems.push({ kind: 'available', model }); } @@ -206,9 +206,9 @@ export function buildModelPickerItems( } if (!model) { const entry = controlModels[id]; - if (entry) { + if (entry && !entry.exists) { markPlaced(id); - promotedItems.push({ kind: 'unavailable', entry, reason: getUnavailableReason(entry) }); + promotedItems.push({ kind: 'unavailable', id, entry, reason: getUnavailableReason(entry) }); return true; } } @@ -226,25 +226,26 @@ export function buildModelPickerItems( } // Featured models from control manifest - for (const entry of Object.values(controlModels)) { - if (!entry.featured || placed.has(entry.id)) { + for (const [entryId, entry] of Object.entries(controlModels)) { + if (!entry.featured || placed.has(entryId)) { continue; } - const model = resolveModel(entry.id); + const model = resolveModel(entryId); if (model && !placed.has(model.identifier)) { markPlaced(model.identifier, model.metadata.id); if (entry.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { - promotedItems.push({ kind: 'unavailable', entry, reason: 'update' }); + promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: 'update' }); } else { promotedItems.push({ kind: 'available', model }); } - } else if (!model) { - markPlaced(entry.id); - promotedItems.push({ kind: 'unavailable', entry, reason: getUnavailableReason(entry) }); + } else if (!model && !entry.exists) { + markPlaced(entryId); + promotedItems.push({ kind: 'unavailable', id: entryId, entry, reason: getUnavailableReason(entry) }); } } // Render promoted section: sorted alphabetically by name + let hasShownActionLink = false; if (promotedItems.length > 0) { promotedItems.sort((a, b) => { const aName = a.kind === 'available' ? a.model.metadata.name : a.entry.label; @@ -252,12 +253,18 @@ export function buildModelPickerItems( return aName.localeCompare(bName); }); - items.push({ kind: ActionListItemKind.Separator }); + if (items.length > 0) { + items.push({ kind: ActionListItemKind.Separator }); + } for (const item of promotedItems) { if (item.kind === 'available') { items.push(createModelItem(createModelAction(item.model, selectedModelId, onSelect), item.model)); } else { - items.push(createUnavailableModelItem(item.entry, item.reason, upgradePlanUrl, updateStateType)); + const showActionLink = item.reason === 'upgrade' ? !hasShownActionLink : true; + if (showActionLink && item.reason === 'upgrade') { + hasShownActionLink = true; + } + items.push(createUnavailableModelItem(item.id, item.entry, item.reason, manageSettingsUrl, updateStateType, undefined, showActionLink)); } } } @@ -276,7 +283,9 @@ export function buildModelPickerItems( }); if (otherModels.length > 0) { - items.push({ kind: ActionListItemKind.Separator }); + if (items.length > 0) { + items.push({ kind: ActionListItemKind.Separator }); + } items.push({ item: { id: 'otherModels', @@ -297,7 +306,7 @@ export function buildModelPickerItems( for (const model of otherModels) { const entry = controlModels[model.metadata.id] ?? controlModels[model.identifier]; if (entry?.minVSCodeVersion && !isVersionAtLeast(currentVSCodeVersion, entry.minVSCodeVersion)) { - items.push(createUnavailableModelItem(entry, 'update', upgradePlanUrl, updateStateType, ModelPickerSection.Other)); + items.push(createUnavailableModelItem(model.metadata.id, entry, 'update', manageSettingsUrl, updateStateType, ModelPickerSection.Other, true)); } else { items.push(createModelItem(createModelAction(model, selectedModelId, onSelect, ModelPickerSection.Other), model)); } @@ -321,7 +330,7 @@ export function buildModelPickerItems( id: 'manageModels', enabled: true, checked: false, - class: 'manage-models-action', + class: undefined, tooltip: localize('chat.manageModels.tooltip', "Manage Language Models"), label: localize('chat.manageModels', "Manage Models..."), icon: Codicon.settingsGear, @@ -332,37 +341,6 @@ export function buildModelPickerItems( group: { title: '', icon: Codicon.settingsGear }, hideIcon: false, section: otherModels.length ? ModelPickerSection.Other : undefined, - className: 'manage-models-link', - showAlways: true, - }); - } - - // Add sign-in / upgrade option if entitlement is anonymous / free / new user - const isNewOrAnonymousUser = !chatEntitlementService.sentiment.installed || - chatEntitlementService.entitlement === ChatEntitlement.Available || - chatEntitlementService.anonymous || - chatEntitlementService.entitlement === ChatEntitlement.Unknown; - if (isNewOrAnonymousUser || chatEntitlementService.entitlement === ChatEntitlement.Free) { - items.push({ kind: ActionListItemKind.Separator }); - items.push({ - item: { - id: 'moreModels', - enabled: true, - checked: false, - class: 'more-models-action', - tooltip: isNewOrAnonymousUser ? localize('chat.moreModels.tooltip', "Add Language Models") : localize('chat.morePremiumModels.tooltip', "Add Premium Models"), - label: isNewOrAnonymousUser ? localize('chat.moreModels', "Add Language Models") : localize('chat.morePremiumModels', "Add Premium Models"), - icon: Codicon.add, - run: () => { - const commandId = isNewOrAnonymousUser ? 'workbench.action.chat.triggerSetup' : 'workbench.action.chat.upgradePlan'; - commandService.executeCommand(commandId); - } - }, - kind: ActionListItemKind.Action, - label: isNewOrAnonymousUser ? localize('chat.moreModels', "Add Language Models") : localize('chat.morePremiumModels', "Add Premium Models"), - group: { title: '', icon: Codicon.add }, - hideIcon: false, - className: 'manage-models-link', showAlways: true, }); } @@ -371,29 +349,32 @@ export function buildModelPickerItems( } function createUnavailableModelItem( + id: string, entry: IModelControlEntry, reason: 'upgrade' | 'update' | 'admin', - upgradePlanUrl: string | undefined, + manageSettingsUrl: string | undefined, updateStateType: StateType, section?: string, + showActionLink: boolean = true, ): IActionListItem { let description: string | MarkdownString | undefined; - let icon: ThemeIcon = Codicon.blank; if (reason === 'upgrade') { - description = upgradePlanUrl - ? new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan]({0})", upgradePlanUrl), { isTrusted: true }) - : localize('chat.modelPicker.upgrade', "Upgrade"); + description = showActionLink + ? new MarkdownString(localize('chat.modelPicker.upgradeLink', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }) + : undefined; + } else if (reason === 'update') { + description = localize('chat.modelPicker.updateDescription', "Update VS Code"); } else { - icon = Codicon.warning; + description = manageSettingsUrl + ? new MarkdownString(localize('chat.modelPicker.adminLink', "[Contact your admin]({0})", manageSettingsUrl), { isTrusted: true }) + : localize('chat.modelPicker.adminDescription', "Contact your admin"); } let hoverContent: MarkdownString; if (reason === 'upgrade') { hoverContent = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); - hoverContent.appendMarkdown(upgradePlanUrl - ? localize('chat.modelPicker.upgradeHover', "This model requires a paid plan. [Upgrade]({0}) to access it.", upgradePlanUrl) - : localize('chat.modelPicker.upgradeHoverNoLink', "This model requires a paid plan.")); + hoverContent.appendMarkdown(localize('chat.modelPicker.upgradeHover', "[Upgrade your plan](command:workbench.action.chat.upgradePlan \" \") to use this model.")); } else if (reason === 'update') { hoverContent = getUpdateHoverContent(updateStateType); } else { @@ -403,7 +384,7 @@ function createUnavailableModelItem( return { item: { - id: entry.id, + id, enabled: false, checked: false, class: undefined, @@ -415,9 +396,10 @@ function createUnavailableModelItem( kind: ActionListItemKind.Action, label: entry.label, description, + group: { title: '', icon: ThemeIcon.fromId(Codicon.blank.id) }, disabled: true, - group: { title: '', icon }, hideIcon: false, + className: 'chat-model-picker-unavailable', section, hover: { content: hoverContent }, }; @@ -539,17 +521,17 @@ export class ModelPickerWidget extends Disposable { this._selectedModel?.identifier, this._languageModelsService.getRecentlyUsedModelIds(), controlModelsForTier, - isPro, this._productService.version, this._updateService.state.type, onSelect, - this._productService.defaultChatAgent?.upgradePlanUrl, + this._productService.defaultChatAgent?.manageSettingsUrl, this._commandService, this._entitlementService ); const listOptions = { showFilter: models.length >= 10, + filterPlaceholder: localize('chat.modelPicker.search', "Search models"), collapsedByDefault: new Set([ModelPickerSection.Other]), minWidth: 300, }; @@ -644,13 +626,6 @@ function getModelHoverContent(model: ILanguageModelChatMetadataAndIdentifier): M const isAuto = model.metadata.id === 'auto' && model.metadata.vendor === 'copilot'; const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); markdown.appendMarkdown(`**${model.metadata.name}**`); - if (!isAuto) { - if (model.metadata.id !== model.metadata.version) { - markdown.appendMarkdown(`  _${model.metadata.id}@${model.metadata.version}_ `); - } else { - markdown.appendMarkdown(`  _${model.metadata.id}_ `); - } - } markdown.appendText(`\n`); if (model.metadata.statusIcon && model.metadata.tooltip) { @@ -662,9 +637,7 @@ function getModelHoverContent(model: ILanguageModelChatMetadataAndIdentifier): M } if (model.metadata.multiplier) { - markdown.appendMarkdown(`${localize('models.cost', 'Multiplier')}: `); - markdown.appendMarkdown(model.metadata.multiplier); - markdown.appendMarkdown(` - ${localize('multiplier.tooltip', "Every chat message counts {0} towards your premium model request quota", model.metadata.multiplier)}`); + markdown.appendMarkdown(`${localize('multiplier.tooltip', "Each chat message counts {0} toward your premium request quota", model.metadata.multiplier)}`); markdown.appendText(`\n`); } diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts index 477895be331..ce838a3e0a1 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts @@ -410,7 +410,8 @@ class ChatTokenDeleter extends Disposable { // If this was a simple delete, try to find out whether it was inside a token if (!change.text && this.widget.viewModel) { - const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionResource, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind }); + const attachmentCapabilities = previousSelectedAgent?.capabilities ?? this.widget.attachmentCapabilities; + const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionResource, previousInputValue, widget.location, { selectedAgent: previousSelectedAgent, mode: this.widget.input.currentModeKind, attachmentCapabilities }); // For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestSlashPromptPart || p instanceof ChatRequestToolPart); diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index 9ff7e671ca3..b10b74d14ff 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -329,8 +329,7 @@ border-right: none; } -.interactive-item-container .value .rendered-markdown table tr:last-child td, -.interactive-item-container .value .rendered-markdown table tr:last-child th { +.interactive-item-container .value .rendered-markdown table tbody tr:last-child td { border-bottom: none; } @@ -805,17 +804,16 @@ have to be updated for changes to the rules above, or to support more deeply nes position: relative; } -/* Context usage widget container - positioned at top right of chat input */ -.interactive-session .chat-input-container .chat-context-usage-container { - position: absolute; - top: 4px; - right: 6px; - z-index: 1; +/* Context usage widget container - positioned in the bottom toolbar */ +.interactive-session .chat-input-toolbars .chat-context-usage-container { + display: flex; + align-items: center; + flex-shrink: 0; + order: 1; } -/* Hide context usage widget in compact mode (quick chat) */ -.interactive-session .interactive-input-part.compact .chat-context-usage-container { - display: none; +.interactive-session .chat-input-toolbars > .chat-execute-toolbar { + order: 2; } .interactive-input-part:has(.chat-editing-session > .chat-editing-session-container) .chat-input-container, @@ -1008,7 +1006,6 @@ have to be updated for changes to the rules above, or to support more deeply nes flex-direction: row; gap: 4px; margin-top: 6px; - margin-right: 20px; flex-wrap: wrap; cursor: default; } @@ -1303,6 +1300,8 @@ have to be updated for changes to the rules above, or to support more deeply nes .interactive-session .chat-input-toolbars { display: flex; + align-items: center; + gap: 6px; } .interactive-session .chat-input-toolbars :not(.responsive.chat-input-toolbar) .actions-container:first-child { @@ -1322,9 +1321,15 @@ have to be updated for changes to the rules above, or to support more deeply nes } .interactive-session .chat-input-toolbars > .chat-input-toolbar { + order: 0; overflow: hidden; min-width: 0px; width: 100%; + color: var(--vscode-icon-foreground); + + .monaco-action-bar .action-item .codicon { + color: var(--vscode-icon-foreground); + } .chat-input-picker-item { min-width: 0px; @@ -1377,29 +1382,13 @@ have to be updated for changes to the rules above, or to support more deeply nes } } -/* Manage Models link style in model picker */ -.action-widget .monaco-list-row.action.manage-models-link { - color: var(--vscode-textLink-foreground) !important; -} - -.action-widget .monaco-list-row.action.manage-models-link .codicon { - color: var(--vscode-textLink-foreground) !important; -} - -.action-widget .monaco-list-row.action.manage-models-link:hover, -.action-widget .monaco-list-row.action.manage-models-link.focused { - color: var(--vscode-textLink-activeForeground) !important; -} - - - .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label, .interactive-session .chat-input-toolbar .chat-sessionPicker-item .action-label { height: 16px; padding: 3px 0px 3px 6px; display: flex; align-items: center; - color: var(--vscode-foreground); + color: var(--vscode-icon-foreground); } .monaco-workbench .interactive-session .chat-input-toolbar .chat-input-picker-item .action-label .codicon-chevron-down, @@ -1418,6 +1407,26 @@ have to be updated for changes to the rules above, or to support more deeply nes max-width: 100%; } +.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item .codicon, +.monaco-workbench .interactive-session .chat-input-toolbars .action-label .codicon { + color: var(--vscode-icon-foreground) !important; +} + +.monaco-workbench .interactive-session .chat-input-toolbars .monaco-action-bar .action-item.disabled .codicon, +.monaco-workbench .interactive-session .chat-input-toolbars .action-label.disabled .codicon { + color: var(--vscode-disabledForeground) !important; +} + +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a, +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:visited { + color: var(--vscode-textLink-foreground); +} + +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:hover, +.action-widget .monaco-list-row.chat-model-picker-unavailable .description a:active { + color: var(--vscode-textLink-activeForeground); +} + .interactive-session .chat-input-toolbars .codicon-debug-stop { color: var(--vscode-icon-foreground) !important; } @@ -1600,7 +1609,6 @@ have to be updated for changes to the rules above, or to support more deeply nes padding: 8px 0 0 0 } -.action-item.chat-attachment-button .action-label, .interactive-session .chat-attached-context .chat-attached-context-attachment { display: flex; overflow: hidden; @@ -1613,40 +1621,11 @@ have to be updated for changes to the rules above, or to support more deeply nes width: fit-content; } -.action-item.chat-attachment-button .action-label { - padding: 0 4px; -} - .interactive-session .interactive-list .chat-attached-context .chat-attached-context-attachment { font-family: var(--vscode-chat-font-family, inherit); font-size: var(--vscode-chat-font-size-body-xs); } -.action-item.chat-attachment-button > .action-label > .codicon { - font-size: 14px; - height: auto; -} - -.interactive-session .action-item.chat-attachment-button .action-label:not(.has-label) { - display: flex; - align-items: center; - justify-content: center; - padding: 2px 4px 2px 4px; - height: fit-content; - gap: 0; - border: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); - border-radius: 4px; - box-sizing: border-box; - - .codicon { - width: 14px; - } -} - -.action-item.chat-attachment-button .codicon { - font-size: 14px; -} - .action-item.chat-mcp { min-width: 22px !important; @@ -1749,11 +1728,6 @@ have to be updated for changes to the rules above, or to support more deeply nes gap: 4px; } -.interactive-session .chat-attachment-toolbar .actions-container { - gap: 4px; - flex-wrap: wrap; -} - .interactive-session .interactive-input-part.compact .chat-attached-context { padding-bottom: 0px; display: flex; diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts index 36c6db7e50d..b20f39c7cf6 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatContextUsageWidget.ts @@ -27,71 +27,55 @@ import { KeyCode } from '../../../../../../base/common/keyCodes.js'; const $ = dom.$; /** - * A reusable circular progress indicator that displays a pie chart. - * The pie fills clockwise from the top based on the percentage value. + * A reusable circular progress indicator that displays a ring. + * The ring fills clockwise from the top based on the percentage value. */ export class CircularProgressIndicator { readonly domNode: SVGSVGElement; - private readonly progressPie: SVGPathElement; + private readonly progressCircle: SVGCircleElement; + private readonly circumference: number; private static readonly CENTER_X = 18; private static readonly CENTER_Y = 18; - private static readonly RADIUS = 16; + private static readonly RADIUS = 14; constructor() { + const r = CircularProgressIndicator.RADIUS; + this.circumference = 2 * Math.PI * r; + this.domNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.domNode.setAttribute('viewBox', '0 0 36 36'); this.domNode.classList.add('circular-progress'); - // Background circle (outline only) + // Background circle const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); bgCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X)); bgCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y)); - bgCircle.setAttribute('r', String(CircularProgressIndicator.RADIUS)); + bgCircle.setAttribute('r', String(r)); bgCircle.classList.add('progress-bg'); this.domNode.appendChild(bgCircle); - // Progress pie (filled arc) - this.progressPie = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - this.progressPie.classList.add('progress-pie'); - this.domNode.appendChild(this.progressPie); + // Progress arc (stroke-based ring) + this.progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + this.progressCircle.setAttribute('cx', String(CircularProgressIndicator.CENTER_X)); + this.progressCircle.setAttribute('cy', String(CircularProgressIndicator.CENTER_Y)); + this.progressCircle.setAttribute('r', String(r)); + this.progressCircle.classList.add('progress-arc'); + this.progressCircle.setAttribute('stroke-dasharray', String(this.circumference)); + this.progressCircle.setAttribute('stroke-dashoffset', String(this.circumference)); + this.domNode.appendChild(this.progressCircle); } /** - * Updates the pie chart to display the given percentage (0-100). - * @param percentage The percentage of the pie to fill (clamped to 0-100) + * Updates the ring to display the given percentage (0-100). + * @param percentage The percentage of the ring to fill (clamped to 0-100) */ setProgress(percentage: number): void { - const cx = CircularProgressIndicator.CENTER_X; - const cy = CircularProgressIndicator.CENTER_Y; - const r = CircularProgressIndicator.RADIUS; - - if (percentage >= 100) { - // Full circle - use a circle element's path equivalent - this.progressPie.setAttribute('d', `M ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${cx - 0.001} ${cy - r} Z`); - } else if (percentage <= 0) { - // Empty - no path - this.progressPie.setAttribute('d', ''); - } else { - // Calculate the arc endpoint - const angle = (percentage / 100) * 360; - const radians = (angle - 90) * (Math.PI / 180); // Start from top (-90 degrees) - const x = cx + r * Math.cos(radians); - const y = cy + r * Math.sin(radians); - const largeArcFlag = angle > 180 ? 1 : 0; - - // Create pie slice path: move to center, line to top, arc to endpoint, close - const d = [ - `M ${cx} ${cy}`, // Move to center - `L ${cx} ${cy - r}`, // Line to top - `A ${r} ${r} 0 ${largeArcFlag} 1 ${x} ${y}`, // Arc to endpoint - 'Z' // Close path back to center - ].join(' '); - - this.progressPie.setAttribute('d', d); - } + const clamped = Math.max(0, Math.min(100, percentage)); + const offset = this.circumference - (clamped / 100) * this.circumference; + this.progressCircle.setAttribute('stroke-dashoffset', String(offset)); } } diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css index 9906a0b8171..c3abb1332f0 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/media/chatContextUsageWidget.css @@ -8,6 +8,7 @@ align-items: center; justify-content: center; height: 22px; + width: 22px; flex-shrink: 0; cursor: pointer; padding: 3px; @@ -19,8 +20,8 @@ display: flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; + width: 14px; + height: 14px; flex-shrink: 0; } @@ -46,22 +47,26 @@ } .chat-context-usage-widget .progress-bg { - fill: transparent; - stroke: var(--vscode-icon-foreground); - stroke-width: 2; - opacity: 0.4; + fill: none; + stroke: var(--vscode-disabledForeground); + stroke-width: 4; + opacity: 0.5; } -.chat-context-usage-widget .progress-pie { - fill: var(--vscode-icon-foreground); - opacity: 0.8; +.chat-context-usage-widget .progress-arc { + fill: none; + stroke: var(--vscode-descriptionForeground); + stroke-width: 4; + stroke-linecap: round; + transform: rotate(-90deg); + transform-origin: center; pointer-events: none; } -.chat-context-usage-widget.warning .progress-pie { - fill: var(--vscode-editorWarning-foreground); +.chat-context-usage-widget.warning .progress-arc { + stroke: var(--vscode-editorWarning-foreground); } -.chat-context-usage-widget.error .progress-pie { - fill: var(--vscode-editorError-foreground); +.chat-context-usage-widget.error .progress-arc { + stroke: var(--vscode-editorError-foreground); } diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index a099229a270..3885b179127 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -111,6 +111,10 @@ export class ChatModeService extends Disposable implements IChatModeService { for (const cachedMode of cachedCustomModes) { if (isCachedChatModeData(cachedMode) && cachedMode.uri) { try { + const visibility = cachedMode.visibility ?? { userInvocable: true, agentInvocable: cachedMode.infer !== false }; + if (!visibility.userInvocable) { + continue; + } const uri = URI.revive(cachedMode.uri); const customChatMode: ICustomAgent = { uri, @@ -122,7 +126,7 @@ export class ChatModeService extends Disposable implements IChatModeService { agentInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] }, handOffs: cachedMode.handOffs, target: cachedMode.target ?? Target.Undefined, - visibility: cachedMode.visibility ?? { userInvocable: true, agentInvocable: cachedMode.infer !== false }, + visibility, agents: cachedMode.agents, source: reviveChatModeSource(cachedMode.source) ?? { storage: PromptsStorage.local } }; diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index eba0df56ba9..e2b5b29c409 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -869,7 +869,7 @@ export class ChatService extends Disposable implements IChatService { private _sendRequestAsync(model: ChatModel, sessionResource: URI, parsedRequest: IParsedChatRequest, attempt: number, enableCommandDetection: boolean, defaultAgent: IChatAgentData, location: ChatAgentLocation, options?: IChatSendRequestOptions): IChatSendRequestResponseState { const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionResource); - let request: ChatRequestModel; + let request: ChatRequestModel | undefined; const agentPart = parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); @@ -917,7 +917,9 @@ export class ChatService extends Disposable implements IChatService { this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progressItem)}`); } - model.acceptResponseProgress(request, progressItem, !isLast); + if (request) { + model.acceptResponseProgress(request, progressItem, !isLast); + } } completeResponseCreated(); }; @@ -1017,7 +1019,7 @@ export class ChatService extends Disposable implements IChatService { return; } - if (tools) { + if (tools && request) { this.chatAgentService.setRequestTools(agent.id, request.id, tools); // in case the request has not been sent out yet: agentRequest.userSelectedTools = tools; @@ -1048,7 +1050,7 @@ export class ChatService extends Disposable implements IChatService { const result = await this.chatAgentService.detectAgentOrCommand(chatAgentRequest, defaultAgentHistory, { location }, token); if (result && this.chatAgentService.getAgent(result.agent.id)?.locations?.includes(location)) { // Update the response in the ChatModel to reflect the detected agent and command - request.response?.setAgent(result.agent, result.command); + request?.response?.setAgent(result.agent, result.command); detectedAgent = result.agent; detectedCommand = result.command; } @@ -1066,7 +1068,7 @@ export class ChatService extends Disposable implements IChatService { const pendingRequest = this._pendingRequests.get(sessionResource); if (pendingRequest) { store.add(autorun(reader => { - if (pendingRequest.yieldRequested.read(reader)) { + if (pendingRequest.yieldRequested.read(reader) && request) { this.chatAgentService.setYieldRequested(agent.id, request.id); } })); @@ -1121,7 +1123,9 @@ export class ChatService extends Disposable implements IChatService { throw new Error(`Cannot handle request`); } - if (token.isCancellationRequested && !rawResult) { + if ((token.isCancellationRequested && !rawResult)) { + return; + } else if (!request) { return; } else { if (!rawResult) { @@ -1155,7 +1159,7 @@ export class ChatService extends Disposable implements IChatService { request.response?.complete(); if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { - model.setFollowups(request, followups); + model.setFollowups(request!, followups); const commandForTelemetry = agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command; this._chatServiceTelemetry.retrievedFollowups(agentPart?.agent.id ?? '', commandForTelemetry, followups?.length ?? 0); }); @@ -1163,15 +1167,15 @@ export class ChatService extends Disposable implements IChatService { } } catch (err) { this.logService.error(`Error while handling chat request: ${toErrorMessage(err, true)}`); - requestTelemetry.complete({ - timeToFirstProgress: undefined, - totalTime: undefined, - result: 'error', - requestType, - detectedAgent, - request, - }); if (request) { + requestTelemetry.complete({ + timeToFirstProgress: undefined, + totalTime: undefined, + result: 'error', + requestType, + detectedAgent, + request, + }); const rawResult: IChatAgentResult = { errorDetails: { message: err.message } }; model.setResponse(request, rawResult); completeResponseCreated(); diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 693f7c1f9da..ed3f2318acc 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -38,6 +38,7 @@ export enum ChatConfiguration { ThinkingPhrases = 'chat.agent.thinking.phrases', AutoExpandToolFailures = 'chat.tools.autoExpandFailures', TodosShowWidget = 'chat.tools.todos.showWidget', + NotifyWindowOnConfirmation = 'chat.notifyWindowOnConfirmation', NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived', ChatViewSessionsEnabled = 'chat.viewSessions.enabled', ChatViewSessionsGrouping = 'chat.viewSessions.grouping', @@ -90,6 +91,12 @@ export enum CollapsedToolsDisplayMode { Always = 'always', } +export enum ChatNotificationMode { + Off = 'off', + WindowNotFocused = 'windowNotFocused', + Always = 'always', +} + export enum AgentsControlClickBehavior { Default = 'default', Cycle = 'cycle', diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index e3f759c00bc..803c331fb67 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -402,10 +402,10 @@ export interface ILanguageModelsService { } export interface IModelControlEntry { - readonly id: string; readonly label: string; readonly featured?: boolean; readonly minVSCodeVersion?: string; + readonly exists: boolean; } export interface IModelsControlManifest { @@ -507,8 +507,8 @@ interface IChatControlResponse { readonly version: number; readonly restrictedChatParticipants: { [name: string]: string[] }; readonly models?: { - readonly free?: Record; - readonly paid?: Record; + readonly free?: Record; + readonly paid?: Record; }; } @@ -542,6 +542,7 @@ export class LanguageModelsService implements ILanguageModelsService { readonly onDidChangeModelsControlManifest = this._onDidChangeModelsControlManifest.event; private _modelsControlManifest: IModelsControlManifest = { free: {}, paid: {} }; + private _modelsControlRawResponse: IChatControlResponse['models'] | undefined; private _chatControlUrl: string | undefined; private _chatControlDisposed = false; @@ -566,7 +567,10 @@ export class LanguageModelsService implements ILanguageModelsService { this._initChatControlData(); this._store.add(this._storageService.onDidChangeValue(StorageScope.PROFILE, CHAT_MODEL_PICKER_PREFERENCES_STORAGE_KEY, this._store)(() => this._onDidChangeModelPickerPreferences())); - this._store.add(this.onDidChangeLanguageModels(() => this._hasUserSelectableModels.set(this._modelCache.size > 0 && Array.from(this._modelCache.values()).some(model => model.isUserSelectable)))); + this._store.add(this.onDidChangeLanguageModels(() => { + this._hasUserSelectableModels.set(this._modelCache.size > 0 && Array.from(this._modelCache.values()).some(model => model.isUserSelectable)); + this._refreshModelsControlManifest(); + })); this._store.add(this._languageModelsConfigurationService.onDidChangeLanguageModelGroups(changedGroups => this._onDidChangeLanguageModelGroups(changedGroups))); this._store.add(languageModelChatProviderExtensionPoint.setHandler((extensions, { added, removed }) => { @@ -1384,7 +1388,7 @@ export class LanguageModelsService implements ILanguageModelsService { // Filter to only include models that still exist in the cache return this._recentlyUsedModelIds .filter(id => this._modelCache.has(id) && id !== 'copilot/auto') - .slice(0, 5); + .slice(0, 4); } addToRecentlyUsedList(modelIdentifier: string): void { @@ -1420,26 +1424,32 @@ export class LanguageModelsService implements ILanguageModelsService { } private _setModelsControlManifest(response: IChatControlResponse['models']): void { + this._modelsControlRawResponse = response; + this._refreshModelsControlManifest(); + } + + private _refreshModelsControlManifest(): void { + const response = this._modelsControlRawResponse; const free: IStringDictionary = {}; const paid: IStringDictionary = {}; if (response?.free) { const freeEntries = Array.isArray(response.free) ? response.free : Object.values(response.free); for (const entry of freeEntries) { - if (!entry || !isObject(entry) || typeof entry.id !== 'string') { + if (!entry || !isObject(entry)) { continue; } - free[entry.id] = { id: entry.id, label: entry.label, featured: entry.featured }; + free[entry.id] = { label: entry.label, featured: entry.featured, exists: this._modelExistsInCache(entry.id) }; } } if (response?.paid) { const paidEntries = Array.isArray(response.paid) ? response.paid : Object.values(response.paid); for (const entry of paidEntries) { - if (!entry || !isObject(entry) || typeof entry.id !== 'string') { + if (!entry || !isObject(entry)) { continue; } - paid[entry.id] = { id: entry.id, label: entry.label, featured: entry.featured, minVSCodeVersion: entry.minVSCodeVersion }; + paid[entry.id] = { label: entry.label, featured: entry.featured, minVSCodeVersion: entry.minVSCodeVersion, exists: this._modelExistsInCache(entry.id) }; } } @@ -1447,6 +1457,15 @@ export class LanguageModelsService implements ILanguageModelsService { this._onDidChangeModelsControlManifest.fire(this._modelsControlManifest); } + private _modelExistsInCache(metadataId: string): boolean { + for (const model of this._modelCache.values()) { + if (model.id === metadataId) { + return true; + } + } + return false; + } + //#region Chat control data private _initChatControlData(): void { @@ -1489,16 +1508,34 @@ export class LanguageModelsService implements ILanguageModelsService { } private async _fetchChatControlData(): Promise { - const context = await this._requestService.request({ type: 'GET', url: this._chatControlUrl! }, CancellationToken.None); + this._logService.trace('[LM] Fetching chat control data from', this._chatControlUrl); - if (context.res.statusCode !== 200) { - throw new Error('Could not get chat control data.'); + let context; + try { + context = await this._requestService.request({ type: 'GET', url: this._chatControlUrl! }, CancellationToken.None); + } catch (err) { + this._logService.warn('[LM] Failed to request chat control data', getErrorMessage(err)); + return; } - const result = await asJson(context); + if (context.res.statusCode !== 200) { + this._logService.warn(`[LM] Chat control data request failed with status ${context.res.statusCode}`); + return; + } + + let result: IChatControlResponse | null; + try { + result = await asJson(context); + } catch (err) { + this._logService.warn('[LM] Failed to parse chat control response', getErrorMessage(err)); + return; + } + + this._logService.trace('[LM] Received chat control response', result ? Object.keys(result) : 'null'); if (!result || result.version !== 1) { - throw new Error('Unexpected chat control response.'); + this._logService.warn('[LM] Unexpected chat control response version', result?.version); + return; } // Update restricted chat participants @@ -1508,6 +1545,7 @@ export class LanguageModelsService implements ILanguageModelsService { // Update models control manifest if (result.models) { + this._logService.trace('[LM] Updating models control manifest', { freeCount: Object.keys(result.models.free ?? {}).length, paidCount: Object.keys(result.models.paid ?? {}).length }); this._setModelsControlManifest(result.models); this._storageService.store(CHAT_MODELS_CONTROL_STORAGE_KEY, JSON.stringify(result.models), StorageScope.APPLICATION, StorageTarget.MACHINE); } diff --git a/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts b/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts index 268dba2ec10..588be79a828 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModelStore.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from '../../../../../base/common/event.js'; -import { DisposableStore, IDisposable, IReference, ReferenceCollection } from '../../../../../base/common/lifecycle.js'; +import { Disposable, IDisposable, IReference, ReferenceCollection } from '../../../../../base/common/lifecycle.js'; import { ObservableMap } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; @@ -27,17 +27,17 @@ export interface ChatModelStoreDelegate { willDisposeModel: (model: ChatModel) => Promise; } -export class ChatModelStore extends ReferenceCollection implements IDisposable { - private readonly _store = new DisposableStore(); +export class ChatModelStore extends Disposable implements IDisposable { + private readonly _refCollection: ReferenceCollection; private readonly _models = new ObservableMap(); private readonly _modelsToDispose = new Set(); private readonly _pendingDisposals = new Set>(); - private readonly _onDidDisposeModel = this._store.add(new Emitter()); + private readonly _onDidDisposeModel = this._register(new Emitter()); public readonly onDidDisposeModel = this._onDidDisposeModel.event; - private readonly _onDidCreateModel = this._store.add(new Emitter()); + private readonly _onDidCreateModel = this._register(new Emitter()); public readonly onDidCreateModel = this._onDidCreateModel.event; constructor( @@ -45,6 +45,16 @@ export class ChatModelStore extends ReferenceCollection implements ID @ILogService private readonly logService: ILogService, ) { super(); + + const self = this; + this._refCollection = new class extends ReferenceCollection { + protected createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { + return self.createReferencedObject(key, props); + } + protected destroyReferencedObject(key: string, object: ChatModel): void { + return self.destroyReferencedObject(key, object); + } + }(); } public get observable() { @@ -71,14 +81,14 @@ export class ChatModelStore extends ReferenceCollection implements ID if (!this._models.has(key)) { return undefined; } - return this.acquire(key); + return this._refCollection.acquire(key); } public acquireOrCreate(props: IStartSessionProps): IReference { - return this.acquire(this.toKey(props.sessionResource), props); + return this._refCollection.acquire(this.toKey(props.sessionResource), props); } - protected createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { + private createReferencedObject(key: string, props?: IStartSessionProps): ChatModel { this._modelsToDispose.delete(key); const existingModel = this._models.get(key); if (existingModel) { @@ -99,7 +109,7 @@ export class ChatModelStore extends ReferenceCollection implements ID return model; } - protected destroyReferencedObject(key: string, object: ChatModel): void { + private destroyReferencedObject(key: string, object: ChatModel): void { this._modelsToDispose.add(key); const promise = this.doDestroyReferencedObject(key, object); this._pendingDisposals.add(promise); @@ -135,8 +145,8 @@ export class ChatModelStore extends ReferenceCollection implements ID return uri.toString(); } - dispose(): void { - this._store.dispose(); + override dispose(): void { + super.dispose(); this._models.forEach(model => model.dispose()); } } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts index db61519b2b8..ed04e3aa641 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.ts @@ -167,6 +167,7 @@ export const DEFAULT_SKILL_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [ export const DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [ { path: INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local }, { path: CLAUDE_RULES_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local }, + { path: '~/.copilot/instructions', source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user }, { path: '~/' + CLAUDE_RULES_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user }, ]; diff --git a/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts index 9994bcffbcb..9ec0508a489 100644 --- a/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/requestParser/chatRequestParser.ts @@ -218,7 +218,8 @@ export class ChatRequestParser { } } - if (!usedAgent || context?.attachmentCapabilities?.supportsPromptAttachments) { + const capabilities = context?.attachmentCapabilities ?? usedAgent?.capabilities ?? context?.attachmentCapabilities; + if (!usedAgent || capabilities?.supportsPromptAttachments) { const slashCommands = this.slashCommandService.getCommands(location, context?.mode ?? ChatModeKind.Ask); const slashCommand = slashCommands.find(c => c.command === command); if (slashCommand) { diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsConfirmationService.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsConfirmationService.ts index 96ea253b754..d77bd07c0c0 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsConfirmationService.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsConfirmationService.ts @@ -17,6 +17,8 @@ export interface ILanguageModelToolConfirmationActions { detail?: string; /** Show a separator before this action */ divider?: boolean; + /** The scope of this action, if applicable */ + scope?: 'session' | 'workspace' | 'profile'; /** Selects this action. Resolves true if the action should be confirmed after selection */ select(): Promise; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts index df645e77bea..b1c79a5e6e6 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatTipService.test.ts @@ -31,6 +31,8 @@ import { CreateSlashCommandsUsageTracker } from '../../browser/createSlashComman import { ChatRequestSlashCommandPart } from '../../common/requestParser/chatParserTypes.js'; import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js'; import { Range } from '../../../../../editor/common/core/range.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js'; class MockContextKeyServiceWithRulesMatching extends MockContextKeyService { override contextMatchesRules(rules: ContextKeyExpression): boolean { @@ -99,6 +101,7 @@ suite('ChatTipService', () => { instantiationService.stub(ILanguageModelToolsService, testDisposables.add(new MockLanguageModelToolsService())); instantiationService.stub(IChatEntitlementService, new TestChatEntitlementService()); instantiationService.stub(IChatService, new MockChatService()); + instantiationService.stub(ITelemetryService, NullTelemetryService); }); test('returns a welcome tip', () => { @@ -924,6 +927,135 @@ suite('ChatTipService', () => { }); } + test('logs telemetry when tip is shown', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + const shownEvents = events.filter(e => e.data.action === 'shown'); + assert.strictEqual(shownEvents.length, 1, 'Should log exactly one shown event'); + assert.strictEqual(shownEvents[0].eventName, 'chatTip'); + assert.strictEqual(shownEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when tip is dismissed', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + service.dismissTip(); + + const dismissEvents = events.filter(e => e.data.action === 'dismissed'); + assert.strictEqual(dismissEvents.length, 1, 'Should log exactly one dismissed event'); + assert.strictEqual(dismissEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when navigating tips', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + const nextTip = service.navigateToNextTip(); + assert.ok(nextTip); + + const navigateEvents = events.filter(e => e.data.action === 'navigateNext'); + assert.strictEqual(navigateEvents.length, 1, 'Should log one navigateNext event'); + assert.strictEqual(navigateEvents[0].data.tipId, tip.id, 'navigateNext should log the tip being navigated away from'); + + const shownEvents = events.filter(e => e.data.action === 'shown'); + assert.strictEqual(shownEvents.length, 2, 'Should log shown for initial and navigated tip'); + assert.strictEqual(shownEvents[1].data.tipId, nextTip.id); + }); + + test('logs telemetry when tip command is clicked', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + if (tip.enabledCommands?.length) { + commandExecutedEmitter.fire({ commandId: tip.enabledCommands[0], args: [] }); + + const clickEvents = events.filter(e => e.data.action === 'commandClicked'); + assert.strictEqual(clickEvents.length, 1, 'Should log one commandClicked event'); + assert.strictEqual(clickEvents[0].data.tipId, tip.id); + assert.strictEqual(clickEvents[0].data.commandId, tip.enabledCommands[0]); + } else { + assert.fail('Tip has no enabled commands; cannot test command click telemetry'); + } + }); + + test('logs telemetry when tip is hidden', () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + service.hideTip(); + + const hiddenEvents = events.filter(e => e.data.action === 'hidden'); + assert.strictEqual(hiddenEvents.length, 1, 'Should log one hidden event'); + assert.strictEqual(hiddenEvents[0].data.tipId, tip.id); + }); + + test('logs telemetry when tips are disabled', async () => { + const events: { eventName: string; data: Record }[] = []; + instantiationService.stub(ITelemetryService, { + ...NullTelemetryService, + publicLog2(eventName: string, data: Record) { + events.push({ eventName, data }); + }, + } as Partial as ITelemetryService); + + const service = createService(); + const tip = service.getWelcomeTip(contextKeyService); + assert.ok(tip); + + await service.disableTips(); + + const disabledEvents = events.filter(e => e.data.action === 'disabled'); + assert.strictEqual(disabledEvents.length, 1, 'Should log one disabled event'); + assert.strictEqual(disabledEvents[0].data.tipId, tip.id); + }); + test('excludeWhenSettingsChanged checks workspaceValue', () => { const workspaceConfigService = new TestConfigurationService(); const originalInspect = workspaceConfigService.inspect.bind(workspaceConfigService); diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts new file mode 100644 index 00000000000..aa5843f4b46 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/promptFileRewriter.test.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; +import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { ContextKeyService } from '../../../../../../platform/contextkey/browser/contextKeyService.js'; +import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; +import { ChatConfiguration } from '../../../common/constants.js'; +import { ILanguageModelToolsService, IToolData, ToolDataSource } from '../../../common/tools/languageModelToolsService.js'; +import { LanguageModelToolsService } from '../../../browser/tools/languageModelToolsService.js'; +import { IPromptsService } from '../../../common/promptSyntax/service/promptsService.js'; +import { getLanguageIdForPromptsType, PromptsType } from '../../../common/promptSyntax/promptTypes.js'; +import { PromptFileParser, PromptHeaderAttributes, parseCommaSeparatedList } from '../../../common/promptSyntax/promptFileParser.js'; +import { PromptFileRewriter } from '../../../browser/promptSyntax/promptFileRewriter.js'; + +suite('PromptFileRewriter', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instaService: TestInstantiationService; + + setup(async () => { + const testConfigService = new TestConfigurationService(); + testConfigService.setUserConfiguration(ChatConfiguration.ExtensionToolsEnabled, true); + instaService = workbenchInstantiationService({ + contextKeyService: () => disposables.add(new ContextKeyService(testConfigService)), + configurationService: () => testConfigService + }, disposables); + + const toolService = disposables.add(instaService.createInstance(LanguageModelToolsService)); + + const testTool1 = { id: 'testTool1', displayName: 'tool1', toolReferenceName: 'testTool1', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 1', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool1)); + const testTool2 = { id: 'testTool2', displayName: 'tool2', toolReferenceName: 'testTool2', canBeReferencedInPrompt: true, modelDescription: 'Test Tool 2', source: ToolDataSource.External, inputSchema: {} } satisfies IToolData; + disposables.add(toolService.registerToolData(testTool2)); + + instaService.set(ILanguageModelToolsService, toolService); + + const parser = new PromptFileParser(); + instaService.stub(IPromptsService, { + getParsedPromptFile(model: import('../../../../../../editor/common/model.js').ITextModel) { + return parser.parse(model.uri, model.getValue()); + }, + }); + }); + + test('rewriteTools preserves tools key when using value range (array syntax)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: [testTool1]', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + const model = disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + const toolService = instaService.get(ILanguageModelToolsService); + const enablementMap = toolService.toToolAndToolSetEnablementMap(['testTool1', 'testTool2'], undefined); + + const rewriter = instaService.createInstance(PromptFileRewriter); + rewriter.rewriteTools(model, enablementMap, toolsAttr.value.range, false); + + const result = model.getValue(); + assert.ok(result.includes('tools:'), `Expected 'tools:' key to be preserved, but got: ${result}`); + assert.ok(result.includes('testTool1'), `Expected 'testTool1' in result: ${result}`); + assert.ok(result.includes('testTool2'), `Expected 'testTool2' in result: ${result}`); + }); + + test('rewriteTools preserves tools key when using value range (string syntax)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: testTool1', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + const model = disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + let value = toolsAttr.value; + if (value.type === 'scalar') { + value = parseCommaSeparatedList(value); + } + + const toolService = instaService.get(ILanguageModelToolsService); + const enablementMap = toolService.toToolAndToolSetEnablementMap(['testTool1', 'testTool2'], undefined); + + const rewriter = instaService.createInstance(PromptFileRewriter); + rewriter.rewriteTools(model, enablementMap, toolsAttr.value.range, true); + + const result = model.getValue(); + assert.ok(result.includes('tools:'), `Expected 'tools:' key to be preserved, but got: ${result}`); + assert.ok(result.includes('testTool1'), `Expected 'testTool1' in result: ${result}`); + assert.ok(result.includes('testTool2'), `Expected 'testTool2' in result: ${result}`); + }); + + test('rewriteTools with attribute range would destroy the tools key (regression)', () => { + const content = [ + '---', + 'description: "Test agent"', + 'tools: [testTool1]', + '---', + ].join('\n'); + const languageId = getLanguageIdForPromptsType(PromptsType.agent); + const uri = URI.parse('test:///test.agent.md'); + disposables.add(createTextModel(content, languageId, undefined, uri)); + + const parser = new PromptFileParser(); + const parsed = parser.parse(uri, content); + const toolsAttr = parsed.header!.getAttribute(PromptHeaderAttributes.tools); + assert.ok(toolsAttr); + + // Verify that the attribute range is larger than the value range + assert.ok( + toolsAttr.range.startColumn < toolsAttr.value.range.startColumn || + toolsAttr.range.startLineNumber < toolsAttr.value.range.startLineNumber, + 'Attribute range should start before value range (includes the key)' + ); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts b/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts index 7e8c1835c3d..8f2f67e90b6 100644 --- a/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/widget/input/chatModelPicker.test.ts @@ -5,7 +5,9 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { Codicon } from '../../../../../../../base/common/codicons.js'; import { IStringDictionary } from '../../../../../../../base/common/collections.js'; +import { MarkdownString } from '../../../../../../../base/common/htmlContent.js'; import { ActionListItemKind, IActionListItem } from '../../../../../../../platform/actionWidget/browser/actionList.js'; import { IActionWidgetDropdownAction } from '../../../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'; @@ -14,12 +16,16 @@ import { buildModelPickerItems } from '../../../../browser/widget/input/chatMode import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, IModelControlEntry } from '../../../../common/languageModels.js'; import { ChatEntitlement, IChatEntitlementService } from '../../../../../../services/chat/common/chatEntitlementService.js'; -const stubChatEntitlementService: Partial = { - entitlement: ChatEntitlement.Pro, - sentiment: { installed: true } as IChatEntitlementService['sentiment'], - isInternal: false, - anonymous: false, -}; +function createStubEntitlementService(opts?: { entitlement?: ChatEntitlement; isInternal?: boolean; anonymous?: boolean }): IChatEntitlementService { + return { + entitlement: opts?.entitlement ?? ChatEntitlement.Pro, + sentiment: { installed: true } as IChatEntitlementService['sentiment'], + isInternal: opts?.isInternal ?? false, + anonymous: opts?.anonymous ?? false, + } as IChatEntitlementService; +} + +const stubChatEntitlementService = createStubEntitlementService(); function createModel(id: string, name: string, vendor = 'copilot'): ILanguageModelChatMetadataAndIdentifier { return { @@ -67,25 +73,29 @@ function callBuild( selectedModelId?: string; recentModelIds?: string[]; controlModels?: IStringDictionary; - isProUser?: boolean; + entitlement?: ChatEntitlement; currentVSCodeVersion?: string; updateStateType?: StateType; - upgradePlanUrl?: string; + manageSettingsUrl?: string; + anonymous?: boolean; } = {}, ): IActionListItem[] { const onSelect = () => { }; + const entitlementService = createStubEntitlementService({ + entitlement: opts.entitlement ?? ChatEntitlement.Pro, + anonymous: opts.anonymous ?? false, + }); return buildModelPickerItems( models, opts.selectedModelId, opts.recentModelIds ?? [], opts.controlModels ?? {}, - opts.isProUser ?? true, opts.currentVSCodeVersion ?? '1.100.0', opts.updateStateType ?? StateType.Idle, onSelect, - opts.upgradePlanUrl, + opts.manageSettingsUrl, stubCommandService, - stubChatEntitlementService as IChatEntitlementService, + entitlementService, ); } @@ -138,7 +148,7 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto, modelA], { selectedModelId: modelA.identifier, controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', minVSCodeVersion: '2.0.0' }, + 'gpt-4o': { label: 'GPT-4o', minVSCodeVersion: '2.0.0', exists: true }, }, currentVSCodeVersion: '1.90.0', }); @@ -169,9 +179,9 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto], { recentModelIds: ['missing-model'], controlModels: { - 'missing-model': { id: 'missing-model', label: 'Missing Model' }, + 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Missing Model'); @@ -184,9 +194,8 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto], { recentModelIds: ['missing-model'], controlModels: { - 'missing-model': { id: 'missing-model', label: 'Missing Model', minVSCodeVersion: '2.0.0' }, + 'missing-model': { label: 'Missing Model', minVSCodeVersion: '2.0.0', exists: false }, }, - isProUser: true, currentVSCodeVersion: '1.90.0', }); const actions = getActionItems(items); @@ -200,9 +209,8 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto], { recentModelIds: ['missing-model'], controlModels: { - 'missing-model': { id: 'missing-model', label: 'Missing Model' }, + 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: true, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Missing Model'); @@ -216,7 +224,7 @@ suite('buildModelPickerItems', () => { const modelB = createModel('claude', 'Claude'); const items = callBuild([auto, modelA, modelB], { controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', featured: true }, + 'gpt-4o': { label: 'GPT-4o', featured: true, exists: true }, }, }); const actions = getActionItems(items); @@ -229,9 +237,9 @@ suite('buildModelPickerItems', () => { const auto = createAutoModel(); const items = callBuild([auto], { controlModels: { - 'premium-model': { id: 'premium-model', label: 'Premium Model', featured: true }, + 'premium-model': { label: 'Premium Model', featured: true, exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Premium Model'); @@ -243,9 +251,8 @@ suite('buildModelPickerItems', () => { const auto = createAutoModel(); const items = callBuild([auto], { controlModels: { - 'premium-model': { id: 'premium-model', label: 'Premium Model', featured: true }, + 'premium-model': { label: 'Premium Model', featured: true, exists: false }, }, - isProUser: true, }); const actions = getActionItems(items); const unavailable = actions.find(a => a.label === 'Premium Model'); @@ -258,7 +265,7 @@ suite('buildModelPickerItems', () => { const modelA = createModel('gpt-4o', 'GPT-4o'); const items = callBuild([auto, modelA], { controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', featured: true, minVSCodeVersion: '2.0.0' }, + 'gpt-4o': { label: 'GPT-4o', featured: true, minVSCodeVersion: '2.0.0', exists: true }, }, currentVSCodeVersion: '1.90.0', }); @@ -274,7 +281,7 @@ suite('buildModelPickerItems', () => { const modelB = createModel('claude', 'Claude'); const items = callBuild([auto, modelA, modelB], { controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', featured: false }, + 'gpt-4o': { label: 'GPT-4o', featured: false, exists: true }, }, }); // With no selected, no recent, and no featured, both models should be in Other @@ -308,9 +315,9 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto, modelA], { recentModelIds: [modelA.identifier, 'missing-model'], controlModels: { - 'missing-model': { id: 'missing-model', label: 'Missing Model' }, + 'missing-model': { label: 'Missing Model', exists: false }, }, - isProUser: false, + entitlement: ChatEntitlement.Free, }); const actions = getActionItems(items); // Auto, then GPT-4o (available), then Missing Model (unavailable) @@ -356,7 +363,7 @@ suite('buildModelPickerItems', () => { const modelA = createModel('gpt-4o', 'GPT-4o'); const items = callBuild([auto, modelA], { controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', minVSCodeVersion: '2.0.0' }, + 'gpt-4o': { label: 'GPT-4o', minVSCodeVersion: '2.0.0', exists: true }, }, currentVSCodeVersion: '1.90.0', }); @@ -375,8 +382,8 @@ suite('buildModelPickerItems', () => { selectedModelId: modelA.identifier, recentModelIds: [modelA.identifier, modelB.identifier], controlModels: { - 'gpt-4o': { id: 'gpt-4o', label: 'GPT-4o', featured: true }, - 'claude': { id: 'claude', label: 'Claude', featured: true }, + 'gpt-4o': { label: 'GPT-4o', featured: true, exists: true }, + 'claude': { label: 'Claude', featured: true, exists: true }, }, }); const labels = getActionLabels(items).filter(l => l !== 'Other Models' && !l.includes('Manage Models')); @@ -391,7 +398,7 @@ suite('buildModelPickerItems', () => { selectedModelId: auto.identifier, recentModelIds: [auto.identifier], controlModels: { - 'auto': { id: 'auto', label: 'Auto', featured: true }, + 'auto': { label: 'Auto', featured: true, exists: true }, }, }); const autoItems = getActionItems(items).filter(a => a.label === 'Auto'); @@ -434,13 +441,12 @@ suite('buildModelPickerItems', () => { undefined, [], {}, - true, '1.100.0', StateType.Idle, onSelect, undefined, stubCommandService, - stubChatEntitlementService as IChatEntitlementService, + stubChatEntitlementService, ); const gptItem = getActionItems(items).find(a => a.label === 'GPT-4o'); assert.ok(gptItem?.item); @@ -497,7 +503,7 @@ suite('buildModelPickerItems', () => { const items = callBuild([auto, modelA, modelB, modelC, modelD], { recentModelIds: [modelC.identifier], controlModels: { - 'alpha': { id: 'alpha', label: 'Alpha', featured: true }, + 'alpha': { label: 'Alpha', featured: true, exists: true }, }, }); const actions = getActionItems(items); @@ -508,4 +514,104 @@ suite('buildModelPickerItems', () => { // Then Other Models toggle assert.ok(actions[3].isSectionToggle); }); + + test('admin unavailable model shows manage settings link in description', () => { + const auto = createAutoModel(); + const items = buildModelPickerItems( + [auto], + undefined, + ['missing-model'], + { 'missing-model': { label: 'Missing Model' } as IModelControlEntry }, + '1.100.0', + StateType.Idle, + () => { }, + 'https://aka.ms/github-copilot-settings', + stubCommandService, + stubChatEntitlementService, + ); + + const adminItem = getActionItems(items).find(a => a.label === 'Missing Model'); + assert.ok(adminItem); + assert.strictEqual(adminItem.disabled, true); + const description = adminItem.description; + assert.ok(description instanceof MarkdownString); + assert.ok(description.value.includes('https://aka.ms/github-copilot-settings')); + }); + + test('unavailable models keep indentation with blank icon', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['missing-model'], + controlModels: { + 'missing-model': { label: 'Missing Model' } as IModelControlEntry, + }, + entitlement: ChatEntitlement.Free, + }); + + const unavailable = getActionItems(items).find(a => a.label === 'Missing Model'); + assert.ok(unavailable); + assert.strictEqual(unavailable.hideIcon, false); + assert.strictEqual(unavailable.group?.icon?.id, Codicon.blank.id); + }); + + test('anonymous user sees upgrade description only on first unavailable model', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['model-a', 'model-b'], + controlModels: { + 'model-a': { label: 'Model A', featured: true, exists: false }, + 'model-b': { label: 'Model B', featured: true, exists: false }, + }, + anonymous: true, + entitlement: ChatEntitlement.Unknown, + }); + const actions = getActionItems(items); + const disabledItems = actions.filter(a => a.disabled); + assert.strictEqual(disabledItems.length, 2); + assert.ok(disabledItems[0].description instanceof MarkdownString); + assert.ok(disabledItems[0].description.value.includes('Upgrade')); + assert.strictEqual(disabledItems[1].description, undefined); + }); + + test('free user sees upgrade description only on first unavailable model', () => { + const auto = createAutoModel(); + const items = callBuild([auto], { + recentModelIds: ['model-a', 'model-b'], + controlModels: { + 'model-a': { label: 'Model A', featured: true, exists: false }, + 'model-b': { label: 'Model B', featured: true, exists: false }, + }, + entitlement: ChatEntitlement.Free, + }); + const actions = getActionItems(items); + const disabledItems = actions.filter(a => a.disabled); + assert.strictEqual(disabledItems.length, 2); + assert.ok(disabledItems[0].description instanceof MarkdownString); + assert.ok(disabledItems[0].description.value.includes('Upgrade')); + assert.strictEqual(disabledItems[1].description, undefined); + }); + + test('anonymous user model selection triggers onSelect normally', () => { + const auto = createAutoModel(); + const modelA = createModel('gpt-4o', 'GPT-4o'); + let selectedModel: ILanguageModelChatMetadataAndIdentifier | undefined; + const onSelect = (m: ILanguageModelChatMetadataAndIdentifier) => { selectedModel = m; }; + const anonymousEntitlementService = createStubEntitlementService({ entitlement: ChatEntitlement.Unknown, anonymous: true }); + const items = buildModelPickerItems( + [auto, modelA], + undefined, + [], + {}, + '1.100.0', + StateType.Idle, + onSelect, + undefined, + stubCommandService, + anonymousEntitlementService, + ); + const gptItem = getActionItems(items).find(a => a.label === 'GPT-4o'); + assert.ok(gptItem?.item); + gptItem.item.run(); + assert.strictEqual(selectedModel?.identifier, modelA.identifier); + }); }); diff --git a/src/vs/workbench/contrib/git/browser/gitService.ts b/src/vs/workbench/contrib/git/browser/gitService.ts index c298ec69e2a..9dfb27c8fad 100644 --- a/src/vs/workbench/contrib/git/browser/gitService.ts +++ b/src/vs/workbench/contrib/git/browser/gitService.ts @@ -3,29 +3,67 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from '../../../../base/common/lifecycle.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; +import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; -import { IGitService, IGitExtensionService } from '../common/gitService.js'; +import { IGitService, IGitExtensionDelegate, GitRef, GitRefQuery, IGitRepository, GitRepositoryState } from '../common/gitService.js'; +import { ISettableObservable, observableValueOpts } from '../../../../base/common/observable.js'; +import { structuralEquals } from '../../../../base/common/equals.js'; export class GitService extends Disposable implements IGitService { declare readonly _serviceBrand: undefined; - private _delegate: IGitExtensionService | undefined; + private _delegate: IGitExtensionDelegate | undefined; + + get repositories(): Iterable { + return this._delegate?.repositories ?? []; + } + + setDelegate(delegate: IGitExtensionDelegate): IDisposable { + // The delegate can only be set once, since the vscode.git + // extension can only run in one extension host process per + // window. + if (this._delegate) { + throw new BugIndicatingError('GitService delegate is already set.'); + } - setDelegate(delegate: IGitExtensionService): void { this._delegate = delegate; + + return toDisposable(() => { + this._delegate = undefined; + }); } - clearDelegate(): void { - this._delegate = undefined; - } - - async openRepository(root: URI): Promise { + async openRepository(uri: URI): Promise { if (!this._delegate) { return undefined; } - const result = await this._delegate.openRepository(root); - return result ? URI.revive(result) : undefined; + return this._delegate.openRepository(uri); + } +} + +export class GitRepository extends Disposable implements IGitRepository { + readonly rootUri: URI; + + readonly state: ISettableObservable; + updateState(state: GitRepositoryState): void { + this.state.set(state, undefined); + } + + constructor( + rootUri: URI, + initialState: GitRepositoryState, + private readonly delegate: IGitExtensionDelegate + ) { + super(); + + this.rootUri = rootUri; + this.state = observableValueOpts({ owner: this, equalsFn: structuralEquals }, initialState); + } + + async getRefs(query: GitRefQuery, token?: CancellationToken): Promise { + return this.delegate.getRefs(this.rootUri, query, token); } } diff --git a/src/vs/workbench/contrib/git/common/gitService.ts b/src/vs/workbench/contrib/git/common/gitService.ts index 30923868669..353686d452a 100644 --- a/src/vs/workbench/contrib/git/common/gitService.ts +++ b/src/vs/workbench/contrib/git/common/gitService.ts @@ -3,16 +3,62 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { IDisposable } from '../../../../base/common/lifecycle.js'; +import { IObservable } from '../../../../base/common/observable.js'; +import { URI } from '../../../../base/common/uri.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -/** - * Delegate interface that bridges to the git extension running - * in the extension host. Set by MainThreadGit when an extension - * host connects. - */ -export interface IGitExtensionService { - openRepository(uri: UriComponents): Promise; +export enum GitRefType { + Head, + RemoteHead, + Tag +} + +export interface GitRef { + readonly type: GitRefType; + readonly name?: string; + readonly commit?: string; + readonly remote?: string; +} + +export interface GitRefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + +export interface GitRepositoryState { + readonly HEAD?: GitBranch; +} + +export interface GitBranch extends GitRef { + readonly upstream?: GitUpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface GitUpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + +export interface IGitRepository { + readonly rootUri: URI; + + readonly state: IObservable; + updateState(state: GitRepositoryState): void; + + getRefs(query: GitRefQuery, token?: CancellationToken): Promise; +} + +export interface IGitExtensionDelegate { + readonly repositories: Iterable; + openRepository(uri: URI): Promise; + + getRefs(root: URI, query?: GitRefQuery, token?: CancellationToken): Promise; } export const IGitService = createDecorator('gitService'); @@ -20,12 +66,9 @@ export const IGitService = createDecorator('gitService'); export interface IGitService { readonly _serviceBrand: undefined; - setDelegate(delegate: IGitExtensionService): void; - clearDelegate(): void; + readonly repositories: Iterable; - /** - * Open a git repository at the given URI. - * @returns The repository root URI or `undefined` if the repository could not be opened. - */ - openRepository(uri: URI): Promise; + setDelegate(delegate: IGitExtensionDelegate): IDisposable; + + openRepository(uri: URI): Promise; } diff --git a/src/vs/workbench/contrib/mcp/common/mcpGatewayToolBrokerChannel.ts b/src/vs/workbench/contrib/mcp/common/mcpGatewayToolBrokerChannel.ts index 796de07cf62..88a1da8b4b6 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpGatewayToolBrokerChannel.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpGatewayToolBrokerChannel.ts @@ -8,8 +8,10 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { autorun } from '../../../../base/common/observable.js'; import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js'; -import { IMcpServer, IMcpService, McpServerCacheState, McpToolVisibility } from './mcpTypes.js'; +import { IGatewayCallToolResult, IGatewayServerResources, IGatewayServerResourceTemplates } from '../../../../platform/mcp/common/mcpGateway.js'; import { MCP } from '../../../../platform/mcp/common/modelContextProtocol.js'; +import { McpServer } from './mcpServer.js'; +import { IMcpServer, IMcpService, McpCapability, McpServerCacheState, McpToolVisibility } from './mcpTypes.js'; import { startServerAndWaitForLiveTools } from './mcpTypesUtils.js'; interface ICallToolArgs { @@ -17,32 +19,74 @@ interface ICallToolArgs { args: Record; } +interface IReadResourceArgs { + serverIndex: number; + uri: string; +} + export class McpGatewayToolBrokerChannel extends Disposable implements IServerChannel { private readonly _onDidChangeTools = this._register(new Emitter()); + private readonly _onDidChangeResources = this._register(new Emitter()); + private readonly _serverIdMap = new Map(); + private _nextServerIndex = 0; constructor( private readonly _mcpService: IMcpService, ) { super(); - let initialized = false; + let toolsInitialized = false; this._register(autorun(reader => { for (const server of this._mcpService.servers.read(reader)) { server.tools.read(reader); } - if (initialized) { + if (toolsInitialized) { this._onDidChangeTools.fire(); } else { - initialized = true; + toolsInitialized = true; } })); + + let resourcesInitialized = false; + this._register(autorun(reader => { + for (const server of this._mcpService.servers.read(reader)) { + server.capabilities.read(reader); + } + + if (resourcesInitialized) { + this._onDidChangeResources.fire(); + } else { + resourcesInitialized = true; + } + })); + } + + private _getServerIndex(server: IMcpServer): number { + const defId = server.definition.id; + let index = this._serverIdMap.get(defId); + if (index === undefined) { + index = this._nextServerIndex++; + this._serverIdMap.set(defId, index); + } + return index; + } + + private _getServerByIndex(serverIndex: number): IMcpServer | undefined { + for (const server of this._mcpService.servers.get()) { + if (this._getServerIndex(server) === serverIndex) { + return server; + } + } + return undefined; } listen(_ctx: unknown, event: string): Event { switch (event) { case 'onDidChangeTools': return this._onDidChangeTools.event as Event; + case 'onDidChangeResources': + return this._onDidChangeResources.event as Event; } throw new Error(`Invalid listen: ${event}`); @@ -59,6 +103,19 @@ export class McpGatewayToolBrokerChannel extends Disposable implements IServerCh const result = await this._callTool(name, args || {}, cancellationToken); return result as T; } + case 'listResources': { + const resources = await this._listResources(); + return resources as T; + } + case 'readResource': { + const { serverIndex, uri } = arg as IReadResourceArgs; + const result = await this._readResource(serverIndex, uri, cancellationToken); + return result as T; + } + case 'listResourceTemplates': { + const templates = await this._listResourceTemplates(); + return templates as T; + } } throw new Error(`Invalid call: ${command}`); @@ -87,20 +144,75 @@ export class McpGatewayToolBrokerChannel extends Disposable implements IServerCh return mcpTools; } - private async _callTool(name: string, args: Record, token: CancellationToken = CancellationToken.None): Promise { + private async _callTool(name: string, args: Record, token: CancellationToken = CancellationToken.None): Promise { for (const server of this._mcpService.servers.get()) { const tool = server.tools.get().find(t => t.definition.name === name && (t.visibility & McpToolVisibility.Model) ); if (tool) { - return tool.call(args, undefined, token); + const result = await tool.call(args, undefined, token); + return { result, serverIndex: this._getServerIndex(server) }; } } throw new Error(`Unknown tool: ${name}`); } + private async _listResources(): Promise { + const results: IGatewayServerResources[] = []; + const servers = this._mcpService.servers.get(); + await Promise.all(servers.map(async server => { + await this._ensureServerReady(server); + + const capabilities = server.capabilities.get(); + if (!capabilities || !(capabilities & McpCapability.Resources)) { + return; + } + + try { + const resources = await McpServer.callOn(server, h => h.listResources()); + results.push({ serverIndex: this._getServerIndex(server), resources }); + } catch { + // Server failed; skip + } + })); + + return results; + } + + private async _readResource(serverIndex: number, uri: string, token: CancellationToken = CancellationToken.None): Promise { + const server = this._getServerByIndex(serverIndex); + if (!server) { + throw new Error(`Unknown server index: ${serverIndex}`); + } + + return McpServer.callOn(server, h => h.readResource({ uri }, token), token); + } + + private async _listResourceTemplates(): Promise { + const results: IGatewayServerResourceTemplates[] = []; + const servers = this._mcpService.servers.get(); + + await Promise.all(servers.map(async server => { + await this._ensureServerReady(server); + + const capabilities = server.capabilities.get(); + if (!capabilities || !(capabilities & McpCapability.Resources)) { + return; + } + + try { + const resourceTemplates = await McpServer.callOn(server, h => h.listResourceTemplates()); + results.push({ serverIndex: this._getServerIndex(server), resourceTemplates }); + } catch { + // Server failed; skip + } + })); + + return results; + } + private async _ensureServerReady(server: IMcpServer): Promise { const cacheState = server.cacheState.get(); if (cacheState !== McpServerCacheState.Unknown && cacheState !== McpServerCacheState.Outdated) { diff --git a/src/vs/workbench/contrib/mcp/test/common/mcpGatewayToolBrokerChannel.test.ts b/src/vs/workbench/contrib/mcp/test/common/mcpGatewayToolBrokerChannel.test.ts index ee893807ecf..53124c8acc3 100644 --- a/src/vs/workbench/contrib/mcp/test/common/mcpGatewayToolBrokerChannel.test.ts +++ b/src/vs/workbench/contrib/mcp/test/common/mcpGatewayToolBrokerChannel.test.ts @@ -7,6 +7,7 @@ import assert from 'assert'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { observableValue } from '../../../../../base/common/observable.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { IGatewayCallToolResult } from '../../../../../platform/mcp/common/mcpGateway.js'; import { MCP } from '../../common/modelContextProtocol.js'; import { McpGatewayToolBrokerChannel } from '../../common/mcpGatewayToolBrokerChannel.js'; import { IMcpIcons, IMcpServer, IMcpTool, McpConnectionState, McpServerCacheState, McpToolVisibility } from '../../common/mcpTypes.js'; @@ -60,18 +61,18 @@ suite('McpGatewayToolBrokerChannel', () => { mcpService.servers.set([serverA, serverB], undefined); - const resultA = await channel.call(undefined, 'callTool', { + const resultA = await channel.call(undefined, 'callTool', { name: 'mcp_serverA_echo', args: { name: 'one' }, }); - const resultB = await channel.call(undefined, 'callTool', { + const resultB = await channel.call(undefined, 'callTool', { name: 'mcp_serverB_echo', args: { name: 'two' }, }); assert.deepStrictEqual(invoked, ['A:one', 'B:two']); - assert.strictEqual((resultA.content[0] as MCP.TextContent).text, 'from A'); - assert.strictEqual((resultB.content[0] as MCP.TextContent).text, 'from B'); + assert.strictEqual((resultA.result.content[0] as MCP.TextContent).text, 'from A'); + assert.strictEqual((resultB.result.content[0] as MCP.TextContent).text, 'from B'); channel.dispose(); }); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index 059b63a92c5..e765fb499e5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -551,7 +551,7 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts index 55901336b32..e48d807dec8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts @@ -254,4 +254,16 @@ suite('TerminalSandboxService - allowTrustedDomains', () => { const config = JSON.parse(configContent); strictEqual(config.network.allowedDomains.length, 0, 'Should have no domains (* filtered out)'); }); + + test('should add ripgrep bin directory to PATH when wrapping command', async () => { + const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService)); + await sandboxService.getSandboxConfigPath(); + + const wrappedCommand = sandboxService.wrapCommand('echo test'); + + ok( + wrappedCommand.includes('PATH') && wrappedCommand.includes('ripgrep'), + 'Wrapped command should include PATH modification with ripgrep' + ); + }); }); diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css b/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css index edc3e1d193d..a28e8a83592 100644 --- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css +++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/media/agentSessionsWelcome.css @@ -168,31 +168,31 @@ * Each pair forms a visual row. * Left column items need to move up by floor((index-1)/2) rows * Right column items need to move right and up by (index/2) rows - * Row height is 44px. + * Row height is 54px (must match AgentSessionsListDelegate.ITEM_HEIGHT). */ /* Left column items (odd positions): move up to form 2-column layout */ /* Item 3: move up 1 row */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(3) { - transform: translateY(-44px); + transform: translateY(-54px); } /* Item 5: move up 2 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(5) { - transform: translateY(-88px); + transform: translateY(-108px); } /* Right column items (even positions): move right and up */ /* Item 2: move right, up 1 row */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(2) { - transform: translateX(100%) translateY(-44px); + transform: translateX(100%) translateY(-54px); } /* Item 4: move right, up 2 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(4) { - transform: translateX(100%) translateY(-88px); + transform: translateX(100%) translateY(-108px); } /* Item 6: move right, up 3 rows */ .agentSessionsWelcome-sessionsGrid .monaco-list-row:nth-child(6) { - transform: translateX(100%) translateY(-132px); + transform: translateX(100%) translateY(-162px); } /* Style individual session items in the welcome page */ diff --git a/src/vs/workbench/services/accounts/browser/defaultAccount.ts b/src/vs/workbench/services/accounts/browser/defaultAccount.ts index 3fba2439fa5..9cc1b8bc960 100644 --- a/src/vs/workbench/services/accounts/browser/defaultAccount.ts +++ b/src/vs/workbench/services/accounts/browser/defaultAccount.ts @@ -28,8 +28,10 @@ import { distinct } from '../../../../base/common/arrays.js'; import { equals } from '../../../../base/common/objects.js'; import { IDefaultChatAgent } from '../../../../base/common/product.js'; import { IRequestContext } from '../../../../base/parts/request/common/request.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { localize2 } from '../../../../nls.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; interface IDefaultAccountConfig { readonly preferredExtensions: string[]; @@ -853,4 +855,17 @@ class DefaultAccountProviderContribution extends Disposable implements IWorkbenc } } +registerAction2(class extends Action2 { + constructor() { + super({ + id: DEFAULT_ACCOUNT_SIGN_IN_COMMAND, + title: localize2('signIn', 'Sign In'), + }); + } + async run(accessor: ServicesAccessor): Promise { + const defaultAccountService = accessor.get(IDefaultAccountService); + await defaultAccountService.signIn(); + } +}); + registerWorkbenchContribution2(DefaultAccountProviderContribution.ID, DefaultAccountProviderContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/services/editor/common/editorGroupFinder.ts b/src/vs/workbench/services/editor/common/editorGroupFinder.ts index f46747726b7..af149b81028 100644 --- a/src/vs/workbench/services/editor/common/editorGroupFinder.ts +++ b/src/vs/workbench/services/editor/common/editorGroupFinder.ts @@ -29,15 +29,16 @@ export function findGroup(accessor: ServicesAccessor, editor: EditorInputWithOpt const group = doFindGroup(editor, preferredGroup, editorGroupService, configurationService); if (group instanceof Promise) { - return group.then(group => handleGroupResult(group, editor, preferredGroup, editorGroupService)); + return group.then(group => handleGroupResult(group, editor, preferredGroup, editorGroupService, configurationService)); } - return handleGroupResult(group, editor, preferredGroup, editorGroupService); + return handleGroupResult(group, editor, preferredGroup, editorGroupService, configurationService); } -function handleGroupResult(group: IEditorGroup, editor: EditorInputWithOptions | IUntypedEditorInput, preferredGroup: PreferredGroup | undefined, editorGroupService: IEditorGroupsService): [IEditorGroup, EditorActivation | undefined] { +function handleGroupResult(group: IEditorGroup, editor: EditorInputWithOptions | IUntypedEditorInput, preferredGroup: PreferredGroup | undefined, editorGroupService: IEditorGroupsService, configurationService: IConfigurationService): [IEditorGroup, EditorActivation | undefined] { const modalEditorPart = editorGroupService.activeModalEditorPart; - if (modalEditorPart && preferredGroup !== MODAL_GROUP) { + const modalEditorMode = configurationService.getValue('workbench.editor.useModal'); + if (modalEditorPart && preferredGroup !== MODAL_GROUP && modalEditorMode !== 'on') { // Only allow to open in modal group if MODAL_GROUP is explicitly requested group = handleModalEditorPart(group, editor, modalEditorPart, editorGroupService); } @@ -124,7 +125,7 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer } // Group: Modal (gated behind a setting) - else if (preferredGroup === MODAL_GROUP && configurationService.getValue('workbench.editor.allowOpenInModalEditor')) { + else if (preferredGroup === MODAL_GROUP && configurationService.getValue('workbench.editor.useModal') !== 'off') { group = editorGroupService.createModalEditorPart(options?.modal) .then(part => part.activeGroup); } @@ -174,6 +175,12 @@ function doFindGroup(input: EditorInputWithOptions | IUntypedEditorInput, prefer } } + // Force modal editor part: redirect to the modal group when setting is 'on' + if (!group && configurationService.getValue('workbench.editor.useModal') === 'on') { + group = editorGroupService.createModalEditorPart(options?.modal) + .then(part => part.activeGroup); + } + // Fallback to active group if target not valid but avoid // locked editor groups unless editor is already opened there if (!group) { diff --git a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts index f33d165e59f..1c0db5ecf58 100644 --- a/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts +++ b/src/vs/workbench/services/editor/test/browser/modalEditorGroup.test.ts @@ -16,6 +16,8 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { Registry } from '../../../../../platform/registry/common/platform.js'; import { MODAL_GROUP, MODAL_GROUP_TYPE } from '../../common/editorService.js'; import { findGroup } from '../../common/editorGroupFinder.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; suite('Modal Editor Group', () => { @@ -504,5 +506,82 @@ suite('Modal Editor Group', () => { modalPart.close(); }); + suite('useModal: on', () => { + + test('findGroup creates modal and returns its active group', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'on'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // findGroup with undefined preferredGroup should create modal and return its group + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const result = instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: input.resource }, undefined)); + + // Should return a promise (async modal creation) + assert.ok(result instanceof Promise); + const [group] = await result; + + // The group should be in the modal part + assert.ok(parts.activeModalEditorPart); + assert.strictEqual(group.id, parts.activeModalEditorPart.activeGroup.id); + + parts.activeModalEditorPart.close(); + }); + + test('findGroup does not auto-close modal', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'on'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // Create modal first + const modalPart = await parts.createModalEditorPart(); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await modalPart.activeGroup.openEditor(input, { pinned: true }); + + // findGroup without MODAL_GROUP should NOT close the modal + const newInput = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + const result = instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: newInput.resource }, undefined)); + + // Since the setting is 'on', modal stays open + const [group] = result instanceof Promise ? await result : result; + + assert.ok(parts.activeModalEditorPart); + assert.strictEqual(group.id, modalPart.activeGroup.id); + + modalPart.close(); + }); + + test('findGroup auto-closes modal when setting is not on', async () => { + const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorFactory).start(accessor)); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench.editor.useModal', 'off'); + instantiationService.stub(IConfigurationService, configurationService); + const parts = await createEditorParts(instantiationService, disposables); + instantiationService.stub(IEditorGroupsService, parts); + + // Create modal + const modalPart = await parts.createModalEditorPart(); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + await modalPart.activeGroup.openEditor(input, { pinned: true }); + + assert.ok(parts.activeModalEditorPart); + + // findGroup without MODAL_GROUP should close the modal + const newInput = createTestFileEditorInput(URI.file('foo/baz'), TEST_EDITOR_INPUT_ID); + instantiationService.invokeFunction(accessor => findGroup(accessor, { resource: newInput.resource }, undefined)); + + assert.strictEqual(parts.activeModalEditorPart, undefined); + }); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts index e428280388d..fd394866237 100644 --- a/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts @@ -1617,7 +1617,7 @@ suite('keyboardMapper', () => { assertNumpadKeyboardEvent(KeyCode.UpArrow, 'Numpad8', 'UpArrow', 'Up', 'up', '[ArrowUp]'); assertNumpadKeyboardEvent(KeyCode.PageUp, 'Numpad9', 'PageUp', 'PageUp', 'pageup', '[PageUp]'); assertNumpadKeyboardEvent(KeyCode.Insert, 'Numpad0', 'Insert', 'Insert', 'insert', '[Insert]'); - assertNumpadKeyboardEvent(KeyCode.Delete, 'NumpadDecimal', 'Delete', 'Delete', 'delete', '[Delete]'); + assertNumpadKeyboardEvent(KeyCode.Delete, 'NumpadDecimal', 'Del', 'Delete', 'delete', '[Delete]'); }); test('issue #24107: Delete, Insert, Home, End, PgUp, PgDn, and arrow keys no longer work editor in 1.11', () => { @@ -1654,7 +1654,7 @@ suite('keyboardMapper', () => { assertKeyboardEvent(KeyCode.DownArrow, 'NumpadEnter', 'DownArrow', 'Down', 'down', '[ArrowDown]'); assertKeyboardEvent(KeyCode.LeftArrow, 'Convert', 'LeftArrow', 'Left', 'left', '[ArrowLeft]'); assertKeyboardEvent(KeyCode.RightArrow, 'NonConvert', 'RightArrow', 'Right', 'right', '[ArrowRight]'); - assertKeyboardEvent(KeyCode.Delete, 'PrintScreen', 'Delete', 'Delete', 'delete', '[Delete]'); + assertKeyboardEvent(KeyCode.Delete, 'PrintScreen', 'Del', 'Delete', 'delete', '[Delete]'); assertKeyboardEvent(KeyCode.Insert, 'NumpadDivide', 'Insert', 'Insert', 'insert', '[Insert]'); assertKeyboardEvent(KeyCode.End, 'Unknown', 'End', 'End', 'end', '[End]'); assertKeyboardEvent(KeyCode.Home, 'IntlRo', 'Home', 'Home', 'home', '[Home]'); @@ -1666,7 +1666,7 @@ suite('keyboardMapper', () => { assertKeyboardEvent(KeyCode.PageUp, 'Lang4', 'PageUp', 'PageUp', 'pageup', '[PageUp]'); assertKeyboardEvent(KeyCode.End, '', 'End', 'End', 'end', '[End]'); assertKeyboardEvent(KeyCode.Home, 'IntlRo', 'Home', 'Home', 'home', '[Home]'); - assertKeyboardEvent(KeyCode.Delete, 'PrintScreen', 'Delete', 'Delete', 'delete', '[Delete]'); + assertKeyboardEvent(KeyCode.Delete, 'PrintScreen', 'Del', 'Delete', 'delete', '[Delete]'); assertKeyboardEvent(KeyCode.Insert, 'NumpadDivide', 'Insert', 'Insert', 'insert', '[Insert]'); assertKeyboardEvent(KeyCode.RightArrow, 'NonConvert', 'RightArrow', 'Right', 'right', '[ArrowRight]'); assertKeyboardEvent(KeyCode.LeftArrow, 'Convert', 'LeftArrow', 'Left', 'left', '[ArrowLeft]'); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index c1c1f94f062..a1ca22b3c4a 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -372,7 +372,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } private getEditorGroupFromOptions(isTextual: boolean, options: { groupId?: number; openToSide?: boolean }): PreferredGroup { - if (!isTextual && this.configurationService.getValue('workbench.editor.allowOpenInModalEditor')) { + if (!isTextual && this.configurationService.getValue('workbench.editor.useModal') !== 'off') { return MODAL_GROUP; } if (options.openToSide) { diff --git a/build/vite/fixtures/aiStats.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts similarity index 89% rename from build/vite/fixtures/aiStats.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts index 5ebe414b52b..7df2b70d7d5 100644 --- a/build/vite/fixtures/aiStats.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/aiStats.fixture.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { observableValue } from '../../../src/vs/base/common/observable'; -import { createAiStatsHover, IAiStatsHoverData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar'; -import { ISessionData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsChart'; -import { Random } from '../../../src/vs/editor/test/common/core/random'; -import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; +import { observableValue } from '../../../../base/common/observable.js'; +import { createAiStatsHover, IAiStatsHoverData } from '../../../contrib/editTelemetry/browser/editStats/aiStatsStatusBar.js'; +import { ISessionData } from '../../../contrib/editTelemetry/browser/editStats/aiStatsChart.js'; +import { Random } from '../../../../editor/test/common/core/random.js'; +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; export default defineThemedFixtureGroup({ AiStatsHover: defineComponentFixture({ diff --git a/build/vite/fixtures/baseUI.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts similarity index 92% rename from build/vite/fixtures/baseUI.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts index 4bcf0be5235..19579a21ab1 100644 --- a/build/vite/fixtures/baseUI.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts @@ -3,21 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from '../../../src/vs/base/browser/dom'; -import { Codicon } from '../../../src/vs/base/common/codicons'; -import { ThemeIcon } from '../../../src/vs/base/common/themables'; -import { Action, Separator } from '../../../src/vs/base/common/actions'; +import { $ } from '../../../../base/browser/dom.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Action, Separator } from '../../../../base/common/actions.js'; // UI Components -import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../src/vs/base/browser/ui/button/button'; -import { Toggle, Checkbox, unthemedToggleStyles } from '../../../src/vs/base/browser/ui/toggle/toggle'; -import { InputBox, MessageType, unthemedInboxStyles } from '../../../src/vs/base/browser/ui/inputbox/inputBox'; -import { CountBadge } from '../../../src/vs/base/browser/ui/countBadge/countBadge'; -import { ActionBar } from '../../../src/vs/base/browser/ui/actionbar/actionbar'; -import { ProgressBar } from '../../../src/vs/base/browser/ui/progressbar/progressbar'; -import { HighlightedLabel } from '../../../src/vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js'; +import { Toggle, Checkbox, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js'; +import { InputBox, MessageType, unthemedInboxStyles } from '../../../../base/browser/ui/inputbox/inputBox.js'; +import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js'; +import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js'; +import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js'; -import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js'; + + +export default defineThemedFixtureGroup({ + Buttons: defineComponentFixture({ + render: renderButtons, + }), + + ButtonBar: defineComponentFixture({ + render: renderButtonBar, + }), + + Toggles: defineComponentFixture({ + render: renderToggles, + }), + + InputBoxes: defineComponentFixture({ + render: renderInputBoxes, + }), + + CountBadges: defineComponentFixture({ + render: renderCountBadges, + }), + + ActionBar: defineComponentFixture({ + render: renderActionBar, + }), + + ProgressBars: defineComponentFixture({ + render: renderProgressBars, + }), + + HighlightedLabels: defineComponentFixture({ + render: renderHighlightedLabels, + }), +}); // ============================================================================ @@ -247,12 +282,6 @@ function renderInputBoxes({ container, disposableStore }: ComponentFixtureContex container.style.gap = '16px'; container.style.width = '350px'; - // Normal input - const normalInput = disposableStore.add(new InputBox(container, undefined, { - placeholder: 'Enter search query...', - inputBoxStyles: themedInputBoxStyles, - })); - // Input with value const filledInput = disposableStore.add(new InputBox(container, undefined, { placeholder: 'File path', @@ -418,11 +447,6 @@ function renderProgressBars({ container, disposableStore }: ComponentFixtureCont return barContainer; }; - // Infinite progress - const infiniteSection = createSection('Infinite Progress (loading...)'); - const infiniteBar = disposableStore.add(new ProgressBar(infiniteSection, themedProgressBarOptions)); - infiniteBar.infinite(); - // Discrete progress - 30% const progress30Section = createSection('Discrete Progress - 30%'); const progress30Bar = disposableStore.add(new ProgressBar(progress30Section, themedProgressBarOptions)); @@ -490,42 +514,3 @@ function renderHighlightedLabels({ container }: ComponentFixtureContext): HTMLEl return container; } - - -// ============================================================================ -// Export Fixtures -// ============================================================================ - -export default defineThemedFixtureGroup({ - Buttons: defineComponentFixture({ - render: renderButtons, - }), - - ButtonBar: defineComponentFixture({ - render: renderButtonBar, - }), - - Toggles: defineComponentFixture({ - render: renderToggles, - }), - - InputBoxes: defineComponentFixture({ - render: renderInputBoxes, - }), - - CountBadges: defineComponentFixture({ - render: renderCountBadges, - }), - - ActionBar: defineComponentFixture({ - render: renderActionBar, - }), - - ProgressBars: defineComponentFixture({ - render: renderProgressBars, - }), - - HighlightedLabels: defineComponentFixture({ - render: renderHighlightedLabels, - }), -}); diff --git a/build/vite/fixtures/editor/codeEditor.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts similarity index 84% rename from build/vite/fixtures/editor/codeEditor.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts index bdaaf35bf0f..af7ff834637 100644 --- a/build/vite/fixtures/editor/codeEditor.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/codeEditor.fixture.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from '../../../../src/vs/base/common/uri'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; +import { URI } from '../../../../base/common/uri.js'; +import { ComponentFixtureContext, createEditorServices, defineThemedFixtureGroup, defineComponentFixture, createTextModel } from './fixtureUtils.js'; +import { ICodeEditorWidgetOptions, CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; + const SAMPLE_CODE = `// Welcome to VS Code function greet(name: string): string { diff --git a/build/vite/fixtures/fixtureUtils.ts b/src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts similarity index 66% rename from build/vite/fixtures/fixtureUtils.ts rename to src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts index fd90169dae7..0aa45800518 100644 --- a/build/vite/fixtures/fixtureUtils.ts +++ b/src/vs/workbench/test/browser/componentFixtures/fixtureUtils.ts @@ -3,98 +3,98 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// This should be the only place that is allowed to import from @vscode/component-explorer +// eslint-disable-next-line local/code-import-patterns import { defineFixture, defineFixtureGroup, defineFixtureVariants } from '@vscode/component-explorer'; -import { DisposableStore, toDisposable } from '../../../src/vs/base/common/lifecycle'; -import { URI } from '../../../src/vs/base/common/uri'; -import '../style.css'; +import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; +// eslint-disable-next-line local/code-import-patterns +import '../../../../../../build/vite/style.css'; // Theme -import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../src/vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from '../../../src/vs/workbench/services/themes/common/colorThemeData'; -import { ColorScheme } from '../../../src/vs/platform/theme/common/theme'; -import { generateColorThemeCSS } from '../../../src/vs/workbench/services/themes/browser/colorThemeCss'; -import { Registry } from '../../../src/vs/platform/registry/common/platform'; -import { Extensions as ThemingExtensions, IThemingRegistry } from '../../../src/vs/platform/theme/common/themeService'; -import { IEnvironmentService } from '../../../src/vs/platform/environment/common/environment'; -import { getIconsStyleSheet } from '../../../src/vs/platform/theme/browser/iconsStyleSheet'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js'; +import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { IColorTheme, IThemeService, IThemingRegistry, Extensions as ThemingExtensions } from '../../../../platform/theme/common/themeService.js'; +import { generateColorThemeCSS } from '../../../services/themes/browser/colorThemeCss.js'; +import { ColorThemeData } from '../../../services/themes/common/colorThemeData.js'; +import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../services/themes/common/workbenchThemeService.js'; // Instantiation -import { ServiceCollection } from '../../../src/vs/platform/instantiation/common/serviceCollection'; -import { SyncDescriptor } from '../../../src/vs/platform/instantiation/common/descriptors'; -import { ServiceIdentifier } from '../../../src/vs/platform/instantiation/common/instantiation'; -import { TestInstantiationService } from '../../../src/vs/platform/instantiation/test/common/instantiationServiceMock'; +import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; +import { ServiceIdentifier } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; // Test service implementations -import { TestAccessibilityService } from '../../../src/vs/platform/accessibility/test/common/testAccessibilityService'; -import { MockKeybindingService, MockContextKeyService } from '../../../src/vs/platform/keybinding/test/common/mockKeybindingService'; -import { TestClipboardService } from '../../../src/vs/platform/clipboard/test/common/testClipboardService'; -import { TestEditorWorkerService } from '../../../src/vs/editor/test/common/services/testEditorWorkerService'; -import { NullOpenerService } from '../../../src/vs/platform/opener/test/common/nullOpenerService'; -import { TestNotificationService } from '../../../src/vs/platform/notification/test/common/testNotificationService'; -import { TestDialogService } from '../../../src/vs/platform/dialogs/test/common/testDialogService'; -import { TestConfigurationService } from '../../../src/vs/platform/configuration/test/common/testConfigurationService'; -import { TestTextResourcePropertiesService } from '../../../src/vs/editor/test/common/services/testTextResourcePropertiesService'; -import { TestThemeService } from '../../../src/vs/platform/theme/test/common/testThemeService'; -import { TestLanguageConfigurationService } from '../../../src/vs/editor/test/common/modes/testLanguageConfigurationService'; -import { TestCodeEditorService, TestCommandService } from '../../../src/vs/editor/test/browser/editorTestServices'; -import { TestTreeSitterLibraryService } from '../../../src/vs/editor/test/common/services/testTreeSitterLibraryService'; -import { TestMenuService } from '../../../src/vs/workbench/test/browser/workbenchTestServices'; - -// Service interfaces -import { IAccessibilityService } from '../../../src/vs/platform/accessibility/common/accessibility'; -import { IKeybindingService } from '../../../src/vs/platform/keybinding/common/keybinding'; -import { IClipboardService } from '../../../src/vs/platform/clipboard/common/clipboardService'; -import { IEditorWorkerService } from '../../../src/vs/editor/common/services/editorWorker'; -import { IOpenerService } from '../../../src/vs/platform/opener/common/opener'; -import { INotificationService } from '../../../src/vs/platform/notification/common/notification'; -import { IDialogService } from '../../../src/vs/platform/dialogs/common/dialogs'; -import { IUndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedo'; -import { UndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedoService'; -import { ILanguageService } from '../../../src/vs/editor/common/languages/language'; -import { LanguageService } from '../../../src/vs/editor/common/services/languageService'; -import { ILanguageConfigurationService } from '../../../src/vs/editor/common/languages/languageConfigurationRegistry'; -import { IConfigurationService } from '../../../src/vs/platform/configuration/common/configuration'; -import { ITextResourcePropertiesService } from '../../../src/vs/editor/common/services/textResourceConfiguration'; -import { IColorTheme, IThemeService } from '../../../src/vs/platform/theme/common/themeService'; -import { ILogService, NullLogService, ILoggerService, NullLoggerService } from '../../../src/vs/platform/log/common/log'; -import { IModelService } from '../../../src/vs/editor/common/services/model'; -import { ModelService } from '../../../src/vs/editor/common/services/modelService'; -import { ICodeEditorService } from '../../../src/vs/editor/browser/services/codeEditorService'; -import { IContextKeyService } from '../../../src/vs/platform/contextkey/common/contextkey'; -import { ICommandService } from '../../../src/vs/platform/commands/common/commands'; -import { ITelemetryService } from '../../../src/vs/platform/telemetry/common/telemetry'; -import { NullTelemetryServiceShape } from '../../../src/vs/platform/telemetry/common/telemetryUtils'; -import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../src/vs/editor/common/services/languageFeatureDebounce'; -import { ILanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeatures'; -import { LanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeaturesService'; -import { ITreeSitterLibraryService } from '../../../src/vs/editor/common/services/treeSitter/treeSitterLibraryService'; -import { IInlineCompletionsService, InlineCompletionsService } from '../../../src/vs/editor/browser/services/inlineCompletionsService'; -import { ICodeLensCache } from '../../../src/vs/editor/contrib/codelens/browser/codeLensCache'; -import { IHoverService } from '../../../src/vs/platform/hover/browser/hover'; -import { IDataChannelService, NullDataChannelService } from '../../../src/vs/platform/dataChannel/common/dataChannel'; -import { IContextMenuService, IContextViewService } from '../../../src/vs/platform/contextview/browser/contextView'; -import { ILabelService } from '../../../src/vs/platform/label/common/label'; -import { IMenuService } from '../../../src/vs/platform/actions/common/actions'; -import { IActionViewItemService, NullActionViewItemService } from '../../../src/vs/platform/actions/browser/actionViewItemService'; -import { IDefaultAccountService } from '../../../src/vs/platform/defaultAccount/common/defaultAccount'; -import { IStorageService, IStorageValueChangeEvent, IWillSaveStateEvent, StorageScope, StorageTarget, IStorageTargetChangeEvent, IStorageEntry, WillSaveStateReason, IWorkspaceStorageValueChangeEvent, IProfileStorageValueChangeEvent, IApplicationStorageValueChangeEvent } from '../../../src/vs/platform/storage/common/storage'; -import { Emitter, Event } from '../../../src/vs/base/common/event'; -import { mock } from '../../../src/vs/base/test/common/mock'; -import { IAnyWorkspaceIdentifier } from '../../../src/vs/platform/workspace/common/workspace'; -import { IUserDataProfile } from '../../../src/vs/platform/userDataProfile/common/userDataProfile'; -import { IUserInteractionService, MockUserInteractionService } from '../../../src/vs/platform/userInteraction/browser/userInteractionService'; +import { TestCodeEditorService, TestCommandService } from '../../../../editor/test/browser/editorTestServices.js'; +import { TestLanguageConfigurationService } from '../../../../editor/test/common/modes/testLanguageConfigurationService.js'; +import { TestEditorWorkerService } from '../../../../editor/test/common/services/testEditorWorkerService.js'; +import { TestTextResourcePropertiesService } from '../../../../editor/test/common/services/testTextResourcePropertiesService.js'; +import { TestTreeSitterLibraryService } from '../../../../editor/test/common/services/testTreeSitterLibraryService.js'; +import { TestAccessibilityService } from '../../../../platform/accessibility/test/common/testAccessibilityService.js'; +import { TestClipboardService } from '../../../../platform/clipboard/test/common/testClipboardService.js'; +import { TestConfigurationService } from '../../../../platform/configuration/test/common/testConfigurationService.js'; +import { TestDialogService } from '../../../../platform/dialogs/test/common/testDialogService.js'; +import { MockContextKeyService, MockKeybindingService } from '../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { TestNotificationService } from '../../../../platform/notification/test/common/testNotificationService.js'; +import { NullOpenerService } from '../../../../platform/opener/test/common/nullOpenerService.js'; +import { TestThemeService } from '../../../../platform/theme/test/common/testThemeService.js'; +import { TestMenuService } from '../workbenchTestServices.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { mock } from '../../../../base/test/common/mock.js'; +import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; +import { IInlineCompletionsService, InlineCompletionsService } from '../../../../editor/browser/services/inlineCompletionsService.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { ILanguageConfigurationService } from '../../../../editor/common/languages/languageConfigurationRegistry.js'; +import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; +import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../../editor/common/services/languageFeatureDebounce.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { LanguageFeaturesService } from '../../../../editor/common/services/languageFeaturesService.js'; +import { LanguageService } from '../../../../editor/common/services/languageService.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { ModelService } from '../../../../editor/common/services/modelService.js'; +import { ITextResourcePropertiesService } from '../../../../editor/common/services/textResourceConfiguration.js'; +import { ITreeSitterLibraryService } from '../../../../editor/common/services/treeSitter/treeSitterLibraryService.js'; +import { ICodeLensCache } from '../../../../editor/contrib/codelens/browser/codeLensCache.js'; +import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; +import { IActionViewItemService, NullActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; +import { IMenuService } from '../../../../platform/actions/common/actions.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { IDataChannelService, NullDataChannelService } from '../../../../platform/dataChannel/common/dataChannel.js'; +import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js'; +import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { ILoggerService, ILogService, NullLoggerService, NullLogService } from '../../../../platform/log/common/log.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IApplicationStorageValueChangeEvent, IProfileStorageValueChangeEvent, IStorageEntry, IStorageService, IStorageTargetChangeEvent, IStorageValueChangeEvent, IWillSaveStateEvent, IWorkspaceStorageValueChangeEvent, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { NullTelemetryServiceShape } from '../../../../platform/telemetry/common/telemetryUtils.js'; +import { IUndoRedoService } from '../../../../platform/undoRedo/common/undoRedo.js'; +import { UndoRedoService } from '../../../../platform/undoRedo/common/undoRedoService.js'; +import { IUserDataProfile } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IUserInteractionService, MockUserInteractionService } from '../../../../platform/userInteraction/browser/userInteractionService.js'; +import { IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; // Editor -import { ITextModel } from '../../../src/vs/editor/common/model'; +import { ITextModel } from '../../../../editor/common/model.js'; // Import color registrations to ensure colors are available -import '../../../src/vs/platform/theme/common/colors/baseColors'; -import '../../../src/vs/platform/theme/common/colors/editorColors'; -import '../../../src/vs/platform/theme/common/colors/listColors'; -import '../../../src/vs/platform/theme/common/colors/miscColors'; -import '../../../src/vs/workbench/common/theme'; +import '../../../../platform/theme/common/colors/baseColors.js'; +import '../../../../platform/theme/common/colors/editorColors.js'; +import '../../../../platform/theme/common/colors/listColors.js'; +import '../../../../platform/theme/common/colors/miscColors.js'; +import '../../../common/theme.js'; /** * A storage service that never stores anything and always returns the default/fallback value. @@ -363,7 +363,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre put: () => { }, get: () => undefined, delete: () => { }, - } as ICodeLensCache); + }); defineInstance(IHoverService, { _serviceBrand: undefined, showDelayedHover: () => undefined, @@ -374,7 +374,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre showAndFocusLastHover: () => { }, setupManagedHover: () => ({ dispose: () => { }, show: () => { }, hide: () => { }, update: () => { } }), showManagedHover: () => { }, - } as IHoverService); + }); defineInstance(IDefaultAccountService, { _serviceBrand: undefined, onDidChangeDefaultAccount: new Emitter().event, @@ -385,7 +385,8 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre setDefaultAccountProvider: () => { }, refresh: async () => null, signIn: async () => null, - } as IDefaultAccountService); + signOut: async () => { }, + }); // User interaction service with focus simulation enabled (all elements appear focused in fixtures) defineInstance(IUserInteractionService, new MockUserInteractionService(true, false)); @@ -397,8 +398,7 @@ export function createEditorServices(disposables: DisposableStore, options?: Cre disposables.add(toDisposable(() => { for (const id of serviceIdentifiers) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const instanceOrDescriptor = services.get(id) as any; + const instanceOrDescriptor = services.get(id); if (typeof instanceOrDescriptor?.dispose === 'function') { instanceOrDescriptor.dispose(); } @@ -417,14 +417,17 @@ export function registerWorkbenchServices(registration: ServiceRegistration): vo showContextMenu: () => { }, onDidShowContextMenu: () => ({ dispose: () => { } }), onDidHideContextMenu: () => ({ dispose: () => { } }), - } as unknown as IContextMenuService); + _serviceBrand: undefined, + }); registration.defineInstance(IContextViewService, { - showContextView: () => ({ dispose: () => { } }), + showContextView: () => ({ close: () => { } }), hideContextView: () => { }, - getContextViewElement: () => null, + getContextViewElement: () => { throw new Error('Not implemented'); }, layout: () => { }, - } as unknown as IContextViewService); + anchorAlignment: 0, + _serviceBrand: undefined, + }); registration.defineInstance(ILabelService, { getUriLabel: (uri: URI) => uri.path, @@ -435,7 +438,9 @@ export function registerWorkbenchServices(registration: ServiceRegistration): vo registerFormatter: () => ({ dispose: () => { } }), onDidChangeFormatters: () => ({ dispose: () => { } }), registerCachedFormatter: () => ({ dispose: () => { } }), - } as unknown as ILabelService); + _serviceBrand: undefined, + getHostTooltip: () => '', + }); registration.define(IMenuService, TestMenuService); registration.define(IActionViewItemService, NullActionViewItemService); @@ -488,10 +493,11 @@ export function defineComponentFixture(options: ComponentFixtureOptions): Themed displayMode: { type: 'component' }, properties: [], background: theme === darkTheme ? 'dark' : 'light', - render: async (container: HTMLElement) => { + render: (container: HTMLElement) => { const disposableStore = new DisposableStore(); setupTheme(container, theme); - return options.render({ container, disposableStore, theme }); + options.render({ container, disposableStore, theme }); + return disposableStore; }, }); diff --git a/build/vite/fixtures/editor/inlineCompletions.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts similarity index 74% rename from build/vite/fixtures/editor/inlineCompletions.fixture.ts rename to src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts index 06abdece6b6..3f30440013a 100644 --- a/build/vite/fixtures/editor/inlineCompletions.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/inlineCompletions.fixture.ts @@ -3,20 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { constObservable } from '../../../../src/vs/base/common/observable'; -import { URI } from '../../../../src/vs/base/common/uri'; -import { Range } from '../../../../src/vs/editor/common/core/range'; -import { IEditorOptions } from '../../../../src/vs/editor/common/config/editorOptions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; -import { EditorExtensionsRegistry } from '../../../../src/vs/editor/browser/editorExtensions'; -import { InlineCompletionsController } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; -import { InlineCompletionsSource, InlineCompletionsState } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource'; -import { InlineEditItem } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem'; -import { TextModelValueReference } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/textModelValueReference'; -import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; // Import to register the inline completions contribution -import '../../../../src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution'; +import { constObservable, IObservableWithChange } from '../../../../base/common/observable.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ComponentFixtureContext, createEditorServices, defineThemedFixtureGroup, defineComponentFixture, createTextModel } from './fixtureUtils.js'; +import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; +import { ICodeEditorWidgetOptions, CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js'; +import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { InlineCompletionsController } from '../../../../editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.js'; +import '../../../../editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.js'; +import { InlineCompletionsSource, InlineCompletionsState } from '../../../../editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.js'; +import { InlineEditItem } from '../../../../editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.js'; +import { TextModelValueReference } from '../../../../editor/contrib/inlineCompletions/browser/model/textModelValueReference.js'; // ============================================================================ @@ -52,15 +52,13 @@ function renderInlineEdit(options: InlineEditOptions): HTMLElement { instantiationService.stubInstance(InlineCompletionsSource, { cancelUpdate: () => { }, clear: () => { }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clearOperationOnTextModelChange: constObservable(undefined) as any, + clearOperationOnTextModelChange: constObservable(undefined) as IObservableWithChange, clearSuggestWidgetInlineCompletions: () => { }, dispose: () => { }, fetch: async () => true, inlineCompletions: constObservable(new InlineCompletionsState([ InlineEditItem.createForTest( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TextModelValueReference.snapshot(textModel as any), + TextModelValueReference.snapshot(textModel), new Range( options.range.startLineNumber, options.range.startColumn, @@ -117,11 +115,11 @@ export default defineThemedFixtureGroup({ render: (context) => renderInlineEdit({ ...context, code: `function greet(name) { - console.log("Hello, " + name); + console.log("Hello, " + name); }`, cursorLine: 2, range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 100 }, - newText: ' console.log(`Hello, ${name}!`);', + newText: '\tconsole.log(`Hello, ${name}!`);', }), }), diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png new file mode 100644 index 00000000000..2d42f424f6b Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png new file mode 100644 index 00000000000..7fb858eae27 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHover/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png new file mode 100644 index 00000000000..9ebb23a3806 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png new file mode 100644 index 00000000000..e631cc0c3c3 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/aiStats/AiStatsHoverNoData/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png new file mode 100644 index 00000000000..0ced565eff4 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png new file mode 100644 index 00000000000..6523771ae70 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ActionBar/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png new file mode 100644 index 00000000000..ab1c28ac44f Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png new file mode 100644 index 00000000000..9afb35e1849 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ButtonBar/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png new file mode 100644 index 00000000000..cd2a52efff4 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png new file mode 100644 index 00000000000..9651d1f2d57 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Buttons/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png new file mode 100644 index 00000000000..e6cc74cdfc2 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png new file mode 100644 index 00000000000..8e9cd825d1f Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/CountBadges/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png new file mode 100644 index 00000000000..36ed40b5266 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png new file mode 100644 index 00000000000..cfca6a3329a Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/HighlightedLabels/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png new file mode 100644 index 00000000000..721a618d708 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png new file mode 100644 index 00000000000..d35879dc975 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/InputBoxes/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png new file mode 100644 index 00000000000..bb25647846e Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png new file mode 100644 index 00000000000..a542ed0e11d Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/ProgressBars/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png new file mode 100644 index 00000000000..a59cc7894d2 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png new file mode 100644 index 00000000000..49c44de392f Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/baseUI/Toggles/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png new file mode 100644 index 00000000000..b5d9569f906 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png new file mode 100644 index 00000000000..0a8e19aeb3a Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/codeEditor/CodeEditor/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png new file mode 100644 index 00000000000..b10535a2ffd Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png new file mode 100644 index 00000000000..a6fd87499a8 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/InsertionView/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png new file mode 100644 index 00000000000..52a2280560a Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png new file mode 100644 index 00000000000..65169b87a11 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/SideBySideView/Light.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png new file mode 100644 index 00000000000..297cb38eade Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Dark.png differ diff --git a/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png new file mode 100644 index 00000000000..3d564e02878 Binary files /dev/null and b/test/componentFixtures/.screenshots/baseline/inlineCompletions/WordReplacementView/Light.png differ diff --git a/test/componentFixtures/component-explorer-diff.json b/test/componentFixtures/component-explorer-diff.json new file mode 100644 index 00000000000..624f67f6ebc --- /dev/null +++ b/test/componentFixtures/component-explorer-diff.json @@ -0,0 +1,30 @@ +{ + "screenshotDir": ".screenshots", + "sessions": [ + { + "name": "current" + }, + { + "name": "baseline", + "source": { + "worktree": { + "ref": "HEAD", + "install": { + "command": "echo 'noop'" + } + } + } + } + ], + "compare": { + "baseline": "baseline", + "current": "current" + }, + "vite": { + "hmr": { + "allowedPaths": [ + "*.css" + ] + } + } +} diff --git a/test/componentFixtures/component-explorer.json b/test/componentFixtures/component-explorer.json new file mode 100644 index 00000000000..12a3fd30d42 --- /dev/null +++ b/test/componentFixtures/component-explorer.json @@ -0,0 +1,16 @@ +{ + "screenshotDir": ".screenshots", + "sessions": [ + { + "name": "current" + } + ], + "viteConfig": "../../build/vite/vite.config.ts", + "vite": { + "hmr": { + "allowedPaths": [ + "*.css" + ] + } + } +}