From 754dd055ba5f8779565140e9b73dc11e9366c50a Mon Sep 17 00:00:00 2001 From: Anisha Agarwal Date: Tue, 1 Sep 2026 14:18:47 -0700 Subject: [PATCH 01/33] gate semantic search for search subagent --- extensions/copilot/package.json | 10 +++ extensions/copilot/package.nls.json | 1 + .../node/searchSubagentToolCallingLoop.ts | 7 +- .../searchSubagentToolCallingLoop.spec.ts | 70 ++++++++++++++++++- .../common/configurationService.ts | 2 + 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index c83a04b76d13dc..529db684406e12 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -5242,6 +5242,16 @@ "onExp" ] }, + "github.copilot.chat.searchSubagent.subagentSemanticSearchEnabled": { + "type": "boolean", + "default": true, + "markdownDescription": "%github.copilot.config.searchSubagent.subagentSemanticSearchEnabled%", + "tags": [ + "advanced", + "experimental", + "onExp" + ] + }, "github.copilot.chat.agentDebugLog.fileLogging.enabled": { "type": "boolean", "default": false, diff --git a/extensions/copilot/package.nls.json b/extensions/copilot/package.nls.json index 8165b301befd75..fa3928b65a197a 100644 --- a/extensions/copilot/package.nls.json +++ b/extensions/copilot/package.nls.json @@ -506,6 +506,7 @@ "github.copilot.config.searchSubagent.model": "Model to use for the search subagent. When useAgenticProxy is enabled, defaults to 'vscode-agentic-search-router-a'. Otherwise defaults to the main agent model.", "github.copilot.config.searchSubagent.toolCallLimit": "Maximum number of tool calls the search subagent can make during exploration.", "github.copilot.config.searchSubagent.thoroughnessEnabled": "Enable the thoroughness parameter on the search subagent tool. When enabled, the caller can pass 'normal' or 'deep' to adjust the number of allowed tool-call turns (1× or 2× the base toolCallLimit respectively).", + "github.copilot.config.searchSubagent.subagentSemanticSearchEnabled": "Enable the semantic search tool for the search subagent.", "copilot.tools.executionSubagent.name": "Execution Subagent", "copilot.tools.executionSubagent.description": "Launch an execution-focused subagent that runs one or more terminal commands to accomplish a task. This subagent is powered by Google's Gemini-3-Flash model. It is designed to select an efficient summary of the terminal outputs to return to the main agent context.", "github.copilot.config.executionSubagent.enabled": "Enable the Execution Subagent tool in Copilot Chat. The Execution Subagent is designed to run terminal commands to accomplish an execution-based task. It is powered by Google's Gemini-3-Flash model.", diff --git a/extensions/copilot/src/extension/prompt/node/searchSubagentToolCallingLoop.ts b/extensions/copilot/src/extension/prompt/node/searchSubagentToolCallingLoop.ts index 6814f49d25bacf..4ab97e291479fb 100644 --- a/extensions/copilot/src/extension/prompt/node/searchSubagentToolCallingLoop.ts +++ b/extensions/copilot/src/extension/prompt/node/searchSubagentToolCallingLoop.ts @@ -197,14 +197,15 @@ export class SearchSubagentToolCallingLoop extends ToolCallingLoop allowedSearchTools.has(tool.name as ToolName)); } diff --git a/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts b/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts index e4b25f9e03221e..1f558d775857d3 100644 --- a/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts +++ b/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import type { CancellationToken, ChatRequest } from 'vscode'; +import type { CancellationToken, ChatRequest, LanguageModelToolInformation } from 'vscode'; import { IChatHookService } from '../../../../platform/chat/common/chatHookService'; import { ChatFetchResponseType, ChatLocation, ChatResponse } from '../../../../platform/chat/common/commonTypes'; import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService'; @@ -26,6 +26,7 @@ import { isContextOverflowBadRequest, } from '../../../prompt/node/searchSubagentToolCallingLoop'; import { createExtensionUnitTestingServices } from '../../../test/node/services'; +import { ToolName } from '../../../tools/common/toolNames'; class TestSearchSubagentToolCallingLoop extends SearchSubagentToolCallingLoop { public buildPromptCalls = 0; @@ -74,6 +75,10 @@ class TestSearchSubagentToolCallingLoop extends SearchSubagentToolCallingLoop { token, ); } + + public callGetAvailableTools(): Promise { + return this.getAvailableTools(); + } } function createMockChatRequest(): ChatRequest { @@ -315,6 +320,69 @@ describe('SearchSubagentToolCallingLoop.shouldAutoRetry', () => { }); }); +describe('SearchSubagentToolCallingLoop.getAvailableTools', () => { + let disposables: DisposableStore; + let instantiationService: IInstantiationService; + let configurationService: IConfigurationService; + + beforeEach(() => { + disposables = new DisposableStore(); + const serviceCollection = disposables.add(createExtensionUnitTestingServices()); + serviceCollection.define(IChatHookService, new MockChatHookService()); + const accessor = serviceCollection.createTestingAccessor(); + instantiationService = accessor.get(IInstantiationService); + configurationService = accessor.get(IConfigurationService); + }); + + afterEach(() => { + disposables.dispose(); + }); + + function createLoop(): TestSearchSubagentToolCallingLoop { + const options: ISearchSubagentToolCallingLoopOptions = { + conversation: createTestConversation(), + toolCallLimit: 10, + request: createMockChatRequest(), + location: ChatLocation.Panel, + promptText: 'find things', + }; + const loop = instantiationService.createInstance(TestSearchSubagentToolCallingLoop, options); + const tools = [ + { name: ToolName.Codebase }, + { name: ToolName.FindFiles }, + { name: ToolName.FindTextInFiles }, + { name: ToolName.ReadFile }, + ] as LanguageModelToolInformation[]; + (loop as any).getEndpoint = async () => loop.fakeEndpoint; + (loop as any).toolsService = { getEnabledTools: () => tools }; + disposables.add(loop); + return loop; + } + + it('includes semantic_search when enabled', async () => { + await configurationService.setConfig(ConfigKey.Advanced.SubagentSemanticSearchEnabled, true); + const tools = await createLoop().callGetAvailableTools(); + + expect(tools.map(tool => tool.name)).toEqual([ + ToolName.Codebase, + ToolName.FindFiles, + ToolName.FindTextInFiles, + ToolName.ReadFile, + ]); + }); + + it('excludes only semantic_search when disabled', async () => { + await configurationService.setConfig(ConfigKey.Advanced.SubagentSemanticSearchEnabled, false); + const tools = await createLoop().callGetAvailableTools(); + + expect(tools.map(tool => tool.name)).toEqual([ + ToolName.FindFiles, + ToolName.FindTextInFiles, + ToolName.ReadFile, + ]); + }); +}); + describe('SearchSubagentToolCallingLoop.getEndpoint (agentic proxy)', () => { let disposables: DisposableStore; let instantiationService: IInstantiationService; diff --git a/extensions/copilot/src/platform/configuration/common/configurationService.ts b/extensions/copilot/src/platform/configuration/common/configurationService.ts index 20c28c726c5009..5176b2311e03c3 100644 --- a/extensions/copilot/src/platform/configuration/common/configurationService.ts +++ b/extensions/copilot/src/platform/configuration/common/configurationService.ts @@ -800,6 +800,8 @@ export namespace ConfigKey { export const SearchSubagentToolCallLimit = defineSetting('chat.searchSubagent.toolCallLimit', ConfigType.ExperimentBased, 4); /** Enable the thoroughness parameter on the search subagent tool, which adjusts turn limits based on requested thoroughness */ export const SearchSubagentThoroughnessEnabled = defineSetting('chat.searchSubagent.thoroughnessEnabled', ConfigType.ExperimentBased, false); + /** Enable semantic search for the search subagent */ + export const SubagentSemanticSearchEnabled = defineSetting('chat.searchSubagent.subagentSemanticSearchEnabled', ConfigType.ExperimentBased, true); export const ExecutionSubagentToolEnabled = defineSetting('chat.executionSubagent.enabled', ConfigType.ExperimentBased, false); export const SkillToolEnabled = defineSetting('chat.skillTool.enabled', ConfigType.ExperimentBased, false); From 8c2aeb735809b595f61ff87be65ce004b450d67c Mon Sep 17 00:00:00 2001 From: Anisha Agarwal Date: Wed, 2 Sep 2026 09:40:21 -0700 Subject: [PATCH 02/33] update test to resolve comment --- .../searchSubagentToolCallingLoop.spec.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts b/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts index 1f558d775857d3..473a67830139a7 100644 --- a/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts +++ b/extensions/copilot/src/extension/prompt/test/node/searchSubagentToolCallingLoop.spec.ts @@ -12,6 +12,7 @@ import { IChatModelInformation } from '../../../../platform/endpoint/common/endp import { ChatEndpoint } from '../../../../platform/endpoint/node/chatEndpoint'; import { SEARCH_AGENT_FAMILY, SearchAgentChatEndpoint } from '../../../../platform/endpoint/node/searchAgentChatEndpoint'; import { IChatEndpoint } from '../../../../platform/networking/common/networking'; +import { mock } from '../../../../util/common/test/simpleMock'; import { CancellationTokenSource } from '../../../../util/vs/base/common/cancellation'; import { DisposableStore } from '../../../../util/vs/base/common/lifecycle'; import { generateUuid } from '../../../../util/vs/base/common/uuid'; @@ -27,6 +28,18 @@ import { } from '../../../prompt/node/searchSubagentToolCallingLoop'; import { createExtensionUnitTestingServices } from '../../../test/node/services'; import { ToolName } from '../../../tools/common/toolNames'; +import { IToolsService } from '../../../tools/common/toolsService'; + +class TestToolsService extends mock() { + override getEnabledTools(): LanguageModelToolInformation[] { + return [ + { name: ToolName.Codebase }, + { name: ToolName.FindFiles }, + { name: ToolName.FindTextInFiles }, + { name: ToolName.ReadFile }, + ] as LanguageModelToolInformation[]; + } +} class TestSearchSubagentToolCallingLoop extends SearchSubagentToolCallingLoop { public buildPromptCalls = 0; @@ -329,6 +342,7 @@ describe('SearchSubagentToolCallingLoop.getAvailableTools', () => { disposables = new DisposableStore(); const serviceCollection = disposables.add(createExtensionUnitTestingServices()); serviceCollection.define(IChatHookService, new MockChatHookService()); + serviceCollection.define(IToolsService, new TestToolsService()); const accessor = serviceCollection.createTestingAccessor(); instantiationService = accessor.get(IInstantiationService); configurationService = accessor.get(IConfigurationService); @@ -347,14 +361,7 @@ describe('SearchSubagentToolCallingLoop.getAvailableTools', () => { promptText: 'find things', }; const loop = instantiationService.createInstance(TestSearchSubagentToolCallingLoop, options); - const tools = [ - { name: ToolName.Codebase }, - { name: ToolName.FindFiles }, - { name: ToolName.FindTextInFiles }, - { name: ToolName.ReadFile }, - ] as LanguageModelToolInformation[]; (loop as any).getEndpoint = async () => loop.fakeEndpoint; - (loop as any).toolsService = { getEnabledTools: () => tools }; disposables.add(loop); return loop; } From b8d568e8e32c57a9fdbbeacb2d1527c688b20f3c Mon Sep 17 00:00:00 2001 From: VS Code PR Bot Date: Wed, 2 Sep 2026 10:44:29 -0700 Subject: [PATCH 03/33] fix: skip real AudioContext in Voice Mode onboarding preview under tests (build fix for vscode-engineering#3742) (#333858) * fix: skip real AudioContext in Voice Mode onboarding preview under tests The onboarding voice-sample preview always built a real AudioContext and connected the analyser to context.destination, opening the host audio output device even when a test-supplied audio element was injected. In headless Electron test runs this device kept spinning with no producer, emitting endless `SyncReader::Read timed out` audio-glitch warnings and hanging the unit test task until timeout. Skip the best-effort analyser graph when an audioFactory override is provided; the sample still plays. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * chore: attest automated pull request --------- Co-authored-by: github-actions[bot] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Megan Rogge Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Megan Rogge Co-authored-by: Dmitriy Vasyura Co-authored-by: Bryan Chen <41454397+bryanchen-d@users.noreply.github.com> Co-authored-by: Giuseppe Cianci --- .../contrib/agentsVoice/browser/voiceModeOnboarding.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts b/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts index 93f8f9184eaeb7..4e71fd4fe5dc9d 100644 --- a/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts +++ b/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts @@ -618,6 +618,11 @@ class VoiceSamplePlayer extends Disposable { audio.src = ''; })); + // Tests inject audio elements to avoid opening the host audio output device. + if (this.audioFactory) { + return audio; + } + try { const context = new targetWindow.AudioContext(); this._register(toDisposable(() => void context.close().catch(() => { /* already closing */ }))); From ca90419648a3785c1c73d59dacad43c9726d9b67 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 2 Sep 2026 19:48:03 +0200 Subject: [PATCH 04/33] Update proxy agent to 0.45.0 (#333953) --- package-lock.json | 8 ++++---- package.json | 2 +- remote/package-lock.json | 8 ++++---- remote/package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a585ac9ad0c34..4395b66400a202 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@vscode/native-watchdog": "^1.4.6", "@vscode/os-proxy-resolver": "^0.4.0", "@vscode/policy-watcher": "^1.4.0", - "@vscode/proxy-agent": "^0.44.0", + "@vscode/proxy-agent": "^0.45.0", "@vscode/ripgrep-universal": "^1.18.0", "@vscode/sandbox-runtime": "0.0.1", "@vscode/spdlog": "^0.15.8", @@ -4964,9 +4964,9 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.44.0.tgz", - "integrity": "sha512-1vv0uJrIGxS89C+0gPmgNuOcw+Pjw0h7y0U3/l7pfwuiDq2Ua3evUVAkDj86v/Mn+UuFn20MWA04YyZ4huJcOA==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.45.0.tgz", + "integrity": "sha512-rSR81pniNECvd3Zr1sq0VqYL7H+02dT723sEexpoweWi9mMklwLoapHTkwK6nAtxhjXNagRkT7l2GWuripXD7Q==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/package.json b/package.json index 78a154e6af25d3..44416be24600c7 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@vscode/native-watchdog": "^1.4.6", "@vscode/os-proxy-resolver": "^0.4.0", "@vscode/policy-watcher": "^1.4.0", - "@vscode/proxy-agent": "^0.44.0", + "@vscode/proxy-agent": "^0.45.0", "@vscode/ripgrep-universal": "^1.18.0", "@vscode/sandbox-runtime": "0.0.1", "@vscode/spdlog": "^0.15.8", diff --git a/remote/package-lock.json b/remote/package-lock.json index a61dff0c49c323..74bee0b2958b44 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -19,7 +19,7 @@ "@vscode/fs-copyfile": "2.0.0", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", - "@vscode/proxy-agent": "^0.44.0", + "@vscode/proxy-agent": "^0.45.0", "@vscode/ripgrep-universal": "^1.18.0", "@vscode/sandbox-runtime": "0.0.1", "@vscode/spdlog": "^0.15.8", @@ -910,9 +910,9 @@ "license": "MIT" }, "node_modules/@vscode/proxy-agent": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.44.0.tgz", - "integrity": "sha512-1vv0uJrIGxS89C+0gPmgNuOcw+Pjw0h7y0U3/l7pfwuiDq2Ua3evUVAkDj86v/Mn+UuFn20MWA04YyZ4huJcOA==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.45.0.tgz", + "integrity": "sha512-rSR81pniNECvd3Zr1sq0VqYL7H+02dT723sEexpoweWi9mMklwLoapHTkwK6nAtxhjXNagRkT7l2GWuripXD7Q==", "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", diff --git a/remote/package.json b/remote/package.json index af7d55252059c3..0ff984f53c144a 100644 --- a/remote/package.json +++ b/remote/package.json @@ -14,7 +14,7 @@ "@vscode/fs-copyfile": "2.0.0", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", - "@vscode/proxy-agent": "^0.44.0", + "@vscode/proxy-agent": "^0.45.0", "@vscode/ripgrep-universal": "^1.18.0", "@vscode/sandbox-runtime": "0.0.1", "@vscode/spdlog": "^0.15.8", From d2c4989a7dcdf19348edaecfa1f11af993b832cd Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Wed, 2 Sep 2026 10:55:01 -0700 Subject: [PATCH 05/33] Revert mouse Back handling in the Chat view (#334052) Revert "Chat: Use mouse Back to return to agent sessions" This reverts commit c178d46ab59196bf7ff3c21aa5118b7f43a77149. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../widgetHosts/viewPane/chatViewPane.ts | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts index f8dca2fd24b90f..1cd2a5eab7c8f7 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts @@ -61,7 +61,6 @@ import { ACTION_ID_NEW_CHAT } from '../../actions/chatActions.js'; import { ChatWidget, layoutChatWidgetForInputHeight } from '../../widget/chatWidget.js'; import { ChatViewWelcomeController, IViewWelcomeDelegate } from '../../viewsWelcome/chatViewWelcomeController.js'; import { IChatViewsWelcomeDescriptor } from '../../viewsWelcome/chatViewsWelcome.js'; -import { MOUSE_BACK_FORWARD_NAVIGATION_SETTING } from '../../../../../services/history/common/history.js'; import { IWorkbenchLayoutService, LayoutSettings, Position } from '../../../../../services/layout/browser/layoutService.js'; import { AgentSessionsViewerOrientation, AgentSessionsViewerPosition } from '../../agentSessions/agentSessions.js'; import { IProgressService } from '../../../../../../platform/progress/common/progress.js'; @@ -369,8 +368,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { // Controls wrapper — sessions + chat live inside here const controlsWrapper = append(parent, $('.voice-agent-controls-wrapper')); this.createControls(controlsWrapper); - const workbenchContainer = this.layoutService.getContainer(getWindow(parent)); - this._register(addDisposableListener(workbenchContainer, EventType.MOUSE_DOWN, event => this.handleMouseBackNavigation(event), true)); // Voice bar — hidden by default, voice is activated via mic button in toolbar. // The widget is still created for PTT keybinding support and session binding. @@ -395,40 +392,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { this.applyModel(); } - private async handleMouseBackNavigation(event: MouseEvent): Promise { - if ( - event.button !== 3 || - this.sessionsViewerOrientation !== AgentSessionsViewerOrientation.Stacked || - this.sessionsViewerVisible || - this._sessionsListSuppressionCount > 0 || - this.welcomeController?.isShowingWelcome.get() - ) { - return; - } - - const viewModel = this._widget.viewModel; - if (!viewModel || (this._widget.isEmpty() && !viewModel.model.title)) { - return; - } - - if ( - !this.configurationService.getValue(MOUSE_BACK_FORWARD_NAVIGATION_SETTING) || - !this.configurationService.getValue(ChatConfiguration.ChatViewSessionsEnabled) - ) { - return; - } - - const activeElement = getWindow(this._widget.domNode).document.activeElement; - if (!activeElement || !this._widget.domNode.contains(activeElement)) { - return; - } - - EventHelper.stop(event, true); - event.stopImmediatePropagation(); - await this.clear(); - this.focusSessions(); - } - private createControls(parent: HTMLElement): void { // Sessions Control From 2583f029c67ae95c939d3dca39764e344cac4bd7 Mon Sep 17 00:00:00 2001 From: roblourens Date: Wed, 2 Sep 2026 10:59:32 -0700 Subject: [PATCH 06/33] Agent Host: Expand E2E coverage for recent changes (#333922) * test: expand recent Agent Host E2E coverage Cover automation, detached worktree, server-tool, session persistence, authentication, and changeset behavior added in recent Agent Host changes. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: normalize detached worktree paths Compare worktree paths through URI comparison keys so Windows slash and path-case normalization do not cause false failures. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: validate detached worktree location Use the shared repository worktree-location predicate instead of assuming the checkout is an immediate child of the worktree root. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: keep worktree assertions portable Assert observable detached-worktree lifecycle behavior without pinning a platform-specific checkout layout. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: require cross-platform Agent Host E2E validation Document Azure CI acceptance, live failure triage, targeted reruns, and repeat validation for known flake surfaces. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: keep Agent Host validation scoped Keep the cross-platform E2E acceptance workflow in the Agent Host E2E skill and restore the general Azure Pipelines skill. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: address Agent Host E2E review feedback Seed the rename test before its provider turn, re-record Codex on the manual rename path, and condense the detached-worktree grace-period explanation. (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/agent-host-e2e-tests/SKILL.md | 17 +- ...nges-advertises-pull-request-creation.yaml | 12 + ...tored-session-survives-a-host-restart.yaml | 12 + ...nd-removes-its-pull-request-operation.yaml | 12 + ...-records-a-reference-in-session-state.yaml | 43 + ...nce-rejects-a-session-management-link.yaml | 43 + ...emove-round-trip-a-recorded-reference.yaml | 209 + ...name-chat-renames-the-chat-it-runs-in.yaml | 40 + ...nges-advertises-pull-request-creation.yaml | 12 + ...tored-session-survives-a-host-restart.yaml | 12 + ...nd-removes-its-pull-request-operation.yaml | 12 + ...-records-a-reference-in-session-state.yaml | 47 + ...nce-rejects-a-session-management-link.yaml | 47 + ...emove-round-trip-a-recorded-reference.yaml | 221 + ...name-chat-renames-the-chat-it-runs-in.yaml | 39 + ...nges-advertises-pull-request-creation.yaml | 12 + ...tored-session-survives-a-host-restart.yaml | 12 + ...nd-removes-its-pull-request-operation.yaml | 12 + ...-records-a-reference-in-session-state.yaml | 43 + ...nce-rejects-a-session-management-link.yaml | 44 + ...emove-round-trip-a-recorded-reference.yaml | 209 + ...name-chat-renames-the-chat-it-runs-in.yaml | 39 + .../node/e2e/coverage/protocol-surface.json | 22 +- .../test/node/e2e/coverage/summary.json | 3842 +++++++++++------ .../node/e2e/suites/agentHostE2ESuites.ts | 4 + .../test/node/e2e/suites/automationsSuite.ts | 385 ++ .../test/node/e2e/suites/changesetSuite.ts | 99 + .../node/e2e/suites/detachedWorktreeSuite.ts | 305 ++ .../test/node/e2e/suites/hostFeaturesSuite.ts | 24 +- .../test/node/e2e/suites/serverToolsSuite.ts | 158 +- .../e2e/suites/sessionPersistenceSuite.ts | 40 +- 31 files changed, 4582 insertions(+), 1446 deletions(-) create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-a-github-remote-with-changes-advertises-pull-request-creation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-archiving-a-never-restored-session-survives-a-host-restart.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-a-github-remote-with-changes-advertises-pull-request-creation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-archiving-a-never-restored-session-survives-a-host-restart.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-a-github-remote-with-changes-advertises-pull-request-creation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-archiving-a-never-restored-session-survives-a-host-restart.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml create mode 100644 src/vs/platform/agentHost/test/node/e2e/suites/automationsSuite.ts create mode 100644 src/vs/platform/agentHost/test/node/e2e/suites/detachedWorktreeSuite.ts diff --git a/.github/skills/agent-host-e2e-tests/SKILL.md b/.github/skills/agent-host-e2e-tests/SKILL.md index b4b31db0f8eea8..8e221cc2e18af2 100644 --- a/.github/skills/agent-host-e2e-tests/SKILL.md +++ b/.github/skills/agent-host-e2e-tests/SKILL.md @@ -1,6 +1,6 @@ --- name: agent-host-e2e-tests -description: Use when writing, recording, updating, or troubleshooting the agent host end-to-end tests under src/vs/platform/agentHost/test/node/e2e (black-box tests that drive the whole agent host over the AHP protocol, using a CapiReplayProxy record/replay system for Claude/Copilot/Codex). Covers adding a cross-provider test, re-recording fixtures after an SDK bump, gating non-deterministic or platform-specific tests, and diagnosing replay cache misses. +description: Use when writing, recording, updating, validating, or troubleshooting the agent host end-to-end tests under src/vs/platform/agentHost/test/node/e2e (black-box tests that drive the whole agent host over the AHP protocol, using a CapiReplayProxy record/replay system for Claude/Copilot/Codex). Covers adding a cross-provider test, re-recording fixtures after an SDK bump, cross-platform Azure validation, gating non-deterministic or platform-specific tests, and diagnosing replay cache misses. --- # Agent host end-to-end tests @@ -27,6 +27,8 @@ It documents the mental model, the fixture format, every config flag, and a symp 2. Keep the prompt minimal and deterministic (fewer model turns → smaller, more robust fixtures). 3. Record fixtures for every enabled provider (Workflow B). Host-only tests need no per-test recording: the shared empty fixture remains strict and fails on any model request. 4. **Review the diff** (Workflow B step 3), then run the test in plain replay mode to confirm it's green, then commit the test + fixtures together. +5. Run the full deterministic suite and coverage workflow described in the E2E README. +6. Open or update a draft PR, then complete the cross-platform Azure validation in Workflow D before considering the tests ready to merge. Provider-specific assertions go in that provider's `*.integrationTest.ts` after the `defineAgentHostE2ETests(config)` call. @@ -57,6 +59,19 @@ Real-time streaming, mid-turn aborts, and POSIX-specific local execution (shell Always add a comment explaining *why* the gate exists. Also add or update the corresponding entry in `e2e/KNOWN_ISSUES.md`. When the variant is enabled again, remove or update the entry in the same change. +## Workflow D — Cross-platform Azure validation + +New Agent Host E2E tests are not ready to merge after local replay alone. Push the branch, open or update a draft PR, then use the `azure-pipelines` skill to validate the real packaged Electron integration-test path. + +1. Queue VS Code pipeline definition `111` with `VSCODE_BUILD_TYPE=CI`; enable Windows, Linux, and macOS x64 while disabling publishing, release, Web, ARM, Alpine, and Snap artifacts. The `azure-pipelines` skill contains the canonical command. +2. Monitor jobs as they finish. Inspect a failed platform's Electron integration-test task immediately rather than waiting for unrelated stages to complete. +3. Treat the Agent Host E2E result as accepted only when the Electron integration tests succeed on Windows, Linux, and macOS. +4. Rerun an apparently unrelated or pre-existing failure in isolation before attributing it to the PR. +5. After a platform-specific fix, rerun at least that platform. Rerun all three platforms when the fix can affect shared behavior, provider fixtures, process lifecycle, or cross-platform paths. +6. Cancel obsolete builds after pushing a replacement commit. + +For additions involving timing, filesystem watching, process lifecycle, worktrees, reconnect/restart, or other known flake surfaces, require **two clean executions of every new test on each supported platform** before merge. A full three-platform build plus a targeted second build is sufficient when the second build runs the relevant tests on all affected platforms. + ## Verifying & troubleshooting - Run a single provider in replay: `./scripts/test-integration.sh --run ` (no env var). diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-a-github-remote-with-changes-advertises-pull-request-creation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-a-github-remote-with-changes-advertises-pull-request-creation.yaml new file mode 100644 index 00000000000000..7e545880af416c --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-a-github-remote-with-changes-advertises-pull-request-creation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-archiving-a-never-restored-session-survives-a-host-restart.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-archiving-a-never-restored-session-survives-a-host-restart.yaml new file mode 100644 index 00000000000000..710a5f7715188a --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-archiving-a-never-restored-session-survives-a-host-restart.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Reply exactly "READY". + response: + content: READY + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml new file mode 100644 index 00000000000000..7e545880af416c --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml new file mode 100644 index 00000000000000..c68f9fba9dc4c3 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml @@ -0,0 +1,43 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + response: + content: + - type: tool_use + id: toolcall_0 + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Agent Host guide — https://example.com/agent-host' + response: + content: recorded + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml new file mode 100644 index 00000000000000..a5f8e32009413a --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml @@ -0,0 +1,43 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + response: + content: + - type: tool_use + id: toolcall_0 + name: mcp__host__add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Invalid add_artifact_or_reference input: sessions and chats created with session-management tools must not be recorded as artifacts or references.' + response: + content: rejected + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml new file mode 100644 index 00000000000000..68eda740449a2c --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml @@ -0,0 +1,209 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + response: + content: + - type: tool_use + id: toolcall_0 + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: added + stopReason: end_turn + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + response: + content: + - type: tool_use + id: toolcall_1 + name: mcp__host__list_artifacts_and_references + input: {} + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: mcp__host__list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + response: + content: listed + stopReason: end_turn + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: mcp__host__list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + response: + content: + - type: tool_use + id: toolcall_2 + name: mcp__host__remove_artifact_or_reference + input: + id: ${uuid_0} + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: mcp__host__add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: mcp__host__list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + - role: assistant + content: + - type: tool_use + name: mcp__host__remove_artifact_or_reference + input: + id: ${uuid_0} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_2 + content: 'Removed reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: removed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml new file mode 100644 index 00000000000000..b019114c44a4ca --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/claude-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml @@ -0,0 +1,40 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + response: + content: + - type: tool_use + id: toolcall_0 + name: mcp__host__rename_chat + input: + title: Coverage audit + automatic: false + stopReason: tool_use + - request: + model: claude-opus-5 + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + - role: assistant + content: + - type: thinking + - type: tool_use + name: mcp__host__rename_chat + input: + title: Coverage audit + automatic: false + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: Renamed chat to "Coverage audit". + response: + content: renamed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-a-github-remote-with-changes-advertises-pull-request-creation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-a-github-remote-with-changes-advertises-pull-request-creation.yaml new file mode 100644 index 00000000000000..93eb80ff51fb0a --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-a-github-remote-with-changes-advertises-pull-request-creation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-archiving-a-never-restored-session-survives-a-host-restart.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-archiving-a-never-restored-session-survives-a-host-restart.yaml new file mode 100644 index 00000000000000..25d126cafb6c23 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-archiving-a-never-restored-session-survives-a-host-restart.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Reply exactly "READY". + response: + content: READY + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml new file mode 100644 index 00000000000000..93eb80ff51fb0a --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml new file mode 100644 index 00000000000000..696583956cfc7c --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml @@ -0,0 +1,47 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + response: + content: + - type: text + text: Got it — I’ll record that reference now. + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + - role: assistant + content: Got it — I’ll record that reference now. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Agent Host guide — https://example.com/agent-host' + response: + content: recorded + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml new file mode 100644 index 00000000000000..d4ffe47aa466c8 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml @@ -0,0 +1,47 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + response: + content: + - type: text + text: I’ll record that artifact now, then return the requested response. + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + - role: assistant + content: I’ll record that artifact now, then return the requested response. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Server tool add_artifact_or_reference failed: Invalid add_artifact_or_reference input: sessions and chats created with session-management tools must not be recorded as artifacts or references.' + response: + content: rejected + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml new file mode 100644 index 00000000000000..b02c9b371f3222 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml @@ -0,0 +1,221 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + response: + content: + - type: text + text: I’ll add that reference now, then confirm. + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: I’ll add that reference now, then confirm. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: added + stopReason: end_turn + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: I’ll add that reference now, then confirm. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + response: + content: + - type: tool_use + id: toolcall_1 + name: list_artifacts_and_references + input: {} + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: I’ll add that reference now, then confirm. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + response: + content: listed + stopReason: end_turn + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: I’ll add that reference now, then confirm. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + response: + content: + - type: tool_use + id: toolcall_2 + name: remove_artifact_or_reference + input: + id: ${uuid_0} + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: I’ll add that reference now, then confirm. + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + - role: assistant + content: + - type: tool_use + name: remove_artifact_or_reference + input: + id: ${uuid_0} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_2 + content: 'Removed reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: removed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml new file mode 100644 index 00000000000000..ba17e83c76ad79 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/codex-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml @@ -0,0 +1,39 @@ +version: 1 +dialect: responses +exchanges: + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + response: + content: + - type: tool_use + id: toolcall_0 + name: rename_chat + input: + title: Coverage audit + automatic: false + stopReason: tool_use + - request: + model: gpt-5.3-codex + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + - role: assistant + content: + - type: tool_use + name: rename_chat + input: + title: Coverage audit + automatic: false + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: Renamed chat to "Coverage audit". + response: + content: renamed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-a-github-remote-with-changes-advertises-pull-request-creation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-a-github-remote-with-changes-advertises-pull-request-creation.yaml new file mode 100644 index 00000000000000..8f0771a3980287 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-a-github-remote-with-changes-advertises-pull-request-creation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-archiving-a-never-restored-session-survives-a-host-restart.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-archiving-a-never-restored-session-survives-a-host-restart.yaml new file mode 100644 index 00000000000000..7dfb7f7b74a81e --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-archiving-a-never-restored-session-survives-a-host-restart.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Reply exactly "READY". + response: + content: READY + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml new file mode 100644 index 00000000000000..8f0771a3980287 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-enabling-agent-merge-adds-and-removes-its-pull-request-operation.yaml @@ -0,0 +1,12 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Reply exactly "ready". + response: + content: ready + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml new file mode 100644 index 00000000000000..b8b08ae422ac36 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-records-a-reference-in-session-state.yaml @@ -0,0 +1,43 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + response: + content: + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Agent Host guide + isArtifact: false + link: https://example.com/agent-host + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Agent Host guide — https://example.com/agent-host' + response: + content: recorded + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml new file mode 100644 index 00000000000000..b220e05a9986dd --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-add-artifact-or-reference-rejects-a-session-management-link.yaml @@ -0,0 +1,44 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + response: + content: + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected". + - role: assistant + content: + - type: thinking + - type: tool_use + name: add_artifact_or_reference + input: + type: resource + label: Spawned session + isArtifact: true + uri: agent-host-session://copilot/spawned + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Invalid add_artifact_or_reference input: sessions and chats created with session-management tools must not be recorded as artifacts or references.' + response: + content: rejected + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml new file mode 100644 index 00000000000000..4debeeeb7cf3c8 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-list-and-remove-round-trip-a-recorded-reference.yaml @@ -0,0 +1,209 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + response: + content: + - type: tool_use + id: toolcall_0 + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: added + stopReason: end_turn + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + response: + content: + - type: tool_use + id: toolcall_1 + name: list_artifacts_and_references + input: {} + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + response: + content: listed + stopReason: end_turn + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + response: + content: + - type: tool_use + id: toolcall_2 + name: remove_artifact_or_reference + input: + id: ${uuid_0} + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added". + - role: assistant + content: + - type: tool_use + name: add_artifact_or_reference + input: + type: website + label: Design notes + isArtifact: false + link: https://example.com/design + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: 'Added reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + - role: assistant + content: added + - role: user + content: Call list_artifacts_and_references exactly once, then reply with exactly "listed". + - role: assistant + content: + - type: tool_use + name: list_artifacts_and_references + input: {} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_1 + content: ${uuid_0} (website, reference) Design notes — https://example.com/design + - role: assistant + content: listed + - role: user + content: Call remove_artifact_or_reference exactly once with id "${uuid_0}", then reply with exactly "removed". + - role: assistant + content: + - type: tool_use + name: remove_artifact_or_reference + input: + id: ${uuid_0} + - role: user + content: + - type: tool_result + tool_use_id: toolcall_2 + content: 'Removed reference: ${uuid_0} (website, reference) Design notes — https://example.com/design' + response: + content: removed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml new file mode 100644 index 00000000000000..0680aef842d5e9 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-server-tool-rename-chat-renames-the-chat-it-runs-in.yaml @@ -0,0 +1,39 @@ +version: 1 +dialect: anthropic +exchanges: + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + response: + content: + - type: tool_use + id: toolcall_0 + name: rename_chat + input: + title: Coverage audit + automatic: false + stopReason: tool_use + - request: + model: claude-sonnet-5 + system: ${system} + messages: + - role: user + content: Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed". + - role: assistant + content: + - type: tool_use + name: rename_chat + input: + title: Coverage audit + automatic: false + - role: user + content: + - type: tool_result + tool_use_id: toolcall_0 + content: Renamed chat to "Coverage audit". + response: + content: renamed + stopReason: end_turn diff --git a/src/vs/platform/agentHost/test/node/e2e/coverage/protocol-surface.json b/src/vs/platform/agentHost/test/node/e2e/coverage/protocol-surface.json index 534c5451f6c605..4e8654d0091024 100644 --- a/src/vs/platform/agentHost/test/node/e2e/coverage/protocol-surface.json +++ b/src/vs/platform/agentHost/test/node/e2e/coverage/protocol-surface.json @@ -5,35 +5,28 @@ "note": "A symbol is \"covered\" when an E2E test sends or receives it; this does not measure how deeply its semantics are asserted." }, "commands": { - "covered": 29, + "covered": 31, "total": 32, - "percentage": 90.62, + "percentage": 96.87, "uncovered": [ - "fetchAutomationRuns", - "listAutomationTriggerDefinitions", "runAutomation" ] }, "notifications": { - "covered": 4, + "covered": 5, "total": 8, - "percentage": 50, + "percentage": 62.5, "uncovered": [ - "auth/required", "otlp/exportMetrics", "otlp/exportTraces", "root/progress" ] }, "actions": { - "covered": 76, - "total": 95, - "percentage": 80, + "covered": 80, + "total": 96, + "percentage": 83.33, "uncovered": [ - "automation/createRequested", - "automation/removed", - "automation/set", - "automation/updateRequested", "automationRun/cancelRequested", "automationRun/lifecycleChanged", "automationRun/primarySessionChanged", @@ -45,6 +38,7 @@ "chat/reasoning", "chat/toolCallAuthRequired", "chat/toolCallAuthResolved", + "chat/turnResume", "session/activityChanged", "session/defaultChatChanged", "session/workingDirectoryReplaced", diff --git a/src/vs/platform/agentHost/test/node/e2e/coverage/summary.json b/src/vs/platform/agentHost/test/node/e2e/coverage/summary.json index e04e4b8fdd316e..9fff945aa2561b 100644 --- a/src/vs/platform/agentHost/test/node/e2e/coverage/summary.json +++ b/src/vs/platform/agentHost/test/node/e2e/coverage/summary.json @@ -15,32 +15,32 @@ }, "total": { "statements": { - "covered": 93666, - "total": 124793, - "percentage": 75.05 + "covered": 101868, + "total": 135034, + "percentage": 75.43 }, "branches": { - "covered": 10985, - "total": 16386, - "percentage": 67.03 + "covered": 12677, + "total": 18587, + "percentage": 68.2 }, "functions": { - "covered": 3534, - "total": 5026, - "percentage": 70.31 + "covered": 3908, + "total": 5528, + "percentage": 70.69 }, "lines": { - "covered": 93666, - "total": 124793, - "percentage": 75.05 + "covered": 101868, + "total": 135034, + "percentage": 75.43 } }, "files": { "src/vs/platform/agentHost/common/agent.ts": { "statements": { - "covered": 1227, - "total": 1246, - "percentage": 98.47 + "covered": 1268, + "total": 1287, + "percentage": 98.52 }, "branches": { "covered": 27, @@ -53,9 +53,9 @@ "percentage": 66.66 }, "lines": { - "covered": 1227, - "total": 1246, - "percentage": 98.47 + "covered": 1268, + "total": 1287, + "percentage": 98.52 } }, "src/vs/platform/agentHost/common/agentClientUri.ts": { @@ -126,8 +126,8 @@ }, "src/vs/platform/agentHost/common/agentHostChangesetOperationService.ts": { "statements": { - "covered": 132, - "total": 132, + "covered": 161, + "total": 161, "percentage": 100 }, "branches": { @@ -141,15 +141,15 @@ "percentage": 100 }, "lines": { - "covered": 132, - "total": 132, + "covered": 161, + "total": 161, "percentage": 100 } }, "src/vs/platform/agentHost/common/agentHostChangesetService.ts": { "statements": { - "covered": 306, - "total": 306, + "covered": 295, + "total": 295, "percentage": 100 }, "branches": { @@ -163,15 +163,15 @@ "percentage": 100 }, "lines": { - "covered": 306, - "total": 306, + "covered": 295, + "total": 295, "percentage": 100 } }, "src/vs/platform/agentHost/common/agentHostChangesetSubscriptionService.ts": { "statements": { - "covered": 38, - "total": 38, + "covered": 47, + "total": 47, "percentage": 100 }, "branches": { @@ -185,9 +185,31 @@ "percentage": 100 }, "lines": { - "covered": 38, - "total": 38, + "covered": 47, + "total": 47, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/common/agentHostChatContributionsService.ts": { + "statements": { + "covered": 322, + "total": 324, + "percentage": 99.38 + }, + "branches": { + "covered": 1, + "total": 1, "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 2, + "percentage": 50 + }, + "lines": { + "covered": 322, + "total": 324, + "percentage": 99.38 } }, "src/vs/platform/agentHost/common/agentHostCheckpointService.ts": { @@ -280,9 +302,9 @@ }, "src/vs/platform/agentHost/common/agentHostConversationContext.ts": { "statements": { - "covered": 82, - "total": 98, - "percentage": 83.67 + "covered": 85, + "total": 109, + "percentage": 77.98 }, "branches": { "covered": 4, @@ -291,13 +313,13 @@ }, "functions": { "covered": 2, - "total": 3, - "percentage": 66.66 + "total": 4, + "percentage": 50 }, "lines": { - "covered": 82, - "total": 98, - "percentage": 83.67 + "covered": 85, + "total": 109, + "percentage": 77.98 } }, "src/vs/platform/agentHost/common/agentHostCustomizationConfig.ts": { @@ -324,31 +346,31 @@ }, "src/vs/platform/agentHost/common/agentHostExtensionProtocol.ts": { "statements": { - "covered": 30, - "total": 30, - "percentage": 100 + "covered": 88, + "total": 94, + "percentage": 93.61 }, "branches": { - "covered": 0, - "total": 0, + "covered": 1, + "total": 1, "percentage": 100 }, "functions": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 1, + "total": 3, + "percentage": 33.33 }, "lines": { - "covered": 30, - "total": 30, - "percentage": 100 + "covered": 88, + "total": 94, + "percentage": 93.61 } }, "src/vs/platform/agentHost/common/agentHostFileSystemProvider.ts": { "statements": { - "covered": 403, - "total": 649, - "percentage": 62.09 + "covered": 421, + "total": 667, + "percentage": 63.11 }, "branches": { "covered": 39, @@ -356,14 +378,14 @@ "percentage": 68.42 }, "functions": { - "covered": 16, - "total": 24, - "percentage": 66.66 + "covered": 17, + "total": 25, + "percentage": 68 }, "lines": { - "covered": 403, - "total": 649, - "percentage": 62.09 + "covered": 421, + "total": 667, + "percentage": 63.11 } }, "src/vs/platform/agentHost/common/agentHostFileSystemService.ts": { @@ -390,9 +412,9 @@ }, "src/vs/platform/agentHost/common/agentHostGitService.ts": { "statements": { - "covered": 464, - "total": 477, - "percentage": 97.27 + "covered": 466, + "total": 479, + "percentage": 97.28 }, "branches": { "covered": 25, @@ -405,15 +427,15 @@ "percentage": 85.71 }, "lines": { - "covered": 464, - "total": 477, - "percentage": 97.27 + "covered": 466, + "total": 479, + "percentage": 97.28 } }, "src/vs/platform/agentHost/common/agentHostGitStateService.ts": { "statements": { - "covered": 63, - "total": 63, + "covered": 60, + "total": 60, "percentage": 100 }, "branches": { @@ -427,8 +449,8 @@ "percentage": 100 }, "lines": { - "covered": 63, - "total": 63, + "covered": 60, + "total": 60, "percentage": 100 } }, @@ -456,9 +478,9 @@ }, "src/vs/platform/agentHost/common/agentHostManagedSettings.ts": { "statements": { - "covered": 186, - "total": 306, - "percentage": 60.78 + "covered": 209, + "total": 341, + "percentage": 61.29 }, "branches": { "covered": 4, @@ -467,20 +489,20 @@ }, "functions": { "covered": 2, - "total": 10, - "percentage": 20 + "total": 11, + "percentage": 18.18 }, "lines": { - "covered": 186, - "total": 306, - "percentage": 60.78 + "covered": 209, + "total": 341, + "percentage": 61.29 } }, "src/vs/platform/agentHost/common/agentHostResourceService.ts": { "statements": { - "covered": 156, - "total": 161, - "percentage": 96.89 + "covered": 161, + "total": 166, + "percentage": 96.98 }, "branches": { "covered": 2, @@ -493,9 +515,9 @@ "percentage": 0 }, "lines": { - "covered": 156, - "total": 161, - "percentage": 96.89 + "covered": 161, + "total": 166, + "percentage": 96.98 } }, "src/vs/platform/agentHost/common/agentHostReviewService.ts": { @@ -522,14 +544,14 @@ }, "src/vs/platform/agentHost/common/agentHostSchema.ts": { "statements": { - "covered": 790, - "total": 881, - "percentage": 89.67 + "covered": 802, + "total": 891, + "percentage": 90.01 }, "branches": { - "covered": 51, - "total": 65, - "percentage": 78.46 + "covered": 53, + "total": 66, + "percentage": 80.3 }, "functions": { "covered": 15, @@ -537,9 +559,9 @@ "percentage": 68.18 }, "lines": { - "covered": 790, - "total": 881, - "percentage": 89.67 + "covered": 802, + "total": 891, + "percentage": 90.01 } }, "src/vs/platform/agentHost/common/agentHostSlashCommand.ts": { @@ -564,16 +586,38 @@ "percentage": 100 } }, + "src/vs/platform/agentHost/common/agentHostSubscriptionService.ts": { + "statements": { + "covered": 39, + "total": 39, + "percentage": 100 + }, + "branches": { + "covered": 7, + "total": 7, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "lines": { + "covered": 39, + "total": 39, + "percentage": 100 + } + }, "src/vs/platform/agentHost/common/agentHostTelemetry.ts": { "statements": { - "covered": 110, - "total": 141, - "percentage": 78.01 + "covered": 119, + "total": 150, + "percentage": 79.33 }, "branches": { "covered": 10, - "total": 31, - "percentage": 32.25 + "total": 32, + "percentage": 31.25 }, "functions": { "covered": 7, @@ -581,9 +625,9 @@ "percentage": 77.77 }, "lines": { - "covered": 110, - "total": 141, - "percentage": 78.01 + "covered": 119, + "total": 150, + "percentage": 79.33 } }, "src/vs/platform/agentHost/common/agentHostTelemetryEnv.ts": { @@ -610,24 +654,24 @@ }, "src/vs/platform/agentHost/common/agentHostUri.ts": { "statements": { - "covered": 142, - "total": 222, - "percentage": 63.96 + "covered": 186, + "total": 275, + "percentage": 67.63 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 1, + "total": 2, + "percentage": 50 }, "functions": { - "covered": 0, - "total": 8, - "percentage": 0 + "covered": 1, + "total": 12, + "percentage": 8.33 }, "lines": { - "covered": 142, - "total": 222, - "percentage": 63.96 + "covered": 186, + "total": 275, + "percentage": 67.63 } }, "src/vs/platform/agentHost/common/agentHostWorkingDirectories.ts": { @@ -654,36 +698,58 @@ }, "src/vs/platform/agentHost/common/agentMerge.ts": { "statements": { - "covered": 255, - "total": 472, - "percentage": 54.02 + "covered": 375, + "total": 678, + "percentage": 55.3 }, "branches": { + "covered": 4, + "total": 35, + "percentage": 11.42 + }, + "functions": { "covered": 3, - "total": 27, - "percentage": 11.11 + "total": 33, + "percentage": 9.09 + }, + "lines": { + "covered": 375, + "total": 678, + "percentage": 55.3 + } + }, + "src/vs/platform/agentHost/common/agentMergePrompt.ts": { + "statements": { + "covered": 113, + "total": 338, + "percentage": 33.43 + }, + "branches": { + "covered": 0, + "total": 0, + "percentage": 100 }, "functions": { - "covered": 2, - "total": 16, - "percentage": 12.5 + "covered": 0, + "total": 18, + "percentage": 0 }, "lines": { - "covered": 255, - "total": 472, - "percentage": 54.02 + "covered": 113, + "total": 338, + "percentage": 33.43 } }, "src/vs/platform/agentHost/common/agentModelByokMeta.ts": { "statements": { - "covered": 38, + "covered": 39, "total": 41, - "percentage": 92.68 + "percentage": 95.12 }, "branches": { - "covered": 1, - "total": 3, - "percentage": 33.33 + "covered": 4, + "total": 5, + "percentage": 80 }, "functions": { "covered": 1, @@ -691,16 +757,38 @@ "percentage": 50 }, "lines": { - "covered": 38, + "covered": 39, "total": 41, - "percentage": 92.68 + "percentage": 95.12 + } + }, + "src/vs/platform/agentHost/common/agentModelNotices.ts": { + "statements": { + "covered": 38, + "total": 64, + "percentage": 59.37 + }, + "branches": { + "covered": 1, + "total": 11, + "percentage": 9.09 + }, + "functions": { + "covered": 1, + "total": 3, + "percentage": 33.33 + }, + "lines": { + "covered": 38, + "total": 64, + "percentage": 59.37 } }, "src/vs/platform/agentHost/common/agentModelPricing.ts": { "statements": { - "covered": 178, - "total": 281, - "percentage": 63.34 + "covered": 181, + "total": 288, + "percentage": 62.84 }, "branches": { "covered": 4, @@ -713,9 +801,9 @@ "percentage": 37.5 }, "lines": { - "covered": 178, - "total": 281, - "percentage": 63.34 + "covered": 181, + "total": 288, + "percentage": 62.84 } }, "src/vs/platform/agentHost/common/agentModelSource.ts": { @@ -786,9 +874,9 @@ }, "src/vs/platform/agentHost/common/agentService.ts": { "statements": { - "covered": 1070, - "total": 1237, - "percentage": 86.49 + "covered": 1094, + "total": 1262, + "percentage": 86.68 }, "branches": { "covered": 9, @@ -801,9 +889,9 @@ "percentage": 11.11 }, "lines": { - "covered": 1070, - "total": 1237, - "percentage": 86.49 + "covered": 1094, + "total": 1262, + "percentage": 86.68 } }, "src/vs/platform/agentHost/common/agentTelemetryCorrelation.ts": { @@ -835,9 +923,9 @@ "percentage": 85.82 }, "branches": { - "covered": 36, - "total": 45, - "percentage": 80 + "covered": 34, + "total": 43, + "percentage": 79.06 }, "functions": { "covered": 12, @@ -872,58 +960,102 @@ "percentage": 95.83 } }, - "src/vs/platform/agentHost/common/changesetUri.ts": { + "src/vs/platform/agentHost/common/autoModeTiers.ts": { "statements": { - "covered": 320, - "total": 370, - "percentage": 86.48 + "covered": 29, + "total": 45, + "percentage": 64.44 }, "branches": { - "covered": 49, - "total": 61, - "percentage": 80.32 + "covered": 0, + "total": 0, + "percentage": 100 }, "functions": { - "covered": 19, - "total": 25, - "percentage": 76 + "covered": 0, + "total": 3, + "percentage": 0 }, "lines": { - "covered": 320, - "total": 370, - "percentage": 86.48 + "covered": 29, + "total": 45, + "percentage": 64.44 } }, - "src/vs/platform/agentHost/common/claudeModelConfig.ts": { + "src/vs/platform/agentHost/common/automationMigration.ts": { "statements": { - "covered": 123, - "total": 126, - "percentage": 97.61 + "covered": 25, + "total": 29, + "percentage": 86.2 }, "branches": { - "covered": 5, - "total": 15, + "covered": 1, + "total": 3, "percentage": 33.33 }, "functions": { - "covered": 3, - "total": 4, - "percentage": 75 + "covered": 1, + "total": 1, + "percentage": 100 }, "lines": { - "covered": 123, - "total": 126, - "percentage": 97.61 + "covered": 25, + "total": 29, + "percentage": 86.2 } }, - "src/vs/platform/agentHost/common/claudeProviders.ts": { + "src/vs/platform/agentHost/common/changesetUri.ts": { "statements": { - "covered": 35, - "total": 35, - "percentage": 100 + "covered": 333, + "total": 388, + "percentage": 85.82 }, "branches": { - "covered": 0, + "covered": 50, + "total": 62, + "percentage": 80.64 + }, + "functions": { + "covered": 19, + "total": 26, + "percentage": 73.07 + }, + "lines": { + "covered": 333, + "total": 388, + "percentage": 85.82 + } + }, + "src/vs/platform/agentHost/common/claudeModelConfig.ts": { + "statements": { + "covered": 123, + "total": 126, + "percentage": 97.61 + }, + "branches": { + "covered": 5, + "total": 15, + "percentage": 33.33 + }, + "functions": { + "covered": 3, + "total": 4, + "percentage": 75 + }, + "lines": { + "covered": 123, + "total": 126, + "percentage": 97.61 + } + }, + "src/vs/platform/agentHost/common/claudeProviders.ts": { + "statements": { + "covered": 35, + "total": 35, + "percentage": 100 + }, + "branches": { + "covered": 0, "total": 0, "percentage": 100 }, @@ -962,9 +1094,9 @@ }, "src/vs/platform/agentHost/common/codexAccount.ts": { "statements": { - "covered": 28, - "total": 61, - "percentage": 45.9 + "covered": 49, + "total": 122, + "percentage": 40.16 }, "branches": { "covered": 0, @@ -973,13 +1105,13 @@ }, "functions": { "covered": 0, - "total": 1, + "total": 3, "percentage": 0 }, "lines": { - "covered": 28, - "total": 61, - "percentage": 45.9 + "covered": 49, + "total": 122, + "percentage": 40.16 } }, "src/vs/platform/agentHost/common/codexSessionConfigKeys.ts": { @@ -1011,9 +1143,9 @@ "percentage": 88.09 }, "branches": { - "covered": 20, - "total": 29, - "percentage": 68.96 + "covered": 19, + "total": 28, + "percentage": 67.85 }, "functions": { "covered": 4, @@ -1028,9 +1160,9 @@ }, "src/vs/platform/agentHost/common/copilotCliConfig.ts": { "statements": { - "covered": 224, - "total": 233, - "percentage": 96.13 + "covered": 253, + "total": 262, + "percentage": 96.56 }, "branches": { "covered": 7, @@ -1043,9 +1175,9 @@ "percentage": 66.66 }, "lines": { - "covered": 224, - "total": 233, - "percentage": 96.13 + "covered": 253, + "total": 262, + "percentage": 96.56 } }, "src/vs/platform/agentHost/common/copilotConfigSlashCommands.ts": { @@ -1160,14 +1292,14 @@ }, "src/vs/platform/agentHost/common/githubEndpoints.ts": { "statements": { - "covered": 106, + "covered": 120, "total": 130, - "percentage": 81.53 + "percentage": 92.3 }, "branches": { - "covered": 4, - "total": 14, - "percentage": 28.57 + "covered": 14, + "total": 23, + "percentage": 60.86 }, "functions": { "covered": 4, @@ -1175,53 +1307,9 @@ "percentage": 100 }, "lines": { - "covered": 106, + "covered": 120, "total": 130, - "percentage": 81.53 - } - }, - "src/vs/platform/agentHost/common/githubIssueReferences.ts": { - "statements": { - "covered": 55, - "total": 74, - "percentage": 74.32 - }, - "branches": { - "covered": 1, - "total": 3, - "percentage": 33.33 - }, - "functions": { - "covered": 1, - "total": 4, - "percentage": 25 - }, - "lines": { - "covered": 55, - "total": 74, - "percentage": 74.32 - } - }, - "src/vs/platform/agentHost/common/githubPullRequestReferences.ts": { - "statements": { - "covered": 38, - "total": 64, - "percentage": 59.37 - }, - "branches": { - "covered": 2, - "total": 5, - "percentage": 40 - }, - "functions": { - "covered": 2, - "total": 4, - "percentage": 50 - }, - "lines": { - "covered": 38, - "total": 64, - "percentage": 59.37 + "percentage": 92.3 } }, "src/vs/platform/agentHost/common/meta/agentChatInputRequestMeta.ts": { @@ -1248,9 +1336,9 @@ }, "src/vs/platform/agentHost/common/meta/agentChatSurfaceMeta.ts": { "statements": { - "covered": 69, - "total": 138, - "percentage": 50 + "covered": 70, + "total": 142, + "percentage": 49.29 }, "branches": { "covered": 3, @@ -1263,21 +1351,21 @@ "percentage": 40 }, "lines": { - "covered": 69, - "total": 138, - "percentage": 50 + "covered": 70, + "total": 142, + "percentage": 49.29 } }, "src/vs/platform/agentHost/common/meta/agentCompletionAttachmentMeta.ts": { "statements": { - "covered": 146, - "total": 219, - "percentage": 66.66 + "covered": 149, + "total": 225, + "percentage": 66.22 }, "branches": { "covered": 2, - "total": 7, - "percentage": 28.57 + "total": 8, + "percentage": 25 }, "functions": { "covered": 2, @@ -1285,9 +1373,9 @@ "percentage": 28.57 }, "lines": { - "covered": 146, - "total": 219, - "percentage": 66.66 + "covered": 149, + "total": 225, + "percentage": 66.22 } }, "src/vs/platform/agentHost/common/meta/agentCustomizationMeta.ts": { @@ -1312,6 +1400,28 @@ "percentage": 83.01 } }, + "src/vs/platform/agentHost/common/meta/agentDevContainerWorktreeMeta.ts": { + "statements": { + "covered": 25, + "total": 34, + "percentage": 73.52 + }, + "branches": { + "covered": 3, + "total": 7, + "percentage": 42.85 + }, + "functions": { + "covered": 2, + "total": 3, + "percentage": 66.66 + }, + "lines": { + "covered": 25, + "total": 34, + "percentage": 73.52 + } + }, "src/vs/platform/agentHost/common/meta/agentEphemeralSessionMeta.ts": { "statements": { "covered": 31, @@ -1358,24 +1468,24 @@ }, "src/vs/platform/agentHost/common/meta/agentFeedbackAnnotations.ts": { "statements": { - "covered": 183, - "total": 195, - "percentage": 93.84 + "covered": 199, + "total": 212, + "percentage": 93.86 }, "branches": { - "covered": 16, - "total": 29, - "percentage": 55.17 + "covered": 17, + "total": 35, + "percentage": 48.57 }, "functions": { - "covered": 7, - "total": 10, - "percentage": 70 + "covered": 8, + "total": 11, + "percentage": 72.72 }, "lines": { - "covered": 183, - "total": 195, - "percentage": 93.84 + "covered": 199, + "total": 212, + "percentage": 93.86 } }, "src/vs/platform/agentHost/common/meta/agentFeedbackAttachments.ts": { @@ -1400,11 +1510,11 @@ "percentage": 38.75 } }, - "src/vs/platform/agentHost/common/meta/agentMessageDelegationMeta.ts": { + "src/vs/platform/agentHost/common/meta/agentMergeMessageMeta.ts": { "statements": { - "covered": 20, - "total": 30, - "percentage": 66.66 + "covered": 23, + "total": 28, + "percentage": 82.14 }, "branches": { "covered": 0, @@ -1417,9 +1527,31 @@ "percentage": 0 }, "lines": { - "covered": 20, - "total": 30, - "percentage": 66.66 + "covered": 23, + "total": 28, + "percentage": 82.14 + } + }, + "src/vs/platform/agentHost/common/meta/agentMessageDelegationMeta.ts": { + "statements": { + "covered": 50, + "total": 54, + "percentage": 92.59 + }, + "branches": { + "covered": 14, + "total": 19, + "percentage": 73.68 + }, + "functions": { + "covered": 3, + "total": 3, + "percentage": 100 + }, + "lines": { + "covered": 50, + "total": 54, + "percentage": 92.59 } }, "src/vs/platform/agentHost/common/meta/agentSnapshotAttachmentMeta.ts": { @@ -1446,9 +1578,9 @@ }, "src/vs/platform/agentHost/common/meta/agentSystemNotificationMeta.ts": { "statements": { - "covered": 27, - "total": 38, - "percentage": 71.05 + "covered": 37, + "total": 49, + "percentage": 75.51 }, "branches": { "covered": 2, @@ -1461,9 +1593,9 @@ "percentage": 0 }, "lines": { - "covered": 27, - "total": 38, - "percentage": 71.05 + "covered": 37, + "total": 49, + "percentage": 75.51 } }, "src/vs/platform/agentHost/common/meta/agentToolCallMeta.ts": { @@ -1488,6 +1620,28 @@ "percentage": 84.81 } }, + "src/vs/platform/agentHost/common/meta/automationMeta.ts": { + "statements": { + "covered": 31, + "total": 35, + "percentage": 88.57 + }, + "branches": { + "covered": 2, + "total": 3, + "percentage": 66.66 + }, + "functions": { + "covered": 2, + "total": 4, + "percentage": 50 + }, + "lines": { + "covered": 31, + "total": 35, + "percentage": 88.57 + } + }, "src/vs/platform/agentHost/common/meta/clientPluginCustomizationMeta.ts": { "statements": { "covered": 39, @@ -1512,24 +1666,24 @@ }, "src/vs/platform/agentHost/common/openSessionLink.ts": { "statements": { - "covered": 120, - "total": 158, - "percentage": 75.94 + "covered": 132, + "total": 174, + "percentage": 75.86 }, "branches": { - "covered": 19, + "covered": 18, "total": 30, - "percentage": 63.33 + "percentage": 60 }, "functions": { - "covered": 4, - "total": 10, - "percentage": 40 + "covered": 5, + "total": 12, + "percentage": 41.66 }, "lines": { - "covered": 120, - "total": 158, - "percentage": 75.94 + "covered": 132, + "total": 174, + "percentage": 75.86 } }, "src/vs/platform/agentHost/common/otel/agentHostOTelService.ts": { @@ -1600,14 +1754,14 @@ }, "src/vs/platform/agentHost/common/pendingRequestRegistry.ts": { "statements": { - "covered": 148, + "covered": 152, "total": 168, - "percentage": 88.09 + "percentage": 90.47 }, "branches": { - "covered": 19, + "covered": 20, "total": 24, - "percentage": 79.16 + "percentage": 83.33 }, "functions": { "covered": 11, @@ -1615,9 +1769,9 @@ "percentage": 91.66 }, "lines": { - "covered": 148, + "covered": 152, "total": 168, - "percentage": 88.09 + "percentage": 90.47 } }, "src/vs/platform/agentHost/common/reasoningEffort.ts": { @@ -1649,9 +1803,9 @@ "percentage": 85.18 }, "branches": { - "covered": 6, - "total": 8, - "percentage": 75 + "covered": 7, + "total": 9, + "percentage": 77.77 }, "functions": { "covered": 2, @@ -1710,8 +1864,8 @@ }, "src/vs/platform/agentHost/common/serverToolNames.ts": { "statements": { - "covered": 35, - "total": 35, + "covered": 46, + "total": 46, "percentage": 100 }, "branches": { @@ -1725,53 +1879,53 @@ "percentage": 100 }, "lines": { - "covered": 35, - "total": 35, + "covered": 46, + "total": 46, "percentage": 100 } }, "src/vs/platform/agentHost/common/sessionArtifactCollection.ts": { "statements": { - "covered": 63, - "total": 148, - "percentage": 42.56 + "covered": 149, + "total": 169, + "percentage": 88.16 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 13, + "total": 27, + "percentage": 48.14 }, "functions": { - "covered": 0, - "total": 8, - "percentage": 0 + "covered": 9, + "total": 9, + "percentage": 100 }, "lines": { - "covered": 63, - "total": 148, - "percentage": 42.56 + "covered": 149, + "total": 169, + "percentage": 88.16 } }, "src/vs/platform/agentHost/common/sessionArtifacts.ts": { "statements": { - "covered": 85, - "total": 145, - "percentage": 58.62 + "covered": 156, + "total": 181, + "percentage": 86.18 }, "branches": { - "covered": 3, - "total": 6, - "percentage": 50 + "covered": 15, + "total": 26, + "percentage": 57.69 }, "functions": { - "covered": 2, + "covered": 7, "total": 8, - "percentage": 25 + "percentage": 87.5 }, "lines": { - "covered": 85, - "total": 145, - "percentage": 58.62 + "covered": 156, + "total": 181, + "percentage": 86.18 } }, "src/vs/platform/agentHost/common/sessionConfigKeys.ts": { @@ -1798,8 +1952,8 @@ }, "src/vs/platform/agentHost/common/sessionDataService.ts": { "statements": { - "covered": 484, - "total": 484, + "covered": 497, + "total": 497, "percentage": 100 }, "branches": { @@ -1813,8 +1967,8 @@ "percentage": 100 }, "lines": { - "covered": 484, - "total": 484, + "covered": 497, + "total": 497, "percentage": 100 } }, @@ -1842,24 +1996,24 @@ }, "src/vs/platform/agentHost/common/state/agentSubscription.ts": { "statements": { - "covered": 588, - "total": 1219, - "percentage": 48.23 + "covered": 628, + "total": 1286, + "percentage": 48.83 }, "branches": { - "covered": 2, - "total": 4, - "percentage": 50 + "covered": 10, + "total": 13, + "percentage": 76.92 }, "functions": { "covered": 1, - "total": 84, - "percentage": 1.19 + "total": 93, + "percentage": 1.07 }, "lines": { - "covered": 588, - "total": 1219, - "percentage": 48.23 + "covered": 628, + "total": 1286, + "percentage": 48.83 } }, "src/vs/platform/agentHost/common/state/chatAttachmentContext.ts": { @@ -1884,10 +2038,32 @@ "percentage": 91.72 } }, + "src/vs/platform/agentHost/common/state/legacyProtocolCompatibility.ts": { + "statements": { + "covered": 40, + "total": 87, + "percentage": 45.97 + }, + "branches": { + "covered": 0, + "total": 0, + "percentage": 100 + }, + "functions": { + "covered": 0, + "total": 4, + "percentage": 0 + }, + "lines": { + "covered": 40, + "total": 87, + "percentage": 45.97 + } + }, "src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts": { "statements": { - "covered": 419, - "total": 419, + "covered": 422, + "total": 422, "percentage": 100 }, "branches": { @@ -1901,8 +2077,8 @@ "percentage": 100 }, "lines": { - "covered": 419, - "total": 419, + "covered": 422, + "total": 422, "percentage": 100 } }, @@ -1974,24 +2150,24 @@ }, "src/vs/platform/agentHost/common/state/protocol/channels-automation/reducer.ts": { "statements": { - "covered": 15, + "covered": 43, "total": 48, - "percentage": 31.25 + "percentage": 89.58 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 7, + "total": 11, + "percentage": 63.63 }, "functions": { - "covered": 0, + "covered": 1, "total": 1, - "percentage": 0 + "percentage": 100 }, "lines": { - "covered": 15, + "covered": 43, "total": 48, - "percentage": 31.25 + "percentage": 89.58 } }, "src/vs/platform/agentHost/common/state/protocol/channels-changeset/reducer.ts": { @@ -2018,24 +2194,24 @@ }, "src/vs/platform/agentHost/common/state/protocol/channels-chat/reducer.ts": { "statements": { - "covered": 685, - "total": 864, - "percentage": 79.28 + "covered": 695, + "total": 906, + "percentage": 76.71 }, "branches": { - "covered": 165, - "total": 232, - "percentage": 71.12 + "covered": 168, + "total": 237, + "percentage": 70.88 }, "functions": { - "covered": 15, - "total": 15, - "percentage": 100 + "covered": 16, + "total": 17, + "percentage": 94.11 }, "lines": { - "covered": 685, - "total": 864, - "percentage": 79.28 + "covered": 695, + "total": 906, + "percentage": 76.71 } }, "src/vs/platform/agentHost/common/state/protocol/channels-resource-watch/reducer.ts": { @@ -2150,8 +2326,8 @@ }, "src/vs/platform/agentHost/common/state/protocol/common/actions.ts": { "statements": { - "covered": 260, - "total": 260, + "covered": 263, + "total": 263, "percentage": 100 }, "branches": { @@ -2165,15 +2341,15 @@ "percentage": 100 }, "lines": { - "covered": 260, - "total": 260, + "covered": 263, + "total": 263, "percentage": 100 } }, "src/vs/platform/agentHost/common/state/protocol/common/commands.ts": { "statements": { - "covered": 1157, - "total": 1157, + "covered": 1172, + "total": 1172, "percentage": 100 }, "branches": { @@ -2187,8 +2363,8 @@ "percentage": 100 }, "lines": { - "covered": 1157, - "total": 1157, + "covered": 1172, + "total": 1172, "percentage": 100 } }, @@ -2216,8 +2392,8 @@ }, "src/vs/platform/agentHost/common/state/protocol/common/notifications.ts": { "statements": { - "covered": 67, - "total": 67, + "covered": 68, + "total": 68, "percentage": 100 }, "branches": { @@ -2231,8 +2407,8 @@ "percentage": 100 }, "lines": { - "covered": 67, - "total": 67, + "covered": 68, + "total": 68, "percentage": 100 } }, @@ -2398,8 +2574,8 @@ }, "branches": { "covered": 9, - "total": 14, - "percentage": 64.28 + "total": 13, + "percentage": 69.23 }, "functions": { "covered": 3, @@ -2414,9 +2590,9 @@ }, "src/vs/platform/agentHost/common/state/protocol/version/registry.ts": { "statements": { - "covered": 216, - "total": 222, - "percentage": 97.29 + "covered": 217, + "total": 223, + "percentage": 97.3 }, "branches": { "covered": 2, @@ -2429,9 +2605,9 @@ "percentage": 50 }, "lines": { - "covered": 216, - "total": 222, - "percentage": 97.29 + "covered": 217, + "total": 223, + "percentage": 97.3 } }, "src/vs/platform/agentHost/common/state/protocolUpgrade.ts": { @@ -2458,31 +2634,31 @@ }, "src/vs/platform/agentHost/common/state/sessionActions.ts": { "statements": { - "covered": 231, - "total": 231, + "covered": 266, + "total": 266, "percentage": 100 }, "branches": { - "covered": 6, - "total": 6, + "covered": 10, + "total": 10, "percentage": 100 }, "functions": { - "covered": 6, - "total": 6, + "covered": 9, + "total": 9, "percentage": 100 }, "lines": { - "covered": 231, - "total": 231, + "covered": 266, + "total": 266, "percentage": 100 } }, "src/vs/platform/agentHost/common/state/sessionProtocol.ts": { "statements": { - "covered": 152, - "total": 154, - "percentage": 98.7 + "covered": 158, + "total": 160, + "percentage": 98.75 }, "branches": { "covered": 5, @@ -2495,9 +2671,9 @@ "percentage": 75 }, "lines": { - "covered": 152, - "total": 154, - "percentage": 98.7 + "covered": 158, + "total": 160, + "percentage": 98.75 } }, "src/vs/platform/agentHost/common/state/sessionReducers.ts": { @@ -2524,31 +2700,31 @@ }, "src/vs/platform/agentHost/common/state/sessionState.ts": { "statements": { - "covered": 1650, - "total": 2060, - "percentage": 80.09 + "covered": 1764, + "total": 2202, + "percentage": 80.1 }, "branches": { - "covered": 234, - "total": 339, - "percentage": 69.02 + "covered": 281, + "total": 398, + "percentage": 70.6 }, "functions": { - "covered": 70, - "total": 96, - "percentage": 72.91 + "covered": 78, + "total": 107, + "percentage": 72.89 }, "lines": { - "covered": 1650, - "total": 2060, - "percentage": 80.09 + "covered": 1764, + "total": 2202, + "percentage": 80.1 } }, "src/vs/platform/agentHost/common/state/sessionWorkingDirectories.ts": { "statements": { - "covered": 49, - "total": 73, - "percentage": 67.12 + "covered": 61, + "total": 109, + "percentage": 55.96 }, "branches": { "covered": 4, @@ -2561,9 +2737,9 @@ "percentage": 60 }, "lines": { - "covered": 49, - "total": 73, - "percentage": 67.12 + "covered": 61, + "total": 109, + "percentage": 55.96 } }, "src/vs/platform/agentHost/common/streamingToolCallDisplay.ts": { @@ -2610,6 +2786,50 @@ "percentage": 100 } }, + "src/vs/platform/agentHost/common/workspacelessScratchDir.ts": { + "statements": { + "covered": 11, + "total": 11, + "percentage": 100 + }, + "branches": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "lines": { + "covered": 11, + "total": 11, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/common/worktreePaths.ts": { + "statements": { + "covered": 35, + "total": 39, + "percentage": 89.74 + }, + "branches": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 2, + "percentage": 50 + }, + "lines": { + "covered": 35, + "total": 39, + "percentage": 89.74 + } + }, "src/vs/platform/agentHost/node/activeClientState.ts": { "statements": { "covered": 160, @@ -2678,31 +2898,31 @@ }, "src/vs/platform/agentHost/node/agentConfigurationService.ts": { "statements": { - "covered": 407, - "total": 442, - "percentage": 92.08 + "covered": 372, + "total": 402, + "percentage": 92.53 }, "branches": { - "covered": 46, - "total": 62, - "percentage": 74.19 + "covered": 48, + "total": 61, + "percentage": 78.68 }, "functions": { - "covered": 18, - "total": 19, - "percentage": 94.73 + "covered": 16, + "total": 17, + "percentage": 94.11 }, "lines": { - "covered": 407, - "total": 442, - "percentage": 92.08 + "covered": 372, + "total": 402, + "percentage": 92.53 } }, "src/vs/platform/agentHost/node/agentHostAuthenticationService.ts": { "statements": { - "covered": 129, - "total": 180, - "percentage": 71.66 + "covered": 136, + "total": 187, + "percentage": 72.72 }, "branches": { "covered": 20, @@ -2715,9 +2935,31 @@ "percentage": 85.71 }, "lines": { - "covered": 129, - "total": 180, - "percentage": 71.66 + "covered": 136, + "total": 187, + "percentage": 72.72 + } + }, + "src/vs/platform/agentHost/node/agentHostAutomationService.ts": { + "statements": { + "covered": 566, + "total": 1099, + "percentage": 51.5 + }, + "branches": { + "covered": 106, + "total": 158, + "percentage": 67.08 + }, + "functions": { + "covered": 38, + "total": 61, + "percentage": 62.29 + }, + "lines": { + "covered": 566, + "total": 1099, + "percentage": 51.5 } }, "src/vs/platform/agentHost/node/agentHostBangCommand.ts": { @@ -2744,36 +2986,36 @@ }, "src/vs/platform/agentHost/node/agentHostBootstrap.ts": { "statements": { - "covered": 184, - "total": 191, - "percentage": 96.33 + "covered": 211, + "total": 216, + "percentage": 97.68 }, "branches": { - "covered": 6, - "total": 9, - "percentage": 66.66 + "covered": 10, + "total": 12, + "percentage": 83.33 }, "functions": { - "covered": 2, - "total": 2, - "percentage": 100 + "covered": 5, + "total": 6, + "percentage": 83.33 }, "lines": { - "covered": 184, - "total": 191, - "percentage": 96.33 + "covered": 211, + "total": 216, + "percentage": 97.68 } }, "src/vs/platform/agentHost/node/agentHostChangesetCoordinator.ts": { "statements": { - "covered": 343, - "total": 367, - "percentage": 93.46 + "covered": 336, + "total": 360, + "percentage": 93.33 }, "branches": { - "covered": 52, - "total": 58, - "percentage": 89.65 + "covered": 53, + "total": 59, + "percentage": 89.83 }, "functions": { "covered": 15, @@ -2781,21 +3023,21 @@ "percentage": 93.75 }, "lines": { - "covered": 343, - "total": 367, - "percentage": 93.46 + "covered": 336, + "total": 360, + "percentage": 93.33 } }, "src/vs/platform/agentHost/node/agentHostChangesetFileMonitorCoordinator.ts": { "statements": { - "covered": 378, + "covered": 386, "total": 440, - "percentage": 85.9 + "percentage": 87.72 }, "branches": { - "covered": 58, - "total": 81, - "percentage": 71.6 + "covered": 66, + "total": 88, + "percentage": 75 }, "functions": { "covered": 29, @@ -2803,53 +3045,53 @@ "percentage": 96.66 }, "lines": { - "covered": 378, + "covered": 386, "total": 440, - "percentage": 85.9 + "percentage": 87.72 } }, "src/vs/platform/agentHost/node/agentHostChangesetOperationService.ts": { "statements": { - "covered": 265, - "total": 296, - "percentage": 89.52 + "covered": 284, + "total": 317, + "percentage": 89.58 }, "branches": { - "covered": 53, - "total": 69, - "percentage": 76.81 + "covered": 60, + "total": 77, + "percentage": 77.92 }, "functions": { - "covered": 13, - "total": 13, + "covered": 14, + "total": 14, "percentage": 100 }, "lines": { - "covered": 265, - "total": 296, - "percentage": 89.52 + "covered": 284, + "total": 317, + "percentage": 89.58 } }, "src/vs/platform/agentHost/node/agentHostChangesetService.ts": { "statements": { - "covered": 1241, - "total": 1653, - "percentage": 75.07 + "covered": 1250, + "total": 1632, + "percentage": 76.59 }, "branches": { - "covered": 189, - "total": 260, - "percentage": 72.69 + "covered": 197, + "total": 270, + "percentage": 72.96 }, "functions": { - "covered": 56, + "covered": 57, "total": 70, - "percentage": 80 + "percentage": 81.42 }, "lines": { - "covered": 1241, - "total": 1653, - "percentage": 75.07 + "covered": 1250, + "total": 1632, + "percentage": 76.59 } }, "src/vs/platform/agentHost/node/agentHostChangesetStateCache.ts": { @@ -2876,14 +3118,14 @@ }, "src/vs/platform/agentHost/node/agentHostChangesetSubscriptionService.ts": { "statements": { - "covered": 42, - "total": 44, - "percentage": 95.45 + "covered": 52, + "total": 55, + "percentage": 94.54 }, "branches": { - "covered": 10, - "total": 11, - "percentage": 90.9 + "covered": 12, + "total": 13, + "percentage": 92.3 }, "functions": { "covered": 5, @@ -2891,9 +3133,9 @@ "percentage": 100 }, "lines": { - "covered": 42, - "total": 44, - "percentage": 95.45 + "covered": 52, + "total": 55, + "percentage": 94.54 } }, "src/vs/platform/agentHost/node/agentHostChangesetTelemetry.ts": { @@ -2940,16 +3182,38 @@ "percentage": 94.44 } }, + "src/vs/platform/agentHost/node/agentHostChatContributionsService.ts": { + "statements": { + "covered": 264, + "total": 294, + "percentage": 89.79 + }, + "branches": { + "covered": 64, + "total": 81, + "percentage": 79.01 + }, + "functions": { + "covered": 22, + "total": 23, + "percentage": 95.65 + }, + "lines": { + "covered": 264, + "total": 294, + "percentage": 89.79 + } + }, "src/vs/platform/agentHost/node/agentHostCheckpointService.ts": { "statements": { - "covered": 360, + "covered": 366, "total": 508, - "percentage": 70.86 + "percentage": 72.04 }, "branches": { - "covered": 77, - "total": 107, - "percentage": 71.96 + "covered": 96, + "total": 123, + "percentage": 78.04 }, "functions": { "covered": 17, @@ -2957,9 +3221,9 @@ "percentage": 80.95 }, "lines": { - "covered": 360, + "covered": 366, "total": 508, - "percentage": 70.86 + "percentage": 72.04 } }, "src/vs/platform/agentHost/node/agentHostClientConnectionService.ts": { @@ -3008,14 +3272,14 @@ }, "src/vs/platform/agentHost/node/agentHostCommitOperationProvider.ts": { "statements": { - "covered": 58, - "total": 58, - "percentage": 100 + "covered": 60, + "total": 62, + "percentage": 96.77 }, "branches": { - "covered": 13, - "total": 14, - "percentage": 92.85 + "covered": 12, + "total": 16, + "percentage": 75 }, "functions": { "covered": 6, @@ -3023,9 +3287,9 @@ "percentage": 100 }, "lines": { - "covered": 58, - "total": 58, - "percentage": 100 + "covered": 60, + "total": 62, + "percentage": 96.77 } }, "src/vs/platform/agentHost/node/agentHostCompletions.ts": { @@ -3052,9 +3316,9 @@ }, "src/vs/platform/agentHost/node/agentHostContributions.ts": { "statements": { - "covered": 47, - "total": 50, - "percentage": 94 + "covered": 50, + "total": 53, + "percentage": 94.33 }, "branches": { "covered": 2, @@ -3067,53 +3331,53 @@ "percentage": 100 }, "lines": { - "covered": 47, - "total": 50, - "percentage": 94 + "covered": 50, + "total": 53, + "percentage": 94.33 } }, "src/vs/platform/agentHost/node/agentHostCustomizationEnablementService.ts": { "statements": { - "covered": 580, - "total": 734, - "percentage": 79.01 + "covered": 566, + "total": 720, + "percentage": 78.61 }, "branches": { - "covered": 107, - "total": 148, - "percentage": 72.29 + "covered": 106, + "total": 147, + "percentage": 72.1 }, "functions": { - "covered": 43, - "total": 48, - "percentage": 89.58 + "covered": 41, + "total": 46, + "percentage": 89.13 }, "lines": { - "covered": 580, - "total": 734, - "percentage": 79.01 + "covered": 566, + "total": 720, + "percentage": 78.61 } }, "src/vs/platform/agentHost/node/agentHostDatabase.ts": { "statements": { - "covered": 327, - "total": 426, - "percentage": 76.76 + "covered": 370, + "total": 450, + "percentage": 82.22 }, "branches": { - "covered": 47, - "total": 63, - "percentage": 74.6 + "covered": 53, + "total": 70, + "percentage": 75.71 }, "functions": { - "covered": 24, - "total": 32, - "percentage": 75 + "covered": 27, + "total": 33, + "percentage": 81.81 }, "lines": { - "covered": 327, - "total": 426, - "percentage": 76.76 + "covered": 370, + "total": 450, + "percentage": 82.22 } }, "src/vs/platform/agentHost/node/agentHostDebugLogs.ts": { @@ -3123,9 +3387,9 @@ "percentage": 87.38 }, "branches": { - "covered": 28, - "total": 40, - "percentage": 70 + "covered": 30, + "total": 42, + "percentage": 71.42 }, "functions": { "covered": 9, @@ -3228,14 +3492,14 @@ }, "src/vs/platform/agentHost/node/agentHostFileMonitorService.ts": { "statements": { - "covered": 169, + "covered": 165, "total": 185, - "percentage": 91.35 + "percentage": 89.18 }, "branches": { - "covered": 28, - "total": 37, - "percentage": 75.67 + "covered": 22, + "total": 34, + "percentage": 64.7 }, "functions": { "covered": 14, @@ -3243,21 +3507,21 @@ "percentage": 100 }, "lines": { - "covered": 169, + "covered": 165, "total": 185, - "percentage": 91.35 + "percentage": 89.18 } }, "src/vs/platform/agentHost/node/agentHostGitHubEndpointService.ts": { "statements": { - "covered": 121, + "covered": 125, "total": 127, - "percentage": 95.27 + "percentage": 98.42 }, "branches": { - "covered": 8, - "total": 9, - "percentage": 88.88 + "covered": 13, + "total": 13, + "percentage": 100 }, "functions": { "covered": 7, @@ -3265,9 +3529,9 @@ "percentage": 87.5 }, "lines": { - "covered": 121, + "covered": 125, "total": 127, - "percentage": 95.27 + "percentage": 98.42 } }, "src/vs/platform/agentHost/node/agentHostGitHubTelemetryRouter.ts": { @@ -3294,46 +3558,46 @@ }, "src/vs/platform/agentHost/node/agentHostGitService.ts": { "statements": { - "covered": 1262, - "total": 1736, - "percentage": 72.69 + "covered": 1274, + "total": 1744, + "percentage": 73.05 }, "branches": { - "covered": 273, - "total": 385, - "percentage": 70.9 + "covered": 286, + "total": 392, + "percentage": 72.95 }, "functions": { - "covered": 63, - "total": 80, - "percentage": 78.75 + "covered": 64, + "total": 81, + "percentage": 79.01 }, "lines": { - "covered": 1262, - "total": 1736, - "percentage": 72.69 + "covered": 1274, + "total": 1744, + "percentage": 73.05 } }, "src/vs/platform/agentHost/node/agentHostGitStateService.ts": { "statements": { - "covered": 234, - "total": 426, - "percentage": 54.92 + "covered": 264, + "total": 394, + "percentage": 67 }, "branches": { - "covered": 62, - "total": 87, - "percentage": 71.26 + "covered": 77, + "total": 96, + "percentage": 80.2 }, "functions": { "covered": 9, - "total": 14, - "percentage": 64.28 + "total": 13, + "percentage": 69.23 }, "lines": { - "covered": 234, - "total": 426, - "percentage": 54.92 + "covered": 264, + "total": 394, + "percentage": 67 } }, "src/vs/platform/agentHost/node/agentHostHeadlessTerminal.ts": { @@ -3365,9 +3629,9 @@ "percentage": 89.02 }, "branches": { - "covered": 47, - "total": 57, - "percentage": 82.45 + "covered": 49, + "total": 59, + "percentage": 83.05 }, "functions": { "covered": 13, @@ -3382,24 +3646,24 @@ }, "src/vs/platform/agentHost/node/agentHostLocalTurns.ts": { "statements": { - "covered": 130, - "total": 161, - "percentage": 80.74 + "covered": 161, + "total": 193, + "percentage": 83.41 }, "branches": { - "covered": 15, - "total": 24, - "percentage": 62.5 + "covered": 19, + "total": 29, + "percentage": 65.51 }, "functions": { - "covered": 9, - "total": 11, - "percentage": 81.81 + "covered": 10, + "total": 12, + "percentage": 83.33 }, "lines": { - "covered": 130, - "total": 161, - "percentage": 80.74 + "covered": 161, + "total": 193, + "percentage": 83.41 } }, "src/vs/platform/agentHost/node/agentHostManagedSettingsService.ts": { @@ -3453,9 +3717,9 @@ "percentage": 81 }, "branches": { - "covered": 24, - "total": 32, - "percentage": 75 + "covered": 25, + "total": 33, + "percentage": 75.75 }, "functions": { "covered": 5, @@ -3534,6 +3798,28 @@ "percentage": 96.66 } }, + "src/vs/platform/agentHost/node/agentHostProviderService.ts": { + "statements": { + "covered": 191, + "total": 233, + "percentage": 81.97 + }, + "branches": { + "covered": 30, + "total": 45, + "percentage": 66.66 + }, + "functions": { + "covered": 12, + "total": 15, + "percentage": 80 + }, + "lines": { + "covered": 191, + "total": 233, + "percentage": 81.97 + } + }, "src/vs/platform/agentHost/node/agentHostProxyResolver.ts": { "statements": { "covered": 158, @@ -3556,11 +3842,33 @@ "percentage": 73.83 } }, + "src/vs/platform/agentHost/node/agentHostPullRequestLifecycleOperationHandler.ts": { + "statements": { + "covered": 75, + "total": 196, + "percentage": 38.26 + }, + "branches": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 8, + "percentage": 12.5 + }, + "lines": { + "covered": 75, + "total": 196, + "percentage": 38.26 + } + }, "src/vs/platform/agentHost/node/agentHostPullRequestOperationHandler.ts": { "statements": { - "covered": 127, - "total": 497, - "percentage": 25.55 + "covered": 137, + "total": 557, + "percentage": 24.59 }, "branches": { "covered": 1, @@ -3569,35 +3877,57 @@ }, "functions": { "covered": 1, - "total": 15, - "percentage": 6.66 + "total": 16, + "percentage": 6.25 }, "lines": { - "covered": 127, - "total": 497, - "percentage": 25.55 + "covered": 137, + "total": 557, + "percentage": 24.59 } }, "src/vs/platform/agentHost/node/agentHostPullRequestOperationProvider.ts": { "statements": { - "covered": 66, - "total": 117, - "percentage": 56.41 + "covered": 200, + "total": 273, + "percentage": 73.26 }, "branches": { - "covered": 14, - "total": 18, - "percentage": 77.77 + "covered": 37, + "total": 41, + "percentage": 90.24 }, "functions": { - "covered": 4, - "total": 8, - "percentage": 50 + "covered": 7, + "total": 12, + "percentage": 58.33 }, "lines": { - "covered": 66, - "total": 117, - "percentage": 56.41 + "covered": 200, + "total": 273, + "percentage": 73.26 + } + }, + "src/vs/platform/agentHost/node/agentHostPullRequestStatusService.ts": { + "statements": { + "covered": 252, + "total": 506, + "percentage": 49.8 + }, + "branches": { + "covered": 27, + "total": 40, + "percentage": 67.5 + }, + "functions": { + "covered": 7, + "total": 24, + "percentage": 29.16 + }, + "lines": { + "covered": 252, + "total": 506, + "percentage": 49.8 } }, "src/vs/platform/agentHost/node/agentHostRenameCommand.ts": { @@ -3690,14 +4020,14 @@ }, "src/vs/platform/agentHost/node/agentHostReviewService.ts": { "statements": { - "covered": 209, + "covered": 211, "total": 264, - "percentage": 79.16 + "percentage": 79.92 }, "branches": { - "covered": 35, - "total": 52, - "percentage": 67.3 + "covered": 38, + "total": 54, + "percentage": 70.37 }, "functions": { "covered": 9, @@ -3705,16 +4035,16 @@ "percentage": 69.23 }, "lines": { - "covered": 209, + "covered": 211, "total": 264, - "percentage": 79.16 + "percentage": 79.92 } }, "src/vs/platform/agentHost/node/agentHostServerMain.ts": { "statements": { - "covered": 351, - "total": 401, - "percentage": 87.53 + "covered": 354, + "total": 402, + "percentage": 88.05 }, "branches": { "covered": 23, @@ -3727,47 +4057,69 @@ "percentage": 85.71 }, "lines": { - "covered": 351, - "total": 401, - "percentage": 87.53 + "covered": 354, + "total": 402, + "percentage": 88.05 } }, "src/vs/platform/agentHost/node/agentHostServices.ts": { "statements": { - "covered": 161, - "total": 165, - "percentage": 97.57 + "covered": 130, + "total": 130, + "percentage": 100 }, "branches": { - "covered": 10, - "total": 13, - "percentage": 76.92 + "covered": 2, + "total": 3, + "percentage": 66.66 }, "functions": { - "covered": 7, - "total": 7, + "covered": 2, + "total": 2, "percentage": 100 }, "lines": { - "covered": 161, - "total": 165, - "percentage": 97.57 + "covered": 130, + "total": 130, + "percentage": 100 } }, - "src/vs/platform/agentHost/node/agentHostSessionRepositories.ts": { + "src/vs/platform/agentHost/node/agentHostSessionOpenTelemetry.ts": { "statements": { - "covered": 87, - "total": 95, - "percentage": 91.57 + "covered": 279, + "total": 306, + "percentage": 91.17 }, "branches": { - "covered": 10, - "total": 12, - "percentage": 83.33 + "covered": 56, + "total": 66, + "percentage": 84.84 }, "functions": { - "covered": 1, - "total": 1, + "covered": 19, + "total": 19, + "percentage": 100 + }, + "lines": { + "covered": 279, + "total": 306, + "percentage": 91.17 + } + }, + "src/vs/platform/agentHost/node/agentHostSessionRepositories.ts": { + "statements": { + "covered": 87, + "total": 95, + "percentage": 91.57 + }, + "branches": { + "covered": 10, + "total": 12, + "percentage": 83.33 + }, + "functions": { + "covered": 1, + "total": 1, "percentage": 100 }, "lines": { @@ -3778,24 +4130,24 @@ }, "src/vs/platform/agentHost/node/agentHostSessionTitleController.ts": { "statements": { - "covered": 638, - "total": 894, - "percentage": 71.36 + "covered": 699, + "total": 914, + "percentage": 76.47 }, "branches": { - "covered": 106, - "total": 155, - "percentage": 68.38 + "covered": 130, + "total": 181, + "percentage": 71.82 }, "functions": { - "covered": 31, + "covered": 34, "total": 46, - "percentage": 67.39 + "percentage": 73.91 }, "lines": { - "covered": 638, - "total": 894, - "percentage": 71.36 + "covered": 699, + "total": 914, + "percentage": 76.47 } }, "src/vs/platform/agentHost/node/agentHostSessionTitleSignal.ts": { @@ -3842,6 +4194,28 @@ "percentage": 85.71 } }, + "src/vs/platform/agentHost/node/agentHostShutdown.ts": { + "statements": { + "covered": 22, + "total": 25, + "percentage": 88 + }, + "branches": { + "covered": 1, + "total": 2, + "percentage": 50 + }, + "functions": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "lines": { + "covered": 22, + "total": 25, + "percentage": 88 + } + }, "src/vs/platform/agentHost/node/agentHostSkillCompletionProvider.ts": { "statements": { "covered": 68, @@ -3888,46 +4262,68 @@ }, "src/vs/platform/agentHost/node/agentHostStateManager.ts": { "statements": { - "covered": 1730, - "total": 1927, - "percentage": 89.77 + "covered": 1828, + "total": 2059, + "percentage": 88.78 }, "branches": { - "covered": 276, - "total": 334, - "percentage": 82.63 + "covered": 305, + "total": 369, + "percentage": 82.65 }, "functions": { - "covered": 77, - "total": 89, - "percentage": 86.51 + "covered": 83, + "total": 96, + "percentage": 86.45 }, "lines": { - "covered": 1730, - "total": 1927, - "percentage": 89.77 + "covered": 1828, + "total": 2059, + "percentage": 88.78 } }, "src/vs/platform/agentHost/node/agentHostStorageService.ts": { "statements": { - "covered": 110, - "total": 121, - "percentage": 90.9 + "covered": 141, + "total": 169, + "percentage": 83.43 }, "branches": { - "covered": 17, - "total": 23, - "percentage": 73.91 + "covered": 22, + "total": 29, + "percentage": 75.86 }, "functions": { - "covered": 10, - "total": 10, + "covered": 13, + "total": 13, "percentage": 100 }, "lines": { - "covered": 110, - "total": 121, - "percentage": 90.9 + "covered": 141, + "total": 169, + "percentage": 83.43 + } + }, + "src/vs/platform/agentHost/node/agentHostSubscriptionService.ts": { + "statements": { + "covered": 52, + "total": 56, + "percentage": 92.85 + }, + "branches": { + "covered": 13, + "total": 13, + "percentage": 100 + }, + "functions": { + "covered": 4, + "total": 6, + "percentage": 66.66 + }, + "lines": { + "covered": 52, + "total": 56, + "percentage": 92.85 } }, "src/vs/platform/agentHost/node/agentHostSyncOperationHandler.ts": { @@ -3976,46 +4372,46 @@ }, "src/vs/platform/agentHost/node/agentHostTelemetryReporter.ts": { "statements": { - "covered": 1176, - "total": 1338, - "percentage": 87.89 + "covered": 1248, + "total": 1412, + "percentage": 88.38 }, "branches": { - "covered": 66, - "total": 96, - "percentage": 68.75 + "covered": 68, + "total": 100, + "percentage": 68 }, "functions": { - "covered": 16, - "total": 23, - "percentage": 69.56 + "covered": 17, + "total": 24, + "percentage": 70.83 }, "lines": { - "covered": 1176, - "total": 1338, - "percentage": 87.89 + "covered": 1248, + "total": 1412, + "percentage": 88.38 } }, "src/vs/platform/agentHost/node/agentHostTelemetryService.ts": { "statements": { - "covered": 198, - "total": 287, - "percentage": 68.98 + "covered": 209, + "total": 295, + "percentage": 70.84 }, "branches": { - "covered": 18, - "total": 47, - "percentage": 38.29 + "covered": 21, + "total": 51, + "percentage": 41.17 }, "functions": { - "covered": 15, + "covered": 16, "total": 30, - "percentage": 50 + "percentage": 53.33 }, "lines": { - "covered": 198, - "total": 287, - "percentage": 68.98 + "covered": 209, + "total": 295, + "percentage": 70.84 } }, "src/vs/platform/agentHost/node/agentHostTerminalManager.ts": { @@ -4025,9 +4421,9 @@ "percentage": 90.78 }, "branches": { - "covered": 120, - "total": 151, - "percentage": 79.47 + "covered": 121, + "total": 152, + "percentage": 79.6 }, "functions": { "covered": 35, @@ -4042,36 +4438,80 @@ }, "src/vs/platform/agentHost/node/agentHostToolCallTracker.ts": { "statements": { - "covered": 266, - "total": 309, - "percentage": 86.08 + "covered": 241, + "total": 283, + "percentage": 85.15 }, "branches": { - "covered": 55, - "total": 62, - "percentage": 88.7 + "covered": 45, + "total": 51, + "percentage": 88.23 }, "functions": { - "covered": 15, - "total": 16, - "percentage": 93.75 + "covered": 13, + "total": 13, + "percentage": 100 }, "lines": { - "covered": 266, - "total": 309, - "percentage": 86.08 + "covered": 241, + "total": 283, + "percentage": 85.15 + } + }, + "src/vs/platform/agentHost/node/agentHostTurnStarter.ts": { + "statements": { + "covered": 90, + "total": 123, + "percentage": 73.17 + }, + "branches": { + "covered": 9, + "total": 12, + "percentage": 75 + }, + "functions": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "lines": { + "covered": 90, + "total": 123, + "percentage": 73.17 + } + }, + "src/vs/platform/agentHost/node/agentHostTurnTelemetryContext.ts": { + "statements": { + "covered": 56, + "total": 57, + "percentage": 98.24 + }, + "branches": { + "covered": 22, + "total": 25, + "percentage": 88 + }, + "functions": { + "covered": 3, + "total": 3, + "percentage": 100 + }, + "lines": { + "covered": 56, + "total": 57, + "percentage": 98.24 } }, "src/vs/platform/agentHost/node/agentHostTurnTracker.ts": { "statements": { - "covered": 474, - "total": 606, - "percentage": 78.21 + "covered": 490, + "total": 622, + "percentage": 78.77 }, "branches": { - "covered": 48, - "total": 67, - "percentage": 71.64 + "covered": 49, + "total": 68, + "percentage": 72.05 }, "functions": { "covered": 25, @@ -4079,9 +4519,9 @@ "percentage": 80.64 }, "lines": { - "covered": 474, - "total": 606, - "percentage": 78.21 + "covered": 490, + "total": 622, + "percentage": 78.77 } }, "src/vs/platform/agentHost/node/agentHostUpgradeChannel.ts": { @@ -4130,24 +4570,24 @@ }, "src/vs/platform/agentHost/node/agentMergeController.ts": { "statements": { - "covered": 329, - "total": 1020, - "percentage": 32.25 + "covered": 393, + "total": 1127, + "percentage": 34.87 }, "branches": { - "covered": 23, - "total": 49, - "percentage": 46.93 + "covered": 31, + "total": 60, + "percentage": 51.66 }, "functions": { "covered": 13, - "total": 49, - "percentage": 26.53 + "total": 48, + "percentage": 27.08 }, "lines": { - "covered": 329, - "total": 1020, - "percentage": 32.25 + "covered": 393, + "total": 1127, + "percentage": 34.87 } }, "src/vs/platform/agentHost/node/agentMergeTools.ts": { @@ -4196,24 +4636,24 @@ }, "src/vs/platform/agentHost/node/agentPeerChats.ts": { "statements": { - "covered": 204, - "total": 390, - "percentage": 52.3 + "covered": 128, + "total": 194, + "percentage": 65.97 }, "branches": { - "covered": 17, - "total": 50, - "percentage": 34 + "covered": 4, + "total": 15, + "percentage": 26.66 }, "functions": { - "covered": 7, - "total": 27, - "percentage": 25.92 + "covered": 4, + "total": 19, + "percentage": 21.05 }, "lines": { - "covered": 204, - "total": 390, - "percentage": 52.3 + "covered": 128, + "total": 194, + "percentage": 65.97 } }, "src/vs/platform/agentHost/node/agentPluginManager.ts": { @@ -4306,119 +4746,559 @@ }, "src/vs/platform/agentHost/node/agentService.ts": { "statements": { - "covered": 5038, - "total": 6935, - "percentage": 72.64 + "covered": 5362, + "total": 7218, + "percentage": 74.28 }, "branches": { - "covered": 890, - "total": 1358, - "percentage": 65.53 + "covered": 1021, + "total": 1509, + "percentage": 67.66 }, "functions": { - "covered": 213, - "total": 283, - "percentage": 75.26 + "covered": 250, + "total": 318, + "percentage": 78.61 }, "lines": { - "covered": 5038, - "total": 6935, - "percentage": 72.64 + "covered": 5362, + "total": 7218, + "percentage": 74.28 } }, "src/vs/platform/agentHost/node/agentServiceComposition.ts": { "statements": { - "covered": 206, - "total": 224, - "percentage": 91.96 + "covered": 180, + "total": 196, + "percentage": 91.83 }, "branches": { - "covered": 12, - "total": 20, - "percentage": 60 + "covered": 7, + "total": 13, + "percentage": 53.84 }, "functions": { - "covered": 10, - "total": 16, - "percentage": 62.5 + "covered": 5, + "total": 9, + "percentage": 55.55 }, "lines": { - "covered": 206, - "total": 224, - "percentage": 91.96 + "covered": 180, + "total": 196, + "percentage": 91.83 } }, "src/vs/platform/agentHost/node/agentServiceFoundation.ts": { "statements": { - "covered": 139, - "total": 152, - "percentage": 91.44 + "covered": 142, + "total": 155, + "percentage": 91.61 }, "branches": { - "covered": 17, - "total": 21, - "percentage": 80.95 + "covered": 19, + "total": 23, + "percentage": 82.6 }, "functions": { - "covered": 17, - "total": 24, - "percentage": 70.83 + "covered": 19, + "total": 27, + "percentage": 70.37 }, "lines": { - "covered": 139, - "total": 152, - "percentage": 91.44 + "covered": 142, + "total": 155, + "percentage": 91.61 } }, "src/vs/platform/agentHost/node/agentSessionRegistry.ts": { "statements": { - "covered": 166, - "total": 212, - "percentage": 78.3 + "covered": 197, + "total": 221, + "percentage": 89.14 }, "branches": { - "covered": 15, - "total": 19, - "percentage": 78.94 + "covered": 21, + "total": 27, + "percentage": 77.77 }, "functions": { - "covered": 10, - "total": 16, - "percentage": 62.5 + "covered": 13, + "total": 17, + "percentage": 76.47 }, "lines": { - "covered": 166, - "total": 212, - "percentage": 78.3 + "covered": 197, + "total": 221, + "percentage": 89.14 + } + }, + "src/vs/platform/agentHost/node/agentSessionResidency.ts": { + "statements": { + "covered": 238, + "total": 277, + "percentage": 85.92 + }, + "branches": { + "covered": 54, + "total": 66, + "percentage": 81.81 + }, + "functions": { + "covered": 12, + "total": 13, + "percentage": 92.3 + }, + "lines": { + "covered": 238, + "total": 277, + "percentage": 85.92 + } + }, + "src/vs/platform/agentHost/node/agentSideEffects.ts": { + "statements": { + "covered": 1602, + "total": 1900, + "percentage": 84.31 + }, + "branches": { + "covered": 295, + "total": 399, + "percentage": 73.93 + }, + "functions": { + "covered": 48, + "total": 54, + "percentage": 88.88 + }, + "lines": { + "covered": 1602, + "total": 1900, + "percentage": 84.31 + } + }, + "src/vs/platform/agentHost/node/appNodeModules.ts": { + "statements": { + "covered": 25, + "total": 27, + "percentage": 92.59 + }, + "branches": { + "covered": 2, + "total": 4, + "percentage": 50 + }, + "functions": { + "covered": 2, + "total": 3, + "percentage": 66.66 + }, + "lines": { + "covered": 25, + "total": 27, + "percentage": 92.59 + } + }, + "src/vs/platform/agentHost/node/automationCron.ts": { + "statements": { + "covered": 67, + "total": 239, + "percentage": 28.03 + }, + "branches": { + "covered": 0, + "total": 0, + "percentage": 100 + }, + "functions": { + "covered": 0, + "total": 11, + "percentage": 0 + }, + "lines": { + "covered": 67, + "total": 239, + "percentage": 28.03 + } + }, + "src/vs/platform/agentHost/node/byokLmBridgeRegistry.ts": { + "statements": { + "covered": 117, + "total": 204, + "percentage": 57.35 + }, + "branches": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 13, + "percentage": 7.69 + }, + "lines": { + "covered": 117, + "total": 204, + "percentage": 57.35 + } + }, + "src/vs/platform/agentHost/node/chatContributions/artifactTools/artifactToolsContribution.ts": { + "statements": { + "covered": 30, + "total": 30, + "percentage": 100 + }, + "branches": { + "covered": 4, + "total": 4, + "percentage": 100 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 30, + "total": 30, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/node/chatContributions/builtInChatContributions.ts": { + "statements": { + "covered": 49, + "total": 49, + "percentage": 100 + }, + "branches": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "functions": { + "covered": 1, + "total": 1, + "percentage": 100 + }, + "lines": { + "covered": 49, + "total": 49, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/node/chatContributions/chatDraft/chatDraftContribution.ts": { + "statements": { + "covered": 71, + "total": 80, + "percentage": 88.75 + }, + "branches": { + "covered": 8, + "total": 13, + "percentage": 61.53 + }, + "functions": { + "covered": 3, + "total": 3, + "percentage": 100 + }, + "lines": { + "covered": 71, + "total": 80, + "percentage": 88.75 + } + }, + "src/vs/platform/agentHost/node/chatContributions/chatSurface/chatSurfaceContribution.ts": { + "statements": { + "covered": 31, + "total": 33, + "percentage": 93.93 + }, + "branches": { + "covered": 2, + "total": 7, + "percentage": 28.57 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 31, + "total": 33, + "percentage": 93.93 + } + }, + "src/vs/platform/agentHost/node/chatContributions/checkpointAndChangeset/checkpointAndChangesetContribution.ts": { + "statements": { + "covered": 51, + "total": 59, + "percentage": 86.44 + }, + "branches": { + "covered": 13, + "total": 14, + "percentage": 92.85 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 51, + "total": 59, + "percentage": 86.44 + } + }, + "src/vs/platform/agentHost/node/chatContributions/githubReferences/githubReferencesContribution.ts": { + "statements": { + "covered": 32, + "total": 32, + "percentage": 100 + }, + "branches": { + "covered": 3, + "total": 4, + "percentage": 75 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 32, + "total": 32, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/node/chatContributions/localCommand/localCommandContribution.ts": { + "statements": { + "covered": 42, + "total": 42, + "percentage": 100 + }, + "branches": { + "covered": 5, + "total": 5, + "percentage": 100 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 42, + "total": 42, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/node/chatContributions/markUnread/markUnreadContribution.ts": { + "statements": { + "covered": 40, + "total": 42, + "percentage": 95.23 + }, + "branches": { + "covered": 8, + "total": 10, + "percentage": 80 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 40, + "total": 42, + "percentage": 95.23 + } + }, + "src/vs/platform/agentHost/node/chatContributions/markdownPlanRichLinks/markdownPlanRichLinksContribution.ts": { + "statements": { + "covered": 31, + "total": 46, + "percentage": 67.39 + }, + "branches": { + "covered": 2, + "total": 3, + "percentage": 66.66 + }, + "functions": { + "covered": 2, + "total": 3, + "percentage": 66.66 + }, + "lines": { + "covered": 31, + "total": 46, + "percentage": 67.39 + } + }, + "src/vs/platform/agentHost/node/chatContributions/persistedTurnUsage/persistedTurnUsageContribution.ts": { + "statements": { + "covered": 147, + "total": 165, + "percentage": 89.09 + }, + "branches": { + "covered": 35, + "total": 44, + "percentage": 79.54 + }, + "functions": { + "covered": 4, + "total": 4, + "percentage": 100 + }, + "lines": { + "covered": 147, + "total": 165, + "percentage": 89.09 + } + }, + "src/vs/platform/agentHost/node/chatContributions/queueDrain/queueDrainContribution.ts": { + "statements": { + "covered": 137, + "total": 166, + "percentage": 82.53 + }, + "branches": { + "covered": 25, + "total": 35, + "percentage": 71.42 + }, + "functions": { + "covered": 7, + "total": 7, + "percentage": 100 + }, + "lines": { + "covered": 137, + "total": 166, + "percentage": 82.53 + } + }, + "src/vs/platform/agentHost/node/chatContributions/sessionFlags/sessionFlagsContribution.ts": { + "statements": { + "covered": 52, + "total": 52, + "percentage": 100 + }, + "branches": { + "covered": 11, + "total": 11, + "percentage": 100 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 52, + "total": 52, + "percentage": 100 + } + }, + "src/vs/platform/agentHost/node/chatContributions/sessionInputNeeded/sessionInputNeededContribution.ts": { + "statements": { + "covered": 206, + "total": 220, + "percentage": 93.63 + }, + "branches": { + "covered": 70, + "total": 76, + "percentage": 92.1 + }, + "functions": { + "covered": 13, + "total": 13, + "percentage": 100 + }, + "lines": { + "covered": 206, + "total": 220, + "percentage": 93.63 + } + }, + "src/vs/platform/agentHost/node/chatContributions/sessionTitle/sessionTitleContribution.ts": { + "statements": { + "covered": 87, + "total": 98, + "percentage": 88.77 + }, + "branches": { + "covered": 21, + "total": 25, + "percentage": 84 + }, + "functions": { + "covered": 6, + "total": 6, + "percentage": 100 + }, + "lines": { + "covered": 87, + "total": 98, + "percentage": 88.77 + } + }, + "src/vs/platform/agentHost/node/chatContributions/sideChat/sideChatContext.ts": { + "statements": { + "covered": 52, + "total": 159, + "percentage": 32.7 + }, + "branches": { + "covered": 2, + "total": 6, + "percentage": 33.33 + }, + "functions": { + "covered": 2, + "total": 9, + "percentage": 22.22 + }, + "lines": { + "covered": 52, + "total": 159, + "percentage": 32.7 } }, - "src/vs/platform/agentHost/node/agentSideEffects.ts": { + "src/vs/platform/agentHost/node/chatContributions/sideChat/sideChatContribution.ts": { "statements": { - "covered": 2104, - "total": 2450, - "percentage": 85.87 + "covered": 70, + "total": 83, + "percentage": 84.33 }, "branches": { - "covered": 419, - "total": 552, - "percentage": 75.9 + "covered": 18, + "total": 26, + "percentage": 69.23 }, "functions": { - "covered": 69, - "total": 77, - "percentage": 89.61 + "covered": 4, + "total": 4, + "percentage": 100 }, "lines": { - "covered": 2104, - "total": 2450, - "percentage": 85.87 + "covered": 70, + "total": 83, + "percentage": 84.33 } }, - "src/vs/platform/agentHost/node/appNodeModules.ts": { + "src/vs/platform/agentHost/node/chatContributions/turnAdmission/turnAdmissionContribution.ts": { "statements": { - "covered": 25, - "total": 27, - "percentage": 92.59 + "covered": 42, + "total": 48, + "percentage": 87.5 }, "branches": { "covered": 2, @@ -4427,35 +5307,57 @@ }, "functions": { "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 42, + "total": 48, + "percentage": 87.5 + } + }, + "src/vs/platform/agentHost/node/chatContributions/turnDelegation/turnDelegationContribution.ts": { + "statements": { + "covered": 85, + "total": 97, + "percentage": 87.62 + }, + "branches": { + "covered": 17, + "total": 23, + "percentage": 73.91 + }, + "functions": { + "covered": 3, "total": 3, - "percentage": 66.66 + "percentage": 100 }, "lines": { - "covered": 25, - "total": 27, - "percentage": 92.59 + "covered": 85, + "total": 97, + "percentage": 87.62 } }, - "src/vs/platform/agentHost/node/byokLmBridgeRegistry.ts": { + "src/vs/platform/agentHost/node/chatContributions/worktreeAnnouncement/worktreeAnnouncementContribution.ts": { "statements": { - "covered": 117, - "total": 204, - "percentage": 57.35 + "covered": 31, + "total": 31, + "percentage": 100 }, "branches": { - "covered": 1, - "total": 1, + "covered": 4, + "total": 4, "percentage": 100 }, "functions": { - "covered": 1, - "total": 13, - "percentage": 7.69 + "covered": 2, + "total": 2, + "percentage": 100 }, "lines": { - "covered": 117, - "total": 204, - "percentage": 57.35 + "covered": 31, + "total": 31, + "percentage": 100 } }, "src/vs/platform/agentHost/node/claude/anthropicBetas.ts": { @@ -4504,24 +5406,24 @@ }, "src/vs/platform/agentHost/node/claude/claudeAgent.ts": { "statements": { - "covered": 2216, - "total": 2713, - "percentage": 81.68 + "covered": 2163, + "total": 2667, + "percentage": 81.1 }, "branches": { - "covered": 223, - "total": 334, - "percentage": 66.76 + "covered": 198, + "total": 301, + "percentage": 65.78 }, "functions": { - "covered": 99, - "total": 128, - "percentage": 77.34 + "covered": 97, + "total": 127, + "percentage": 76.37 }, "lines": { - "covered": 2216, - "total": 2713, - "percentage": 81.68 + "covered": 2163, + "total": 2667, + "percentage": 81.1 } }, "src/vs/platform/agentHost/node/claude/claudeAgentSdkService.ts": { @@ -4548,31 +5450,31 @@ }, "src/vs/platform/agentHost/node/claude/claudeAgentSession.ts": { "statements": { - "covered": 1270, + "covered": 1276, "total": 1631, - "percentage": 77.86 + "percentage": 78.23 }, "branches": { - "covered": 82, - "total": 137, - "percentage": 59.85 + "covered": 88, + "total": 141, + "percentage": 62.41 }, "functions": { - "covered": 42, + "covered": 43, "total": 69, - "percentage": 60.86 + "percentage": 62.31 }, "lines": { - "covered": 1270, + "covered": 1276, "total": 1631, - "percentage": 77.86 + "percentage": 78.23 } }, "src/vs/platform/agentHost/node/claude/claudeCanUseTool.ts": { "statements": { - "covered": 257, - "total": 301, - "percentage": 85.38 + "covered": 258, + "total": 302, + "percentage": 85.43 }, "branches": { "covered": 22, @@ -4585,9 +5487,9 @@ "percentage": 71.42 }, "lines": { - "covered": 257, - "total": 301, - "percentage": 85.38 + "covered": 258, + "total": 302, + "percentage": 85.43 } }, "src/vs/platform/agentHost/node/claude/claudeElicitation.ts": { @@ -4614,9 +5516,9 @@ }, "src/vs/platform/agentHost/node/claude/claudeElicitationBridge.ts": { "statements": { - "covered": 36, - "total": 77, - "percentage": 46.75 + "covered": 35, + "total": 76, + "percentage": 46.05 }, "branches": { "covered": 0, @@ -4629,9 +5531,9 @@ "percentage": 0 }, "lines": { - "covered": 36, - "total": 77, - "percentage": 46.75 + "covered": 35, + "total": 76, + "percentage": 46.05 } }, "src/vs/platform/agentHost/node/claude/claudeFileEditObserver.ts": { @@ -4751,9 +5653,9 @@ "percentage": 83.83 }, "branches": { - "covered": 37, - "total": 48, - "percentage": 77.08 + "covered": 38, + "total": 49, + "percentage": 77.55 }, "functions": { "covered": 11, @@ -4883,9 +5785,9 @@ "percentage": 77.3 }, "branches": { - "covered": 61, - "total": 116, - "percentage": 52.58 + "covered": 59, + "total": 114, + "percentage": 51.75 }, "functions": { "covered": 18, @@ -4949,9 +5851,9 @@ "percentage": 77.53 }, "branches": { - "covered": 41, - "total": 65, - "percentage": 63.07 + "covered": 44, + "total": 68, + "percentage": 64.7 }, "functions": { "covered": 21, @@ -4988,24 +5890,24 @@ }, "src/vs/platform/agentHost/node/claude/claudeSessionMetadataStore.ts": { "statements": { - "covered": 225, + "covered": 205, "total": 261, - "percentage": 86.2 + "percentage": 78.54 }, "branches": { - "covered": 26, - "total": 41, - "percentage": 63.41 + "covered": 17, + "total": 35, + "percentage": 48.57 }, "functions": { - "covered": 9, + "covered": 8, "total": 9, - "percentage": 100 + "percentage": 88.88 }, "lines": { - "covered": 225, + "covered": 205, "total": 261, - "percentage": 86.2 + "percentage": 78.54 } }, "src/vs/platform/agentHost/node/claude/claudeSessionPermissionMode.ts": { @@ -5037,9 +5939,9 @@ "percentage": 93.72 }, "branches": { - "covered": 42, - "total": 51, - "percentage": 82.35 + "covered": 40, + "total": 49, + "percentage": 81.63 }, "functions": { "covered": 17, @@ -5054,24 +5956,24 @@ }, "src/vs/platform/agentHost/node/claude/claudeSubagentResolver.ts": { "statements": { - "covered": 236, + "covered": 212, "total": 439, - "percentage": 53.75 + "percentage": 48.29 }, "branches": { - "covered": 13, - "total": 20, - "percentage": 65 + "covered": 8, + "total": 11, + "percentage": 72.72 }, "functions": { - "covered": 11, + "covered": 8, "total": 21, - "percentage": 52.38 + "percentage": 38.09 }, "lines": { - "covered": 236, + "covered": 212, "total": 439, - "percentage": 53.75 + "percentage": 48.29 } }, "src/vs/platform/agentHost/node/claude/claudeSubagentSignals.ts": { @@ -5142,14 +6044,14 @@ }, "src/vs/platform/agentHost/node/claude/claudeToolDisplay.ts": { "statements": { - "covered": 469, + "covered": 480, "total": 590, - "percentage": 79.49 + "percentage": 81.35 }, "branches": { - "covered": 73, - "total": 131, - "percentage": 55.72 + "covered": 77, + "total": 132, + "percentage": 58.33 }, "functions": { "covered": 15, @@ -5157,9 +6059,9 @@ "percentage": 88.23 }, "lines": { - "covered": 469, + "covered": 480, "total": 590, - "percentage": 79.49 + "percentage": 81.35 } }, "src/vs/platform/agentHost/node/claude/claudeTransportMode.ts": { @@ -5340,24 +6242,24 @@ }, "src/vs/platform/agentHost/node/claude/customizations/claudeSessionClientCustomizationsModel.ts": { "statements": { - "covered": 169, + "covered": 171, "total": 237, - "percentage": 71.3 + "percentage": 72.15 }, "branches": { - "covered": 10, - "total": 14, - "percentage": 71.42 + "covered": 11, + "total": 15, + "percentage": 73.33 }, "functions": { - "covered": 9, + "covered": 10, "total": 14, - "percentage": 64.28 + "percentage": 71.42 }, "lines": { - "covered": 169, + "covered": 171, "total": 237, - "percentage": 71.3 + "percentage": 72.15 } }, "src/vs/platform/agentHost/node/claude/customizations/claudeSessionCustomizationDiscovery.ts": { @@ -5367,9 +6269,9 @@ "percentage": 77.77 }, "branches": { - "covered": 47, - "total": 71, - "percentage": 66.19 + "covered": 48, + "total": 72, + "percentage": 66.66 }, "functions": { "covered": 13, @@ -5516,36 +6418,36 @@ }, "src/vs/platform/agentHost/node/codex/codexAgent.ts": { "statements": { - "covered": 4475, - "total": 6780, - "percentage": 66 + "covered": 5557, + "total": 7847, + "percentage": 70.81 }, "branches": { - "covered": 517, - "total": 908, - "percentage": 56.93 + "covered": 847, + "total": 1343, + "percentage": 63.06 }, "functions": { - "covered": 186, - "total": 256, - "percentage": 72.65 + "covered": 232, + "total": 292, + "percentage": 79.45 }, "lines": { - "covered": 4475, - "total": 6780, - "percentage": 66 + "covered": 5557, + "total": 7847, + "percentage": 70.81 } }, "src/vs/platform/agentHost/node/codex/codexAppServerClient.ts": { "statements": { - "covered": 414, + "covered": 417, "total": 481, - "percentage": 86.07 + "percentage": 86.69 }, "branches": { - "covered": 38, - "total": 59, - "percentage": 64.4 + "covered": 42, + "total": 62, + "percentage": 67.74 }, "functions": { "covered": 17, @@ -5553,21 +6455,21 @@ "percentage": 89.47 }, "lines": { - "covered": 414, + "covered": 417, "total": 481, - "percentage": 86.07 + "percentage": 86.69 } }, "src/vs/platform/agentHost/node/codex/codexClientCustomizations.ts": { "statements": { - "covered": 302, + "covered": 308, "total": 372, - "percentage": 81.18 + "percentage": 82.79 }, "branches": { - "covered": 32, - "total": 63, - "percentage": 50.79 + "covered": 37, + "total": 70, + "percentage": 52.85 }, "functions": { "covered": 20, @@ -5575,31 +6477,31 @@ "percentage": 83.33 }, "lines": { - "covered": 302, + "covered": 308, "total": 372, - "percentage": 81.18 + "percentage": 82.79 } }, "src/vs/platform/agentHost/node/codex/codexCustomizations.ts": { "statements": { - "covered": 213, - "total": 286, - "percentage": 74.47 + "covered": 287, + "total": 335, + "percentage": 85.67 }, "branches": { - "covered": 17, - "total": 35, - "percentage": 48.57 + "covered": 33, + "total": 52, + "percentage": 63.46 }, "functions": { - "covered": 8, - "total": 9, - "percentage": 88.88 + "covered": 9, + "total": 10, + "percentage": 90 }, "lines": { - "covered": 213, - "total": 286, - "percentage": 74.47 + "covered": 287, + "total": 335, + "percentage": 85.67 } }, "src/vs/platform/agentHost/node/codex/codexDelegation.ts": { @@ -5670,24 +6572,24 @@ }, "src/vs/platform/agentHost/node/codex/codexForkPlan.ts": { "statements": { - "covered": 54, + "covered": 80, "total": 86, - "percentage": 62.79 + "percentage": 93.02 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 3, + "total": 7, + "percentage": 42.85 }, "functions": { - "covered": 0, + "covered": 2, "total": 2, - "percentage": 0 + "percentage": 100 }, "lines": { - "covered": 54, + "covered": 80, "total": 86, - "percentage": 62.79 + "percentage": 93.02 } }, "src/vs/platform/agentHost/node/codex/codexGuardianReview.ts": { @@ -5719,9 +6621,9 @@ "percentage": 73.84 }, "branches": { - "covered": 5, - "total": 18, - "percentage": 27.77 + "covered": 7, + "total": 19, + "percentage": 36.84 }, "functions": { "covered": 3, @@ -5736,24 +6638,24 @@ }, "src/vs/platform/agentHost/node/codex/codexMapAppServerEvents.ts": { "statements": { - "covered": 657, + "covered": 667, "total": 1281, - "percentage": 51.28 + "percentage": 52.06 }, "branches": { - "covered": 61, - "total": 116, - "percentage": 52.58 + "covered": 70, + "total": 126, + "percentage": 55.55 }, "functions": { - "covered": 19, + "covered": 20, "total": 43, - "percentage": 44.18 + "percentage": 46.51 }, "lines": { - "covered": 657, + "covered": 667, "total": 1281, - "percentage": 51.28 + "percentage": 52.06 } }, "src/vs/platform/agentHost/node/codex/codexMcpServers.ts": { @@ -5763,9 +6665,9 @@ "percentage": 83.44 }, "branches": { - "covered": 44, - "total": 61, - "percentage": 72.13 + "covered": 50, + "total": 67, + "percentage": 74.62 }, "functions": { "covered": 20, @@ -5778,16 +6680,38 @@ "percentage": 83.44 } }, + "src/vs/platform/agentHost/node/codex/codexProfileImage.ts": { + "statements": { + "covered": 87, + "total": 363, + "percentage": 23.96 + }, + "branches": { + "covered": 0, + "total": 0, + "percentage": 100 + }, + "functions": { + "covered": 0, + "total": 20, + "percentage": 0 + }, + "lines": { + "covered": 87, + "total": 363, + "percentage": 23.96 + } + }, "src/vs/platform/agentHost/node/codex/codexPromptResolver.ts": { "statements": { - "covered": 79, - "total": 194, - "percentage": 40.72 + "covered": 121, + "total": 206, + "percentage": 58.73 }, "branches": { - "covered": 2, - "total": 4, - "percentage": 50 + "covered": 11, + "total": 17, + "percentage": 64.7 }, "functions": { "covered": 1, @@ -5795,9 +6719,9 @@ "percentage": 25 }, "lines": { - "covered": 79, - "total": 194, - "percentage": 40.72 + "covered": 121, + "total": 206, + "percentage": 58.73 } }, "src/vs/platform/agentHost/node/codex/codexProviderConfiguration.ts": { @@ -5824,14 +6748,14 @@ }, "src/vs/platform/agentHost/node/codex/codexProxyService.ts": { "statements": { - "covered": 390, + "covered": 393, "total": 484, - "percentage": 80.57 + "percentage": 81.19 }, "branches": { - "covered": 33, - "total": 79, - "percentage": 41.77 + "covered": 36, + "total": 82, + "percentage": 43.9 }, "functions": { "covered": 11, @@ -5839,43 +6763,43 @@ "percentage": 78.57 }, "lines": { - "covered": 390, + "covered": 393, "total": 484, - "percentage": 80.57 + "percentage": 81.19 } }, "src/vs/platform/agentHost/node/codex/codexReplayMapper.ts": { "statements": { - "covered": 79, - "total": 378, - "percentage": 20.89 + "covered": 178, + "total": 380, + "percentage": 46.84 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 6, + "total": 26, + "percentage": 23.07 }, "functions": { - "covered": 0, - "total": 12, - "percentage": 0 + "covered": 4, + "total": 13, + "percentage": 30.76 }, "lines": { - "covered": 79, - "total": 378, - "percentage": 20.89 + "covered": 178, + "total": 380, + "percentage": 46.84 } }, "src/vs/platform/agentHost/node/codex/codexRolloutMetadata.ts": { "statements": { - "covered": 117, + "covered": 125, "total": 159, - "percentage": 73.58 + "percentage": 78.61 }, "branches": { - "covered": 25, - "total": 34, - "percentage": 73.52 + "covered": 30, + "total": 36, + "percentage": 83.33 }, "functions": { "covered": 5, @@ -5883,9 +6807,9 @@ "percentage": 100 }, "lines": { - "covered": 117, + "covered": 125, "total": 159, - "percentage": 73.58 + "percentage": 78.61 } }, "src/vs/platform/agentHost/node/codex/codexSessionConfigKeys.ts": { @@ -5895,9 +6819,9 @@ "percentage": 87.87 }, "branches": { - "covered": 22, - "total": 44, - "percentage": 50 + "covered": 25, + "total": 46, + "percentage": 54.34 }, "functions": { "covered": 11, @@ -5912,14 +6836,14 @@ }, "src/vs/platform/agentHost/node/codex/codexSessionMetadataStore.ts": { "statements": { - "covered": 230, + "covered": 233, "total": 256, - "percentage": 89.84 + "percentage": 91.01 }, "branches": { - "covered": 20, - "total": 37, - "percentage": 54.05 + "covered": 32, + "total": 47, + "percentage": 68.08 }, "functions": { "covered": 7, @@ -5927,21 +6851,21 @@ "percentage": 100 }, "lines": { - "covered": 230, + "covered": 233, "total": 256, - "percentage": 89.84 + "percentage": 91.01 } }, "src/vs/platform/agentHost/node/codex/codexShellCommand.ts": { "statements": { - "covered": 36, + "covered": 38, "total": 42, - "percentage": 85.71 + "percentage": 90.47 }, "branches": { - "covered": 2, - "total": 6, - "percentage": 33.33 + "covered": 7, + "total": 9, + "percentage": 77.77 }, "functions": { "covered": 2, @@ -5949,31 +6873,31 @@ "percentage": 100 }, "lines": { - "covered": 36, + "covered": 38, "total": 42, - "percentage": 85.71 + "percentage": 90.47 } }, "src/vs/platform/agentHost/node/codex/codexThreadCoordination.ts": { "statements": { - "covered": 42, + "covered": 67, "total": 189, - "percentage": 22.22 + "percentage": 35.44 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 1, + "total": 9, + "percentage": 11.11 }, "functions": { - "covered": 0, + "covered": 1, "total": 8, - "percentage": 0 + "percentage": 12.5 }, "lines": { - "covered": 42, + "covered": 67, "total": 189, - "percentage": 22.22 + "percentage": 35.44 } }, "src/vs/platform/agentHost/node/codex/codexThreadList.ts": { @@ -6000,24 +6924,24 @@ }, "src/vs/platform/agentHost/node/codex/codexUserInputMapper.ts": { "statements": { - "covered": 32, + "covered": 78, "total": 88, - "percentage": 36.36 + "percentage": 88.63 }, "branches": { - "covered": 0, - "total": 0, - "percentage": 100 + "covered": 12, + "total": 18, + "percentage": 66.66 }, "functions": { - "covered": 0, + "covered": 4, "total": 4, - "percentage": 0 + "percentage": 100 }, "lines": { - "covered": 32, + "covered": 78, "total": 88, - "percentage": 36.36 + "percentage": 88.63 } }, "src/vs/platform/agentHost/node/codexCompactCommand.ts": { @@ -6044,14 +6968,14 @@ }, "src/vs/platform/agentHost/node/commandAutoApprover.ts": { "statements": { - "covered": 567, + "covered": 563, "total": 704, - "percentage": 80.53 + "percentage": 79.97 }, "branches": { - "covered": 39, - "total": 82, - "percentage": 47.56 + "covered": 29, + "total": 76, + "percentage": 38.15 }, "functions": { "covered": 18, @@ -6059,9 +6983,9 @@ "percentage": 85.71 }, "lines": { - "covered": 567, + "covered": 563, "total": 704, - "percentage": 80.53 + "percentage": 79.97 } }, "src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts": { @@ -6154,68 +7078,68 @@ }, "src/vs/platform/agentHost/node/copilot/copilotAgent.ts": { "statements": { - "covered": 4880, - "total": 6477, - "percentage": 75.34 + "covered": 5083, + "total": 6832, + "percentage": 74.39 }, "branches": { - "covered": 805, - "total": 1225, - "percentage": 65.71 + "covered": 855, + "total": 1270, + "percentage": 67.32 }, "functions": { - "covered": 275, - "total": 335, - "percentage": 82.08 + "covered": 280, + "total": 349, + "percentage": 80.22 }, "lines": { - "covered": 4880, - "total": 6477, - "percentage": 75.34 + "covered": 5083, + "total": 6832, + "percentage": 74.39 } }, "src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts": { "statements": { - "covered": 4460, - "total": 6062, - "percentage": 73.57 + "covered": 4616, + "total": 6392, + "percentage": 72.21 }, "branches": { - "covered": 822, - "total": 1183, - "percentage": 69.48 + "covered": 873, + "total": 1274, + "percentage": 68.52 }, "functions": { - "covered": 197, - "total": 244, - "percentage": 80.73 + "covered": 201, + "total": 252, + "percentage": 79.76 }, "lines": { - "covered": 4460, - "total": 6062, - "percentage": 73.57 + "covered": 4616, + "total": 6392, + "percentage": 72.21 } }, "src/vs/platform/agentHost/node/copilot/copilotAgentStartupConfig.ts": { "statements": { - "covered": 38, + "covered": 45, "total": 45, - "percentage": 84.44 + "percentage": 100 }, "branches": { - "covered": 6, - "total": 6, - "percentage": 100 + "covered": 10, + "total": 12, + "percentage": 83.33 }, "functions": { - "covered": 3, + "covered": 5, "total": 5, - "percentage": 60 + "percentage": 100 }, "lines": { - "covered": 38, + "covered": 45, "total": 45, - "percentage": 84.44 + "percentage": 100 } }, "src/vs/platform/agentHost/node/copilot/copilotAttachmentUtils.ts": { @@ -6242,14 +7166,14 @@ }, "src/vs/platform/agentHost/node/copilot/copilotCliEnvironment.ts": { "statements": { - "covered": 32, - "total": 32, + "covered": 40, + "total": 40, "percentage": 100 }, "branches": { - "covered": 7, - "total": 7, - "percentage": 100 + "covered": 8, + "total": 9, + "percentage": 88.88 }, "functions": { "covered": 1, @@ -6257,8 +7181,8 @@ "percentage": 100 }, "lines": { - "covered": 32, - "total": 32, + "covered": 40, + "total": 40, "percentage": 100 } }, @@ -6286,14 +7210,14 @@ }, "src/vs/platform/agentHost/node/copilot/copilotGitHubTelemetryForwarder.ts": { "statements": { - "covered": 250, - "total": 256, - "percentage": 97.65 + "covered": 243, + "total": 247, + "percentage": 98.38 }, "branches": { "covered": 5, - "total": 10, - "percentage": 50 + "total": 9, + "percentage": 55.55 }, "functions": { "covered": 2, @@ -6301,9 +7225,9 @@ "percentage": 100 }, "lines": { - "covered": 250, - "total": 256, - "percentage": 97.65 + "covered": 243, + "total": 247, + "percentage": 98.38 } }, "src/vs/platform/agentHost/node/copilot/copilotGitProject.ts": { @@ -6352,24 +7276,24 @@ }, "src/vs/platform/agentHost/node/copilot/copilotPluginConverters.ts": { "statements": { - "covered": 375, - "total": 520, - "percentage": 72.11 + "covered": 385, + "total": 530, + "percentage": 72.64 }, "branches": { - "covered": 42, - "total": 78, - "percentage": 53.84 + "covered": 44, + "total": 81, + "percentage": 54.32 }, "functions": { - "covered": 19, - "total": 25, - "percentage": 76 + "covered": 20, + "total": 26, + "percentage": 76.92 }, "lines": { - "covered": 375, - "total": 520, - "percentage": 72.11 + "covered": 385, + "total": 530, + "percentage": 72.64 } }, "src/vs/platform/agentHost/node/copilot/copilotSdkChatError.ts": { @@ -6380,8 +7304,8 @@ }, "branches": { "covered": 3, - "total": 16, - "percentage": 18.75 + "total": 15, + "percentage": 20 }, "functions": { "covered": 3, @@ -6394,48 +7318,70 @@ "percentage": 90.62 } }, + "src/vs/platform/agentHost/node/copilot/copilotSecondaryAssignmentContext.ts": { + "statements": { + "covered": 28, + "total": 31, + "percentage": 90.32 + }, + "branches": { + "covered": 2, + "total": 5, + "percentage": 40 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 28, + "total": 31, + "percentage": 90.32 + } + }, "src/vs/platform/agentHost/node/copilot/copilotSessionLauncher.ts": { "statements": { - "covered": 701, - "total": 906, - "percentage": 77.37 + "covered": 810, + "total": 1034, + "percentage": 78.33 }, "branches": { - "covered": 91, - "total": 136, - "percentage": 66.91 + "covered": 100, + "total": 154, + "percentage": 64.93 }, "functions": { - "covered": 35, - "total": 47, - "percentage": 74.46 + "covered": 42, + "total": 52, + "percentage": 80.76 }, "lines": { - "covered": 701, - "total": 906, - "percentage": 77.37 + "covered": 810, + "total": 1034, + "percentage": 78.33 } }, "src/vs/platform/agentHost/node/copilot/copilotSessionWrapper.ts": { "statements": { - "covered": 324, - "total": 338, - "percentage": 95.85 + "covered": 329, + "total": 343, + "percentage": 95.91 }, "branches": { - "covered": 66, - "total": 66, + "covered": 67, + "total": 67, "percentage": 100 }, "functions": { - "covered": 56, - "total": 58, - "percentage": 96.55 + "covered": 57, + "total": 59, + "percentage": 96.61 }, "lines": { - "covered": 324, - "total": 338, - "percentage": 95.85 + "covered": 329, + "total": 343, + "percentage": 95.91 } }, "src/vs/platform/agentHost/node/copilot/copilotShellTools.ts": { @@ -6463,8 +7409,8 @@ "src/vs/platform/agentHost/node/copilot/copilotSlashCommandCompletionProvider.ts": { "statements": { "covered": 100, - "total": 270, - "percentage": 37.03 + "total": 271, + "percentage": 36.9 }, "branches": { "covered": 2, @@ -6478,8 +7424,8 @@ }, "lines": { "covered": 100, - "total": 270, - "percentage": 37.03 + "total": 271, + "percentage": 36.9 } }, "src/vs/platform/agentHost/node/copilot/copilotSlashCommandProvider.ts": { @@ -6550,36 +7496,36 @@ }, "src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts": { "statements": { - "covered": 977, - "total": 1214, - "percentage": 80.47 + "covered": 1008, + "total": 1258, + "percentage": 80.12 }, "branches": { - "covered": 165, - "total": 278, - "percentage": 59.35 + "covered": 168, + "total": 283, + "percentage": 59.36 }, "functions": { - "covered": 27, - "total": 35, - "percentage": 77.14 + "covered": 29, + "total": 37, + "percentage": 78.37 }, "lines": { - "covered": 977, - "total": 1214, - "percentage": 80.47 + "covered": 1008, + "total": 1258, + "percentage": 80.12 } }, "src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts": { "statements": { - "covered": 672, - "total": 956, - "percentage": 70.29 + "covered": 694, + "total": 1021, + "percentage": 67.97 }, "branches": { - "covered": 85, - "total": 166, - "percentage": 51.2 + "covered": 93, + "total": 186, + "percentage": 50 }, "functions": { "covered": 21, @@ -6587,30 +7533,52 @@ "percentage": 95.45 }, "lines": { - "covered": 672, - "total": 956, - "percentage": 70.29 + "covered": 694, + "total": 1021, + "percentage": 67.97 + } + }, + "src/vs/platform/agentHost/node/copilot/modelCallTurnCorrelation.ts": { + "statements": { + "covered": 60, + "total": 69, + "percentage": 86.95 + }, + "branches": { + "covered": 8, + "total": 13, + "percentage": 61.53 + }, + "functions": { + "covered": 5, + "total": 5, + "percentage": 100 + }, + "lines": { + "covered": 60, + "total": 69, + "percentage": 86.95 } }, "src/vs/platform/agentHost/node/copilot/modelIdentifiers.ts": { "statements": { - "covered": 14, - "total": 14, + "covered": 19, + "total": 19, "percentage": 100 }, "branches": { - "covered": 2, - "total": 2, + "covered": 3, + "total": 3, "percentage": 100 }, "functions": { - "covered": 1, - "total": 1, + "covered": 2, + "total": 2, "percentage": 100 }, "lines": { - "covered": 14, - "total": 14, + "covered": 19, + "total": 19, "percentage": 100 } }, @@ -6682,24 +7650,24 @@ }, "src/vs/platform/agentHost/node/copilot/prompts/promptRegistry.ts": { "statements": { - "covered": 206, - "total": 229, - "percentage": 89.95 + "covered": 210, + "total": 233, + "percentage": 90.12 }, "branches": { - "covered": 13, - "total": 23, - "percentage": 56.52 + "covered": 14, + "total": 24, + "percentage": 58.33 }, "functions": { - "covered": 7, - "total": 7, + "covered": 9, + "total": 9, "percentage": 100 }, "lines": { - "covered": 206, - "total": 229, - "percentage": 89.95 + "covered": 210, + "total": 233, + "percentage": 90.12 } }, "src/vs/platform/agentHost/node/copilot/prompts/systemMessage.ts": { @@ -6726,24 +7694,24 @@ }, "src/vs/platform/agentHost/node/copilot/prompts/toolInstructions.ts": { "statements": { - "covered": 119, - "total": 138, - "percentage": 86.23 + "covered": 144, + "total": 163, + "percentage": 88.34 }, "branches": { - "covered": 8, - "total": 19, - "percentage": 42.1 + "covered": 9, + "total": 21, + "percentage": 42.85 }, "functions": { - "covered": 6, - "total": 7, - "percentage": 85.71 + "covered": 7, + "total": 8, + "percentage": 87.5 }, "lines": { - "covered": 119, - "total": 138, - "percentage": 86.23 + "covered": 144, + "total": 163, + "percentage": 88.34 } }, "src/vs/platform/agentHost/node/copilot/sandboxConfigForSdk.ts": { @@ -6770,14 +7738,14 @@ }, "src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts": { "statements": { - "covered": 1151, + "covered": 1144, "total": 1279, - "percentage": 89.99 + "percentage": 89.44 }, "branches": { - "covered": 229, - "total": 281, - "percentage": 81.49 + "covered": 230, + "total": 284, + "percentage": 80.98 }, "functions": { "covered": 38, @@ -6785,16 +7753,16 @@ "percentage": 97.43 }, "lines": { - "covered": 1151, + "covered": 1144, "total": 1279, - "percentage": 89.99 + "percentage": 89.44 } }, "src/vs/platform/agentHost/node/copilot/toolSearchDeferral.ts": { "statements": { - "covered": 26, - "total": 45, - "percentage": 57.77 + "covered": 23, + "total": 42, + "percentage": 54.76 }, "branches": { "covered": 0, @@ -6807,9 +7775,9 @@ "percentage": 0 }, "lines": { - "covered": 26, - "total": 45, - "percentage": 57.77 + "covered": 23, + "total": 42, + "percentage": 54.76 } }, "src/vs/platform/agentHost/node/diffComputeService.ts": { @@ -6841,9 +7809,9 @@ "percentage": 54.02 }, "branches": { - "covered": 14, - "total": 19, - "percentage": 73.68 + "covered": 15, + "total": 20, + "percentage": 75 }, "functions": { "covered": 4, @@ -6902,14 +7870,14 @@ }, "src/vs/platform/agentHost/node/localCommands/localChatCommand.ts": { "statements": { - "covered": 244, - "total": 255, - "percentage": 95.68 + "covered": 239, + "total": 250, + "percentage": 95.6 }, "branches": { - "covered": 28, + "covered": 27, "total": 36, - "percentage": 77.77 + "percentage": 75 }, "functions": { "covered": 13, @@ -6917,9 +7885,9 @@ "percentage": 100 }, "lines": { - "covered": 244, - "total": 255, - "percentage": 95.68 + "covered": 239, + "total": 250, + "percentage": 95.6 } }, "src/vs/platform/agentHost/node/localCommands/localChatCommands.contribution.ts": { @@ -6973,9 +7941,9 @@ "percentage": 94.89 }, "branches": { - "covered": 31, - "total": 38, - "percentage": 81.57 + "covered": 30, + "total": 37, + "percentage": 81.08 }, "functions": { "covered": 8, @@ -7034,24 +8002,24 @@ }, "src/vs/platform/agentHost/node/protocolServerHandler.ts": { "statements": { - "covered": 1704, - "total": 1947, - "percentage": 87.51 + "covered": 1803, + "total": 2128, + "percentage": 84.72 }, "branches": { - "covered": 350, - "total": 439, - "percentage": 79.72 + "covered": 375, + "total": 488, + "percentage": 76.84 }, "functions": { - "covered": 85, - "total": 95, - "percentage": 89.47 + "covered": 88, + "total": 96, + "percentage": 91.66 }, "lines": { - "covered": 1704, - "total": 1947, - "percentage": 87.51 + "covered": 1803, + "total": 2128, + "percentage": 84.72 } }, "src/vs/platform/agentHost/node/serverUrls.ts": { @@ -7076,82 +8044,60 @@ "percentage": 50 } }, - "src/vs/platform/agentHost/node/sessionCoordination.ts": { - "statements": { - "covered": 78, - "total": 159, - "percentage": 49.05 - }, - "branches": { - "covered": 10, - "total": 17, - "percentage": 58.82 - }, - "functions": { - "covered": 4, - "total": 6, - "percentage": 66.66 - }, - "lines": { - "covered": 78, - "total": 159, - "percentage": 49.05 - } - }, "src/vs/platform/agentHost/node/sessionDataService.ts": { "statements": { - "covered": 157, - "total": 198, - "percentage": 79.29 + "covered": 166, + "total": 207, + "percentage": 80.19 }, "branches": { - "covered": 22, - "total": 25, - "percentage": 88 + "covered": 27, + "total": 31, + "percentage": 87.09 }, "functions": { - "covered": 12, - "total": 14, - "percentage": 85.71 + "covered": 13, + "total": 15, + "percentage": 86.66 }, "lines": { - "covered": 157, - "total": 198, - "percentage": 79.29 + "covered": 166, + "total": 207, + "percentage": 80.19 } }, "src/vs/platform/agentHost/node/sessionDatabase.ts": { "statements": { - "covered": 772, - "total": 909, - "percentage": 84.92 + "covered": 807, + "total": 946, + "percentage": 85.3 }, "branches": { - "covered": 121, - "total": 150, - "percentage": 80.66 + "covered": 130, + "total": 160, + "percentage": 81.25 }, "functions": { - "covered": 41, - "total": 55, - "percentage": 74.54 + "covered": 43, + "total": 57, + "percentage": 75.43 }, "lines": { - "covered": 772, - "total": 909, - "percentage": 84.92 + "covered": 807, + "total": 946, + "percentage": 85.3 } }, "src/vs/platform/agentHost/node/sessionDiffAggregator.ts": { "statements": { - "covered": 277, + "covered": 271, "total": 507, - "percentage": 54.63 + "percentage": 53.45 }, "branches": { - "covered": 24, - "total": 43, - "percentage": 55.81 + "covered": 21, + "total": 41, + "percentage": 51.21 }, "functions": { "covered": 4, @@ -7159,38 +8105,38 @@ "percentage": 66.66 }, "lines": { - "covered": 277, + "covered": 271, "total": 507, - "percentage": 54.63 + "percentage": 53.45 } }, "src/vs/platform/agentHost/node/sessionPermissions.ts": { "statements": { - "covered": 549, - "total": 707, - "percentage": 77.65 + "covered": 573, + "total": 760, + "percentage": 75.39 }, "branches": { - "covered": 94, - "total": 137, - "percentage": 68.61 + "covered": 85, + "total": 132, + "percentage": 64.39 }, "functions": { - "covered": 24, - "total": 29, - "percentage": 82.75 + "covered": 25, + "total": 30, + "percentage": 83.33 }, "lines": { - "covered": 549, - "total": 707, - "percentage": 77.65 + "covered": 573, + "total": 760, + "percentage": 75.39 } }, "src/vs/platform/agentHost/node/shared/agentBranchNameGenerator.ts": { "statements": { - "covered": 129, - "total": 169, - "percentage": 76.33 + "covered": 134, + "total": 174, + "percentage": 77.01 }, "branches": { "covered": 7, @@ -7203,9 +8149,9 @@ "percentage": 85.71 }, "lines": { - "covered": 129, - "total": 169, - "percentage": 76.33 + "covered": 134, + "total": 174, + "percentage": 77.01 } }, "src/vs/platform/agentHost/node/shared/agentEditAttributionService.ts": { @@ -7237,9 +8183,9 @@ "percentage": 89.82 }, "branches": { - "covered": 60, - "total": 98, - "percentage": 61.22 + "covered": 61, + "total": 99, + "percentage": 61.61 }, "functions": { "covered": 26, @@ -7281,9 +8227,9 @@ "percentage": 48 }, "branches": { - "covered": 2, - "total": 3, - "percentage": 66.66 + "covered": 4, + "total": 4, + "percentage": 100 }, "functions": { "covered": 2, @@ -7298,24 +8244,24 @@ }, "src/vs/platform/agentHost/node/shared/agentServerToolHost.ts": { "statements": { - "covered": 192, - "total": 203, - "percentage": 94.58 + "covered": 247, + "total": 258, + "percentage": 95.73 }, "branches": { - "covered": 23, - "total": 31, - "percentage": 74.19 + "covered": 44, + "total": 53, + "percentage": 83.01 }, "functions": { - "covered": 11, - "total": 11, + "covered": 12, + "total": 12, "percentage": 100 }, "lines": { - "covered": 192, - "total": 203, - "percentage": 94.58 + "covered": 247, + "total": 258, + "percentage": 95.73 } }, "src/vs/platform/agentHost/node/shared/arcToolEdit.ts": { @@ -7342,24 +8288,24 @@ }, "src/vs/platform/agentHost/node/shared/artifactServerTools.ts": { "statements": { - "covered": 108, - "total": 175, - "percentage": 61.71 + "covered": 198, + "total": 209, + "percentage": 94.73 }, "branches": { - "covered": 2, - "total": 2, - "percentage": 100 + "covered": 38, + "total": 51, + "percentage": 74.5 }, "functions": { - "covered": 2, - "total": 8, - "percentage": 25 + "covered": 9, + "total": 9, + "percentage": 100 }, "lines": { - "covered": 108, - "total": 175, - "percentage": 61.71 + "covered": 198, + "total": 209, + "percentage": 94.73 } }, "src/vs/platform/agentHost/node/shared/copilotApiService.ts": { @@ -7369,9 +8315,9 @@ "percentage": 87.31 }, "branches": { - "covered": 52, - "total": 91, - "percentage": 57.14 + "covered": 59, + "total": 97, + "percentage": 60.82 }, "functions": { "covered": 25, @@ -7391,9 +8337,9 @@ "percentage": 88.64 }, "branches": { - "covered": 53, - "total": 59, - "percentage": 89.83 + "covered": 51, + "total": 57, + "percentage": 89.47 }, "functions": { "covered": 8, @@ -7474,14 +8420,14 @@ }, "src/vs/platform/agentHost/node/shared/editSurvivalTracker.ts": { "statements": { - "covered": 209, + "covered": 205, "total": 236, - "percentage": 88.55 + "percentage": 86.86 }, "branches": { - "covered": 21, - "total": 28, - "percentage": 75 + "covered": 18, + "total": 26, + "percentage": 69.23 }, "functions": { "covered": 6, @@ -7489,21 +8435,21 @@ "percentage": 75 }, "lines": { - "covered": 209, + "covered": 205, "total": 236, - "percentage": 88.55 + "percentage": 86.86 } }, "src/vs/platform/agentHost/node/shared/fileEditTracker.ts": { "statements": { - "covered": 239, + "covered": 237, "total": 253, - "percentage": 94.46 + "percentage": 93.67 }, "branches": { - "covered": 35, - "total": 41, - "percentage": 85.36 + "covered": 32, + "total": 39, + "percentage": 82.05 }, "functions": { "covered": 7, @@ -7511,9 +8457,9 @@ "percentage": 100 }, "lines": { - "covered": 239, + "covered": 237, "total": 253, - "percentage": 94.46 + "percentage": 93.67 } }, "src/vs/platform/agentHost/node/shared/folderPickerDecision.ts": { @@ -7562,14 +8508,14 @@ }, "src/vs/platform/agentHost/node/shared/loopbackProxyServer.ts": { "statements": { - "covered": 286, + "covered": 287, "total": 332, - "percentage": 86.14 + "percentage": 86.44 }, "branches": { - "covered": 22, - "total": 32, - "percentage": 68.75 + "covered": 23, + "total": 34, + "percentage": 67.64 }, "functions": { "covered": 10, @@ -7577,31 +8523,31 @@ "percentage": 76.92 }, "lines": { - "covered": 286, + "covered": 287, "total": 332, - "percentage": 86.14 + "percentage": 86.44 } }, "src/vs/platform/agentHost/node/shared/mcpCustomizationController.ts": { "statements": { - "covered": 487, + "covered": 506, "total": 586, - "percentage": 83.1 + "percentage": 86.34 }, "branches": { - "covered": 105, - "total": 122, - "percentage": 86.06 + "covered": 110, + "total": 129, + "percentage": 85.27 }, "functions": { - "covered": 26, + "covered": 28, "total": 32, - "percentage": 81.25 + "percentage": 87.5 }, "lines": { - "covered": 487, + "covered": 506, "total": 586, - "percentage": 83.1 + "percentage": 86.34 } }, "src/vs/platform/agentHost/node/shared/mcpServerWorkingDirectory.ts": { @@ -7626,6 +8572,28 @@ "percentage": 90 } }, + "src/vs/platform/agentHost/node/shared/modelRefreshRetry.ts": { + "statements": { + "covered": 13, + "total": 16, + "percentage": 81.25 + }, + "branches": { + "covered": 0, + "total": 0, + "percentage": 100 + }, + "functions": { + "covered": 0, + "total": 1, + "percentage": 0 + }, + "lines": { + "covered": 13, + "total": 16, + "percentage": 81.25 + } + }, "src/vs/platform/agentHost/node/shared/persistSessionMetadata.ts": { "statements": { "covered": 57, @@ -7672,14 +8640,14 @@ }, "src/vs/platform/agentHost/node/shared/serverToolGroups.ts": { "statements": { - "covered": 77, - "total": 79, - "percentage": 97.46 + "covered": 85, + "total": 91, + "percentage": 93.4 }, "branches": { - "covered": 9, - "total": 10, - "percentage": 90 + "covered": 13, + "total": 16, + "percentage": 81.25 }, "functions": { "covered": 3, @@ -7687,21 +8655,21 @@ "percentage": 100 }, "lines": { - "covered": 77, - "total": 79, - "percentage": 97.46 + "covered": 85, + "total": 91, + "percentage": 93.4 } }, "src/vs/platform/agentHost/node/shared/sessionMcpDiscovery.ts": { "statements": { - "covered": 162, + "covered": 166, "total": 200, - "percentage": 81 + "percentage": 83 }, "branches": { - "covered": 27, - "total": 33, - "percentage": 81.81 + "covered": 33, + "total": 38, + "percentage": 86.84 }, "functions": { "covered": 10, @@ -7709,31 +8677,31 @@ "percentage": 100 }, "lines": { - "covered": 162, + "covered": 166, "total": 200, - "percentage": 81 + "percentage": 83 } }, "src/vs/platform/agentHost/node/shared/sessionServerTools.ts": { "statements": { - "covered": 1112, - "total": 1400, - "percentage": 79.42 + "covered": 1214, + "total": 1439, + "percentage": 84.36 }, "branches": { - "covered": 160, - "total": 276, - "percentage": 57.97 + "covered": 208, + "total": 347, + "percentage": 59.94 }, "functions": { - "covered": 49, - "total": 60, - "percentage": 81.66 + "covered": 56, + "total": 62, + "percentage": 90.32 }, "lines": { - "covered": 1112, - "total": 1400, - "percentage": 79.42 + "covered": 1214, + "total": 1439, + "percentage": 84.36 } }, "src/vs/platform/agentHost/node/shared/shellCommandExecution.ts": { @@ -7758,26 +8726,48 @@ "percentage": 61.82 } }, + "src/vs/platform/agentHost/node/shared/toolCallContributor.ts": { + "statements": { + "covered": 48, + "total": 49, + "percentage": 97.95 + }, + "branches": { + "covered": 10, + "total": 11, + "percentage": 90.9 + }, + "functions": { + "covered": 2, + "total": 2, + "percentage": 100 + }, + "lines": { + "covered": 48, + "total": 49, + "percentage": 97.95 + } + }, "src/vs/platform/agentHost/node/shared/worktreeIsolation.ts": { "statements": { - "covered": 908, - "total": 1234, - "percentage": 73.58 + "covered": 1191, + "total": 1529, + "percentage": 77.89 }, "branches": { - "covered": 96, - "total": 163, - "percentage": 58.89 + "covered": 160, + "total": 238, + "percentage": 67.22 }, "functions": { - "covered": 39, - "total": 52, - "percentage": 75 + "covered": 49, + "total": 87, + "percentage": 56.32 }, "lines": { - "covered": 908, - "total": 1234, - "percentage": 73.58 + "covered": 1191, + "total": 1529, + "percentage": 77.89 } }, "src/vs/platform/agentHost/node/webSocketTransport.ts": { @@ -7804,24 +8794,24 @@ }, "src/vs/platform/agentHost/node/workspacelessScratchDir.ts": { "statements": { - "covered": 20, - "total": 25, - "percentage": 80 + "covered": 16, + "total": 21, + "percentage": 76.19 }, "branches": { - "covered": 1, - "total": 1, + "covered": 0, + "total": 0, "percentage": 100 }, "functions": { - "covered": 1, - "total": 2, - "percentage": 50 + "covered": 0, + "total": 1, + "percentage": 0 }, "lines": { - "covered": 20, - "total": 25, - "percentage": 80 + "covered": 16, + "total": 21, + "percentage": 76.19 } } } diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/agentHostE2ESuites.ts b/src/vs/platform/agentHost/test/node/e2e/suites/agentHostE2ESuites.ts index 424b794d0c5b6c..3aefe4bf299509 100644 --- a/src/vs/platform/agentHost/test/node/e2e/suites/agentHostE2ESuites.ts +++ b/src/vs/platform/agentHost/test/node/e2e/suites/agentHostE2ESuites.ts @@ -25,6 +25,8 @@ import { defineTurnLifecycleTests } from './turnLifecycleSuite.js'; import { defineWorkspaceTests } from './workspaceSuite.js'; import { defineCopilotCoverageTests } from './copilotCoverageSuite.js'; import { defineManagementExtensionTests } from './managementExtensionsSuite.js'; +import { defineAutomationsTests } from './automationsSuite.js'; +import { defineDetachedWorktreeTests } from './detachedWorktreeSuite.js'; import type { AgentHostE2ETier, IAgentHostE2ETestContext } from './e2eTestContext.js'; const isLinux = process.platform === 'linux'; @@ -146,12 +148,14 @@ function defineSuite(config: IAgentHostE2EProviderConfig, options: IDefineOption // Suites that contain only conformance-tier scenarios. if (options.tier === 'conformance') { + defineAutomationsTests(context); defineHostFeaturesTests(context); defineStateOperationsTests(context); defineClientFilesystemTests(context); defineClientHostedFilesystemTests(context); defineAnnotationsTests(context); defineProtocolContractTests(context); + defineDetachedWorktreeTests(context); } // Suites that contain only parity-tier scenarios. diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/automationsSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/automationsSuite.ts new file mode 100644 index 00000000000000..f5e065f088b381 --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/suites/automationsSuite.ts @@ -0,0 +1,385 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { equals } from '../../../../../../base/common/objects.js'; +import { generateUuid } from '../../../../../../base/common/uuid.js'; +import { AGENT_HOST_AUTOMATIONS_ENABLED_CONFIG_KEY, AGENT_HOST_AUTOMATION_MIGRATION_CONFIG_KEY } from '../../../../common/automationMigration.js'; +import type { FetchAutomationRunsResult, InitializeResult, ListAutomationTriggerDefinitionsResult, SubscribeResult } from '../../../../common/state/protocol/commands.js'; +import { AutomationOperation, type AutomationDefinition, type AutomationEntry } from '../../../../common/state/protocol/state.js'; +import { PROTOCOL_VERSION } from '../../../../common/state/protocol/version/registry.js'; +import { ActionType, type AutomationRemovedAction, type AutomationSetAction } from '../../../../common/state/sessionActions.js'; +import type { AhpNotification } from '../../../../common/state/sessionProtocol.js'; +import { AUTOMATION_CATALOG_URI, MessageKind, ROOT_STATE_URI, type AutomationState, type RootState } from '../../../../common/state/sessionState.js'; +import { getActionEnvelope, isActionNotification } from '../../serverIntegrationTestHelpers.js'; +import { conformanceTest, type IAgentHostE2ETestContext } from './e2eTestContext.js'; + +/** The migration gate's message, checked before the enablement gate's. */ +const MIGRATION_REQUIRED_MESSAGE = 'Automation migration must complete before automations can be accessed or run.'; +const AUTOMATIONS_DISABLED_MESSAGE = 'Automations are disabled.'; +/** Mirrors the host's advertised `runHistoryLimit`. */ +const RUN_HISTORY_LIMIT = 50; + +const UNGATED_OPERATIONS = [AutomationOperation.Update, AutomationOperation.Remove]; +const GATED_OPERATIONS = [AutomationOperation.Update, AutomationOperation.Remove, AutomationOperation.Run]; + +/** + * The host-owned automation catalogue, exercised entirely over AHP. + * + * Everything here stays on the host side of the model boundary: an automation + * is only a durable definition until something starts a run, and no test here + * runs one. Every definition is therefore manual-only (`triggers: []`), which + * also keeps the host's cron scheduler — which reads the real clock and has no + * injectable seam — out of the suite. + */ +export function defineAutomationsTests(context: IAgentHostE2ETestContext): void { + const { config } = context; + let clientSeq = 1; + + function nextClientSeq(): number { + return clientSeq++; + } + + async function initializeRoot(prefix: string): Promise { + return context.client.call('initialize', { + channel: ROOT_STATE_URI, + protocolVersions: [PROTOCOL_VERSION], + clientId: `${prefix}-${config.provider}`, + }, 30_000); + } + + async function rootConfigValues(): Promise>> { + const result = await context.client.call('subscribe', { channel: ROOT_STATE_URI }); + return (result.snapshot!.state as RootState).config?.values ?? {}; + } + + /** + * Replaces one root-config value, skipping the dispatch when the host already + * holds it. An unchanged patch is a deliberate no-op in the state manager: it + * emits no action at all, so waiting for the echo would hang. Both automation + * gates are durable for the life of the shared host, so tests re-open them + * defensively and hit that no-op constantly. + */ + async function setRootConfigValue(key: string, value: unknown): Promise { + if (equals((await rootConfigValues())[key], value)) { + return; + } + const seq = nextClientSeq(); + context.client.dispatch({ + channel: ROOT_STATE_URI, + clientSeq: seq, + action: { type: ActionType.RootConfigChanged, config: { [key]: value } }, + }); + await context.client.waitForNotification(notification => + isActionNotification(notification, ActionType.RootConfigChanged) + && getActionEnvelope(notification).channel === ROOT_STATE_URI + && getActionEnvelope(notification).origin?.clientSeq === seq, + ); + } + + function setAutomationsEnabled(enabled: boolean): Promise { + return setRootConfigValue(AGENT_HOST_AUTOMATIONS_ENABLED_CONFIG_KEY, enabled); + } + + /** + * Completes automation migration. The host requires this as an isolated + * root-config patch and refuses it while automations are disabled, so it is + * always dispatched on its own and after {@link setAutomationsEnabled}. + */ + function completeAutomationMigration(): Promise { + return setRootConfigValue(AGENT_HOST_AUTOMATION_MIGRATION_CONFIG_KEY, { version: 1, status: 'complete', resources: [] }); + } + + /** Opens both gates. Idempotent, so each test can stand on its own. */ + async function openAutomationGates(): Promise { + await setAutomationsEnabled(true); + await completeAutomationMigration(); + } + + async function subscribeCatalog(): Promise { + const result = await context.client.call('subscribe', { channel: AUTOMATION_CATALOG_URI }); + return result.snapshot!.state as AutomationState; + } + + function automationResource(prefix: string): string { + return `ahp-automation:/${prefix}-${generateUuid()}`; + } + + function buildDefinition(title: string): AutomationDefinition { + return { + title, + // An automation message must declare an automation origin, and an empty + // session template is enough for a definition that is never run. + message: { text: 'Reply exactly "ran".', origin: { kind: MessageKind.Automation } }, + session: {}, + enabled: false, + triggers: [], + }; + } + + function automationSetFor(resource: string, accept: (automation: AutomationEntry) => boolean): (notification: AhpNotification) => boolean { + return notification => { + if (!isActionNotification(notification, ActionType.AutomationSet) || getActionEnvelope(notification).channel !== AUTOMATION_CATALOG_URI) { + return false; + } + const { automation } = getActionEnvelope(notification).action as AutomationSetAction; + return automation.resource === resource && accept(automation); + }; + } + + /** + * Waits for the authoritative `automation/set` the host publishes after it has + * persisted a mutation. The client's own `automation/createRequested` is never + * echoed back, so this is the only accept signal; a failed mutation instead + * comes back as a rejected envelope carrying the request's action type. + */ + async function waitForAutomationSet(resource: string, accept: (automation: AutomationEntry) => boolean = () => true): Promise { + const notification = await context.client.waitForNotification(automationSetFor(resource, accept)); + return (getActionEnvelope(notification).action as AutomationSetAction).automation; + } + + async function createAutomation(resource: string, definition: AutomationDefinition): Promise { + context.client.dispatch({ + channel: AUTOMATION_CATALOG_URI, + clientSeq: nextClientSeq(), + action: { type: ActionType.AutomationCreateRequested, resource, definition }, + }); + return waitForAutomationSet(resource); + } + + /** The message a failed request reported, or a marker when it unexpectedly succeeded. */ + async function rejectionMessage(request: Promise): Promise { + try { + await request; + return ''; + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + } + + function listTriggerDefinitions(): Promise { + return context.client.call('listAutomationTriggerDefinitions', { channel: ROOT_STATE_URI }); + } + + function entryFor(catalog: AutomationState, resource: string): AutomationEntry | undefined { + return catalog.entries.find(entry => entry.resource === resource); + } + + // Registered first, before anything in this file opens a gate or writes an + // entry: both assertions describe a host that has never had an automation. + conformanceTest(context, 'a fresh agent host advertises the automation catalogue and its capabilities', async function () { + const initialized = await initializeRoot('automations-capabilities'); + + const catalog = await subscribeCatalog(); + + // The catalogue and its commands are advertised before either gate opens: + // a client can always render the (empty) catalogue and author into it. + assert.deepStrictEqual({ + automations: initialized.automations, + entries: catalog.entries, + }, { + automations: { create: {}, schedules: {}, runCancellation: {}, runHistoryLimit: RUN_HISTORY_LIMIT }, + entries: [], + }); + }); + + // Migration completion is durable for the life of the host — including across + // restarts, since it is stored alongside the catalogue — so this is the only + // test that can observe the pre-migration gate. It must stay registered ahead + // of every test that calls `openAutomationGates`. + conformanceTest(context, 'automation commands are rejected until automations are enabled and migration completes', async function () { + await initializeRoot('automations-gates'); + + const beforeAnyGate = await rejectionMessage(listTriggerDefinitions()); + await setAutomationsEnabled(true); + const afterEnabling = await rejectionMessage(listTriggerDefinitions()); + await completeAutomationMigration(); + const afterMigration = await listTriggerDefinitions(); + await setAutomationsEnabled(false); + const afterDisabling = await rejectionMessage(listTriggerDefinitions()); + // Leave the host enabled so a later test does not depend on this one's tail. + await setAutomationsEnabled(true); + + // The migration gate is checked first, so enabling alone changes nothing. + // Once both are open the host answers, and the answer is deliberately + // empty: it defines no event triggers today. + assert.deepStrictEqual({ + beforeAnyGate: beforeAnyGate.includes(MIGRATION_REQUIRED_MESSAGE), + afterEnabling: afterEnabling.includes(MIGRATION_REQUIRED_MESSAGE), + afterMigration, + afterDisabling: afterDisabling.includes(AUTOMATIONS_DISABLED_MESSAGE), + }, { + beforeAnyGate: true, + afterEnabling: true, + afterMigration: { items: [] }, + afterDisabling: true, + }); + }); + + conformanceTest(context, 'an automation created while automations are disabled gains its run operation when they are enabled', async function () { + await initializeRoot('automations-run-grant'); + // Granting `run` needs the enablement flag *and* completed migration. + // Migration cannot be undone on a host that has already migrated, so the + // enablement flag is the half of the gate a test can reproduce. + await openAutomationGates(); + await subscribeCatalog(); + await setAutomationsEnabled(false); + const resource = automationResource('run-grant'); + + context.client.clearReceived(); + const whileDisabled = await createAutomation(resource, buildDefinition('Run grant')); + context.client.clearReceived(); + await setAutomationsEnabled(true); + const afterEnabling = await waitForAutomationSet(resource, automation => automation.operations.includes(AutomationOperation.Run)); + + // The definition is authored either way; only the operations a client may + // offer for it change, and the host republishes the entry to say so. + assert.deepStrictEqual({ + whileDisabled: whileDisabled.operations, + afterEnabling: afterEnabling.operations, + title: afterEnabling.definition.title, + }, { + whileDisabled: UNGATED_OPERATIONS, + afterEnabling: GATED_OPERATIONS, + title: 'Run grant', + }); + }); + + conformanceTest(context, 'creating an automation is idempotent only for an identical definition', async function () { + await initializeRoot('automations-idempotent-create'); + await openAutomationGates(); + await subscribeCatalog(); + const resource = automationResource('idempotent-create'); + const definition = buildDefinition('Stable definition'); + const created = await createAutomation(resource, definition); + + context.client.clearReceived(); + const repeated = await createAutomation(resource, definition); + const conflictingDefinition = buildDefinition('Conflicting definition'); + context.client.clearReceived(); + context.client.dispatch({ + channel: AUTOMATION_CATALOG_URI, + clientSeq: nextClientSeq(), + action: { type: ActionType.AutomationCreateRequested, resource, definition: conflictingDefinition }, + }); + const rejected = await context.client.waitForNotification(notification => + isActionNotification(notification, ActionType.AutomationCreateRequested) + && getActionEnvelope(notification).channel === AUTOMATION_CATALOG_URI + && getActionEnvelope(notification).rejectionReason !== undefined, + ); + + assert.deepStrictEqual({ + createdAtUnchanged: repeated.createdAt === created.createdAt, + modifiedAtUnchanged: repeated.modifiedAt === created.modifiedAt, + title: repeated.definition.title, + rejection: getActionEnvelope(rejected).rejectionReason, + }, { + createdAtUnchanged: true, + modifiedAtUnchanged: true, + title: 'Stable definition', + rejection: `Automation already exists: ${resource}`, + }); + }); + + conformanceTest(context, 'updating and removing an automation keeps the catalogue authoritative', async function () { + await initializeRoot('automations-update-remove'); + await openAutomationGates(); + await subscribeCatalog(); + const resource = automationResource('update-remove'); + await createAutomation(resource, buildDefinition('Original title')); + + context.client.clearReceived(); + context.client.dispatch({ + channel: AUTOMATION_CATALOG_URI, + clientSeq: nextClientSeq(), + action: { type: ActionType.AutomationUpdateRequested, resource, changes: { title: 'Renamed title' } }, + }); + const updated = await waitForAutomationSet(resource); + context.client.clearReceived(); + context.client.dispatch({ + channel: AUTOMATION_CATALOG_URI, + clientSeq: nextClientSeq(), + action: { type: ActionType.AutomationRemoved, resource }, + }); + await context.client.waitForNotification(notification => { + if (!isActionNotification(notification, ActionType.AutomationRemoved) || getActionEnvelope(notification).channel !== AUTOMATION_CATALOG_URI) { + return false; + } + const envelope = getActionEnvelope(notification) as { rejectionReason?: string; action: AutomationRemovedAction }; + return envelope.action.resource === resource && envelope.rejectionReason === undefined; + }); + const catalog = await subscribeCatalog(); + + // A patch replaces only the fields it names, and removal is republished as + // the same action so every subscriber converges on the host's catalogue. + assert.deepStrictEqual({ + updatedTitle: updated.definition.title, + updatedOperations: updated.operations, + updatedRuns: updated.runs, + survivesRemoval: entryFor(catalog, resource) !== undefined, + }, { + updatedTitle: 'Renamed title', + updatedOperations: GATED_OPERATIONS, + updatedRuns: [], + survivesRemoval: false, + }); + }); + + conformanceTest(context, 'fetchAutomationRuns acknowledges an automation that has never run and rejects an unknown one', async function () { + await initializeRoot('automations-fetch-runs'); + await openAutomationGates(); + await subscribeCatalog(); + const resource = automationResource('fetch-runs'); + await createAutomation(resource, buildDefinition('Fetch runs')); + const unknownResource = automationResource('fetch-runs-unknown'); + + const acknowledged = await context.client.call('fetchAutomationRuns', { + channel: AUTOMATION_CATALOG_URI, + automation: resource, + }); + const rejected = await rejectionMessage(context.client.call('fetchAutomationRuns', { + channel: AUTOMATION_CATALOG_URI, + automation: unknownResource, + })); + const catalog = await subscribeCatalog(); + + // The result is a bare acknowledgement by contract — run history reaches + // clients through `automation/set` — so an automation with no history has + // nothing to page and nothing to republish. + assert.deepStrictEqual({ + acknowledged, + runs: entryFor(catalog, resource)?.runs, + runsNextCursor: entryFor(catalog, resource)?.runsNextCursor, + rejected: rejected.includes(`Automation not found: ${unknownResource}`), + }, { + acknowledged: {}, + runs: [], + runsNextCursor: undefined, + rejected: true, + }); + }); + + conformanceTest(context, 'a created automation survives an agent host restart', async function () { + await initializeRoot('automations-restart'); + await openAutomationGates(); + await subscribeCatalog(); + const resource = automationResource('restart'); + const created = await createAutomation(resource, buildDefinition('Survives restart')); + + await context.restartServer(); + await initializeRoot('automations-restart-verify'); + const restored = entryFor(await subscribeCatalog(), resource); + + // The host persists a mutation before it publishes it, so a definition a + // client has seen is recoverable — with its operations — after a restart. + assert.deepStrictEqual({ + created: { title: created.definition.title, operations: created.operations }, + restored: restored && { title: restored.definition.title, operations: restored.operations }, + }, { + created: { title: 'Survives restart', operations: GATED_OPERATIONS }, + restored: { title: 'Survives restart', operations: GATED_OPERATIONS }, + }); + }); +} diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/changesetSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/changesetSuite.ts index c05f711fccb4f7..f4365f8d07be95 100644 --- a/src/vs/platform/agentHost/test/node/e2e/suites/changesetSuite.ts +++ b/src/vs/platform/agentHost/test/node/e2e/suites/changesetSuite.ts @@ -30,6 +30,7 @@ import { retry } from '../../../../../../base/common/async.js'; import { join } from '../../../../../../base/common/path.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; +import { AgentMergeConfigKey } from '../../../../common/agentMerge.js'; import type { ListSessionsResult, ResourceReadResult, SubscribeResult } from '../../../../common/state/protocol/commands.js'; import { ContentEncoding } from '../../../../common/state/protocol/common/commands.js'; import { PROTOCOL_VERSION } from '../../../../common/state/protocol/version/registry.js'; @@ -70,6 +71,7 @@ interface IOperationsChangedAction { interface IObservedOperation { readonly id: string; + readonly group?: string; readonly scopes: readonly string[]; readonly status: string; } @@ -91,6 +93,15 @@ const CHANGESET_OPERATION_TIMEOUT_MS = 60_000; export function defineChangesetTests(context: IAgentHostE2ETestContext): void { const { config, createdSessions, tempDirs } = context; + function parityTest(title: string, run: Mocha.AsyncFunc): void { + if (context.tier === 'parity') { + test(title, function () { + this.timeout(180_000); + return run.call(this); + }); + } + } + /** * Client sequence numbers must strictly increase for the lifetime of a * client, and the suite shares one across tests, so they cannot be @@ -143,6 +154,21 @@ export function defineChangesetTests(context: IAgentHostE2ETestContext): void { return createRealSession(context.client, config, `${prefix}-${config.provider}`, createdSessions, URI.file(workspace)); } + async function setRootConfig(values: Readonly>): Promise { + await context.client.call('subscribe', { channel: ROOT_STATE_URI }); + const clientSeq = nextClientSeq(); + context.client.dispatch({ + channel: ROOT_STATE_URI, + clientSeq, + action: { type: ActionType.RootConfigChanged, config: values }, + }); + await context.client.waitForNotification(notification => + isActionNotification(notification, ActionType.RootConfigChanged) + && getActionEnvelope(notification).channel === ROOT_STATE_URI + && getActionEnvelope(notification).origin?.clientSeq === clientSeq, + ); + } + async function createWorktreeSessionIn(workspace: string, prefix: string): Promise { tempDirs.push(`${workspace}.worktrees`); context.client.setWorkingDirectory(workspace); @@ -789,6 +815,79 @@ export function defineChangesetTests(context: IAgentHostE2ETestContext): void { ]); }); + parityTest('a GitHub remote with changes advertises pull request creation', async function () { + const workspace = createGitWorkspace('ahp-changeset-pr-ops-'); + execFileSync('git', ['remote', 'add', 'origin', 'https://github.com/microsoft/vscode.git'], { cwd: workspace }); + const sessionUri = await createSessionIn(workspace, 'changeset-pr-ops'); + const uncommittedUri = buildUncommittedChangesetUri(sessionUri); + await context.client.call('subscribe', { channel: uncommittedUri }); + await driveTurnToCompletion(context.client, sessionUri, 'turn-changeset-pr-materialize', 'Reply exactly "ready".', nextClientSeq()); + await runBangTurn(sessionUri, 'turn-changeset-pr-ops', writeFileCommand('pull-request.txt', 'PR'), nextClientSeq()); + + await waitForOperation(uncommittedUri, 'create-pr'); + const operations = (await changesetState(uncommittedUri)).operations ?? []; + const pullRequestOperations = operations + .filter(operation => operation.id.startsWith('create-pr') || operation.id === 'create-draft-pr') + .map(operation => ({ id: operation.id, group: operation.group, scopes: operation.scopes })); + + assert.deepStrictEqual(pullRequestOperations, [ + { id: 'create-pr', group: 'pull-request', scopes: ['changeset'] }, + { id: 'create-pr-auto-merge', group: 'pull-request', scopes: ['changeset'] }, + { id: 'create-pr-auto-squash', group: 'pull-request', scopes: ['changeset'] }, + { id: 'create-pr-auto-rebase', group: 'pull-request', scopes: ['changeset'] }, + { id: 'create-draft-pr', group: 'pull-request_draft', scopes: ['changeset'] }, + ]); + }); + + parityTest('enabling Agent Merge adds and removes its pull request operation', async function () { + const workspace = createGitWorkspace('ahp-changeset-agent-merge-'); + execFileSync('git', ['remote', 'add', 'origin', 'https://github.com/microsoft/vscode.git'], { cwd: workspace }); + const sessionUri = await createSessionIn(workspace, 'changeset-agent-merge'); + const uncommittedUri = buildUncommittedChangesetUri(sessionUri); + await context.client.call('subscribe', { channel: uncommittedUri }); + await driveTurnToCompletion(context.client, sessionUri, 'turn-changeset-agent-merge-materialize', 'Reply exactly "ready".', nextClientSeq()); + await runBangTurn(sessionUri, 'turn-changeset-agent-merge', writeFileCommand('agent-merge.txt', 'AGENT MERGE'), nextClientSeq()); + await waitForOperation(uncommittedUri, 'create-pr'); + + try { + await setRootConfig({ [AgentMergeConfigKey.Enabled]: true }); + const operation = await waitForOperation(uncommittedUri, 'create-pr-agent-merge'); + assert.deepStrictEqual({ + id: operation.id, + group: operation.group, + scopes: operation.scopes, + }, { + id: 'create-pr-agent-merge', + group: 'pull-request', + scopes: ['changeset'], + }); + } finally { + await setRootConfig({ [AgentMergeConfigKey.Enabled]: false }); + } + + await waitForOperationRemoved(uncommittedUri, 'create-pr-agent-merge'); + }); + + conformanceTest(context, 'a folder session advertises commit on its branch changeset', async function () { + const workspace = createGitWorkspace('ahp-changeset-branch-commit-'); + const sessionUri = await createSessionIn(workspace, 'changeset-branch-commit'); + const branchUri = buildBranchChangesetUri(sessionUri); + await context.client.call('subscribe', { channel: branchUri }); + await runBangTurn(sessionUri, 'turn-changeset-branch-commit', writeFileCommand('branch-commit.txt', 'COMMIT'), 1); + + const operation = await waitForOperation(branchUri, 'commit'); + + assert.deepStrictEqual({ + id: operation.id, + group: operation.group, + scopes: operation.scopes, + }, { + id: 'commit', + group: 'commit', + scopes: ['changeset'], + }); + }); + conformanceTest(context, 'a branch with an upstream and no outgoing commits omits sync', async function () { const { workspace } = createRemoteGitWorkspace('ahp-sync-none'); const sessionUri = await createSessionIn(workspace, 'sync-none'); diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/detachedWorktreeSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/detachedWorktreeSuite.ts new file mode 100644 index 00000000000000..1bba064842280e --- /dev/null +++ b/src/vs/platform/agentHost/test/node/e2e/suites/detachedWorktreeSuite.ts @@ -0,0 +1,305 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Detached worktrees: a git worktree the host materializes *before* any session + * owns it. + * + * A client that wants a worktree ready before the user sends their first + * message (the Agents window does this so a draft session already has a + * checkout) asks the host for one through the `vscode/…DetachedWorktree` + * extension methods. The worktree is keyed by an opaque handle rather than by a + * session, so its whole lifecycle — materialize, claim, archive/unarchive, + * delete, reconcile — is addressable over the protocol without a turn ever + * running. + * + * Everything here is host-local: git commands against a temporary repository + * plus the host's own per-handle record. Nothing crosses the model boundary, so + * every scenario is registered as a conformance-tier host-only test and runs + * against the strict shared empty fixture. + */ + +import assert from 'assert'; +import { execFileSync } from 'child_process'; +import { existsSync, mkdtempSync, realpathSync, writeFileSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from '../../../../../../base/common/path.js'; +import { getComparisonKey } from '../../../../../../base/common/resources.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { generateUuid } from '../../../../../../base/common/uuid.js'; +import { + ClaimAgentHostDetachedWorktreeExtensionMethod, + CreateAgentHostDetachedWorktreeExtensionMethod, + DeleteAgentHostDetachedWorktreeExtensionMethod, + ReconcileAgentHostDetachedWorktreesExtensionMethod, + SetAgentHostDetachedWorktreeArchivedExtensionMethod, + type IAgentHostExtensionCommandMap, +} from '../../../../common/agentHostExtensionProtocol.js'; +import { isAgentDevContainerWorktreeHandle } from '../../../../common/meta/agentDevContainerWorktreeMeta.js'; +import type { SubscribeResult } from '../../../../common/state/protocol/commands.js'; +import { PROTOCOL_VERSION } from '../../../../common/state/protocol/version/registry.js'; +import { ROOT_STATE_URI, SessionLifecycle, type SessionState } from '../../../../common/state/sessionState.js'; +import { initTestGitRepo, resolveGitHubToken } from '../harness/agentHostE2ETestHarness.js'; +import { vscodeAgentHostTarget } from '../harness/agentHostTarget.js'; +import { conformanceTest, type IAgentHostE2ETestContext } from './e2eTestContext.js'; + +type CreateDetachedWorktreeResult = IAgentHostExtensionCommandMap[typeof CreateAgentHostDetachedWorktreeExtensionMethod]['result']; + +/** The `agents/` prefix the host puts in front of every branch it generates for an isolated checkout. */ +const AGENT_BRANCH_PREFIX = 'agents/'; + +/** + * Resolves a path through symlinks when it exists, and returns it unchanged + * when it does not. Temp directories are symlinked on macOS (`/var` -> + * `/private/var`), and these tests compare paths that git printed against paths + * the host returned, so both sides have to be canonicalized the same way — + * including after a worktree has been removed, when the path no longer resolves. + */ +function canonicalPath(candidate: string): string { + try { + return realpathSync(candidate); + } catch { + return candidate; + } +} + +function pathComparisonKey(candidate: string): string { + return getComparisonKey(URI.file(canonicalPath(candidate))); +} + +export function defineDetachedWorktreeTests(context: IAgentHostE2ETestContext): void { + // The detached-worktree family is an AHP *extension* method set rather than + // part of the core protocol, so only the VS Code agent host answers it. + if (context.targetId !== vscodeAgentHostTarget.id) { + return; + } + + const { config, createdSessions, tempDirs } = context; + const enabled = config.supportsWorktreeIsolation; + + let clientOrdinal = 0; + + /** A git repository with one commit, so a worktree has a branch point to check out. */ + function createGitWorkspace(prefix: string): string { + // Canonicalized up front: the host resolves the repository root through + // git, which reports the real path, and the worktree container is derived + // from that root. + const workspace = realpathSync(mkdtempSync(join(tmpdir(), prefix))); + tempDirs.push(workspace, `${workspace}.worktrees`); + initTestGitRepo(workspace); + writeFileSync(join(workspace, 'seed.txt'), 'seed\n'); + execFileSync('git', ['add', '.'], { cwd: workspace }); + execFileSync('git', ['commit', '-q', '-m', 'seed'], { cwd: workspace }); + return workspace; + } + + function git(cwd: string, ...args: string[]): string { + return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim(); + } + + /** The worktrees git currently has registered for `repository`, canonicalized. */ + function registeredWorktrees(repository: string): string[] { + return git(repository, 'worktree', 'list', '--porcelain') + .split('\n') + .filter(line => line.startsWith('worktree ')) + .map(line => pathComparisonKey(line.slice('worktree '.length).trim())); + } + + function isRegisteredWorktree(repository: string, worktreePath: string): boolean { + return registeredWorktrees(repository).includes(pathComparisonKey(worktreePath)); + } + + function branchExists(repository: string, branchName: string): boolean { + return git(repository, 'branch', '--list', branchName).length > 0; + } + + /** + * Creates a session configured for worktree isolation and stops before the + * first turn, which is exactly the state a detached worktree is requested + * from: the host has a session record but has deliberately not resolved its + * working directory yet. + */ + async function createUnstartedWorktreeSession(workspace: string, prefix: string): Promise { + context.client.setWorkingDirectory(workspace); + await context.client.call('initialize', { + channel: ROOT_STATE_URI, + protocolVersions: [PROTOCOL_VERSION], + clientId: `${prefix}-${config.provider}-${clientOrdinal++}`, + }, 30_000); + await context.client.call('authenticate', { + channel: ROOT_STATE_URI, + resource: 'https://api.github.com', + token: config.githubToken ?? resolveGitHubToken(), + }, 30_000); + + const sessionUri = URI.from({ scheme: config.scheme, path: `/${generateUuid()}` }).toString(); + await context.client.call('createSession', { + channel: sessionUri, + provider: config.provider, + workingDirectories: [URI.file(workspace).toString()], + config: { isolation: 'worktree', branch: git(workspace, 'branch', '--show-current') }, + }, 30_000); + createdSessions.push(sessionUri); + return sessionUri; + } + + function createDetachedWorktree(session: string, prompt: string): Promise { + return context.client.call(CreateAgentHostDetachedWorktreeExtensionMethod, { session, prompt }, 60_000); + } + + function claimDetachedWorktree(handle: string): Promise { + return context.client.call(ClaimAgentHostDetachedWorktreeExtensionMethod, { handle }, 30_000); + } + + function setDetachedWorktreeArchived(handle: string, archived: boolean): Promise { + return context.client.call(SetAgentHostDetachedWorktreeArchivedExtensionMethod, { handle, archived }, 60_000); + } + + function deleteDetachedWorktree(handle: string): Promise { + return context.client.call(DeleteAgentHostDetachedWorktreeExtensionMethod, { handle }, 60_000); + } + + function reconcileDetachedWorktrees(scope: string, activeHandles: readonly string[]): Promise { + return context.client.call(ReconcileAgentHostDetachedWorktreesExtensionMethod, { scope, activeHandles: [...activeHandles] }, 60_000); + } + + conformanceTest(context, 'creating a detached worktree materializes a checkout for an unstarted session', async function () { + const workspace = createGitWorkspace('ahp-detached-create-'); + const sessionUri = await createUnstartedWorktreeSession(workspace, 'detached-create'); + const sessionState = (await context.client.call('subscribe', { channel: sessionUri })).snapshot!.state as SessionState; + + const created = await createDetachedWorktree(sessionUri, 'summarize the seed file'); + const worktreePath = URI.parse(created.resource).fsPath; + + // The handle is opaque to the client, the checkout is a real git worktree + // of the session's repository, and it carries the repository's content — + // the three things a client needs before it can hand the directory to a + // user. The session itself stays unstarted: a detached worktree is not + // (yet) anybody's working directory. + assert.deepStrictEqual({ + sessionLifecycle: sessionState.lifecycle, + handleIsOpaqueId: isAgentDevContainerWorktreeHandle(created.handle), + existsOnDisk: existsSync(worktreePath), + registeredWithGit: isRegisteredWorktree(workspace, worktreePath), + checkedOutRepositoryContent: existsSync(join(worktreePath, 'seed.txt')), + onGeneratedAgentBranch: git(worktreePath, 'rev-parse', '--abbrev-ref', 'HEAD').startsWith(AGENT_BRANCH_PREFIX), + isSeparateFromWorkspace: pathComparisonKey(worktreePath) !== pathComparisonKey(workspace), + }, { + sessionLifecycle: SessionLifecycle.Creating, + handleIsOpaqueId: true, + existsOnDisk: true, + registeredWithGit: true, + checkedOutRepositoryContent: true, + onGeneratedAgentBranch: true, + isSeparateFromWorkspace: true, + }); + }, enabled); + + conformanceTest(context, 'reconciling detached worktrees keeps every handle inside its retention window', async function () { + const workspace = createGitWorkspace('ahp-detached-reconcile-'); + const sessionUri = await createUnstartedWorktreeSession(workspace, 'detached-reconcile'); + + const held = await createDetachedWorktree(sessionUri, 'reconcile the held checkout'); + const dropped = await createDetachedWorktree(sessionUri, 'reconcile the dropped checkout'); + const heldPath = URI.parse(held.resource).fsPath; + const droppedPath = URI.parse(dropped.resource).fsPath; + await claimDetachedWorktree(held.handle); + + // Omitted handles remain claimable until the retention grace period expires. + await reconcileDetachedWorktrees(getComparisonKey(URI.parse(held.resource)), [held.handle]); + await reconcileDetachedWorktrees(getComparisonKey(URI.parse(dropped.resource)), []); + + await claimDetachedWorktree(dropped.handle); + + assert.deepStrictEqual({ + heldExists: existsSync(heldPath), + droppedExists: existsSync(droppedPath), + heldRegistered: isRegisteredWorktree(workspace, heldPath), + droppedRegistered: isRegisteredWorktree(workspace, droppedPath), + areDistinctCheckouts: pathComparisonKey(heldPath) !== pathComparisonKey(droppedPath), + }, { + heldExists: true, + droppedExists: true, + heldRegistered: true, + droppedRegistered: true, + areDistinctCheckouts: true, + }); + }, enabled); + + conformanceTest(context, 'archiving a detached worktree removes its checkout and unarchiving recreates it', async function () { + const workspace = createGitWorkspace('ahp-detached-archive-'); + const sessionUri = await createUnstartedWorktreeSession(workspace, 'detached-archive'); + + const created = await createDetachedWorktree(sessionUri, 'archive and restore this checkout'); + const worktreePath = URI.parse(created.resource).fsPath; + const branchName = git(worktreePath, 'rev-parse', '--abbrev-ref', 'HEAD'); + + // Archiving reclaims the disk a dormant checkout is holding, but it must + // preserve the branch: that branch is the only thing that makes the + // checkout reconstructible, so dropping it would turn "archive" into + // "discard". + await setDetachedWorktreeArchived(created.handle, true); + const archived = { + existsOnDisk: existsSync(worktreePath), + registeredWithGit: isRegisteredWorktree(workspace, worktreePath), + branchPreserved: branchExists(workspace, branchName), + }; + + // Unarchiving puts the same branch back at the same path, so a client that + // stored the path before archiving still resolves to a valid checkout. + await setDetachedWorktreeArchived(created.handle, false); + const restored = { + existsOnDisk: existsSync(worktreePath), + registeredWithGit: isRegisteredWorktree(workspace, worktreePath), + checkedOutRepositoryContent: existsSync(join(worktreePath, 'seed.txt')), + branch: git(worktreePath, 'rev-parse', '--abbrev-ref', 'HEAD'), + }; + + assert.deepStrictEqual({ archived, restored }, { + archived: { + existsOnDisk: false, + registeredWithGit: false, + branchPreserved: true, + }, + restored: { + existsOnDisk: true, + registeredWithGit: true, + checkedOutRepositoryContent: true, + branch: branchName, + }, + }); + }, enabled); + + conformanceTest(context, 'deleting a detached worktree removes its checkout and forgets its handle', async function () { + const workspace = createGitWorkspace('ahp-detached-delete-'); + const sessionUri = await createUnstartedWorktreeSession(workspace, 'detached-delete'); + + const created = await createDetachedWorktree(sessionUri, 'delete this checkout'); + const worktreePath = URI.parse(created.resource).fsPath; + + await deleteDetachedWorktree(created.handle); + + // Deletion takes the checkout off disk *and* drops the host's record for + // the handle. The record is not directly observable, so the oracle is the + // handle no longer resolving — a client cannot claim what the host forgot. + await assert.rejects(claimDetachedWorktree(created.handle), /Unknown detached worktree handle/); + await assert.rejects(claimDetachedWorktree(generateUuid()), /Unknown detached worktree handle/); + + // Deletion is idempotent: a client retrying after a dropped response, or + // two clients reacting to the same removal, must not turn the second + // attempt into an error. + await deleteDetachedWorktree(created.handle); + + assert.deepStrictEqual({ + existsOnDisk: existsSync(worktreePath), + registeredWithGit: isRegisteredWorktree(workspace, worktreePath), + repositoryStillIntact: existsSync(join(workspace, 'seed.txt')), + }, { + existsOnDisk: false, + registeredWithGit: false, + repositoryStillIntact: true, + }); + }, enabled); +} diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/hostFeaturesSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/hostFeaturesSuite.ts index af203abb603ff2..2d5da44e2c60e5 100644 --- a/src/vs/platform/agentHost/test/node/e2e/suites/hostFeaturesSuite.ts +++ b/src/vs/platform/agentHost/test/node/e2e/suites/hostFeaturesSuite.ts @@ -11,10 +11,12 @@ import { join } from '../../../../../../base/common/path.js'; import { basename, extUriBiasedIgnorePathCase } from '../../../../../../base/common/resources.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; +import { AgentHostConfigKey } from '../../../../common/agentHostCustomizationConfig.js'; import { AgentHostCopilotMultiRootEnabledConfigKey } from '../../../../common/agentHostSchema.js'; +import { deriveGitHubEndpoints, gitHubCopilotResource } from '../../../../common/githubEndpoints.js'; import { CompletionItemKind, type CompletionsResult, type InitializeResult, type ResolveSessionConfigResult, type SessionConfigCompletionsResult, type SubscribeResult } from '../../../../common/state/protocol/commands.js'; import { PROTOCOL_VERSION } from '../../../../common/state/protocol/version/registry.js'; -import { ActionType } from '../../../../common/state/sessionActions.js'; +import { ActionType, AuthRequiredReason, type AuthRequiredParams } from '../../../../common/state/sessionActions.js'; import { buildDefaultChatUri, MessageAttachmentKind, ROOT_STATE_URI, ToolCallConfirmationReason, type TerminalState, type ToolResultContent } from '../../../../common/state/sessionState.js'; import { createRealSession, @@ -140,6 +142,26 @@ export function defineHostFeaturesTests(context: IAgentHostE2ETestContext): void }); }); + conformanceTest(context, 'configuring a GitHub Enterprise host asks the client to re-authenticate', async function () { + const enterpriseUri = 'https://enterprise.example.com'; + await createSession('enterprise-auth-required'); + await context.client.call('subscribe', { channel: ROOT_STATE_URI }); + context.client.clearReceived(); + try { + const required = context.client.waitForNotification(notification => notification.method === 'auth/required'); + await setRootConfig({ [AgentHostConfigKey.GithubEnterpriseUri]: enterpriseUri }); + const notification = await required; + + assert.deepStrictEqual(notification.params as AuthRequiredParams, { + channel: ROOT_STATE_URI, + resource: gitHubCopilotResource(deriveGitHubEndpoints(enterpriseUri)), + reason: AuthRequiredReason.Required, + }); + } finally { + await setRootConfig({ [AgentHostConfigKey.GithubEnterpriseUri]: '' }); + } + }); + conformanceTest(context, 'workspace file completions are filtered, attached, and cached', async function () { const workspace = createWorkspace('ahp-file-completions-'); diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/serverToolsSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/serverToolsSuite.ts index 04ddaa514b69f0..9fd2eaaf647d11 100644 --- a/src/vs/platform/agentHost/test/node/e2e/suites/serverToolsSuite.ts +++ b/src/vs/platform/agentHost/test/node/e2e/suites/serverToolsSuite.ts @@ -10,10 +10,13 @@ import { retry } from '../../../../../../base/common/async.js'; import { join } from '../../../../../../base/common/path.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; +import { AgentHostActiveAgentTitleGenerationConfigKey, AgentHostArtifactToolsConfigKey } from '../../../../common/agentHostSchema.js'; import { FEEDBACK_ANNOTATION_META_KEY, type IFeedbackAnnotationMeta } from '../../../../common/meta/agentFeedbackAnnotations.js'; import { buildAnnotationsUri } from '../../../../common/annotationsUri.js'; import { buildOpenSessionLinkUri } from '../../../../common/openSessionLink.js'; -import { SessionServerToolName } from '../../../../common/serverToolNames.js'; +import { SessionConfigKey } from '../../../../common/sessionConfigKeys.js'; +import { ArtifactServerToolName, SessionServerToolName } from '../../../../common/serverToolNames.js'; +import { readSessionArtifacts } from '../../../../common/sessionArtifacts.js'; import type { ListSessionsResult, SubscribeResult } from '../../../../common/state/protocol/commands.js'; import { ActionType, NotificationType, type ChatToolCallCompleteAction, type ChatToolCallStartAction, type SessionAddedParams, type StateAction } from '../../../../common/state/sessionActions.js'; import { @@ -101,7 +104,7 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void return { sessionUri, chatUri, workspace }; } - async function createSession(prefix: string, stableResource = false): Promise { + async function createSession(prefix: string, stableResource = false, beforeCreateSession?: () => Promise): Promise { const workspace = mkdtempSync(join(tmpdir(), `ahp-server-tools-${prefix}-`)); tempDirs.push(workspace); if (!stableResource) { @@ -111,6 +114,7 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void `server-tools-${prefix}-${config.provider}`, createdSessions, URI.file(workspace), + beforeCreateSession, ); context.client.clearReceived(); return { sessionUri, chatUri: buildDefaultChatUri(sessionUri), workspace }; @@ -157,6 +161,11 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void ); } + async function setRootConfig(values: Readonly>): Promise { + await context.client.call('subscribe', { channel: ROOT_STATE_URI }); + await dispatchAndWait(ROOT_STATE_URI, { type: ActionType.RootConfigChanged, config: values }); + } + async function seedFeedback(sessionUri: string, options: ISeedFeedbackOptions): Promise { const annotationsUri = buildAnnotationsUri(sessionUri); await context.client.call('subscribe', { channel: annotationsUri }); @@ -267,6 +276,148 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void assert.deepStrictEqual(toolNames, [...feedbackToolNames, ...sessionToolNames]); }); + serverToolTest('server tool: rename_chat renames the chat it runs in', async function () { + try { + const session = await createSession('rename-chat', false, () => setRootConfig({ + [AgentHostActiveAgentTitleGenerationConfigKey]: true, + })); + await driveTurnToCompletion( + context.client, + session.sessionUri, + 'turn-rename-chat-seed', + '/rename Seeded Chat', + reserveClientSequenceBlock(), + ); + const { tool } = await driveServerTool( + session, + 'turn-rename-chat', + 'Call the rename_chat tool exactly once with title "Coverage audit" and automatic false, then reply with exactly "renamed".', + SessionServerToolName.RenameChat, + ); + const renamed = await retry(async () => { + const sessionTitle = (await sessionState(session.sessionUri)).title; + const chatTitle = (await chatState(session.chatUri)).title; + if (sessionTitle !== 'Coverage audit' || chatTitle !== 'Coverage audit') { + throw new Error('The chat rename has not completed'); + } + return { sessionTitle, chatTitle }; + }, 100, 100); + + assert.deepStrictEqual({ + succeeded: tool.completion.result.success, + ...renamed, + }, { + succeeded: true, + sessionTitle: 'Coverage audit', + chatTitle: 'Coverage audit', + }); + } finally { + await setRootConfig({ [AgentHostActiveAgentTitleGenerationConfigKey]: false }); + } + }); + + serverToolTest('server tool: add_artifact_or_reference records a reference in session state', async function () { + try { + const session = await createSession('artifact-add', false, () => setRootConfig({ + [AgentHostArtifactToolsConfigKey]: true, + })); + await driveServerTool( + session, + 'turn-artifact-add', + 'Call add_artifact_or_reference exactly once with type "website", label "Agent Host guide", isArtifact false, and link "https://example.com/agent-host". Then reply with exactly "recorded".', + ArtifactServerToolName.AddArtifactOrReference, + { result: [/Added reference:/, /Agent Host guide/, /https:\/\/example\.com\/agent-host/] }, + ); + const [artifact] = readSessionArtifacts((await sessionState(session.sessionUri))._meta); + + assert.deepStrictEqual({ + artifact: artifact && { + type: artifact.type, + label: artifact.label, + isArtifact: artifact.isArtifact, + link: artifact.link, + }, + }, { + artifact: { + type: 'website', + label: 'Agent Host guide', + isArtifact: false, + link: 'https://example.com/agent-host', + }, + }); + } finally { + await setRootConfig({ [AgentHostArtifactToolsConfigKey]: false }); + } + }); + + serverToolTest('server tool: add_artifact_or_reference rejects a session-management link', async function () { + try { + const session = await createSession('artifact-reject-session', false, () => setRootConfig({ + [AgentHostArtifactToolsConfigKey]: true, + })); + const { tool } = await driveServerTool( + session, + 'turn-artifact-reject-session', + 'Call add_artifact_or_reference exactly once with type "resource", label "Spawned session", isArtifact true, and uri "agent-host-session://copilot/spawned". Then reply with exactly "rejected".', + ArtifactServerToolName.AddArtifactOrReference, + { + success: false, + result: [/sessions and chats created with session-management tools must not be recorded/], + }, + ); + + assert.deepStrictEqual({ + succeeded: tool.completion.result.success, + artifacts: readSessionArtifacts((await sessionState(session.sessionUri))._meta), + }, { + succeeded: false, + artifacts: [], + }); + } finally { + await setRootConfig({ [AgentHostArtifactToolsConfigKey]: false }); + } + }); + + serverToolTest('server tool: list and remove round-trip a recorded reference', async function () { + try { + const session = await createSession('artifact-list-remove', false, () => setRootConfig({ + [AgentHostArtifactToolsConfigKey]: true, + })); + await driveServerTool( + session, + 'turn-artifact-list-remove-add', + 'Call add_artifact_or_reference exactly once with type "website", label "Design notes", isArtifact false, and link "https://example.com/design". Then reply with exactly "added".', + ArtifactServerToolName.AddArtifactOrReference, + ); + const [artifact] = readSessionArtifacts((await sessionState(session.sessionUri))._meta); + assert.ok(artifact); + const listed = await driveServerTool( + session, + 'turn-artifact-list-remove-list', + 'Call list_artifacts_and_references exactly once, then reply with exactly "listed".', + ArtifactServerToolName.ListArtifactsAndReferences, + ); + const removed = await driveServerTool( + session, + 'turn-artifact-list-remove-remove', + `Call remove_artifact_or_reference exactly once with id "${artifact.id}", then reply with exactly "removed".`, + ArtifactServerToolName.RemoveArtifactOrReference, + ); + + assert.deepStrictEqual({ + listed: listed.tool.resultText.includes(`${artifact.id} (website, reference) Design notes — https://example.com/design`), + removed: removed.tool.resultText.includes(`Removed reference: ${artifact.id}`), + artifacts: readSessionArtifacts((await sessionState(session.sessionUri))._meta), + }, { + listed: true, + removed: true, + artifacts: [], + }); + } finally { + await setRootConfig({ [AgentHostArtifactToolsConfigKey]: false }); + } + }); + serverToolTest('server tool: listComments executes in-process with an empty annotation channel', async function () { const session = await createSession('comments-empty'); const { tool } = await driveServerTool( @@ -875,9 +1026,11 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void return request; }, 50, 600); const childState = await waitForChatIdle(buildDefaultChatUri(child.resource)); + const childSessionState = await sessionState(child.resource); assert.deepStrictEqual({ sawPendingConfirmation: turn.sawPendingConfirmation, provider: child.provider, + isolation: childSessionState.config?.values[SessionConfigKey.Isolation], messages: childState.turns.map(turn => turn.message.text), title: childState.title, childRequestModel: childRequest.model, @@ -885,6 +1038,7 @@ export function defineServerToolsTests(context: IAgentHostE2ETestContext): void }, { sawPendingConfirmation: true, provider: model.provider, + isolation: 'folder', messages: [childPrompt], title: 'Created Child', childRequestModel: createSessionModelWireTarget, diff --git a/src/vs/platform/agentHost/test/node/e2e/suites/sessionPersistenceSuite.ts b/src/vs/platform/agentHost/test/node/e2e/suites/sessionPersistenceSuite.ts index 21ad491425261f..2ab33a1f90505a 100644 --- a/src/vs/platform/agentHost/test/node/e2e/suites/sessionPersistenceSuite.ts +++ b/src/vs/platform/agentHost/test/node/e2e/suites/sessionPersistenceSuite.ts @@ -9,9 +9,10 @@ import { tmpdir } from 'os'; import { retry, timeout } from '../../../../../../base/common/async.js'; import { URI } from '../../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../../base/common/uuid.js'; -import type { SubscribeResult } from '../../../../common/state/protocol/commands.js'; +import type { ListSessionsResult, SubscribeResult } from '../../../../common/state/protocol/commands.js'; +import type { SessionSummaryChangedParams } from '../../../../common/state/protocol/channels-root/notifications.js'; import { ActionType } from '../../../../common/state/sessionActions.js'; -import { buildChatUri, buildDefaultChatUri, MessageKind, ROOT_STATE_URI, type ChatState, type SessionState } from '../../../../common/state/sessionState.js'; +import { buildChatUri, buildDefaultChatUri, MessageKind, ROOT_STATE_URI, SessionStatus, type ChatState, type SessionState } from '../../../../common/state/sessionState.js'; import { PROTOCOL_VERSION } from '../../../../common/state/protocol/version/registry.js'; import { createRealSession, driveTurnToCompletion, resolveGitHubToken } from '../harness/agentHostE2ETestHarness.js'; import { fetchSessionWithChat, getActionEnvelope, isActionNotification } from '../../serverIntegrationTestHelpers.js'; @@ -131,6 +132,41 @@ export function defineSessionPersistenceTests(context: IAgentHostE2ETestContext) }); }); + test('archiving a never-restored session survives a host restart', async function () { + this.timeout(240_000); + const workspace = fs.mkdtempSync(`${tmpdir()}/ahp-archive-unrestored-`); + tempDirs.push(workspace); + const sessionUri = await createRealSession(context.client, config, `archive-unrestored-${config.provider}`, createdSessions, URI.file(workspace)); + await driveTurnToCompletion(context.client, sessionUri, 'turn-archive-unrestored-seed', 'Reply exactly "READY".', 1); + await restartAndInitialize(`archive-unrestored-reconnect-${config.provider}`, workspace); + await context.client.call('subscribe', { channel: ROOT_STATE_URI }); + const before = await context.client.call('listSessions', { channel: ROOT_STATE_URI }); + assert.strictEqual(before.items.some(item => item.resource === sessionUri), true); + context.client.clearReceived(); + context.client.dispatch({ + channel: sessionUri, + clientSeq: 1, + action: { type: ActionType.SessionIsArchivedChanged, isArchived: true }, + }); + await context.client.waitForNotification(notification => + notification.method === 'root/sessionSummaryChanged' + && (notification.params as SessionSummaryChangedParams).session === sessionUri + && (((notification.params as SessionSummaryChangedParams).changes.status ?? 0) & SessionStatus.IsArchived) !== 0, + ); + + await restartAndInitialize(`archive-unrestored-verify-${config.provider}`, workspace); + const after = await context.client.call('listSessions', { channel: ROOT_STATE_URI, includeArchived: true }); + const restored = after.items.find(item => item.resource === sessionUri); + + assert.deepStrictEqual({ + restored: restored !== undefined, + isArchived: restored !== undefined && (restored.status & SessionStatus.IsArchived) !== 0, + }, { + restored: true, + isArchived: true, + }); + }); + const peerChatPersistenceEnabled = config.supportsMultipleChats && (config.supportsMultipleChatsE2E !== false || RECORDING) && (!(context.isWindows && config.provider === 'copilotcli') || context.runKnownIssueTests); From 3be03545a60724b6c4f97845216762072798331c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 15:27:12 +0200 Subject: [PATCH 07/33] Add extensible Markdown iframe editors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- .vscode/launch.json | 19 + .vscode/tasks.json | 7 + .../markdown-editor-src/editor.ts | 107 ++- .../package-lock.json | 757 +----------------- .../markdown-language-features/package.json | 2 +- .../schemas/package.schema.json | 12 +- .../src/markdownExtensions.ts | 4 + .../src/preview/markdownEditorProvider.ts | 257 +++++- .../src/test/markdownEditorProvider.test.ts | 30 +- .../test-workspace/checkbox-count-demo.md | 20 + .../checkbox-count-extension/.gitignore | 2 + .../checkbox-count-extension/README.md | 20 + .../editor/formatCheckboxLabel.ts | 8 + .../editor/index.html | 24 + .../checkbox-count-extension/editor/main.ts | 88 ++ .../checkbox-count-extension/editor/style.css | 58 ++ .../package-lock.json | 523 ++++++++++++ .../checkbox-count-extension/package.json | 46 ++ .../checkbox-count-extension/src/extension.ts | 109 +++ .../electron-main/webviewProtocolProvider.ts | 2 +- .../pre/{fake.html => iframe-bootstrap.html} | 2 +- .../contrib/webview/browser/pre/index.html | 15 +- 22 files changed, 1357 insertions(+), 755 deletions(-) create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-demo.md create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/.gitignore create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/README.md create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/formatCheckboxLabel.ts create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/index.html create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/main.ts create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/style.css create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json create mode 100644 extensions/markdown-language-features/test-workspace/checkbox-count-extension/src/extension.ts rename src/vs/workbench/contrib/webview/browser/pre/{fake.html => iframe-bootstrap.html} (83%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 709ebc458c2fdb..52b463ac95a19b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -537,6 +537,25 @@ "order": 10 } }, + { + "type": "extensionHost", + "request": "launch", + "name": "Markdown Code Block Editor Demo", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/extensions/markdown-language-features/test-workspace", + "--extensionDevelopmentPath=${workspaceFolder}/extensions/markdown-language-features", + "--extensionDevelopmentPath=${workspaceFolder}/extensions/markdown-language-features/test-workspace/checkbox-count-extension" + ], + "outFiles": [ + "${workspaceFolder}/extensions/markdown-language-features/out/**/*.js" + ], + "preLaunchTask": "Build Markdown Code Block Editor Demo", + "presentation": { + "group": "4_demo", + "order": 1 + } + }, { "type": "extensionHost", "request": "launch", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b4f2b517111ce1..ad9a81a94aa321 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -674,6 +674,13 @@ "Write-Output \"134 passed, 0 failed, 1 skipped, 135 total\"; Start-Sleep -Seconds 2; Write-Output \"[PASS] E2E Tests\"; Write-Output \"Watching for changes...\"" ], "isBackground": false + }, + { + "label": "Build Markdown Code Block Editor Demo", + "type": "npm", + "script": "build", + "path": "extensions/markdown-language-features/test-workspace/checkbox-count-extension", + "problemMatcher": [] } ] } diff --git a/extensions/markdown-language-features/markdown-editor-src/editor.ts b/extensions/markdown-language-features/markdown-editor-src/editor.ts index d2287cac2786bf..ab39883d0cd09f 100644 --- a/extensions/markdown-language-features/markdown-editor-src/editor.ts +++ b/extensions/markdown-language-features/markdown-editor-src/editor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AsyncClipboardStrategy, CommentModeController, CommentsModel, CommentsView, EditorController, EditorModel, EditorView, GutterMarker, OffsetRange, Selection, StringEdit, StringReplacement, StringValue, commands, findNodeOffsetById, vscodeHostKeyboardProfile, vscodeLocalKeyboardProfile, type CodeBlockAstNode, type LinkPresentationKind } from '@vscode/markdown-editor'; -import { VirtualizedIframeEmbeddedEditorFactory, type IframeEmbeddedEditorProvider, type IframeEmbeddedEditorProviderSelector, type ResolvedIframeEmbeddedEditor } from '@vscode/markdown-editor/web-editors'; +import { VirtualizedIframeEmbeddedEditorFactory, type IframeEmbeddedEditorHostTransport, type IframeEmbeddedEditorProvider, type IframeEmbeddedEditorProviderSelector, type ResolvedIframeEmbeddedEditor } from '@vscode/markdown-editor/web-editors'; import { Disposable, autorun, observableValue } from '@vscode/observables'; import 'katex/dist/katex.min.css'; import '@vscode/markdown-editor/editor.css'; @@ -47,6 +47,68 @@ interface InitialState { readonly linkPresentationRules: readonly { id: string; source: string; flags: string; kind: LinkPresentationKind }[]; } +class CodeBlockEditorHostTransport implements IframeEmbeddedEditorHostTransport { + readonly #listeners = new Set<(message: unknown) => void>(); + readonly #pendingMessages: unknown[] = []; + readonly #postMessage: (message: unknown) => void; + readonly #onDispose: () => void; + #activated = false; + #disposed = false; + + readonly onMessage: IframeEmbeddedEditorHostTransport['onMessage'] = listener => { + if (this.#disposed) { + throw new Error('Code block editor host transport is disposed'); + } + this.#listeners.add(listener); + if (!this.#activated) { + this.#activated = true; + for (const message of this.#pendingMessages.splice(0)) { + listener(message); + } + } + return { dispose: () => this.#listeners.delete(listener) }; + }; + + constructor( + readonly runtimeId: string, + postMessage: (message: unknown) => void, + onDispose: () => void, + ) { + this.#postMessage = postMessage; + this.#onDispose = onDispose; + } + + sendMessage(message: unknown): void { + if (this.#disposed) { + throw new Error('Code block editor host transport is disposed'); + } + this.#postMessage(message); + } + + acceptMessage(message: unknown): void { + if (this.#disposed) { + return; + } + if (!this.#activated) { + this.#pendingMessages.push(message); + return; + } + for (const listener of this.#listeners) { + listener(message); + } + } + + dispose(): void { + if (this.#disposed) { + return; + } + this.#disposed = true; + this.#pendingMessages.length = 0; + this.#listeners.clear(); + this.#onDispose(); + } +} + class Editor extends Disposable { readonly model = new EditorModel(); isUpdatingFromExtension = false; @@ -54,7 +116,9 @@ class Editor extends Disposable { #mermaidCounter = 0; #codeBlockEditorProviders: readonly CodeBlockEditorProviderDefinition[] = []; #nextCodeBlockEditorRequestId = 1; + #nextCodeBlockEditorRuntimeId = 1; readonly #codeBlockEditorRequests = new Map void>(); + readonly #codeBlockEditorHostTransports = new Map(); #controller: EditorController | undefined; #view: EditorView | undefined; #embeddedCodeEditorFactory: VirtualizedIframeEmbeddedEditorFactory | undefined; @@ -126,6 +190,12 @@ class Editor extends Disposable { } break; } + case 'codeBlockEditorHostTransportMessage': { + if (typeof message.runtimeId === 'string') { + this.#codeBlockEditorHostTransports.get(message.runtimeId)?.acceptMessage(message.message); + } + break; + } case 'gutterMarkers': { const markers: GutterMarker[] = message.markers.map((marker: { start: number; endExclusive: number; type: GutterMarker['type'] }) => ({ range: OffsetRange.fromTo(marker.start, marker.endExclusive), @@ -168,6 +238,9 @@ class Editor extends Disposable { resolve(undefined); } this.#codeBlockEditorRequests.clear(); + for (const transport of Array.from(this.#codeBlockEditorHostTransports.values())) { + transport.dispose(); + } }, }); } @@ -393,12 +466,40 @@ class Editor extends Disposable { return definitions.map(definition => ({ id: definition.id, selector: definition.selector, + createHostTransport: runtimeKey => this.#createCodeBlockEditorHostTransport(definition.id, runtimeKey), resolve: definition.source.kind === 'static' ? async () => definition.source.kind === 'static' ? definition.source.descriptor : undefined : language => this.#resolveCodeBlockEditor(definition.id, language), })); } + #createCodeBlockEditorHostTransport(providerId: string, runtimeKey: string): CodeBlockEditorHostTransport { + const runtimeId = `${providerId}:${this.#nextCodeBlockEditorRuntimeId++}`; + const transport = new CodeBlockEditorHostTransport( + runtimeId, + message => this.#vscode.postMessage({ + type: 'codeBlockEditorHostTransportMessage', + runtimeId, + message, + }), + () => { + this.#codeBlockEditorHostTransports.delete(runtimeId); + this.#vscode.postMessage({ + type: 'disposeCodeBlockEditorHostTransport', + runtimeId, + }); + }, + ); + this.#codeBlockEditorHostTransports.set(runtimeId, transport); + this.#vscode.postMessage({ + type: 'createCodeBlockEditorHostTransport', + runtimeId, + providerId, + runtimeKey, + }); + return transport; + } + #resolveCodeBlockEditor(providerId: string, language: string): Promise { const requestId = this.#nextCodeBlockEditorRequestId++; return new Promise(resolve => { @@ -515,6 +616,10 @@ function readResolvedCodeBlockEditor(value: unknown): ResolvedIframeEmbeddedEdit const descriptor = value as Record; if ( typeof descriptor.html !== 'string' + || typeof descriptor.runtimeKey !== 'string' + || descriptor.runtimeKey.length === 0 + || (descriptor.resourceBaseUrl !== undefined && typeof descriptor.resourceBaseUrl !== 'string') + || (descriptor.hostTransport !== undefined && typeof descriptor.hostTransport !== 'boolean') || (descriptor.contentType !== 'text' && descriptor.contentType !== 'json') || (descriptor.cacheKey !== undefined && typeof descriptor.cacheKey !== 'string') || (descriptor.initialHeight !== undefined && (typeof descriptor.initialHeight !== 'number' || !Number.isFinite(descriptor.initialHeight) || descriptor.initialHeight <= 0)) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 14795de9066d10..df82cfd0fd8dd0 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "^0.0.2-84", + "@vscode/markdown-editor": "file:../../../vscode-packages/vscode-team-tools/packages/markdown-editor", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", @@ -43,6 +43,44 @@ "vscode": "^1.70.0" } }, + "../../../vscode-packages/vscode-team-tools/packages/markdown-editor": { + "name": "@vscode/markdown-editor", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@vscode/codicons": "0.0.46-36", + "@vscode/diff": "0.0.2-0", + "@vscode/observables": "^0.1.1-0", + "katex": "^0.16.33", + "micromark": "^4.0.1", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm": "^3.0.0", + "micromark-extension-gfm-strikethrough": "^2.1.0", + "micromark-extension-gfm-table": "^2.1.1", + "micromark-extension-gfm-task-list-item": "^2.1.0", + "micromark-extension-math": "^3.1.0" + }, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/katex": "^0.16.8", + "@types/markdown-it": "^14.1.2", + "@vscode/component-explorer": "workspace:*", + "@vscode/component-explorer-cli": "workspace:*", + "@vscode/component-explorer-vite-plugin": "workspace:*", + "@vscode/hubrpc": "workspace:*", + "@vscode/sample-web-editor": "workspace:*", + "@vscode/web-editors": "workspace:*", + "github-markdown-css": "^5.9.0", + "markdown-it": "^14.2.0", + "mermaid": "^11.15.0", + "monaco-editor": "^0.52.2", + "typescript": "^5.7.0", + "vite": "^6.4.3", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.1.0", + "zod": "^4.3.6" + } + }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -481,15 +519,6 @@ "@types/d3-selection": "*" } }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -505,12 +534,6 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, - "node_modules/@types/katex": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", - "license": "MIT" - }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -550,12 +573,6 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.12.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", @@ -601,18 +618,6 @@ "d3-transition": "^3.0.1" } }, - "node_modules/@vscode/codicons": { - "version": "0.0.46-36", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.46-36.tgz", - "integrity": "sha512-K030Z2AGo4P1gIZfT8X6dNyjficRhA3mDbBfuTObHY4M8+QdyIocc7c2nLfLhZC629oUVVPflJery0CjSfrrUw==", - "license": "CC-BY-4.0" - }, - "node_modules/@vscode/diff": { - "version": "0.0.2-0", - "resolved": "https://registry.npmjs.org/@vscode/diff/-/diff-0.0.2-0.tgz", - "integrity": "sha512-gmwM9W6mLnqNxcCd0u9WTuL3JJjaAuicoNcPWNEbHFe8OS8SvdQ6q+txVQTwLT6ezUnXQ6e8sQwmjPSE384yxQ==", - "license": "MIT" - }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", @@ -633,23 +638,8 @@ "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==" }, "node_modules/@vscode/markdown-editor": { - "version": "0.0.2-84", - "resolved": "https://registry.npmjs.org/@vscode/markdown-editor/-/markdown-editor-0.0.2-84.tgz", - "integrity": "sha512-CD/FrfJTNnc3nVrdE6oVjHP+e12/4Ie84eaxSOSLkf7QhktkLhdrYFHz+tJ45jn9wGARMl41Bz0emnUSru80OA==", - "license": "MIT", - "dependencies": { - "@vscode/codicons": "0.0.46-36", - "@vscode/diff": "0.0.2-0", - "@vscode/observables": "^0.1.1-0", - "katex": "^0.16.33", - "micromark": "^4.0.1", - "micromark-extension-frontmatter": "^2.0.0", - "micromark-extension-gfm": "^3.0.0", - "micromark-extension-gfm-strikethrough": "^2.1.0", - "micromark-extension-gfm-table": "^2.1.1", - "micromark-extension-gfm-task-list-item": "^2.1.0", - "micromark-extension-math": "^3.1.0" - } + "resolved": "../../../vscode-packages/vscode-team-tools/packages/markdown-editor", + "link": true }, "node_modules/@vscode/markdown-it-katex": { "version": "1.1.1", @@ -693,16 +683,6 @@ "concat-map": "0.0.1" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -1268,36 +1248,6 @@ "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "license": "MIT" }, - "node_modules/debug": { - "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" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", @@ -1307,28 +1257,6 @@ "robust-predicates": "^3.0.2" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1424,27 +1352,6 @@ "strictdom": "^1.0.1" } }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -1643,582 +1550,6 @@ "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", - "license": "MIT", - "dependencies": { - "@types/katex": "^0.16.0", - "devlop": "^1.0.0", - "katex": "^0.16.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2237,12 +1568,6 @@ "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==", "license": "MIT" }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/node-html-parser": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index ca96b6345b8467..b9f8c1e9d4d358 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -1791,7 +1791,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "^0.0.2-84", + "@vscode/markdown-editor": "file:../../../vscode-packages/vscode-team-tools/packages/markdown-editor", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", diff --git a/extensions/markdown-language-features/schemas/package.schema.json b/extensions/markdown-language-features/schemas/package.schema.json index 434e1b69d51b2e..dc8f89cb2bb577 100644 --- a/extensions/markdown-language-features/schemas/package.schema.json +++ b/extensions/markdown-language-features/schemas/package.schema.json @@ -69,7 +69,7 @@ }, "entrypoint": { "type": "string", - "description": "Extension-relative path to a self-contained HTML document" + "description": "Extension-relative path to the editor's HTML entrypoint. Relative scripts and assets are resolved from this file's directory" }, "contentType": { "type": "string", @@ -85,7 +85,7 @@ }, "markdown.codeBlockEditorProviders": { "type": "array", - "description": "Providers for self-contained HTML editors used by fenced code blocks in the Markdown editor", + "description": "Providers for HTML editors used by fenced code blocks in the Markdown editor", "items": { "type": "object", "additionalProperties": false, @@ -100,6 +100,12 @@ "minLength": 1, "description": "Identifier for this provider, unique within the extension" }, + "runtimeKey": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "Opaque identity for the editor runtime template. Only compatible editors with equal runtime keys may reuse an iframe" + }, "selector": { "oneOf": [ { @@ -140,7 +146,7 @@ }, "entrypoint": { "type": "string", - "description": "Extension-relative path to a self-contained HTML document" + "description": "Extension-relative path to the editor's HTML entrypoint. Relative scripts and assets are resolved from this file's directory" } } }, diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts index 0507ecd05cf234..d24b9108a042fe 100644 --- a/extensions/markdown-language-features/src/markdownExtensions.ts +++ b/extensions/markdown-language-features/src/markdownExtensions.ts @@ -48,6 +48,7 @@ export interface MarkdownCodeBlockEditorProvider { readonly providerId: string; readonly extension: vscode.Extension; readonly extensionVersion: string; + readonly runtimeKey?: string; readonly selector: MarkdownCodeBlockEditorSelector; readonly source: MarkdownCodeBlockEditorSource; readonly contentType: 'text' | 'json'; @@ -100,6 +101,7 @@ export namespace MarkdownContributions { && x.providerId === y.providerId && x.extension.id === y.extension.id && x.extensionVersion === y.extensionVersion + && x.runtimeKey === y.runtimeKey && selectorEqual(x.selector, y.selector) && sourceEqual(x.source, y.source) && x.contentType === y.contentType @@ -179,6 +181,7 @@ export namespace MarkdownContributions { typeof provider.id !== 'string' || !selector || !source + || (provider.runtimeKey !== undefined && (typeof provider.runtimeKey !== 'string' || provider.runtimeKey.length === 0 || provider.runtimeKey.length > 256)) || (provider.contentType !== undefined && provider.contentType !== 'text' && provider.contentType !== 'json') || (provider.initialHeight !== undefined && !isPositiveNumber(provider.initialHeight)) ) { @@ -189,6 +192,7 @@ export namespace MarkdownContributions { providerId: provider.id, extension, extensionVersion: typeof extension.packageJSON?.version === 'string' ? extension.packageJSON.version : '', + runtimeKey: provider.runtimeKey as string | undefined, selector, source, contentType: provider.contentType ?? 'text', diff --git a/extensions/markdown-language-features/src/preview/markdownEditorProvider.ts b/extensions/markdown-language-features/src/preview/markdownEditorProvider.ts index ff74f091048194..989d3ea4a3f2c9 100644 --- a/extensions/markdown-language-features/src/preview/markdownEditorProvider.ts +++ b/extensions/markdown-language-features/src/preview/markdownEditorProvider.ts @@ -28,6 +28,9 @@ interface CodeBlockEditorProviderDefinition { interface ResolvedCodeBlockEditor { readonly cacheKey?: string; readonly html: string; + readonly runtimeKey: string; + readonly resourceBaseUrl?: string; + readonly hostTransport?: boolean; readonly contentType: 'text' | 'json'; readonly initialHeight?: number; readonly sandbox?: MarkdownCodeBlockEditorSandbox; @@ -37,6 +40,10 @@ export interface MarkdownCodeBlockEditorApiV1 { getProvider(providerId: string): MarkdownCodeBlockEditorProviderApi | undefined; } +export interface MarkdownCodeBlockEditorApiV2 { + getProvider(providerId: string): MarkdownCodeBlockEditorProviderApi | undefined; +} + interface MarkdownCodeBlockEditorProviderApi { resolve( request: { @@ -46,18 +53,30 @@ interface MarkdownCodeBlockEditorProviderApi { }, token: vscode.CancellationToken, ): vscode.ProviderResult; + createHostTransport?( + transport: MarkdownCodeBlockEditorHostTransport, + token: vscode.CancellationToken, + ): vscode.ProviderResult; } interface ProviderResolvedCodeBlockEditor { readonly content: - | { readonly html: string; readonly uri?: undefined } + | { readonly html: string; readonly baseUri?: vscode.Uri; readonly uri?: undefined } | { readonly html?: undefined; readonly uri: vscode.Uri }; readonly contentType?: 'text' | 'json'; readonly cacheKey?: string; + readonly runtimeKey?: string; readonly initialHeight?: number; readonly sandbox?: MarkdownCodeBlockEditorSandbox; } +interface MarkdownCodeBlockEditorHostTransport { + readonly runtimeKey: string; + readonly onDidReceiveMessage: vscode.Event; + readonly onDidDispose: vscode.Event; + sendMessage(message: unknown): void; +} + /** * Authenticates messages sent from the extension host to one Markdown editor webview. */ @@ -76,6 +95,66 @@ class AuthenticatedWebview { } } +class CodeBlockEditorHostTransportState implements vscode.Disposable { + readonly #onDidReceiveMessage = new vscode.EventEmitter(); + readonly #onDidDispose = new vscode.EventEmitter(); + readonly #pendingMessages: unknown[] = []; + #providerDisposable: vscode.Disposable | undefined; + #ready = false; + #disposed = false; + + readonly transport: MarkdownCodeBlockEditorHostTransport; + + constructor(runtimeKey: string, sendMessage: (message: unknown) => void) { + this.transport = Object.freeze({ + runtimeKey, + onDidReceiveMessage: this.#onDidReceiveMessage.event, + onDidDispose: this.#onDidDispose.event, + sendMessage: (message: unknown) => { + if (this.#disposed) { + throw new Error('Code block editor host transport is disposed'); + } + sendMessage(message); + }, + }); + } + + acceptMessage(message: unknown): void { + if (this.#disposed) { + return; + } + if (!this.#ready) { + this.#pendingMessages.push(message); + return; + } + this.#onDidReceiveMessage.fire(message); + } + + setReady(providerDisposable: vscode.Disposable | undefined): void { + if (this.#disposed) { + providerDisposable?.dispose(); + return; + } + this.#providerDisposable = providerDisposable; + this.#ready = true; + for (const message of this.#pendingMessages.splice(0)) { + this.#onDidReceiveMessage.fire(message); + } + } + + dispose(): void { + if (this.#disposed) { + return; + } + this.#disposed = true; + this.#pendingMessages.length = 0; + this.#onDidDispose.fire(); + this.#providerDisposable?.dispose(); + this.#onDidReceiveMessage.dispose(); + this.#onDidDispose.dispose(); + } +} + /** * Experimental hybrid (WYSIWYG) Markdown editor backed by the * `@vscode/markdown-editor` component. The {@link vscode.TextDocument} remains @@ -167,16 +246,19 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT } const webview = new AuthenticatedWebview(webviewPanel.webview); this.#webviewPanels.set(webviewPanel, webview); - const codeBlockEditorProviders = this.#loadCodeBlockEditorProviders(); + const codeBlockEditorProviders = this.#loadCodeBlockEditorProviders(webviewPanel.webview); this.#wireSingle(document, webviewPanel, originalDocument, codeBlockEditorProviders, webview); this.#configureWebview(document, webview); } #configureWebview(document: vscode.TextDocument, editorWebview: AuthenticatedWebview): void { const webview = editorWebview.webview; + const codeBlockEditorResourceRoots = vscode.workspace.isTrusted + ? this.#contributions.contributions.codeBlockEditorProviders.map(provider => provider.extension.extensionUri) + : []; webview.options = { enableScripts: true, - localResourceRoots: getMarkdownLocalResourceRoots(document.uri, [this.#mediaRoot], { + localResourceRoots: getMarkdownLocalResourceRoots(document.uri, [this.#mediaRoot, ...codeBlockEditorResourceRoots], { includeWorkspaceResources: vscode.workspace.isTrusted, }), }; @@ -196,6 +278,49 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT let codeBlockEditorProviders: readonly CodeBlockEditorProviderDefinition[] | undefined; let contributionUpdate = 0; const resolveCancellation = new vscode.CancellationTokenSource(); + const hostTransports = new Map(); + const disposeHostTransport = (runtimeId: string, expected?: CodeBlockEditorHostTransportState): void => { + const transport = hostTransports.get(runtimeId); + if (transport && (!expected || transport === expected)) { + hostTransports.delete(runtimeId); + transport.dispose(); + } + }; + const disposeHostTransports = (): void => { + for (const runtimeId of Array.from(hostTransports.keys())) { + disposeHostTransport(runtimeId); + } + }; + const createHostTransport = ( + runtimeId: string, + providerId: string, + runtimeKey: string, + ): void => { + disposeHostTransport(runtimeId); + const contribution = this.#contributions.contributions.codeBlockEditorProviders.find(candidate => candidate.id === providerId); + if (contribution?.source.kind !== 'exportApi' || contribution.source.apiVersion < 2 || !vscode.workspace.isTrusted) { + return; + } + const state = new CodeBlockEditorHostTransportState( + runtimeKey, + message => editorWebview.postMessage({ + type: 'codeBlockEditorHostTransportMessage', + runtimeId, + message, + }), + ); + hostTransports.set(runtimeId, state); + void this.#initializeCodeBlockEditorHostTransport(contribution, state, resolveCancellation.token).then(initialized => { + if (!initialized) { + disposeHostTransport(runtimeId, state); + } + }, error => { + if (!resolveCancellation.token.isCancellationRequested) { + this.#logger.trace('Markdown code block editor', `Provider ${providerId} failed to initialize a host transport`, error); + } + disposeHostTransport(runtimeId, state); + }); + }; const richLinks = new MarkdownEditorRichLinkController( document, this.#linkOpener, @@ -237,7 +362,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT ? this.#contributions.contributions.codeBlockEditorProviders.find(candidate => candidate.id === message.providerId) : undefined; const descriptor = provider && typeof message.language === 'string' - ? await this.#resolveCodeBlockEditor(provider, document.uri, message.language) + ? await this.#resolveCodeBlockEditor(provider, document.uri, message.language, editorWebview.webview) : undefined; if (resolveCancellation.token.isCancellationRequested) { break; @@ -250,6 +375,31 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT break; } + case 'createCodeBlockEditorHostTransport': { + if ( + typeof message.runtimeId === 'string' + && typeof message.providerId === 'string' + && typeof message.runtimeKey === 'string' + ) { + createHostTransport(message.runtimeId, message.providerId, message.runtimeKey); + } + break; + } + + case 'codeBlockEditorHostTransportMessage': { + if (typeof message.runtimeId === 'string') { + hostTransports.get(message.runtimeId)?.acceptMessage(message.message); + } + break; + } + + case 'disposeCodeBlockEditorHostTransport': { + if (typeof message.runtimeId === 'string') { + disposeHostTransport(message.runtimeId); + } + break; + } + case 'codeBlockEditorDiagnostic': { if (typeof message.message === 'string') { this.#logger.trace('Markdown code block editor', message.message); @@ -339,7 +489,9 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT const comments = this.#wireComments(document, editorWebview); const onDidGrantWorkspaceTrust = vscode.workspace.onDidGrantWorkspaceTrust(() => { webviewReady = false; + disposeHostTransports(); this.#configureWebview(document, editorWebview); + void refreshCodeBlockEditorProviders(true, true); }); const refreshCodeBlockEditorProviders = async (clearProviderApis: boolean, force: boolean): Promise => { const update = ++contributionUpdate; @@ -349,7 +501,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT this.#resolvedCodeBlockEditors.clear(); this.#resolvedCodeBlockEditorResources.clear(); } - const updatedCodeBlockEditorProviders = await this.#loadCodeBlockEditorProviders(); + const updatedCodeBlockEditorProviders = await this.#loadCodeBlockEditorProviders(editorWebview.webview); if ( update !== contributionUpdate || (!force && codeBlockEditorProviders && codeBlockEditorDefinitionsEqual(codeBlockEditorProviders, updatedCodeBlockEditorProviders)) @@ -381,12 +533,14 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT if (event.affectsConfiguration('markdown.experimental.richLinks.enabled', document.uri)) { richLinks.updateTargets([]); webviewReady = false; + disposeHostTransports(); this.#configureWebview(document, editorWebview); } }); const onDidChangeLinkPresentationRules = vscode.window.onDidChangeLinkPresentationRules(() => { richLinks.updateTargets([]); webviewReady = false; + disposeHostTransports(); this.#configureWebview(document, editorWebview); }); @@ -394,6 +548,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT contributionUpdate++; resolveCancellation.cancel(); resolveCancellation.dispose(); + disposeHostTransports(); this.#webviewPanels.delete(webviewPanel); this.#focusedWebviewPanels.delete(webviewPanel); this.#updateEditorFocusContext(); @@ -429,7 +584,10 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT await vscode.commands.executeCommand('setContext', 'markdownEditorFocus', focused); } - async #loadCodeBlockEditorProviders(): Promise { + async #loadCodeBlockEditorProviders(webview: vscode.Webview): Promise { + if (!vscode.workspace.isTrusted) { + return []; + } const result: CodeBlockEditorProviderDefinition[] = []; for (const provider of this.#contributions.contributions.codeBlockEditorProviders) { if (provider.source.kind === 'exportApi') { @@ -453,6 +611,8 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT kind: 'static', descriptor: { html: new TextDecoder('utf-8', { fatal: true }).decode(bytes), + runtimeKey: provider.runtimeKey ?? `${provider.id}@${provider.extensionVersion}`, + resourceBaseUrl: getCodeBlockEditorResourceBaseUrl(webview, provider.source.resource), contentType: provider.contentType, initialHeight: provider.initialHeight, sandbox: provider.sandbox, @@ -470,6 +630,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT contribution: MarkdownCodeBlockEditorProvider, documentUri: vscode.Uri, language: string, + webview: vscode.Webview, ): Promise { if (contribution.source.kind !== 'exportApi' || !vscode.workspace.isTrusted) { return undefined; @@ -477,7 +638,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT const requestCacheKey = `${contribution.id}\0${documentUri.toString()}\0${language}`; let cached = this.#resolvedCodeBlockEditors.get(requestCacheKey); if (!cached) { - cached = this.#doResolveCodeBlockEditor(contribution, documentUri, language); + cached = this.#doResolveCodeBlockEditor(contribution, documentUri, language, webview); this.#resolvedCodeBlockEditors.set(requestCacheKey, cached); cached.then(result => { if (!result) { @@ -498,6 +659,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT contribution: MarkdownCodeBlockEditorProvider, documentUri: vscode.Uri, language: string, + webview: vscode.Webview, ): Promise { const cancellation = new vscode.CancellationTokenSource(); let timedOut = false; @@ -526,7 +688,13 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT if (value.content.uri !== undefined) { this.#resolvedCodeBlockEditorResources.add(value.content.uri.toString()); } - return await this.#readResolvedCodeBlockEditor(contribution, value); + return await this.#readResolvedCodeBlockEditor( + contribution, + value, + language, + webview, + typeof provider.createHostTransport === 'function', + ); }; const result = await Promise.race([operation(), cancelled]); if (timedOut) { @@ -545,16 +713,19 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT } async #getCodeBlockEditorProvider(contribution: MarkdownCodeBlockEditorProvider): Promise { - if (contribution.source.kind !== 'exportApi' || !isSupportedMarkdownCodeBlockEditorApiVersion(contribution.source.apiVersion)) { + const source = contribution.source; + if (source.kind !== 'exportApi' || !isSupportedMarkdownCodeBlockEditorApiVersion(source.apiVersion)) { return undefined; } let cached = this.#providerApis.get(contribution.id); if (!cached) { cached = (async () => { const exports = await contribution.extension.activate(); - const api = getMarkdownCodeBlockEditorApiV1(exports); + const api = source.apiVersion === 1 + ? getMarkdownCodeBlockEditorApiV1(exports) + : getMarkdownCodeBlockEditorApiV2(exports); if (!api) { - this.#logger.trace('Markdown code block editor', `Extension ${contribution.extension.id} does not export markdownCodeBlockEditors.apiV1`); + this.#logger.trace('Markdown code block editor', `Extension ${contribution.extension.id} does not export markdownCodeBlockEditors.apiV${source.apiVersion}`); return undefined; } const provider = api.getProvider(contribution.providerId); @@ -572,14 +743,19 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT async #readResolvedCodeBlockEditor( contribution: MarkdownCodeBlockEditorProvider, value: ProviderResolvedCodeBlockEditor, + language: string, + webview: vscode.Webview, + hasHostTransport: boolean, ): Promise { if (!isProviderResolvedCodeBlockEditor(value)) { this.#logger.trace('Markdown code block editor', `Provider ${contribution.id} returned an invalid descriptor`); return undefined; } let html: string; + let baseUri: vscode.Uri | undefined; if (value.content.html !== undefined) { html = value.content.html; + baseUri = value.content.baseUri; } else { if (!isAllowedCodeBlockEditorResource(value.content.uri, contribution.extension.extensionUri)) { this.#logger.trace('Markdown code block editor', `Provider ${contribution.id} returned a resource outside its extension and the workspace`); @@ -587,16 +763,38 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT } const bytes = await vscode.workspace.fs.readFile(value.content.uri); html = new TextDecoder('utf-8', { fatal: true }).decode(bytes); + baseUri = value.content.uri; + } + if (baseUri && !isAllowedCodeBlockEditorResource(baseUri, contribution.extension.extensionUri)) { + this.#logger.trace('Markdown code block editor', `Provider ${contribution.id} returned a base URI outside its extension and the workspace`); + return undefined; } return { cacheKey: value.cacheKey, html, + runtimeKey: value.runtimeKey ?? contribution.runtimeKey ?? value.cacheKey ?? `${contribution.id}@${contribution.extensionVersion}:${language}`, + resourceBaseUrl: baseUri ? getCodeBlockEditorResourceBaseUrl(webview, baseUri, value.content.html === undefined) : undefined, + hostTransport: hasHostTransport && contribution.source.kind === 'exportApi' && contribution.source.apiVersion >= 2, contentType: value.contentType ?? contribution.contentType, initialHeight: value.initialHeight ?? contribution.initialHeight, sandbox: intersectSandbox(contribution.sandbox, value.sandbox), }; } + async #initializeCodeBlockEditorHostTransport( + contribution: MarkdownCodeBlockEditorProvider, + state: CodeBlockEditorHostTransportState, + token: vscode.CancellationToken, + ): Promise { + const provider = await this.#getCodeBlockEditorProvider(contribution); + if (!provider?.createHostTransport || token.isCancellationRequested) { + return false; + } + const disposable = await provider.createHostTransport(state.transport, token); + state.setReady(disposable ?? undefined); + return !token.isCancellationRequested; + } + #clearCodeBlockEditorCaches(): void { this.#providerApis.clear(); this.#resolvedCodeBlockEditors.clear(); @@ -779,7 +977,7 @@ export class MarkdownEditorProvider extends Disposable implements vscode.CustomT + content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; font-src ${webview.cspSource}; img-src ${webview.cspSource} https: data:; media-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}' ${webview.cspSource}; worker-src ${webview.cspSource} blob:; connect-src ${webview.cspSource}; frame-src 'self';" /> @@ -812,6 +1010,9 @@ function codeBlockEditorDefinitionsEqual( function resolvedCodeBlockEditorsEqual(a: ResolvedCodeBlockEditor, b: ResolvedCodeBlockEditor): boolean { return a.cacheKey === b.cacheKey && a.html === b.html + && a.runtimeKey === b.runtimeKey + && a.resourceBaseUrl === b.resourceBaseUrl + && a.hostTransport === b.hostTransport && a.contentType === b.contentType && a.initialHeight === b.initialHeight && a.sandbox?.forms === b.sandbox?.forms @@ -821,6 +1022,17 @@ function resolvedCodeBlockEditorsEqual(a: ResolvedCodeBlockEditor, b: ResolvedCo } export function getMarkdownCodeBlockEditorApiV1(value: unknown): MarkdownCodeBlockEditorApiV1 | undefined { + return getMarkdownCodeBlockEditorApi(value, 1); +} + +export function getMarkdownCodeBlockEditorApiV2(value: unknown): MarkdownCodeBlockEditorApiV2 | undefined { + return getMarkdownCodeBlockEditorApi(value, 2); +} + +function getMarkdownCodeBlockEditorApi(value: unknown, apiVersion: 1): MarkdownCodeBlockEditorApiV1 | undefined; +function getMarkdownCodeBlockEditorApi(value: unknown, apiVersion: 2): MarkdownCodeBlockEditorApiV2 | undefined; +function getMarkdownCodeBlockEditorApi(value: unknown, apiVersion: 1 | 2): MarkdownCodeBlockEditorApiV1 | MarkdownCodeBlockEditorApiV2 | undefined; +function getMarkdownCodeBlockEditorApi(value: unknown, apiVersion: 1 | 2): MarkdownCodeBlockEditorApiV1 | MarkdownCodeBlockEditorApiV2 | undefined { if (!value || typeof value !== 'object') { return undefined; } @@ -828,15 +1040,15 @@ export function getMarkdownCodeBlockEditorApiV1(value: unknown): MarkdownCodeBlo if (!namespace || typeof namespace !== 'object') { return undefined; } - const api = (namespace as Record).apiV1; - return isMarkdownCodeBlockEditorApiV1(api) ? api : undefined; + const api = (namespace as Record)[`apiV${apiVersion}`]; + return isMarkdownCodeBlockEditorApi(api) ? api : undefined; } -export function isSupportedMarkdownCodeBlockEditorApiVersion(value: number): value is 1 { - return value === 1; +export function isSupportedMarkdownCodeBlockEditorApiVersion(value: number): value is 1 | 2 { + return value === 1 || value === 2; } -function isMarkdownCodeBlockEditorApiV1(value: unknown): value is MarkdownCodeBlockEditorApiV1 { +function isMarkdownCodeBlockEditorApi(value: unknown): value is MarkdownCodeBlockEditorApiV1 | MarkdownCodeBlockEditorApiV2 { return typeof value === 'object' && value !== null && typeof (value as Record).getProvider === 'function'; @@ -856,6 +1068,7 @@ function isProviderResolvedCodeBlockEditor(value: unknown): value is ProviderRes if ( (descriptor.contentType !== undefined && descriptor.contentType !== 'text' && descriptor.contentType !== 'json') || (descriptor.cacheKey !== undefined && typeof descriptor.cacheKey !== 'string') + || (descriptor.runtimeKey !== undefined && (typeof descriptor.runtimeKey !== 'string' || descriptor.runtimeKey.length === 0 || descriptor.runtimeKey.length > 256)) || (descriptor.initialHeight !== undefined && (!Number.isFinite(descriptor.initialHeight) || (descriptor.initialHeight as number) <= 0)) || !isSandbox(descriptor.sandbox) || !descriptor.content @@ -864,7 +1077,7 @@ function isProviderResolvedCodeBlockEditor(value: unknown): value is ProviderRes return false; } const content = descriptor.content as Record; - return (typeof content.html === 'string' && content.uri === undefined) + return (typeof content.html === 'string' && content.uri === undefined && (content.baseUri === undefined || content.baseUri instanceof vscode.Uri)) || (content.html === undefined && content.uri instanceof vscode.Uri); } @@ -896,6 +1109,7 @@ function isAllowedCodeBlockEditorResource(resource: vscode.Uri, extensionUri: vs if (vscode.workspace.getWorkspaceFolder(resource)) { return true; } + if (resource.scheme !== extensionUri.scheme || resource.authority !== extensionUri.authority) { return false; } @@ -906,6 +1120,13 @@ function isAllowedCodeBlockEditorResource(resource: vscode.Uri, extensionUri: vs return resourcePath === extensionPath || resourcePath.startsWith(extensionPrefix); } +function getCodeBlockEditorResourceBaseUrl(webview: vscode.Webview, resource: vscode.Uri, resourceIsEntrypoint = true): string { + const path = resourceIsEntrypoint + ? resource.path.slice(0, resource.path.lastIndexOf('/') + 1) + : resource.path.endsWith('/') ? resource.path : `${resource.path}/`; + return webview.asWebviewUri(resource.with({ path, query: '', fragment: '' })).toString(); +} + function getNonce(): string { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/extensions/markdown-language-features/src/test/markdownEditorProvider.test.ts b/extensions/markdown-language-features/src/test/markdownEditorProvider.test.ts index 672c608d41fd12..b7dce252bae913 100644 --- a/extensions/markdown-language-features/src/test/markdownEditorProvider.test.ts +++ b/extensions/markdown-language-features/src/test/markdownEditorProvider.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { MarkdownContributions } from '../markdownExtensions'; -import { getMarkdownCodeBlockEditorApiV1, isSupportedMarkdownCodeBlockEditorApiVersion, lineRangesToGutterMarkers } from '../preview/markdownEditorProvider'; +import { getMarkdownCodeBlockEditorApiV1, getMarkdownCodeBlockEditorApiV2, isSupportedMarkdownCodeBlockEditorApiVersion, lineRangesToGutterMarkers } from '../preview/markdownEditorProvider'; import { encodeWebviewInitialState } from '../preview/webviewInitialState'; suite('Markdown editor diff', () => { @@ -79,13 +79,32 @@ suite('Markdown code block editor API versioning', () => { }), undefined); }); - test('only advertises API version 1', () => { - assert.strictEqual(isSupportedMarkdownCodeBlockEditorApiVersion(1), true); - assert.strictEqual(isSupportedMarkdownCodeBlockEditorApiVersion(2), false); + test('accepts the namespaced V2 extension API', () => { + const apiV2 = { getProvider: () => undefined }; + assert.strictEqual(getMarkdownCodeBlockEditorApiV2({ + markdownCodeBlockEditors: { apiV2 }, + }), apiV2); + assert.strictEqual(getMarkdownCodeBlockEditorApiV2({ + markdownCodeBlockEditors: { apiV1: apiV2 }, + }), undefined); + }); + + test('advertises API versions 1 and 2', () => { + assert.deepStrictEqual( + [0, 1, 2, 3].map(isSupportedMarkdownCodeBlockEditorApiVersion), + [false, true, true, false], + ); + }); + + test('reads the optional runtime key', () => { + assert.strictEqual(readCodeBlockEditorProviders( + { kind: 'exportApi', apiVersion: 2 }, + 'shared-runtime', + )[0]?.runtimeKey, 'shared-runtime'); }); }); -function readCodeBlockEditorProviders(source: unknown) { +function readCodeBlockEditorProviders(source: unknown, runtimeKey?: string) { const extension = { id: 'test.markdown-code-block-editor', extensionUri: vscode.Uri.file('/test/markdown-code-block-editor'), @@ -96,6 +115,7 @@ function readCodeBlockEditorProviders(source: unknown) { id: 'test', selector: { language: 'test' }, source, + runtimeKey, }], }, }, diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-demo.md b/extensions/markdown-language-features/test-workspace/checkbox-count-demo.md new file mode 100644 index 00000000000000..a51a51bf8a842c --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-demo.md @@ -0,0 +1,20 @@ + +```checkbox-count +This code block is replaced by the extension-provided editor. +``` + + +# Checkbox count transport demo + +Edit or toggle these tasks. The code block editor below is updated by the +workspace extension through the Markdown editor's host transport. + +- [ ] First unchecked task +- [x] Completed task +- [ ] Second unchecked task + + +## More tasks + +* [ ] Third unchecked task +* [ ] Another completed task diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/.gitignore b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/.gitignore new file mode 100644 index 00000000000000..a5ce37086b732c --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/.gitignore @@ -0,0 +1,2 @@ +editor/dist/ +dist/ diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/README.md b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/README.md new file mode 100644 index 00000000000000..f1eee7435aa7b0 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/README.md @@ -0,0 +1,20 @@ +# Markdown checkbox-count demo extension + +This workspace extension demonstrates a Markdown code block editor whose UI is +loaded from external HTML, CSS, and TypeScript modules. The iframe communicates +with this extension through `WebEditorClient.hostTransport`. + +From the VS Code repository: + +1. Build `@vscode/markdown-editor` and `@vscode/web-editors` in the adjacent + `vscode-packages` checkout. +2. Run `npm install` and `npm run build` in this folder, then run `npm install` + in `extensions/markdown-language-features`. +3. Start the **Markdown Code Block Editor Demo** launch configuration. +4. Open `checkbox-count-demo.md` with the Markdown editor. +5. Toggle or edit task-list checkboxes. The count rendered by the code block + editor updates through the extension host. + +The build bundles `@vscode/web-editors` and its transitive dependencies into +the iframe entrypoint. The formatter remains a separate generated chunk so the +demo also exercises relative dynamic imports. diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/formatCheckboxLabel.ts b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/formatCheckboxLabel.ts new file mode 100644 index 00000000000000..9fc41b7da5a813 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/formatCheckboxLabel.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function formatTaskProgressLabel(checked: number, total: number): string { + return `${checked}/${total} tasks done`; +} diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/index.html b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/index.html new file mode 100644 index 00000000000000..161cd73c2c6e9d --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/index.html @@ -0,0 +1,24 @@ + + + + + + + + Task Progress + + +
+
+ Loading task progress... +
+
+
+
+
+ + + diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/main.ts b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/main.ts new file mode 100644 index 00000000000000..622b89eb8a97e0 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/main.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WebEditorClient } from '../node_modules/@vscode/web-editors/dist/index.js'; + +interface TaskProgressMessage { + readonly type: 'taskProgress'; + readonly checked: number; + readonly total: number; +} + +const mainElement = getElement('main'); +const progressElement = getElement('#progress'); +const progressLabelElement = getElement('#progress-label'); +const client = await WebEditorClient.connect({ connection: 'windowParent' }); +let reportedHeight: number | undefined; +let progressMessageVersion = 0; + +const reportSize = () => { + const mainHeight = Math.ceil(mainElement.getBoundingClientRect().height); + if (mainHeight === reportedHeight) { + return; + } + reportedHeight = mainHeight; + console.log('[checkbox-count] reporting iframe size', { + height: mainHeight, + documentScrollHeight: document.documentElement.scrollHeight, + bodyScrollHeight: document.body.scrollHeight, + mainHeight: mainElement.getBoundingClientRect().height, + }); + client.reportSize(mainHeight); +}; +const resizeObserver = new ResizeObserver(reportSize); +resizeObserver.observe(mainElement); +requestAnimationFrame(reportSize); + +if (!client.hostTransport) { + progressLabelElement.textContent = 'Host transport unavailable'; +} else { + client.hostTransport.onMessage(async message => { + if (!isTaskProgressMessage(message)) { + return; + } + const messageVersion = ++progressMessageVersion; + const { formatTaskProgressLabel } = await import('./formatCheckboxLabel.js'); + if (messageVersion !== progressMessageVersion) { + return; + } + const progressMaximum = Math.max(message.total, 1); + progressElement.setAttribute('aria-valuemax', progressMaximum); + progressElement.setAttribute('aria-valuenow', message.checked); + progressElement.style.setProperty('--task-progress-ratio', message.checked / progressMaximum); + progressLabelElement.textContent = formatTaskProgressLabel(message.checked, message.total); + reportSize(); + }); + client.hostTransport.sendMessage({ type: 'ready' }); +} + +window.addEventListener('beforeunload', () => { + resizeObserver.disconnect(); + client.dispose(); +}, { once: true }); + +function getElement(selector: string): T { + const element = document.querySelector(selector); + if (!element) { + throw new Error(`Missing required element: ${selector}`); + } + return element; +} + +function isTaskProgressMessage(message: unknown): message is TaskProgressMessage { + return typeof message === 'object' + && message !== null + && 'type' in message + && message.type === 'taskProgress' + && 'checked' in message + && typeof message.checked === 'number' + && Number.isInteger(message.checked) + && 'total' in message + && typeof message.total === 'number' + && Number.isInteger(message.total) + && message.checked >= 0 + && message.total >= 0 + && message.checked <= message.total; +} diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/style.css b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/style.css new file mode 100644 index 00000000000000..3701c977467864 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/editor/style.css @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +:root { + color-scheme: light dark; +} + +html, +body { + background: var(--vscode-editorWidget-background); + color: var(--vscode-editorWidget-foreground); +} + +body { + margin: 0; +} + +main { + display: grid; + gap: var(--vscode-spacing-size80); + box-sizing: border-box; + padding: var(--vscode-spacing-size120); + border-left: var(--vscode-strokeThickness) solid var(--vscode-textLink-foreground); + background: var(--vscode-editorWidget-background); + color: var(--vscode-editorWidget-foreground); + font-family: var(--vscode-font-family); +} + +.task-progress-summary { + font-size: var(--vscode-fontSize-body1); + font-weight: var(--vscode-fontWeight-semiBold); +} + +.task-progress { + --task-progress-ratio: 0; + + width: 100%; + height: var(--vscode-spacing-size80); + overflow: hidden; + border-radius: var(--vscode-cornerRadius-xSmall); + background: var(--vscode-editor-background); +} + +.task-progress-value { + width: 100%; + height: 100%; + transform: scaleX(var(--task-progress-ratio)); + transform-origin: left; + background: var(--vscode-progressBar-background); +} + +@media (prefers-reduced-motion: no-preference) { + .task-progress-value { + transition: transform 160ms ease-out; + } +} diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json new file mode 100644 index 00000000000000..75a54261836814 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json @@ -0,0 +1,523 @@ +{ + "name": "markdown-checkbox-count-demo", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "markdown-checkbox-count-demo", + "version": "0.0.1", + "dependencies": { + "@vscode/web-editors": "file:../../../../../vscode-packages/vscode-team-tools/packages/web-editors" + }, + "devDependencies": { + "esbuild": "0.27.2" + }, + "engines": { + "vscode": "^1.109.0" + } + }, + "../../../../../vscode-packages/vscode-team-tools/packages/web-editors": { + "name": "@vscode/web-editors", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@vscode/hubrpc": "workspace:*" + }, + "devDependencies": { + "tsdown": "^0.22.3", + "tslib": "^2.8.1", + "typescript": "^6.0.3", + "zod": "^4.4.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@vscode/web-editors": { + "resolved": "../../../../../vscode-packages/vscode-team-tools/packages/web-editors", + "link": true + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + } + } +} diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json new file mode 100644 index 00000000000000..0e29c6b7e2319a --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json @@ -0,0 +1,46 @@ +{ + "name": "markdown-checkbox-count-demo", + "displayName": "Markdown Checkbox Count Demo", + "description": "Demonstrates a Markdown code block editor communicating with its extension host.", + "version": "0.0.1", + "publisher": "vscode-samples", + "private": true, + "engines": { + "vscode": "^1.109.0" + }, + "main": "./dist/extension.js", + "scripts": { + "build": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=dist/extension.js && esbuild editor/main.ts --bundle --format=esm --splitting --outdir=editor/dist --entry-names=[name] --chunk-names=chunks/[name]-[hash]" + }, + "activationEvents": [ + "onStartupFinished" + ], + "capabilities": { + "untrustedWorkspaces": { + "supported": false + } + }, + "contributes": { + "markdown.codeBlockEditorProviders": [ + { + "id": "checkboxCount", + "runtimeKey": "checkbox-count-v1", + "selector": { + "language": "checkbox-count" + }, + "source": { + "kind": "exportApi", + "apiVersion": 2 + }, + "contentType": "text", + "initialHeight": 84 + } + ] + }, + "dependencies": { + "@vscode/web-editors": "file:../../../../../vscode-packages/vscode-team-tools/packages/web-editors" + }, + "devDependencies": { + "esbuild": "0.27.2" + } +} diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/src/extension.ts b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/src/extension.ts new file mode 100644 index 00000000000000..159a9d87e78d27 --- /dev/null +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/src/extension.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +const providerId = 'checkboxCount'; +const runtimeUris = new Map(); +const runtimeKeys = new Map(); +let nextRuntimeId = 1; + +interface MarkdownCodeBlockEditorResolveRequest { + readonly documentUri: vscode.Uri; +} + +interface MarkdownCodeBlockEditorHostTransport { + readonly runtimeKey: string; + sendMessage(message: TaskProgressMessage): void; + onDidReceiveMessage(listener: (message: unknown) => void): vscode.Disposable; + onDidDispose(listener: () => void): vscode.Disposable; +} + +interface TaskProgressMessage { + readonly type: 'taskProgress'; + readonly checked: number; + readonly total: number; +} + +export function activate(context: vscode.ExtensionContext) { + const provider = { + async resolve(request: MarkdownCodeBlockEditorResolveRequest) { + const documentKey = request.documentUri.toString(); + let runtimeKey = runtimeKeys.get(documentKey); + if (!runtimeKey) { + runtimeKey = `checkbox-count:${nextRuntimeId++}`; + runtimeKeys.set(documentKey, runtimeKey); + runtimeUris.set(runtimeKey, request.documentUri); + } + + return { + content: { + uri: vscode.Uri.joinPath(context.extensionUri, 'editor', 'index.html'), + }, + runtimeKey, + contentType: 'text', + initialHeight: 84, + }; + }, + + async createHostTransport(transport: MarkdownCodeBlockEditorHostTransport, token: vscode.CancellationToken) { + const documentUri = runtimeUris.get(transport.runtimeKey); + if (!documentUri) { + throw new Error(`Unknown checkbox-count runtime: ${transport.runtimeKey}`); + } + + const document = await vscode.workspace.openTextDocument(documentUri); + if (token.isCancellationRequested) { + return; + } + + const update = () => { + const progress = getTaskProgress(document.getText()); + transport.sendMessage({ + type: 'taskProgress', + checked: progress.checked, + total: progress.total, + }); + }; + + const documentListener = vscode.workspace.onDidChangeTextDocument(event => { + if (event.document.uri.toString() === document.uri.toString()) { + update(); + } + }); + const messageListener = transport.onDidReceiveMessage(message => { + if (isReadyMessage(message)) { + update(); + } + }); + const disposeListener = transport.onDidDispose(() => { + console.log(`Disposed checkbox-count runtime ${transport.runtimeKey}`); + }); + return vscode.Disposable.from(documentListener, messageListener, disposeListener); + }, + }; + + return { + markdownCodeBlockEditors: { + apiV2: { + getProvider(id) { + return id === providerId ? provider : undefined; + }, + }, + }, + }; +} + +function isReadyMessage(message: unknown): message is { readonly type: 'ready' } { + return typeof message === 'object' && message !== null && 'type' in message && message.type === 'ready'; +} + +function getTaskProgress(text: string) { + const tasks = Array.from(text.matchAll(/^\s*[-*+]\s+\[(?[ x])\]/gim)); + return { + checked: tasks.filter(task => task.groups?.state.toLowerCase() === 'x').length, + total: tasks.length, + }; +} diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index 9f8a59c68aed44..f3d1da7f7a7631 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -14,7 +14,7 @@ export class WebviewProtocolProvider implements IDisposable { private static validWebviewFilePaths = new Map([ ['/index.html', { mime: 'text/html' }], - ['/fake.html', { mime: 'text/html' }], + ['/iframe-bootstrap.html', { mime: 'text/html' }], ['/service-worker.js', { mime: 'application/javascript' }], ]); diff --git a/src/vs/workbench/contrib/webview/browser/pre/fake.html b/src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html similarity index 83% rename from src/vs/workbench/contrib/webview/browser/pre/fake.html rename to src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html index 960d1186be5def..a9cc256d7e7703 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/fake.html +++ b/src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html @@ -3,7 +3,7 @@ - Fake + Iframe Bootstrap diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 42c7c01ade59a4..4c109f42692e63 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-NDdxj93lukza2qOTD0F7vgfe6gwSBPEyomXcdWJHRME=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Wed, 2 Sep 2026 17:15:43 +0200 Subject: [PATCH 08/33] Update Markdown editor packages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- .../package-lock.json | 757 +++++++++++++++++- .../markdown-language-features/package.json | 2 +- .../package-lock.json | 90 ++- .../checkbox-count-extension/package.json | 2 +- 4 files changed, 791 insertions(+), 60 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index df82cfd0fd8dd0..95af12158dd078 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "file:../../../vscode-packages/vscode-team-tools/packages/markdown-editor", + "@vscode/markdown-editor": "^0.0.2-86", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", @@ -43,44 +43,6 @@ "vscode": "^1.70.0" } }, - "../../../vscode-packages/vscode-team-tools/packages/markdown-editor": { - "name": "@vscode/markdown-editor", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@vscode/codicons": "0.0.46-36", - "@vscode/diff": "0.0.2-0", - "@vscode/observables": "^0.1.1-0", - "katex": "^0.16.33", - "micromark": "^4.0.1", - "micromark-extension-frontmatter": "^2.0.0", - "micromark-extension-gfm": "^3.0.0", - "micromark-extension-gfm-strikethrough": "^2.1.0", - "micromark-extension-gfm-table": "^2.1.1", - "micromark-extension-gfm-task-list-item": "^2.1.0", - "micromark-extension-math": "^3.1.0" - }, - "devDependencies": { - "@playwright/test": "^1.58.2", - "@types/katex": "^0.16.8", - "@types/markdown-it": "^14.1.2", - "@vscode/component-explorer": "workspace:*", - "@vscode/component-explorer-cli": "workspace:*", - "@vscode/component-explorer-vite-plugin": "workspace:*", - "@vscode/hubrpc": "workspace:*", - "@vscode/sample-web-editor": "workspace:*", - "@vscode/web-editors": "workspace:*", - "github-markdown-css": "^5.9.0", - "markdown-it": "^14.2.0", - "mermaid": "^11.15.0", - "monaco-editor": "^0.52.2", - "typescript": "^5.7.0", - "vite": "^6.4.3", - "vite-plugin-dts": "^4.5.4", - "vitest": "^4.1.0", - "zod": "^4.3.6" - } - }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -519,6 +481,15 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -534,6 +505,12 @@ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -573,6 +550,12 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.12.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", @@ -618,6 +601,18 @@ "d3-transition": "^3.0.1" } }, + "node_modules/@vscode/codicons": { + "version": "0.0.46-36", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.46-36.tgz", + "integrity": "sha512-K030Z2AGo4P1gIZfT8X6dNyjficRhA3mDbBfuTObHY4M8+QdyIocc7c2nLfLhZC629oUVVPflJery0CjSfrrUw==", + "license": "CC-BY-4.0" + }, + "node_modules/@vscode/diff": { + "version": "0.0.2-0", + "resolved": "https://registry.npmjs.org/@vscode/diff/-/diff-0.0.2-0.tgz", + "integrity": "sha512-gmwM9W6mLnqNxcCd0u9WTuL3JJjaAuicoNcPWNEbHFe8OS8SvdQ6q+txVQTwLT6ezUnXQ6e8sQwmjPSE384yxQ==", + "license": "MIT" + }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", @@ -638,8 +633,23 @@ "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==" }, "node_modules/@vscode/markdown-editor": { - "resolved": "../../../vscode-packages/vscode-team-tools/packages/markdown-editor", - "link": true + "version": "0.0.2-86", + "resolved": "https://registry.npmjs.org/@vscode/markdown-editor/-/markdown-editor-0.0.2-86.tgz", + "integrity": "sha512-tTu4/p89O710Er/uH9Q1w6oANlbKglarNU+MpxkB4rgcXZGpmCHWDOF5+Gp1TkfAmyUZoGfj0OWqhvLeSMN9dA==", + "license": "MIT", + "dependencies": { + "@vscode/codicons": "0.0.46-36", + "@vscode/diff": "0.0.2-0", + "@vscode/observables": "^0.1.1-0", + "katex": "^0.16.33", + "micromark": "^4.0.1", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm": "^3.0.0", + "micromark-extension-gfm-strikethrough": "^2.1.0", + "micromark-extension-gfm-table": "^2.1.1", + "micromark-extension-gfm-task-list-item": "^2.1.0", + "micromark-extension-math": "^3.1.0" + } }, "node_modules/@vscode/markdown-it-katex": { "version": "1.1.1", @@ -683,6 +693,16 @@ "concat-map": "0.0.1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -1248,6 +1268,36 @@ "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "license": "MIT" }, + "node_modules/debug": { + "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" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", @@ -1257,6 +1307,28 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1352,6 +1424,27 @@ "strictdom": "^1.0.1" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -1550,6 +1643,582 @@ "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1568,6 +2237,12 @@ "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==", "license": "MIT" }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/node-html-parser": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index b9f8c1e9d4d358..41a69170105faa 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -1791,7 +1791,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "file:../../../vscode-packages/vscode-team-tools/packages/markdown-editor", + "@vscode/markdown-editor": "^0.0.2-86", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json index 75a54261836814..7408f83c6142ce 100644 --- a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package-lock.json @@ -8,7 +8,7 @@ "name": "markdown-checkbox-count-demo", "version": "0.0.1", "dependencies": { - "@vscode/web-editors": "file:../../../../../vscode-packages/vscode-team-tools/packages/web-editors" + "@vscode/web-editors": "^0.0.2-41" }, "devDependencies": { "esbuild": "0.27.2" @@ -17,20 +17,6 @@ "vscode": "^1.109.0" } }, - "../../../../../vscode-packages/vscode-team-tools/packages/web-editors": { - "name": "@vscode/web-editors", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@vscode/hubrpc": "workspace:*" - }, - "devDependencies": { - "tsdown": "^0.22.3", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "zod": "^4.4.3" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -473,9 +459,48 @@ "node": ">=18" } }, + "node_modules/@hpke/common": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@hpke/common/-/common-1.10.1.tgz", + "integrity": "sha512-moJwhmtLtuxiUzzNp1jpfBfx8yefKoO9D/RCR9dmwrnc7qjJqId1rEtQz+lSlU5cabX8daToMSx/7HayXOiaFw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@hpke/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@hpke/core/-/core-1.9.0.tgz", + "integrity": "sha512-pFxWl1nNJeQCSUFs7+GAblHvXBCjn9EPN65vdKlYQil2aURaRxfGMO6vBKGqm1YHTKwiAxJQNEI70PbSowMP9Q==", + "license": "MIT", + "dependencies": { + "@hpke/common": "^1.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@vscode/hubrpc": { + "version": "0.0.2-15", + "resolved": "https://registry.npmjs.org/@vscode/hubrpc/-/hubrpc-0.0.2-15.tgz", + "integrity": "sha512-eN520Qs/0UmI5l/nEWYLqQrYfx9JX7zZ116m13YTs/8lK55iq+DLeM6GpMWWWf8TE4+OMi8hDabkl/FYRp9XPQ==", + "license": "MIT", + "dependencies": { + "@hpke/core": "^1.9.0", + "ws": "^8.18.0" + }, + "peerDependencies": { + "zod": "^4.4.3" + } + }, "node_modules/@vscode/web-editors": { - "resolved": "../../../../../vscode-packages/vscode-team-tools/packages/web-editors", - "link": true + "version": "0.0.2-41", + "resolved": "https://registry.npmjs.org/@vscode/web-editors/-/web-editors-0.0.2-41.tgz", + "integrity": "sha512-HAaDy/gdiSk0Yp38lpZ1Je1Cd7NcWuq1W+aUxwRO/JlOijmsJuZXukAdlIL78hLb0eT+Awrr8VGseY42FUT8rA==", + "license": "MIT", + "dependencies": { + "@vscode/hubrpc": "next" + } }, "node_modules/esbuild": { "version": "0.27.2", @@ -518,6 +543,37 @@ "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json index 0e29c6b7e2319a..04fc29cf5cd265 100644 --- a/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json +++ b/extensions/markdown-language-features/test-workspace/checkbox-count-extension/package.json @@ -38,7 +38,7 @@ ] }, "dependencies": { - "@vscode/web-editors": "file:../../../../../vscode-packages/vscode-team-tools/packages/web-editors" + "@vscode/web-editors": "^0.0.2-41" }, "devDependencies": { "esbuild": "0.27.2" From 4601a548aaee0efc4223a199755100b55509e6cf Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 17:46:53 +0200 Subject: [PATCH 09/33] Reuse existing webview bootstrap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- .vscode/launch.json | 19 ------------------- .vscode/tasks.json | 7 ------- .../electron-main/webviewProtocolProvider.ts | 2 +- .../pre/{iframe-bootstrap.html => fake.html} | 2 +- .../contrib/webview/browser/pre/index.html | 15 +++++++++------ 5 files changed, 11 insertions(+), 34 deletions(-) rename src/vs/workbench/contrib/webview/browser/pre/{iframe-bootstrap.html => fake.html} (83%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 52b463ac95a19b..709ebc458c2fdb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -537,25 +537,6 @@ "order": 10 } }, - { - "type": "extensionHost", - "request": "launch", - "name": "Markdown Code Block Editor Demo", - "runtimeExecutable": "${execPath}", - "args": [ - "${workspaceFolder}/extensions/markdown-language-features/test-workspace", - "--extensionDevelopmentPath=${workspaceFolder}/extensions/markdown-language-features", - "--extensionDevelopmentPath=${workspaceFolder}/extensions/markdown-language-features/test-workspace/checkbox-count-extension" - ], - "outFiles": [ - "${workspaceFolder}/extensions/markdown-language-features/out/**/*.js" - ], - "preLaunchTask": "Build Markdown Code Block Editor Demo", - "presentation": { - "group": "4_demo", - "order": 1 - } - }, { "type": "extensionHost", "request": "launch", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ad9a81a94aa321..b4f2b517111ce1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -674,13 +674,6 @@ "Write-Output \"134 passed, 0 failed, 1 skipped, 135 total\"; Start-Sleep -Seconds 2; Write-Output \"[PASS] E2E Tests\"; Write-Output \"Watching for changes...\"" ], "isBackground": false - }, - { - "label": "Build Markdown Code Block Editor Demo", - "type": "npm", - "script": "build", - "path": "extensions/markdown-language-features/test-workspace/checkbox-count-extension", - "problemMatcher": [] } ] } diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index f3d1da7f7a7631..9f8a59c68aed44 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -14,7 +14,7 @@ export class WebviewProtocolProvider implements IDisposable { private static validWebviewFilePaths = new Map([ ['/index.html', { mime: 'text/html' }], - ['/iframe-bootstrap.html', { mime: 'text/html' }], + ['/fake.html', { mime: 'text/html' }], ['/service-worker.js', { mime: 'application/javascript' }], ]); diff --git a/src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html b/src/vs/workbench/contrib/webview/browser/pre/fake.html similarity index 83% rename from src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html rename to src/vs/workbench/contrib/webview/browser/pre/fake.html index a9cc256d7e7703..960d1186be5def 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/iframe-bootstrap.html +++ b/src/vs/workbench/contrib/webview/browser/pre/fake.html @@ -3,7 +3,7 @@ - Iframe Bootstrap + Fake diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 4c109f42692e63..42c7c01ade59a4 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-FFQoOVVa2tOE3uqUvirwaMNT20TZmrHcL2aaOjJ8BUo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Wed, 2 Sep 2026 17:55:52 +0200 Subject: [PATCH 10/33] Configure the Markdown iframe bootstrap Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- .../markdown-language-features/markdown-editor-src/editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/markdown-language-features/markdown-editor-src/editor.ts b/extensions/markdown-language-features/markdown-editor-src/editor.ts index ab39883d0cd09f..263339ed71d5a7 100644 --- a/extensions/markdown-language-features/markdown-editor-src/editor.ts +++ b/extensions/markdown-language-features/markdown-editor-src/editor.ts @@ -252,6 +252,7 @@ class Editor extends Disposable { providers: this.#createIframeProviders(this.#codeBlockEditorProviders), scriptNonce, themeCss: () => `:root { ${document.documentElement.getAttribute('style') ?? ''} }`, + iframeBootstrapUrl: location.href, onAmbiguous: (language, providers) => this.#vscode.postMessage({ type: 'codeBlockEditorDiagnostic', message: `Ambiguous providers for ${language}: ${providers.map(provider => provider.id).join(', ')}`, From b7282225c92af60dc9af5645b0e4bda31ccd85b8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 18:34:54 +0200 Subject: [PATCH 11/33] Update host-configurable Markdown editor Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- extensions/markdown-language-features/package-lock.json | 8 ++++---- extensions/markdown-language-features/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 95af12158dd078..bca87cd3d18dd8 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "^0.0.2-86", + "@vscode/markdown-editor": "^0.0.2-87", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", @@ -633,9 +633,9 @@ "integrity": "sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==" }, "node_modules/@vscode/markdown-editor": { - "version": "0.0.2-86", - "resolved": "https://registry.npmjs.org/@vscode/markdown-editor/-/markdown-editor-0.0.2-86.tgz", - "integrity": "sha512-tTu4/p89O710Er/uH9Q1w6oANlbKglarNU+MpxkB4rgcXZGpmCHWDOF5+Gp1TkfAmyUZoGfj0OWqhvLeSMN9dA==", + "version": "0.0.2-87", + "resolved": "https://registry.npmjs.org/@vscode/markdown-editor/-/markdown-editor-0.0.2-87.tgz", + "integrity": "sha512-c1T5c2p2btf8NGVARkDN/qGezyZ25xCL/2KXqwsQeh9NmcH4bPZi2CtaeaLmQU2EyHS7WpdYKcIdwGMrJstLEg==", "license": "MIT", "dependencies": { "@vscode/codicons": "0.0.46-36", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 41a69170105faa..998abf85286920 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -1791,7 +1791,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "@vscode/markdown-editor": "^0.0.2-86", + "@vscode/markdown-editor": "^0.0.2-87", "@vscode/observables": "^0.1.1-0", "dompurify": "^3.4.10", "highlight.js": "^11.8.0", From 06d46c446e3e525403a12fcfe8f344f1a275fbae Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 18:50:36 +0200 Subject: [PATCH 12/33] Fix Markdown editor transport listener type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- .../markdown-language-features/markdown-editor-src/editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/markdown-editor-src/editor.ts b/extensions/markdown-language-features/markdown-editor-src/editor.ts index 263339ed71d5a7..501b09a0c60657 100644 --- a/extensions/markdown-language-features/markdown-editor-src/editor.ts +++ b/extensions/markdown-language-features/markdown-editor-src/editor.ts @@ -55,7 +55,7 @@ class CodeBlockEditorHostTransport implements IframeEmbeddedEditorHostTransport #activated = false; #disposed = false; - readonly onMessage: IframeEmbeddedEditorHostTransport['onMessage'] = listener => { + readonly onMessage: IframeEmbeddedEditorHostTransport['onMessage'] = (listener: (message: unknown) => void) => { if (this.#disposed) { throw new Error('Code block editor host transport is disposed'); } From 6a6b6e642ca7fa7e2db652371a47d86d106210c8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 19:17:48 +0200 Subject: [PATCH 13/33] Add web-editors MIT license override Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: edab674c-fabb-4d28-b950-e5c2eb419329 --- cglicenses.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/cglicenses.json b/cglicenses.json index fab28867d222bd..b928f3808d9758 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -1227,5 +1227,33 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE" ] + }, + { + // Reason: @vscode/web-editors declares MIT in its package.json, but its npm + // tarball does not include a LICENSE file and ClearlyDefined does not cover it. + "name": "@vscode/web-editors", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] } ] From 45f3f80c81c77d878ff93b9222acb7e8514ae996 Mon Sep 17 00:00:00 2001 From: "vs-code-engineering[bot]" <122617954+vs-code-engineering[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:22:59 -0700 Subject: [PATCH 14/33] chore: bump @github/copilot to 1.0.83-2 (#333970) * chore: bump @github/copilot to 1.0.83-2 * chore: re-trigger validation for @github/copilot 1.0.83-2 bump The prior product build (470206) failed only on the macOS CLI (ARM64) Rust compile+notarize job, which is structurally independent of the @github/copilot npm bump (the CLI pipeline references no copilot packages) and is flagged in-pipeline as a known build flake (todo@connor4312, MSRUSTUP_LOG=debug). All other CLI-arch jobs passed; every other job cascade-cancelled. No VS Code integration change is warranted; re-run the authoritative product build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: vs-code-engineering[bot] <122617954+vs-code-engineering[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 72 ++++++++++++++++++++-------------------- package.json | 2 +- remote/package-lock.json | 72 ++++++++++++++++++++-------------------- remote/package.json | 2 +- 4 files changed, 74 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4395b66400a202..88da7f3b3ca9d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.82.0", "@devcontainers/cli": "0.88.0", - "@github/copilot": "1.0.83-0", + "@github/copilot": "1.0.83-2", "@github/copilot-sdk": "1.0.13-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", @@ -1155,9 +1155,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-0.tgz", - "integrity": "sha512-Nv4IsqsveMgghwaBhgvSBZyIyvsqNBZTqnbVnv69+9+Suyq20vJcv6aB74UcJ7VPCMxIGJJUaJkugEtkMNv6wA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-2.tgz", + "integrity": "sha512-ntRvwGdZbZJjOvdV3LVBQ7z69W19DO4MWe/dh6pP8kYeumbzC4udVj7mCtoSwBOs0hwXirhXY+BZwqSJUp5FLQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -1166,20 +1166,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.83-0", - "@github/copilot-darwin-x64": "1.0.83-0", - "@github/copilot-linux-arm64": "1.0.83-0", - "@github/copilot-linux-x64": "1.0.83-0", - "@github/copilot-linuxmusl-arm64": "1.0.83-0", - "@github/copilot-linuxmusl-x64": "1.0.83-0", - "@github/copilot-win32-arm64": "1.0.83-0", - "@github/copilot-win32-x64": "1.0.83-0" + "@github/copilot-darwin-arm64": "1.0.83-2", + "@github/copilot-darwin-x64": "1.0.83-2", + "@github/copilot-linux-arm64": "1.0.83-2", + "@github/copilot-linux-x64": "1.0.83-2", + "@github/copilot-linuxmusl-arm64": "1.0.83-2", + "@github/copilot-linuxmusl-x64": "1.0.83-2", + "@github/copilot-win32-arm64": "1.0.83-2", + "@github/copilot-win32-x64": "1.0.83-2" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-0.tgz", - "integrity": "sha512-0KQjKS9vd4QGxLAbFJcvyv/zsC5kivrtDe0UZhHt/43nUGqoS61DFcsM596/kg75vNE6c9J4gmZ5fUPYef+0hw==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-2.tgz", + "integrity": "sha512-RBjF/zTJe+gp9PsthYtIHF36+y3A3Zv3w/2FAa9nHF6ople1+lYoQ0OC1z6tWQSpBx3n/fSkNt0ebMmfhWlQyw==", "cpu": [ "arm64" ], @@ -1193,9 +1193,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-0.tgz", - "integrity": "sha512-fiyW+hy4c8AI7ONxN623f9cmJGRpbqTztc0jSVXc9z9WwzcWi39X0nxUprRM2l2Dq6YQ3guPCqGl/g1T5bQfQg==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-2.tgz", + "integrity": "sha512-GzaarluCiHUA4yrYxWIFEklwPLTYjxGvLyYjI0wW5IPseT7GLFjN7AkDGRU4Ny/mxTj1MbUEsJAWVtiNy5EUHg==", "cpu": [ "x64" ], @@ -1209,9 +1209,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-0.tgz", - "integrity": "sha512-RWbRU+KgEmtAdKp1GQVTqfdwg4Ti/OVmgZGkXq4lMYj3wnBBQcayFpSLHg5ShzDSS0RglD4b8Z27NjPrm7bXxA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-2.tgz", + "integrity": "sha512-iGD5Pc7vnzrAjqTK/BrI8MDtMqXyxUe7P6u8TiGt+4DzKe3v2myT6eFQ+z/JJOHNpiu6LUpTDzwS8WttZqqJpA==", "cpu": [ "arm64" ], @@ -1228,9 +1228,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-0.tgz", - "integrity": "sha512-5COXUNT+jDfkeyqrymZMvhTogkBYUXt+wuRwKrK6ol5vaw5SoDP1DYbI2hIEfoUj4g7XTHLUCD1s3lw8eicqUA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-2.tgz", + "integrity": "sha512-jOQwz075vRWat+RJiMNtD+7vm4DQ83Dvw4iakpNkLfqWMPXlliVr/tqL+SffeSo87LnhOKobZOJOjELS27drdg==", "cpu": [ "x64" ], @@ -1247,9 +1247,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-0.tgz", - "integrity": "sha512-7sYf364iz6s97ClviBRQusTKz3S3TgoKniyYv8+aRi5f5w6TL8NTPnGX1bXMeU0VZmk5VKQTlxVRO2yA4uFwpg==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-2.tgz", + "integrity": "sha512-6/+ByEBEV/vDofrCymaKrEZ5/p+TVHQfgWu8ywvWEPALfxj/iHpAF3grM2bj7uED4C19LTSarHNCkCqdrAUzIQ==", "cpu": [ "arm64" ], @@ -1266,9 +1266,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-0.tgz", - "integrity": "sha512-jze/f6Yd3Y83kxUa88kXUiwHlZmHDwAqudswdHT6f6q+K1ZEELFGEzbB6Ku4i0L8M6wHXO1EF/zaiSFWQaM4Tw==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-2.tgz", + "integrity": "sha512-8dbiPUHaDemDibLfKXDe5xublJxt6fOht4yYDk/ikmOGAVezBUwRVO27PchXQBwGjP2PVNAASkv2WB4Ubw72lA==", "cpu": [ "x64" ], @@ -1300,9 +1300,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-0.tgz", - "integrity": "sha512-93jln98UAJpslMQ7n+wAmCpoOWGEV5lXxV/DaEajySvYrCU33D2yj7d9kl8X2CgaUVBas6sNWSWKtqy+rKJxXQ==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-2.tgz", + "integrity": "sha512-ZE1iUXlJSnNIH+VoRG96emy2e7jMJm85/pcKCrgLvqhDAcQSqiqLVv3OJp2VX2IcDxToOpiaGccU8Ab9zHQqkQ==", "cpu": [ "arm64" ], @@ -1316,9 +1316,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-0.tgz", - "integrity": "sha512-+4Htk3CixO1qcOtYegjn33/8bSDdx8QXDpgVBak2D4Y5hzBWPO5IuQoICwvjaW5VOIW+I7Q62RK2pupSjxB38Q==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-2.tgz", + "integrity": "sha512-qfturLon+1oaWqSaXCISL6BEf5K+ijJFl0m/OZwstWYSlIlaE2+iOz5LcRukoxm25OG1JZDHQFp1k2PY2A+LbA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 44416be24600c7..8ad14851ea7c90 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "dependencies": { "@anthropic-ai/sdk": "^0.82.0", "@devcontainers/cli": "0.88.0", - "@github/copilot": "1.0.83-0", + "@github/copilot": "1.0.83-2", "@github/copilot-sdk": "1.0.13-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", diff --git a/remote/package-lock.json b/remote/package-lock.json index 74bee0b2958b44..a17926c6a37af7 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -8,7 +8,7 @@ "name": "vscode-reh", "version": "0.0.0", "dependencies": { - "@github/copilot": "1.0.83-0", + "@github/copilot": "1.0.83-2", "@github/copilot-sdk": "1.0.13-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", @@ -61,9 +61,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-0.tgz", - "integrity": "sha512-Nv4IsqsveMgghwaBhgvSBZyIyvsqNBZTqnbVnv69+9+Suyq20vJcv6aB74UcJ7VPCMxIGJJUaJkugEtkMNv6wA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.83-2.tgz", + "integrity": "sha512-ntRvwGdZbZJjOvdV3LVBQ7z69W19DO4MWe/dh6pP8kYeumbzC4udVj7mCtoSwBOs0hwXirhXY+BZwqSJUp5FLQ==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -72,20 +72,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.83-0", - "@github/copilot-darwin-x64": "1.0.83-0", - "@github/copilot-linux-arm64": "1.0.83-0", - "@github/copilot-linux-x64": "1.0.83-0", - "@github/copilot-linuxmusl-arm64": "1.0.83-0", - "@github/copilot-linuxmusl-x64": "1.0.83-0", - "@github/copilot-win32-arm64": "1.0.83-0", - "@github/copilot-win32-x64": "1.0.83-0" + "@github/copilot-darwin-arm64": "1.0.83-2", + "@github/copilot-darwin-x64": "1.0.83-2", + "@github/copilot-linux-arm64": "1.0.83-2", + "@github/copilot-linux-x64": "1.0.83-2", + "@github/copilot-linuxmusl-arm64": "1.0.83-2", + "@github/copilot-linuxmusl-x64": "1.0.83-2", + "@github/copilot-win32-arm64": "1.0.83-2", + "@github/copilot-win32-x64": "1.0.83-2" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-0.tgz", - "integrity": "sha512-0KQjKS9vd4QGxLAbFJcvyv/zsC5kivrtDe0UZhHt/43nUGqoS61DFcsM596/kg75vNE6c9J4gmZ5fUPYef+0hw==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.83-2.tgz", + "integrity": "sha512-RBjF/zTJe+gp9PsthYtIHF36+y3A3Zv3w/2FAa9nHF6ople1+lYoQ0OC1z6tWQSpBx3n/fSkNt0ebMmfhWlQyw==", "cpu": [ "arm64" ], @@ -99,9 +99,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-0.tgz", - "integrity": "sha512-fiyW+hy4c8AI7ONxN623f9cmJGRpbqTztc0jSVXc9z9WwzcWi39X0nxUprRM2l2Dq6YQ3guPCqGl/g1T5bQfQg==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.83-2.tgz", + "integrity": "sha512-GzaarluCiHUA4yrYxWIFEklwPLTYjxGvLyYjI0wW5IPseT7GLFjN7AkDGRU4Ny/mxTj1MbUEsJAWVtiNy5EUHg==", "cpu": [ "x64" ], @@ -115,9 +115,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-0.tgz", - "integrity": "sha512-RWbRU+KgEmtAdKp1GQVTqfdwg4Ti/OVmgZGkXq4lMYj3wnBBQcayFpSLHg5ShzDSS0RglD4b8Z27NjPrm7bXxA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.83-2.tgz", + "integrity": "sha512-iGD5Pc7vnzrAjqTK/BrI8MDtMqXyxUe7P6u8TiGt+4DzKe3v2myT6eFQ+z/JJOHNpiu6LUpTDzwS8WttZqqJpA==", "cpu": [ "arm64" ], @@ -134,9 +134,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-0.tgz", - "integrity": "sha512-5COXUNT+jDfkeyqrymZMvhTogkBYUXt+wuRwKrK6ol5vaw5SoDP1DYbI2hIEfoUj4g7XTHLUCD1s3lw8eicqUA==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.83-2.tgz", + "integrity": "sha512-jOQwz075vRWat+RJiMNtD+7vm4DQ83Dvw4iakpNkLfqWMPXlliVr/tqL+SffeSo87LnhOKobZOJOjELS27drdg==", "cpu": [ "x64" ], @@ -153,9 +153,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-0.tgz", - "integrity": "sha512-7sYf364iz6s97ClviBRQusTKz3S3TgoKniyYv8+aRi5f5w6TL8NTPnGX1bXMeU0VZmk5VKQTlxVRO2yA4uFwpg==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.83-2.tgz", + "integrity": "sha512-6/+ByEBEV/vDofrCymaKrEZ5/p+TVHQfgWu8ywvWEPALfxj/iHpAF3grM2bj7uED4C19LTSarHNCkCqdrAUzIQ==", "cpu": [ "arm64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-0.tgz", - "integrity": "sha512-jze/f6Yd3Y83kxUa88kXUiwHlZmHDwAqudswdHT6f6q+K1ZEELFGEzbB6Ku4i0L8M6wHXO1EF/zaiSFWQaM4Tw==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.83-2.tgz", + "integrity": "sha512-8dbiPUHaDemDibLfKXDe5xublJxt6fOht4yYDk/ikmOGAVezBUwRVO27PchXQBwGjP2PVNAASkv2WB4Ubw72lA==", "cpu": [ "x64" ], @@ -206,9 +206,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-0.tgz", - "integrity": "sha512-93jln98UAJpslMQ7n+wAmCpoOWGEV5lXxV/DaEajySvYrCU33D2yj7d9kl8X2CgaUVBas6sNWSWKtqy+rKJxXQ==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.83-2.tgz", + "integrity": "sha512-ZE1iUXlJSnNIH+VoRG96emy2e7jMJm85/pcKCrgLvqhDAcQSqiqLVv3OJp2VX2IcDxToOpiaGccU8Ab9zHQqkQ==", "cpu": [ "arm64" ], @@ -222,9 +222,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.83-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-0.tgz", - "integrity": "sha512-+4Htk3CixO1qcOtYegjn33/8bSDdx8QXDpgVBak2D4Y5hzBWPO5IuQoICwvjaW5VOIW+I7Q62RK2pupSjxB38Q==", + "version": "1.0.83-2", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.83-2.tgz", + "integrity": "sha512-qfturLon+1oaWqSaXCISL6BEf5K+ijJFl0m/OZwstWYSlIlaE2+iOz5LcRukoxm25OG1JZDHQFp1k2PY2A+LbA==", "cpu": [ "x64" ], diff --git a/remote/package.json b/remote/package.json index 0ff984f53c144a..309557bc0b3c9c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { - "@github/copilot": "1.0.83-0", + "@github/copilot": "1.0.83-2", "@github/copilot-sdk": "1.0.13-preview.4", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", From 7514a726399bc81ba7e6664f1d37d6ffd3c5ad3b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 2 Sep 2026 18:47:04 +0200 Subject: [PATCH 15/33] Show binary files in multi-diff editors Keep changed binary resources visible with an accessible placeholder and provide a secondary action that opens the normal diff editor for richer binary handling. Fixes #206062 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 88f03546-c3b6-4f5d-88ac-a8d24407a97c --- .../multiDiffEditor/diffEditorItemTemplate.ts | 70 +++++++++++++- .../browser/widget/multiDiffEditor/model.ts | 5 + .../multiDiffEditorViewModel.ts | 4 +- .../multiDiffEditor/multiDiffEditorWidget.ts | 4 + .../multiDiffEditorWidgetImpl.ts | 45 ++++++--- .../browser/widget/multiDiffEditor/style.css | 19 ++++ .../workbenchUIElementFactory.ts | 3 + .../widget/multiDiffEditorWidget.test.ts | 95 +++++++++++++++++++ .../changes/browser/sessionChangesEditor.ts | 17 ++-- .../browser/multiDiffEditor.ts | 11 ++- .../browser/multiDiffEditorInput.ts | 29 ++++-- .../test/browser/multiDiffEditorInput.test.ts | 66 ++++++++++++- 12 files changed, 333 insertions(+), 35 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index 44e683fe5dce4a..26f7a34034fc8e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -8,6 +8,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { autorun, derived, globalTransaction, IObservable, observableValue } from '../../../../base/common/observable.js'; +import { localize } from '../../../../nls.js'; import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { MenuId } from '../../../../platform/actions/common/actions.js'; @@ -15,6 +16,7 @@ import { IContextKeyService, type IScopedContextKeyService } from '../../../../p import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; +import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { IDiffEditorOptions } from '../../../common/config/editorOptions.js'; import { OffsetRange } from '../../../common/core/ranges/offsetRange.js'; import { observableCodeEditor } from '../../observableCodeEditor.js'; @@ -24,6 +26,8 @@ import { ActionRunnerWithContext } from './utils.js'; import { IVirtualizedItemBindingContext, VirtualizedItemBinding, VirtualizedItemTemplate } from './virtualizedItemManager.js'; import { IWorkbenchUIElementFactory, MultiDiffEditorItemLabelKind } from './workbenchUIElementFactory.js'; +export const binaryFilePlaceholderContentHeight = 100; + export class DiffEditorItemTemplate extends VirtualizedItemTemplate { private readonly _viewModel; @@ -46,11 +50,13 @@ export class DiffEditorItemTemplate extends VirtualizedItemTemplate; this.editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { @@ -125,7 +138,28 @@ export class DiffEditorItemTemplate extends VirtualizedItemTemplate this.isModifedFocused.read(reader) || this.isOriginalFocused.read(reader)); + this.isBinaryFilePlaceholderFocused = observableValue(this, false); + const binaryFilePlaceholderFocus = this._register(trackFocus(this._elements.binaryFilePlaceholder)); + this._register(binaryFilePlaceholderFocus.onDidFocus(() => this.isBinaryFilePlaceholderFocused.set(true, undefined))); + this._register(binaryFilePlaceholderFocus.onDidBlur(() => this.isBinaryFilePlaceholderFocused.set(false, undefined))); + this.isFocused = derived(this, reader => + this.isModifedFocused.read(reader) + || this.isOriginalFocused.read(reader) + || this.isBinaryFilePlaceholderFocused.read(reader) + ); + this._elements.binaryFilePlaceholder.tabIndex = 0; + if (this._workbenchUIElementFactory.openDiffEditor) { + this._openBinaryDiffButton = this._register(new Button(this._elements.binaryFilePlaceholderActions, { ...defaultButtonStyles, secondary: true })); + this._openBinaryDiffButton.label = localize('openBinaryDiff', "Open Diff"); + this._register(this._openBinaryDiffButton.onDidClick(() => { + const item = this._viewModel.get(); + if (item?.originalUri && item.modifiedUri) { + this._workbenchUIElementFactory.openDiffEditor?.(item.originalUri, item.modifiedUri); + } + })); + } else { + this._openBinaryDiffButton = undefined; + } this._resourceLabel = this._workbenchUIElementFactory.createResourceLabel ? this._register(this._workbenchUIElementFactory.createResourceLabel(this._elements.primaryPath, MultiDiffEditorItemLabelKind.Primary)) : undefined; @@ -202,7 +236,13 @@ export class DiffEditorItemTemplate extends VirtualizedItemTemplate { const collapsed = this._collapsed.read(reader); - this._elements.editor.style.display = collapsed ? 'none' : 'block'; + const isBinary = this._viewModel.read(reader)?.documentDiffItem.isBinary === true; + const item = this._viewModel.read(reader); + const canOpenDiff = !!(item?.originalUri && item.modifiedUri && this._openBinaryDiffButton); + this._elements.editor.style.display = collapsed || isBinary ? 'none' : 'block'; + this._elements.binaryFilePlaceholder.style.display = !collapsed && isBinary ? 'grid' : 'none'; + this._elements.binaryFilePlaceholder.tabIndex = canOpenDiff ? -1 : 0; + this._elements.binaryFilePlaceholderActions.style.display = canOpenDiff ? '' : 'none'; if (this._workbenchUIElementFactory.headerClickToCollapse) { this._elements.header.setAttribute('aria-expanded', String(!collapsed)); } @@ -224,7 +264,7 @@ export class DiffEditorItemTemplate extends VirtualizedItemTemplate { + if (item.documentDiffItem.isBinary) { + return; + } const viewModel = item.diffEditorViewModel; if (!viewModel.isDiffUpToDate.read(reader)) { return; @@ -470,6 +515,15 @@ export class DiffEditorItemTemplate extends VirtualizedItemTemplate; readonly contextKeys?: Record; diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts index 06827aa7300199..8bd812b5d82a33 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts @@ -133,8 +133,8 @@ export class DocumentDiffItemViewModel extends Disposable { { expandedContentHeight: 500, selections: undefined, } ); - public get originalUri(): URI | undefined { return this.documentDiffItem.original?.uri; } - public get modifiedUri(): URI | undefined { return this.documentDiffItem.modified?.uri; } + public get originalUri(): URI | undefined { return this.documentDiffItem.originalUri ?? this.documentDiffItem.original?.uri; } + public get modifiedUri(): URI | undefined { return this.documentDiffItem.modifiedUri ?? this.documentDiffItem.modified?.uri; } public readonly isActive: IObservable = derived(this, reader => this._editorViewModel.activeDiffItem.read(reader) === this); public readonly isFirst: IObservable = derived(this, reader => this._editorViewModel.items.read(reader)[0] === this); diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index befafc3fcf06d4..08a7507a3f5047 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -127,6 +127,10 @@ export class MultiDiffEditorWidget extends Disposable { public readonly onDidChangeActiveControl = Event.fromObservableLight(this._activeControl); + public focus(): boolean { + return this._widgetImpl.get().focus(); + } + public getViewState(): IMultiDiffEditorViewState { return this._widgetImpl.get().getViewState(); } diff --git a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index cdf7d096bc0b60..aeacaaea54a88e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -23,7 +23,7 @@ import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { ICodeEditor } from '../../editorBrowser.js'; import { CompressedVirtualizedScrollView, ICompressedVirtualizedScrollItem, ICompressedVirtualizedScrollItemContext } from './compressedVirtualizedScrollView.js'; import { ICompressedVirtualizedScrollLayout } from './compressedVirtualizedScrollLayout.js'; -import { DiffEditorItemBinding, DiffEditorItemTemplate } from './diffEditorItemTemplate.js'; +import { binaryFilePlaceholderContentHeight, DiffEditorItemBinding, DiffEditorItemTemplate } from './diffEditorItemTemplate.js'; import { IDocumentDiffItem } from './model.js'; import { formatDiffItemKey, formatUri, ILoggedDiffItem, MultiDiffEditorLogger } from './multiDiffEditorLogging.js'; import { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from './multiDiffEditorViewModel.js'; @@ -100,9 +100,18 @@ export class MultiDiffEditorWidgetImpl extends Disposable { const manager = this._register(new VirtualizedItemManager(sourceItems, context, { getId: item => item, getTemplateId: () => 'diffEditor', - getUnboundSize: item => derived(item, reader => item.collapsed.read(reader) - ? this._workbenchUIElementFactory.diffEditorItemHeaderHeight ?? 40 - : item.lastTemplateData.read(reader).expandedContentHeight), + getUnboundSize: item => derived(item, reader => { + const headerHeight = this._workbenchUIElementFactory.diffEditorItemHeaderHeight ?? 40; + if (item.collapsed.read(reader)) { + return headerHeight; + } + if (item.documentDiffItem.isBinary) { + return headerHeight + + (this._workbenchUIElementFactory.diffEditorItemContentBottomPadding ?? 0) + + binaryFilePlaceholderContentHeight; + } + return item.lastTemplateData.read(reader).expandedContentHeight; + }), createTemplate: () => this._instantiationService.createInstance( DiffEditorItemTemplate, context.contentDomNode, @@ -516,30 +525,40 @@ export class MultiDiffEditorWidgetImpl extends Disposable { viewModel.activeDiffItem.setCache(target, undefined); if (!this._preserveFocusOnLoad) { - this._viewItemsInfo.get().getItem(target).template.get()?.editor.focus(); + this._viewItemsInfo.get().getItem(target).binding.get()?.focus(); } return true; } public findDocumentDiffItem(resource: URI): IDocumentDiffItem | undefined { const item = this._viewItems.get().find(v => - v.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString() - || v.viewModel.diffEditorViewModel.model.original.uri.toString() === resource.toString() + v.viewModel.modifiedUri?.toString() === resource.toString() + || v.viewModel.originalUri?.toString() === resource.toString() ); return item?.viewModel.documentDiffItem; } + public focus(): boolean { + const activeDiffItem = this._viewModel.get()?.activeDiffItem.get(); + if (!activeDiffItem) { + return false; + } + const binding = this._viewItemsInfo.get().getItem(activeDiffItem).binding.get(); + binding?.focus(); + return binding !== undefined; + } + public tryGetCodeEditor(resource: URI): { diffEditor: IDiffEditor; editor: ICodeEditor } | undefined { const item = this._viewItems.get().find(v => - v.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString() - || v.viewModel.diffEditorViewModel.model.original.uri.toString() === resource.toString() + v.viewModel.modifiedUri?.toString() === resource.toString() + || v.viewModel.originalUri?.toString() === resource.toString() ); const editor = item?.template.get()?.editor; - if (!editor) { + if (!editor || item.viewModel.documentDiffItem.isBinary) { return undefined; } - if (item.viewModel.diffEditorViewModel.model.modified.uri.toString() === resource.toString()) { + if (item.viewModel.modifiedUri?.toString() === resource.toString()) { return { diffEditor: editor, editor: editor.getModifiedEditor() }; } else { return { diffEditor: editor, editor: editor.getOriginalEditor() }; @@ -617,7 +636,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } if (focusEditor) { - editor?.focus(); + item.binding.get()?.focus(); } } @@ -786,7 +805,7 @@ class VirtualizedViewItem extends Disposable implements ILoggedDiffItem, ICompre } public override toString(): string { - return `VirtualViewItem(${this.viewModel.documentDiffItem.modified?.uri.toString()})`; + return `VirtualViewItem(${this.viewModel.modifiedUri?.toString() ?? this.viewModel.originalUri?.toString()})`; } public getKey(): string { diff --git a/src/vs/editor/browser/widget/multiDiffEditor/style.css b/src/vs/editor/browser/widget/multiDiffEditor/style.css index 595eba2d6407b4..138aa773b0b21b 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditor/style.css @@ -154,5 +154,24 @@ .editorContainer { flex: 1; } + + .binary-file-placeholder { + display: none; + flex: 1; + place-items: center; + color: var(--vscode-descriptionForeground); + + &:focus-visible { + outline: var(--vscode-strokeThickness) solid var(--vscode-focusBorder); + outline-offset: calc(-1 * var(--vscode-strokeThickness)); + } + + .binary-file-placeholder-content { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--vscode-spacing-size80); + } + } } } diff --git a/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts b/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts index f853df6d75ad9f..a5f61ea899d1c7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts @@ -46,6 +46,9 @@ export interface IWorkbenchUIElementFactory { /** Handles a middle-click on an entry header. Returns whether the event was handled. */ handleHeaderMiddleClick?(resource: URI): boolean; + /** Opens an entry in a standalone diff editor. */ + openDiffEditor?(original: URI, modified: URI): void; + /** * Optional override for how individual actions render in the per-file header * toolbar (`MenuId.MultiDiffEditorFileToolbar`). Return `undefined` to fall diff --git a/src/vs/editor/test/browser/widget/multiDiffEditorWidget.test.ts b/src/vs/editor/test/browser/widget/multiDiffEditorWidget.test.ts index 7150e1bc0e3cef..5133128ee54b82 100644 --- a/src/vs/editor/test/browser/widget/multiDiffEditorWidget.test.ts +++ b/src/vs/editor/test/browser/widget/multiDiffEditorWidget.test.ts @@ -6,6 +6,7 @@ import assert from 'assert'; import sinon from 'sinon'; import { Dimension } from '../../../../base/browser/dom.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; import { Event, ValueWithChangeEvent } from '../../../../base/common/event.js'; import { autorun, waitForState } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; @@ -82,6 +83,100 @@ suite('MultiDiffEditorWidget', () => { } }); + test('renders binary files as a placeholder', async () => { + const services = new ServiceCollection(); + services.set(IAccessibilitySignalService, new class extends mock() { }()); + services.set(IActionViewItemService, new NullActionViewItemService()); + services.set(IEditorProgressService, new class extends mock() { }()); + services.set(IDiffProviderFactoryService, new TestDiffProviderFactoryService()); + services.set(IStorageService, disposables.add(new InMemoryStorageService())); + services.set(IMenuService, new class extends mock() { + override createMenu(): IMenu { + return new class extends mock() { + override readonly onDidChange = Event.None; + override getActions() { return []; } + override dispose(): void { } + }(); + } + }()); + const instantiationService = createCodeEditorServices(disposables, services); + const originalUri = URI.parse('inmemory://original/image.png'); + const modifiedUri = URI.parse('inmemory://modified/image.png'); + const documentItem = RefCounted.createOfNonDisposable({ + originalUri, + modifiedUri, + original: undefined, + modified: undefined, + isBinary: true, + }, { dispose() { } }); + const model: IMultiDiffEditorModel = { + documents: ValueWithChangeEvent.const([documentItem]), + }; + let openedDiff: { original: URI; modified: URI } | undefined; + const container = document.createElement('div'); + const widget = instantiationService.createInstance( + MultiDiffEditorWidget, + container, + { + openDiffEditor: (original, modified) => openedDiff = { original, modified }, + } satisfies IWorkbenchUIElementFactory, + undefined, + ); + widget.layout(new Dimension(800, 600)); + const viewModel = widget.createViewModel(model); + await waitForState(viewModel.items, items => items.length === 1); + widget.setViewModel(viewModel); + widget.reveal({ original: originalUri, modified: modifiedUri }, { highlight: false }); + await waitForState(widget.getLayoutDebugState(), state => state.items[0]?.hasTemplate === true); + + try { + const placeholder = widget.getRootElement().querySelector('.binary-file-placeholder'); + const editor = widget.getRootElement().querySelector('.editorContainer'); + const openDiffButton = placeholder?.querySelector('.monaco-button'); + const focusSpy = sinon.spy(Button.prototype, 'focus'); + const canFocusActiveItem = widget.focus(); + openDiffButton?.click(); + assert.deepStrictEqual({ + text: placeholder?.textContent, + display: placeholder?.style.display, + tabIndex: placeholder?.tabIndex, + role: placeholder?.getAttribute('role'), + ariaLabel: placeholder?.getAttribute('aria-label'), + openDiffButtonText: openDiffButton?.textContent, + openDiffButtonSecondary: openDiffButton?.classList.contains('secondary'), + openDiffButtonFocused: focusSpy.calledOnce, + openedOriginalUri: openedDiff?.original.toString(), + openedModifiedUri: openedDiff?.modified.toString(), + editorDisplay: editor?.style.display, + itemHeight: widget.getLayoutDebugState().get().items[0].verticalState.contentHeight, + canFocusActiveItem, + findsDocumentItem: widget.findDocumentDiffItem(modifiedUri) === documentItem.object, + hasCodeEditorForBinaryResource: widget.tryGetCodeEditor(modifiedUri) !== undefined, + }, { + text: 'Binary file changedOpen Diff', + display: 'grid', + tabIndex: -1, + role: 'group', + ariaLabel: 'Binary file changed', + openDiffButtonText: 'Open Diff', + openDiffButtonSecondary: true, + openDiffButtonFocused: true, + openedOriginalUri: originalUri.toString(), + openedModifiedUri: modifiedUri.toString(), + editorDisplay: 'none', + itemHeight: 140, + canFocusActiveItem: true, + findsDocumentItem: true, + hasCodeEditorForBinaryResource: false, + }); + } finally { + widget.setViewModel(undefined); + viewModel.dispose(); + widget.dispose(); + documentItem.dispose(); + } + }); + test('applies document and responsive layout options before attaching the diff model', async () => { const services = new ServiceCollection(); services.set(IAccessibilitySignalService, new class extends mock() { }()); diff --git a/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts b/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts index 6e924a37405f41..c97a4759304d37 100644 --- a/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts +++ b/src/vs/sessions/contrib/changes/browser/sessionChangesEditor.ts @@ -83,6 +83,7 @@ class SessionChangesUIElementFactory implements IWorkbenchUIElementFactory { @ICommandService private readonly commandService: ICommandService, @IChangesViewService private readonly changesViewService: IChangesViewService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IEditorService private readonly editorService: IEditorService, ) { } createResourceLabel(element: HTMLElement, kind: MultiDiffEditorItemLabelKind): IResourceLabel { @@ -111,6 +112,14 @@ class SessionChangesUIElementFactory implements IWorkbenchUIElementFactory { } return undefined; } + + openDiffEditor(original: URI, modified: URI): void { + void this.editorService.openEditor({ + original: { resource: original }, + modified: { resource: modified }, + options: { pinned: true }, + }); + } } class SessionChangesResourceLabel extends Disposable implements IResourceLabel { @@ -436,9 +445,7 @@ export class SessionChangesEditor extends AbstractEditorWithViewState