Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
import { EnablementState, IExtensionManagementServerService, IPublisherInfo, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';
import { IWorkspaceExtensionsConfigService } from '../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js';
import { EXTENSIONS_SUPPORT_AGENTS_WINDOW } from '../../../services/extensions/common/extensionManifestPropertiesService.js';
import { IHostService } from '../../../services/host/browser/host.js';
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
Expand Down Expand Up @@ -248,24 +247,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
}]
},
[EXTENSIONS_SUPPORT_AGENTS_WINDOW]: {
type: 'object',
scope: ConfigurationScope.APPLICATION,
markdownDescription: localize('extensions.supportAgentsWindow', "Override the Agents window support of an extension. Extensions using `true` will be enabled in the Agents window even when they would otherwise be disabled."),
patternProperties: {
'([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': {
type: 'boolean',
default: false
}
},
additionalProperties: false,
default: {},
defaultSnippets: [{
'body': {
'pub.name': true
}
}]
},
'extensions.experimental.affinity': {
type: 'object',
markdownDescription: localize('extensions.affinity', "Configure an extension to execute in a different extension host process."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,35 @@ suite('ExtensionEnablementService Test', () => {
]);
});

for (const isSessionsWindow of [false, true]) {
for (const managementEnabled of [false, true]) {
test(`test product allow-listed opt-out in ${isSessionsWindow ? 'sessions' : 'editor'} window with extension management ${managementEnabled ? 'enabled' : 'disabled'}`, async () => {
const configurationService = instantiationService.get(IConfigurationService) as TestConfigurationService;
await configurationService.setUserConfiguration(EXTENSIONS_SUPPORT_AGENTS_WINDOW, { 'vue.volar': false, 'PUB.enabled': true });
await configurationService.setUserConfiguration(EXTENSIONS_AGENTS_WINDOW_MANAGEMENT, managementEnabled);
await configurationService.setUserConfiguration(EXTENSIONS_AGENTS_WINDOW_AUTO_ENABLE, true);
instantiationService.stub(IWorkbenchEnvironmentService, { isSessionsWindow });
instantiationService.stub(IExtensionManifestPropertiesService, disposableStore.add(new ExtensionManifestPropertiesService(
{ ...TestProductService, sessionsWindowAllowedExtensions: ['Vue.volar', 'Pub.Allowed', 'Pub.Enabled', 'Pub.Disabled'] },
configurationService, new TestWorkspaceTrustEnablementService(), new NullLogService()
)));
testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService));
const optedOut = aLocalExtension2('Vue.volar', { main: 'main.js', contributes: aContributes('grammars') });
const allowed = aLocalExtension2('pub.allowed', { main: 'main.js' });
const enabled = aLocalExtension2('pub.enabled', { main: 'main.js' });
const disabled = aLocalExtension2('pub.disabled', { main: 'main.js' });
await testObject.setEnablement([disabled], EnablementState.DisabledGlobally);

assert.deepStrictEqual([optedOut, allowed, enabled, disabled].map(ext => testObject.getEnablementState(ext)), [
isSessionsWindow ? (managementEnabled ? EnablementState.DisabledWorkspace : EnablementState.DisabledByEnvironment) : EnablementState.EnabledGlobally,
EnablementState.EnabledGlobally,
EnablementState.EnabledGlobally,
EnablementState.DisabledGlobally,
]);
});
}
}

test('test extensions declaring agents window support can still be disabled by the user', async () => {
await (instantiationService.get(IConfigurationService) as TestConfigurationService).setUserConfiguration(EXTENSIONS_ENABLE_AGENTS_WINDOW_CAPABILITY, true);
instantiationService.stub(IWorkbenchEnvironmentService, { isSessionsWindow: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { isBoolean } from '../../../../base/common/types.js';
import { IWorkspaceTrustEnablementService } from '../../../../platform/workspace/common/workspaceTrust.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { isWeb } from '../../../../base/common/platform.js';
import { localize } from '../../../../nls.js';
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';

export const IExtensionManifestPropertiesService = createDecorator<IExtensionManifestPropertiesService>('extensionManifestPropertiesService');

Expand Down Expand Up @@ -159,16 +162,15 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
}

canExecuteOnSessionsWindow(manifest: IExtensionManifest, isBuiltin: boolean = false): boolean {
// Allow-listed extensions are always supported in the sessions window.
if (this._productSessionsWindowAllowedExtensions.has(getGalleryExtensionId(manifest.publisher, manifest.name))) {
return true;
}

const configuredSessionsWindowSupport = this.getConfiguredSessionsWindowSupport(manifest);
if (configuredSessionsWindowSupport !== undefined) {
return configuredSessionsWindowSupport;
}

if (this._productSessionsWindowAllowedExtensions.has(getGalleryExtensionId(manifest.publisher, manifest.name))) {
return true;
}

const declaredSessionsWindowSupport = manifest.capabilities?.agentsWindow?.supported;
if (declaredSessionsWindowSupport !== undefined && (isBuiltin || this.configurationService.getValue<boolean>(EXTENSIONS_ENABLE_AGENTS_WINDOW_CAPABILITY))) {
return declaredSessionsWindowSupport;
Expand Down Expand Up @@ -511,3 +513,27 @@ export class ExtensionManifestPropertiesService extends Disposable implements IE
}

registerSingleton(IExtensionManifestPropertiesService, ExtensionManifestPropertiesService, InstantiationType.Delayed);

Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
id: 'extensions',
properties: {
[EXTENSIONS_SUPPORT_AGENTS_WINDOW]: {
type: 'object',
scope: ConfigurationScope.APPLICATION,
markdownDescription: localize('extensions.supportAgentsWindow', "Override the Agents window support of an extension. Set an extension to `true` to enable it even when it would otherwise be disabled, or to `false` to disable it even when it is supported by default. This does not affect editor windows. Reload the Agents window to apply changes."),
patternProperties: {
'([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$': {
type: 'boolean',
default: false
}
},
additionalProperties: false,
default: {},
defaultSnippets: [{
'body': {
'pub.name': true
}
}]
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js';
import { isWeb } from '../../../../../base/common/platform.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js';
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
import { EXTENSIONS_ENABLE_AGENTS_WINDOW_CAPABILITY, ExtensionUntrustedWorkspaceSupportType, IExtensionContributions, IExtensionManifest } from '../../../../../platform/extensions/common/extensions.js';
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { NullLogService } from '../../../../../platform/log/common/log.js';
import { IProductService } from '../../../../../platform/product/common/productService.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
import { IWorkspaceTrustEnablementService } from '../../../../../platform/workspace/common/workspaceTrust.js';
import { EXTENSIONS_SUPPORT_AGENTS_WINDOW, ExtensionManifestPropertiesService, getSessionsWindowSuggestedContributions } from '../../common/extensionManifestPropertiesService.js';
import { TestProductService, TestWorkspaceTrustEnablementService } from '../../../../test/common/workbenchTestServices.js';
Expand Down Expand Up @@ -181,17 +183,53 @@ suite('ExtensionManifestPropertiesService - SessionsWindowSupport', () => {
});
}

test('supports product allow-listed extensions regardless of overrides and declarations', async () => {
await testConfigurationService.setUserConfiguration(EXTENSIONS_SUPPORT_AGENTS_WINDOW, { 'pub.a': false });
test('registers sessions window support configuration without editor window contributions', () => {
testObject = createTestObject();
const property = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties()[EXTENSIONS_SUPPORT_AGENTS_WINDOW];

assert.deepStrictEqual({
type: property?.type,
scope: property?.scope,
default: property?.default,
additionalProperties: property?.additionalProperties,
valueTypes: Object.values(property?.patternProperties ?? {}).map(value => value.type),
}, {
type: 'object',
scope: ConfigurationScope.APPLICATION,
default: {},
additionalProperties: false,
valueTypes: ['boolean'],
});
});

test('supports product allow-listed extensions regardless of declarations when not configured', () => {
testObject = disposables.add(new ExtensionManifestPropertiesService({ ...TestProductService, sessionsWindowAllowedExtensions: ['Pub.A'] }, testConfigurationService, new TestWorkspaceTrustEnablementService(), new NullLogService()));

assert.deepStrictEqual([
testObject.canExecuteOnSessionsWindow(getExtensionManifest({ main: './out/extension.js', contributes: { commands: [] } })),
testObject.canExecuteOnSessionsWindow(getExtensionManifest({ main: './out/extension.js', contributes: { views: {} } }), true),
testObject.canExecuteOnSessionsWindow(getExtensionManifest({ main: './out/extension.js', capabilities: { agentsWindow: { supported: false } }, contributes: { commands: [] } })),
testObject.canExecuteOnSessionsWindow(getExtensionManifest({ main: './out/extension.js', capabilities: { agentsWindow: { supported: false } }, contributes: { views: {} } }), true),
testObject.canExecuteOnSessionsWindow(getExtensionManifest({ name: 'b', main: './out/extension.js' })),
], [true, true, false]);
});

for (const supported of [false, true]) {
test(`configured ${supported} overrides product allow-listed extensions`, async () => {
await testConfigurationService.setUserConfiguration(EXTENSIONS_SUPPORT_AGENTS_WINDOW, { 'PUB.a': supported });
testObject = disposables.add(new ExtensionManifestPropertiesService({ ...TestProductService, sessionsWindowAllowedExtensions: ['Pub.A'] }, testConfigurationService, new TestWorkspaceTrustEnablementService(), new NullLogService()));
const manifest = getExtensionManifest({ main: './out/extension.js', contributes: { grammars: [{ language: 'a' }] } });

assert.deepStrictEqual({
user: testObject.canExecuteOnSessionsWindow(manifest),
builtin: testObject.canExecuteOnSessionsWindow(manifest, true),
autoEnable: testObject.canAutoEnableOnSessionsWindow(manifest),
}, {
user: supported,
builtin: supported,
autoEnable: false,
});
});
}

test('ignores declared agents window support for user extensions when the capability setting is disabled', async () => {
await testConfigurationService.setUserConfiguration(EXTENSIONS_ENABLE_AGENTS_WINDOW_CAPABILITY, false);
testObject = createTestObject();
Expand Down
Loading