From 2f190b2227eabf80eb4b771ae91fc1d97b95d85c Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 16 Oct 2026 12:35:26 -0700 Subject: [PATCH 1/2] API: Query capabilities --- docs/README.md | 9 ++ src/extensionApi.ts | 78 +++++++++- src/test/extensionApi.unit.test.ts | 239 ++++++++++++++++++++++++++++- 3 files changed, 319 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 843db29b2..d8a299b9c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1871,6 +1871,15 @@ classification, not exact installation intent. Selection is scoped environment selection, not shell activation. Pip reports installation, uninstallation, and upgrades as unsupported for Python 2 environments. +Public environment and package operations initiate the corresponding capability +queries before delegating to managers. These queries are observational: operations +do not wait for them or use their results to gate execution, and probe failures +are logged without changing operation results. They use the existing +`getEnvironmentManagerCapability` and `getPackageManagerCapability` methods, +including their legacy fallback and query telemetry. Package queries include the +routed manager's project when applicable. Registration, item factories, project +APIs, and execution APIs have no corresponding capability checks. + Use the exported `EnvironmentCapability` and `PackageCapability` constants or their literal string values. `PackageCapability.ListDirect` is `packages.list.direct`; diff --git a/src/extensionApi.ts b/src/extensionApi.ts index de407f52e..8fa92447f 100644 --- a/src/extensionApi.ts +++ b/src/extensionApi.ts @@ -1,7 +1,7 @@ import { Disposable, Event, EventEmitter, l10n, TaskExecution, Terminal, Uri } from 'vscode'; import * as path from 'path'; import type { CapabilityContext, EnvironmentManagerCapability, PackageManagerCapability, Support } from './capabilities'; -import { isKnownManagerCapability } from './capabilities'; +import { EnvironmentCapability, isKnownManagerCapability, PackageCapability } from './capabilities'; import { explain as parsePep440Version } from '@renovatebot/pep440'; import { EventNames } from './common/telemetry/constants'; import { sendTelemetryEvent } from './common/telemetry/sender'; @@ -84,6 +84,10 @@ type ManagerCapabilityQuery = context: CapabilityContext & { environment: PythonEnvironment }; }; +function logCapabilityQueryError(error: unknown): void { + traceError('Failed to query manager capability:', error); +} + export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { readonly __pythonTools: PythonToolsApi; private readonly _onDidChangeEnvironments = new EventEmitter(); @@ -314,6 +318,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { throw new Error('No environment manager found'); } + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Create, { scope }) + .catch(logCapabilityQueryError); if (!manager.supportsCreate) { throw new Error(`Environment manager does not support creating environments: ${manager.id}`); } @@ -344,6 +350,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { throw new Error('No environment manager found'); } + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Create, { scope }) + .catch(logCapabilityQueryError); const result = await manager.create(scope, options); return result; } @@ -354,6 +362,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { return Promise.reject(new Error('No environment manager found')); } + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Remove, { environment }) + .catch(logCapabilityQueryError); return manager.remove(environment, options); } async refreshEnvironments(scope: RefreshEnvironmentsScope): Promise { @@ -361,7 +371,11 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (currentScope === undefined) { await waitForAllEnvManagers(); - await Promise.all(this.envManagers.managers.map((manager) => manager.refresh(currentScope))); + await Promise.all(this.envManagers.managers.map((manager) => { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope }) + .catch(logCapabilityQueryError); + return manager.refresh(currentScope); + })); return Promise.resolve(); } @@ -370,13 +384,19 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { return Promise.reject(new Error(`No environment manager found for: ${currentScope.fsPath}`)); } + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope }) + .catch(logCapabilityQueryError); return manager.refresh(currentScope); } async getEnvironments(scope: GetEnvironmentsScope): Promise { const currentScope = checkUri(scope) as GetEnvironmentsScope; if (currentScope === 'all' || currentScope === 'global') { await waitForAllEnvManagers(); - const promises = this.envManagers.managers.map((manager) => manager.getEnvironments(currentScope)); + const promises = this.envManagers.managers.map((manager) => { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope }) + .catch(logCapabilityQueryError); + return manager.getEnvironments(currentScope); + }); const items = await Promise.all(promises); return items.flat(); } @@ -387,6 +407,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { return []; } + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.List, { scope: currentScope }) + .catch(logCapabilityQueryError); const items = await manager.getEnvironments(currentScope); return items; } @@ -396,6 +418,29 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { await waitForEnvManager( currentScope ? (currentScope instanceof Uri ? [currentScope] : currentScope) : undefined, ); + if (Array.isArray(currentScope) && !environment) { + const scopesByManager = new Map(); + for (const uri of currentScope) { + const manager = this.envManagers.getEnvironmentManager(uri); + if (manager) { + scopesByManager.set(manager, [...(scopesByManager.get(manager) ?? []), uri]); + } + } + for (const [manager, scopes] of scopesByManager) { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.SetSelected, { scope: scopes }) + .catch(logCapabilityQueryError); + } + } else { + const manager = this.envManagers.getEnvironmentManager( + environment ?? (currentScope instanceof Uri ? currentScope : undefined), + ); + if (manager) { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.SetSelected, { + scope: currentScope, + environment, + }).catch(logCapabilityQueryError); + } + } return this.envManagers.setEnvironment(currentScope, environment); } async getEnvironment(scope: GetEnvironmentScope): Promise { @@ -408,6 +453,11 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { // via onDidChangeEnvironment once it settles. const resolution = (async () => { await waitForEnvManager(currentScope ? [currentScope] : undefined); + const manager = this.envManagers.getEnvironmentManager(currentScope); + if (manager) { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.GetSelected, { scope: currentScope }) + .catch(logCapabilityQueryError); + } return this.envManagers.getEnvironment(currentScope); })(); @@ -443,6 +493,10 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { } }); + for (const manager of new Set([...projectEnvManagers, ...this.envManagers.managers])) { + void this.getEnvironmentManagerCapability(manager.id, EnvironmentCapability.Resolve) + .catch(logCapabilityQueryError); + } return await handlePythonPath(context, this.envManagers.managers, projectEnvManagers); } @@ -455,6 +509,18 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { return Promise.reject(new Error('No package manager found')); } + const hasInstall = !!options.install?.length; + const hasUninstall = !!options.uninstall?.length; + const interactive = !hasInstall && !hasUninstall; + for (const [capability, requested] of [ + [options.upgrade ? PackageCapability.Upgrade : PackageCapability.Install, hasInstall || interactive], + [PackageCapability.Uninstall, hasUninstall || interactive], + [PackageCapability.Install, interactive && options.upgrade], + ] as const) { + if (requested) { + void this.getPackageManagerCapability(context, capability, manager.project).catch(logCapabilityQueryError); + } + } return manager.manage(context, options); } async refreshPackages(context: PythonEnvironment): Promise { @@ -463,6 +529,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { return Promise.reject(new Error('No package manager found')); } + void this.getPackageManagerCapability(context, PackageCapability.List, manager.project) + .catch(logCapabilityQueryError); return manager.refresh(context); } async getPackages(context: PythonEnvironment, options?: GetPackagesOptions): Promise { @@ -471,6 +539,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { if (!manager) { return Promise.resolve(undefined); } + void this.getPackageManagerCapability(context, PackageCapability.List, manager.project) + .catch(logCapabilityQueryError); return manager.getPackages(context, options); } getPackageAvailableVersions( @@ -498,6 +568,8 @@ export class PythonEnvironmentApiImpl implements PythonEnvironmentApi { } return undefined; } + void this.getPackageManagerCapability(context, PackageCapability.FetchAvailableVersions, manager.project) + .catch(logCapabilityQueryError); return manager.getPackageAvailableVersions(context, packageName, options); } onDidChangePackages: Event = this._onDidChangePackages.event; diff --git a/src/test/extensionApi.unit.test.ts b/src/test/extensionApi.unit.test.ts index f47ee1896..d514c4472 100644 --- a/src/test/extensionApi.unit.test.ts +++ b/src/test/extensionApi.unit.test.ts @@ -2,13 +2,21 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { EventEmitter, Uri } from 'vscode'; import { + EnvironmentManager, + PackageManagementOptions, PythonEnvironment, PythonProject, } from '../api'; +import { EnvironmentCapability, PackageCapability } from '../capabilities'; +import * as logging from '../common/logging'; +import { createDeferred } from '../common/utils/deferred'; +import { EventNames } from '../common/telemetry/constants'; +import * as telemetry from '../common/telemetry/sender'; import * as managerReady from '../features/common/managerReady'; import { PythonEnvironmentApiImpl } from '../extensionApi'; import type { PythonProjectManager } from '../features/projectManager'; -import type { InternalPackageManager } from '../managers/common/registeredManagers'; +import { InternalEnvironmentManager, InternalPackageManager } from '../managers/common/registeredManagers'; +import { createMockPythonEnvironment } from './mocks/pythonEnvironment'; suite('PythonEnvironmentApiImpl - onDidChangePythonProjects', () => { test('fires event with correct added and removed projects', () => { @@ -107,7 +115,11 @@ suite('PythonEnvironmentApiImpl - getEnvironment timeout fallback', () => { }), ), getLastKnownEnvironment: sinon.stub().withArgs(scope).returns(lastKnown), - getEnvironmentManager: sinon.stub().returns({ id: 'ms-python.python:venv' }), + getEnvironmentManager: sinon.stub().returns({ + id: 'ms-python.python:venv', + getCapability: sinon.stub().resolves({ supported: true }), + getVersion: sinon.stub().resolves(undefined), + }), } as unknown as ApiArgs[0]; const mockProjectCreators = {} as unknown as ApiArgs[2]; const mockTerminalManager = {} as unknown as ApiArgs[3]; @@ -151,7 +163,11 @@ suite('PythonEnvironmentApiImpl - getEnvironment timeout fallback', () => { }), ), getLastKnownEnvironment: sinon.stub().returns(lastKnown), - getEnvironmentManager: sinon.stub().returns({ id: 'ms-python.python:inline-script' }), + getEnvironmentManager: sinon.stub().returns({ + id: 'ms-python.python:inline-script', + getCapability: sinon.stub().resolves({ supported: true }), + getVersion: sinon.stub().resolves(undefined), + }), } as unknown as ApiArgs[0]; const api = new PythonEnvironmentApiImpl( @@ -234,7 +250,11 @@ suite('PythonEnvironmentApiImpl - package resolution', () => { test('delegates to the resolved manager', async () => { const manage = sinon.stub().resolves(); - const manager = { manage } as unknown as InternalPackageManager; + const manager = { + manage, + getCapability: sinon.stub().resolves({ supported: true }), + getVersion: sinon.stub().resolves(undefined), + } as unknown as InternalPackageManager; const { api, getPackageManager } = createApi(manager); await api.managePackages(environment, { install: ['example'] }); @@ -242,4 +262,215 @@ suite('PythonEnvironmentApiImpl - package resolution', () => { assert.ok(getPackageManager.calledWithExactly(environment)); assert.ok(manage.calledOnceWithExactly(environment, { install: ['example'] })); }); + +}); + +suite('PythonEnvironmentApiImpl - operational capability mapping', () => { + const scope = Uri.file(process.cwd()); + const project: PythonProject = { name: 'project', uri: scope }; + const environment = createMockPythonEnvironment({ envPath: process.cwd(), managerId: 'test:environment' }); + let api: PythonEnvironmentApiImpl; + let environmentManager: InternalEnvironmentManager; + let packageManager: InternalPackageManager; + let environmentProbe: sinon.SinonStub; + let packageProbe: sinon.SinonStub; + let getEnvironmentManager: sinon.SinonStub; + let getPackageManager: sinon.SinonStub; + let getSelected: sinon.SinonStub; + let setSelected: sinon.SinonStub; + let sendTelemetry: sinon.SinonStub; + let disposables: { dispose(): void }[]; + + setup(() => { + sendTelemetry = sinon.stub(telemetry, 'sendTelemetryEvent'); + sinon.stub(managerReady, 'waitForEnvManager').resolves(); + sinon.stub(managerReady, 'waitForEnvManagerId').resolves(); + sinon.stub(managerReady, 'waitForAllEnvManagers').resolves(); + const provider: EnvironmentManager = { + name: 'environment', + preferredPackageManagerId: 'test:packages', + create: sinon.stub().resolves(environment), + remove: sinon.stub().resolves(), + refresh: sinon.stub().resolves(), + getEnvironments: sinon.stub().resolves([environment]), + get: sinon.stub().resolves(environment), + set: sinon.stub().resolves(), + resolve: sinon.stub().resolves(environment), + }; + environmentManager = new InternalEnvironmentManager('test:environment', provider); + packageManager = new InternalPackageManager('test:packages', { + name: 'packages', + manage: sinon.stub().resolves(), + refresh: sinon.stub().resolves(), + getPackages: sinon.stub().resolves([]), + getPackageAvailableVersions: sinon.stub().resolves([]), + }, project); + environmentProbe = sinon.stub(environmentManager, 'getCapability').resolves({ supported: true }); + packageProbe = sinon.stub(packageManager, 'getCapability').resolves({ supported: true }); + getEnvironmentManager = sinon.stub().returns(environmentManager); + getPackageManager = sinon.stub().returns(packageManager); + getSelected = sinon.stub().resolves(environment); + setSelected = sinon.stub().resolves(); + const projectChanges = new EventEmitter(); + const selectionChanges = new EventEmitter(); + const packageChanges = new EventEmitter(); + const variableChanges = new EventEmitter(); + disposables = [projectChanges, selectionChanges, packageChanges, variableChanges]; + type ApiArgs = ConstructorParameters; + api = new PythonEnvironmentApiImpl( + { + managers: [environmentManager], + getEnvironmentManager, + getPackageManager, + getEnvironment: getSelected, + setEnvironment: setSelected, + onDidChangeActiveEnvironment: selectionChanges.event, + onDidChangePackageProviderPackages: packageChanges.event, + } as unknown as ApiArgs[0], + { + getProjects: () => [project], + onDidChangeProjects: projectChanges.event, + } as unknown as ApiArgs[1], + {} as ApiArgs[2], + {} as ApiArgs[3], + { onDidChangeEnvironmentVariables: variableChanges.event } as unknown as ApiArgs[4], + disposables, + ); + }); + + teardown(() => { + disposables.forEach((disposable) => disposable.dispose()); + sinon.restore(); + }); + + test('maps environment operations to their capabilities before delegation', async () => { + const cases = [ + { + run: () => api.createEnvironment(scope, undefined), + capability: EnvironmentCapability.Create, + context: { scope }, + operation: 'create' as const, + }, + { + run: () => api.removeEnvironment(environment), + capability: EnvironmentCapability.Remove, + context: { environment }, + operation: 'remove' as const, + }, + { + run: () => api.refreshEnvironments(undefined), + capability: EnvironmentCapability.List, + context: { scope: undefined }, + operation: 'refresh' as const, + }, + { + run: () => api.getEnvironments('all'), + capability: EnvironmentCapability.List, + context: { scope: 'all' }, + operation: 'getEnvironments' as const, + }, + { + run: () => api.setEnvironment([scope], environment), + capability: EnvironmentCapability.SetSelected, + context: { scope: [scope], environment }, + operation: undefined, + }, + { + run: () => api.getEnvironment(scope), + capability: EnvironmentCapability.GetSelected, + context: { scope }, + operation: undefined, + }, + { + run: () => api.resolveEnvironment(scope), + capability: EnvironmentCapability.Resolve, + context: undefined, + operation: 'resolve' as const, + }, + ]; + for (const { run, capability, context, operation } of cases) { + environmentProbe.resetHistory(); + const delegate = operation ? sinon.spy(environmentManager, operation) + : capability === EnvironmentCapability.SetSelected ? setSelected : getSelected; + await run(); + assert.ok(environmentProbe.calledOnceWithExactly(capability, context), capability); + assert.ok(environmentProbe.calledBefore(delegate), capability); + if (operation) { + delegate.restore(); + } + } + assert.ok(getEnvironmentManager.calledWithExactly(environment)); + await new Promise(setImmediate); + assert.deepStrictEqual( + sendTelemetry.withArgs(EventNames.MANAGER_CAPABILITY_QUERY).getCalls().map((call) => call.args[2]), + cases.map(({ capability }) => ({ + managerId: environmentManager.id, + managerKind: 'environment', + managerVersion: 'unknown', + capability, + outcome: 'supported', + isLegacy: false, + })), + ); + }); + + test('maps package reads and requested mutations on the existing project-bound manager', async () => { + await api.refreshPackages(environment); + await api.getPackages(environment); + await api.getPackageAvailableVersions(environment, 'example'); + assert.deepStrictEqual(packageProbe.getCalls().map((call) => call.args), [ + [PackageCapability.List, { environment, project }], + [PackageCapability.List, { environment, project }], + [PackageCapability.FetchAvailableVersions, { environment, project }], + ]); + + const cases: [PackageManagementOptions, string[]][] = [ + [{ install: ['example'] }, [PackageCapability.Install]], + [{ install: ['example'], upgrade: true }, [PackageCapability.Upgrade]], + [{ uninstall: ['example'] }, [PackageCapability.Uninstall]], + [{ install: ['one'], uninstall: ['two'] }, [PackageCapability.Install, PackageCapability.Uninstall]], + [{ install: ['one'], uninstall: ['two'], upgrade: true }, [PackageCapability.Upgrade, PackageCapability.Uninstall]], + [{ install: [] }, [PackageCapability.Install, PackageCapability.Uninstall]], + [{ install: [], upgrade: true }, [PackageCapability.Upgrade, PackageCapability.Uninstall, PackageCapability.Install]], + ]; + const manage = sinon.spy(packageManager, 'manage'); + for (const [options, capabilities] of cases) { + packageProbe.resetHistory(); + manage.resetHistory(); + await api.managePackages(environment, options); + assert.deepStrictEqual(packageProbe.getCalls().map((call) => call.args), capabilities.map( + (capability) => [capability, { environment, project }], + )); + assert.ok(packageProbe.calledBefore(manage)); + assert.ok(manage.calledOnceWithExactly(environment, options)); + } + assert.ok(getPackageManager.calledWithExactly(environment)); + assert.ok(getPackageManager.calledWithExactly(project.uri)); + await new Promise(setImmediate); + assert.ok(sendTelemetry.calledWithExactly( + EventNames.MANAGER_CAPABILITY_QUERY, + undefined, + sinon.match({ managerId: packageManager.id, managerKind: 'package', capability: PackageCapability.Install }), + )); + }); + + test('capability probes cannot gate, delay, or replace operation results and errors', async () => { + const trace = sinon.stub(logging, 'traceError'); + const pending = createDeferred(); + environmentProbe.returns(pending.promise); + packageProbe.resolves({ supported: false, reason: 'Not supported' }); + assert.strictEqual(await api.getEnvironment(scope), environment); + assert.deepStrictEqual(await api.getPackages(environment), []); + + const probeFailure = new Error('Probe failed'); + environmentProbe.throws(probeFailure); + packageProbe.rejects(probeFailure); + assert.strictEqual(await api.getEnvironment(scope), environment); + assert.deepStrictEqual(await api.getPackages(environment), []); + assert.ok(trace.calledWith(sinon.match.string, probeFailure)); + + const operationFailure = new Error('Operation failed'); + getSelected.rejects(operationFailure); + await assert.rejects(api.getEnvironment(scope), (error) => error === operationFailure); + }); }); From a7bb5ca6c3251b995b89ffcc44d3415c4bc104c2 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 16 Oct 2026 14:32:16 -0700 Subject: [PATCH 2/2] Fix tests --- .../instructions/testing-workflow.instructions.md | 1 + .../internal/publicApiCompatibility.unit.test.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/instructions/testing-workflow.instructions.md b/.github/instructions/testing-workflow.instructions.md index 4582a44ce..8ab48d677 100644 --- a/.github/instructions/testing-workflow.instructions.md +++ b/.github/instructions/testing-workflow.instructions.md @@ -18,6 +18,7 @@ This guide covers the full testing lifecycle: ## Learnings +- Public API manager fixtures must return internal manager wrappers. In execution-policy tests, stub the wrapper's `getVersion()` to isolate operation subprocesses from capability telemetry (1). - Pip commands that return JSON must pass `--disable-pip-version-check`; the process helper combines stderr with stdout, so update notices can otherwise make valid JSON unparseable (1). - When a view subscribes to a newly added provider event, TypeMoq-based view tests must return a real `EventEmitter.event`; an unstubbed event yields an undefined disposable and fails during teardown (1). - Test agent selections across a full reload with `python.defaultInterpreterPath` set. An effective manager value equal to the extension default does not prove a workspace value was saved; tool-owned persistence must inspect `workspaceValue` or startup can restore the global interpreter (1). diff --git a/src/test/internal/publicApiCompatibility.unit.test.ts b/src/test/internal/publicApiCompatibility.unit.test.ts index 6bbc4f4ab..089177795 100644 --- a/src/test/internal/publicApiCompatibility.unit.test.ts +++ b/src/test/internal/publicApiCompatibility.unit.test.ts @@ -22,6 +22,7 @@ import * as helpers from '../../managers/builtin/helpers'; import { PipPackageManager } from '../../managers/builtin/pipPackageManager'; import { VenvManager } from '../../managers/builtin/venvManager'; import * as packageChanges from '../../managers/common/packageChanges'; +import { InternalPackageManager } from '../../managers/common/registeredManagers'; import { CondaPackageManager } from '../../managers/conda/condaPackageManager'; import * as condaUtils from '../../managers/conda/condaUtils'; import { PoetryManager } from '../../managers/poetry/poetryManager'; @@ -114,6 +115,15 @@ suite('Public package API execution policy', () => { }); function publicApi(manager: PackageManager): PythonEnvironmentApi { + const project = { name: 'project', uri: scope }; + const registeredManager = new InternalPackageManager( + `test:${manager.name}`, + manager, + manager.createForProject ? project : undefined, + ); + disposables.push(registeredManager); + // Isolate operation process policy from capability telemetry's version subprocess. + sinon.stub(registeredManager, 'getVersion').resolves(undefined); const event = () => { const emitter = new EventEmitter(); disposables.push(emitter); @@ -124,9 +134,9 @@ suite('Public package API execution policy', () => { { onDidChangeActiveEnvironment: event(), onDidChangePackageProviderPackages: event(), - getPackageManager: () => manager, + getPackageManager: () => registeredManager, } as unknown as Args[0], - { getProjects: () => [], onDidChangeProjects: event() } as unknown as Args[1], + { getProjects: () => [project], onDidChangeProjects: event() } as unknown as Args[1], {} as Args[2], {} as Args[3], { onDidChangeEnvironmentVariables: event() } as unknown as Args[4],