Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 3 additions & 10 deletions src/vs/platform/agentHost/common/agentHostManagedSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface IAgentHostManagedSettingsPermissions {
ask?: string[];
}

/** Deprecated and ignored; honoring `false` would let a user lift an admin restriction. */
export const AgentHostMapLegacySettingsToManagedSettingsSettingId = 'chat.agentHost.copilot.mapLegacySettingsToManagedSettings';

/**
Expand Down Expand Up @@ -276,7 +277,6 @@ const managedPermissionsSettings: readonly IManagedPermissionsSettingMapping[] =
];

export const managedPermissionsConfigurationIds = [
AgentHostMapLegacySettingsToManagedSettingsSettingId,
...managedPermissionsSettings.flatMap(mapping => [mapping.settingId, ...mapping.additionalSettingIds ?? []]),
];

Expand All @@ -301,17 +301,10 @@ function isStringArrayOrUndefined(value: unknown): boolean {
* Combines every mapping's contribution into the single document sent to the
* host, deduplicating rules that more than one setting produced.
*
* Contributing any rule at all makes the runtime's managed policy "active",
* which causes unmatched shell, read, write, URL and factory requests to require
* approval. That is broader than any individual mapping intends, but it errs
* toward prompting, and the alternative — an `allow` list — resolves to
* auto-approval. See the module comment.
* Client-injected managed permissions are non-activating, so these rules bind
* without forcing unmatched requests to prompt. See the module comment.
*/
export function resolveManagedSettingsPermissions(configurationService: IConfigurationService): IAgentHostManagedSettingsPermissions {
if (getGlobalConfigurationValue<boolean>(configurationService, AgentHostMapLegacySettingsToManagedSettingsSettingId) !== true) {
return {};
}

const deny = new Set<string>();
const ask = new Set<string>();
let disableBypassPermissionsMode: 'disable' | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ suite('AgentHostManagedSettings', () => {

test('combines restrictive contributions from explicitly configured global values', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, policyValue: false },
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: true, userValue: false },
});
Expand All @@ -35,7 +34,6 @@ suite('AgentHostManagedSettings', () => {

test('respects global precedence and ignores defaults and workspace values', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false, policyValue: true },
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: false, workspaceValue: false, workspaceFolderValue: false },
});
Expand All @@ -45,11 +43,9 @@ suite('AgentHostManagedSettings', () => {

test('does not promote user or application preferences to managed bypass restrictions', () => {
const userConfigurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false },
});
const applicationConfigurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, applicationValue: false },
});

Expand All @@ -59,27 +55,47 @@ suite('AgentHostManagedSettings', () => {
], [{}, {}]);
});

test('does not map legacy settings while the compatibility bridge is disabled', () => {
test('maps legacy settings without any opt-in present', () => {
Comment thread
joshspicer marked this conversation as resolved.
Comment thread
joshspicer marked this conversation as resolved.
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, userValue: false },
[TERMINAL_AUTO_APPROVE_ENABLED_SETTING_ID]: { defaultValue: true, userValue: false },
});

assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {});
assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {
ask: ['Shell'],
});
});

test('returns an empty contribution after explicit restrictions are removed', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, applicationValue: true },
});
test('ignores the deprecated opt-in, so it cannot switch off a mapped restriction', () => {
const restricted = {
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.example'] },
};

assert.deepStrictEqual([
resolveManagedSettingsPermissions(createConfigurationService({
...restricted,
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: true, userValue: false },
})),
resolveManagedSettingsPermissions(createConfigurationService({
...restricted,
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: true, userValue: true },
})),
], [
{ deny: ['Domain(evil.example)'] },
{ deny: ['Domain(evil.example)'] },
]);
});

test('returns an empty contribution when no legacy setting is restricted', () => {
const configurationService = createConfigurationService({});

assert.deepStrictEqual(resolveManagedSettingsPermissions(configurationService), {});
});

test('deduplicates a rule that more than one entry produces', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
// Three spellings of the same host, which all normalize to one rule.
Expand All @@ -96,7 +112,6 @@ suite('AgentHostManagedSettings', () => {

test('reduces denied domains to the host the network filter matches on', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: {
Expand All @@ -112,7 +127,6 @@ suite('AgentHostManagedSettings', () => {

test('denies configured domains while the network filter is on', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.com', '*.tracker.example'] },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [], policyValue: ['github.com'] },
Expand All @@ -125,7 +139,6 @@ suite('AgentHostManagedSettings', () => {

test('denies every domain when the filter is on and neither list is configured', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [] },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
Expand All @@ -136,7 +149,6 @@ suite('AgentHostManagedSettings', () => {

test('contributes nothing from domain lists while the network filter is off', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['evil.com'] },
});
Expand All @@ -146,7 +158,6 @@ suite('AgentHostManagedSettings', () => {

test('skips denied domain patterns the SDK cannot express', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['$(evil)', 'ok.example'] },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
Expand All @@ -159,7 +170,6 @@ suite('AgentHostManagedSettings', () => {

test('maps a bare wildcard denial onto the all-domains family rule', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[AgentNetworkDomainSettingId.NetworkFilter]: { defaultValue: false, policyValue: true },
[AgentNetworkDomainSettingId.DeniedNetworkDomains]: { defaultValue: [], policyValue: ['*'] },
[AgentNetworkDomainSettingId.AllowedNetworkDomains]: { defaultValue: [] },
Expand All @@ -170,7 +180,6 @@ suite('AgentHostManagedSettings', () => {

test('requires approval for explicitly denied terminal commands', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
defaultValue: {},
policyValue: { rm: false, 'git push': false, npm: true },
Expand All @@ -184,7 +193,6 @@ suite('AgentHostManagedSettings', () => {

test('skips terminal denials the SDK shell grammar cannot express', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
defaultValue: {},
policyValue: {
Expand All @@ -202,7 +210,6 @@ suite('AgentHostManagedSettings', () => {

test('keeps an absolute command path that VS Code treats as a literal', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
// Starts and ends with `/` but the trailing segment is not a flag list,
// so the auto-approver reads it as a path rather than a regular expression.
[TERMINAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: {}, policyValue: { '/usr/bin/rm': false } },
Expand All @@ -215,7 +222,6 @@ suite('AgentHostManagedSettings', () => {

test('skips a wildcard command key rather than broadening it', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
// `*` is a literal in VS Code but a command-boundary wildcard in the SDK,
// so bridging this would require approval for every git command.
[TERMINAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: {}, policyValue: { 'git *': false, 'rm': false } },
Expand All @@ -228,7 +234,6 @@ suite('AgentHostManagedSettings', () => {

test('treats a long-form sub-command denial like a bare false', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[TERMINAL_AUTO_APPROVE_SETTING_ID]: {
defaultValue: {},
policyValue: { rm: { approve: false }, ls: { approve: true } },
Expand All @@ -242,7 +247,6 @@ suite('AgentHostManagedSettings', () => {

test('locks the bypass mode when a tool is marked ineligible for auto-approval', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: false } },
});

Expand All @@ -253,7 +257,6 @@ suite('AgentHostManagedSettings', () => {

test('contributes nothing when every tool is left eligible for auto-approval', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
// Only `true` entries: the policy re-affirms the default and removes nothing.
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: true, runTask: true } },
});
Expand All @@ -263,7 +266,6 @@ suite('AgentHostManagedSettings', () => {

test('contributes nothing from the default empty per-tool auto-approval map', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: {} },
});

Expand All @@ -272,15 +274,12 @@ suite('AgentHostManagedSettings', () => {

test('does not promote user, application, or workspace per-tool auto-approval values', () => {
const userConfigurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, userValue: { fetch: false } },
});
const applicationConfigurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, applicationValue: { fetch: false } },
});
const workspaceConfigurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, workspaceValue: { fetch: false } },
});

Expand All @@ -293,7 +292,6 @@ suite('AgentHostManagedSettings', () => {

test('clears the bypass lock after the per-tool auto-approval policy is removed', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {} },
});

Expand All @@ -302,7 +300,6 @@ suite('AgentHostManagedSettings', () => {

test('ignores a malformed per-tool auto-approval value without throwing', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: ['fetch'] },
});

Expand All @@ -311,7 +308,6 @@ suite('AgentHostManagedSettings', () => {

test('fails closed and locks the bypass mode for a non-boolean per-tool auto-approval entry', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: 'no' } },
});

Expand All @@ -322,7 +318,6 @@ suite('AgentHostManagedSettings', () => {

test('does not duplicate the bypass lock across the global and per-tool auto-approval policies', () => {
const configurationService = createConfigurationService({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: { defaultValue: false, userValue: true },
[GLOBAL_AUTO_APPROVE_SETTING_ID]: { defaultValue: false, policyValue: false },
[ELIGIBLE_FOR_AUTO_APPROVAL_SETTING_ID]: { defaultValue: {}, policyValue: { fetch: false } },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1614,9 +1614,9 @@ configurationRegistry.registerConfiguration({
[AgentHostMapLegacySettingsToManagedSettingsSettingId]: {
type: 'boolean',
markdownDescription: nls.localize('chat.agentHost.copilot.mapLegacySettingsToManagedSettings', "When enabled, maps supported legacy VS Code settings to equivalent Copilot SDK managed settings for local Agent Host sessions. Only restrictions are mapped, and only from globally-scoped values — workspace and folder values are ignored. Applies to local sessions using the Copilot agent; remote hosts and other agents are unaffected. This compatibility bridge is temporary and is not used for new settings."),
Comment thread
joshspicer marked this conversation as resolved.
Outdated
default: false,
default: true,
scope: ConfigurationScope.APPLICATION_MACHINE,
tags: ['experimental', 'advanced'],
tags: ['advanced'],
},
[AgentHostOpus48PromptEnabledSettingId]: {
type: 'boolean',
Expand Down
Loading