From 60085147ed79fc195df5f079985883feab00a0a3 Mon Sep 17 00:00:00 2001 From: cwebster-99 Date: Fri, 5 Jun 2026 15:32:51 -0500 Subject: [PATCH 1/2] Disable Cloud for Free/EDU users --- .../actionWidget/browser/actionList.ts | 2 +- .../actionWidget/browser/actionWidget.css | 9 +++++ .../browser/actionWidgetDropdown.ts | 3 +- .../delegationSessionPickerActionItem.ts | 4 ++- .../input/sessionTargetPickerActionItem.ts | 34 +++++++++++++++++-- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index e03d976de4537b..d137009d1efeb9 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -49,7 +49,7 @@ export interface IActionListItemHover { /** * Content to display in the hover. Can be a markdown string or an HTMLElement for full DOM control. */ - readonly content?: string | MarkdownString | HTMLElement; + readonly content?: string | IMarkdownString | HTMLElement; /** * Optional disposable associated with the hover content (e.g. from rendered markdown). */ diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index cae9ecf30b6abb..7ecf1399614c63 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -238,6 +238,15 @@ font-size: 12px; } +.action-widget .monaco-list .monaco-list-row .description a { + color: var(--vscode-textLink-foreground); +} + +.action-widget .monaco-list .monaco-list-row .description a:hover, +.action-widget .monaco-list .monaco-list-row .description a:active { + color: var(--vscode-textLink-activeForeground); +} + .action-widget .monaco-list-row.action .group-title { color: var(--vscode-descriptionForeground); margin-left: 0.5em; diff --git a/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts b/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts index 5adc5dde9d3b5c..d3b3be94ee9828 100644 --- a/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts +++ b/src/vs/platform/actionWidget/browser/actionWidgetDropdown.ts @@ -8,6 +8,7 @@ import { BaseDropdown, IActionProvider, IBaseDropdownOptions } from '../../../ba import { IListAccessibilityProvider } from '../../../base/browser/ui/list/listWidget.js'; import { IAction } from '../../../base/common/actions.js'; import { Codicon } from '../../../base/common/codicons.js'; +import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IKeybindingService } from '../../keybinding/common/keybinding.js'; @@ -18,7 +19,7 @@ import { IActionWidgetService } from './actionWidget.js'; export interface IActionWidgetDropdownAction extends IAction { category?: { label: string; order: number; showHeader?: boolean }; icon?: ThemeIcon; - description?: string; + description?: string | IMarkdownString; /** * Optional detail text displayed as a second line below the label. */ diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts index 94e8ed6cae12b0..8a83462b282f27 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts @@ -17,6 +17,7 @@ import { IKeybindingService } from '../../../../../../platform/keybinding/common import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; import { IsSessionsWindowContext } from '../../../../../common/contextkeys.js'; +import { IChatEntitlementService } from '../../../../../services/chat/common/chatEntitlementService.js'; import { IChatSessionsService } from '../../../common/chatSessionsService.js'; import { ACTION_ID_NEW_CHAT } from '../../actions/chatActions.js'; import { AgentSessionProviders, AgentSessionTarget, getAgentCanContinueIn, getAgentSessionProvider, isFirstPartyAgentSessionProvider } from '../../agentSessions/agentSessions.js'; @@ -45,9 +46,10 @@ export class DelegationSessionPickerActionItem extends SessionTypePickerActionIt @ICommandService commandService: ICommandService, @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, + @IChatEntitlementService chatEntitlementService: IChatEntitlementService, @IGitService private readonly gitService: IGitService, ) { - super(action, chatSessionPosition, delegate, pickerOptions, actionWidgetService, keybindingService, contextKeyService, chatSessionsService, commandService, openerService, telemetryService); + super(action, chatSessionPosition, delegate, pickerOptions, actionWidgetService, keybindingService, contextKeyService, chatSessionsService, commandService, openerService, telemetryService, chatEntitlementService); this._isSessionsWindow = IsSessionsWindowContext.getValue(contextKeyService) === true; } diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts index 04d1b84511ab30..83144c0b6ad878 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts @@ -7,6 +7,7 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { renderLabelWithIcons } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../../../base/common/actions.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; +import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; import { IDisposable } from '../../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -19,6 +20,7 @@ import { IContextKeyService } from '../../../../../../platform/contextkey/common import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; +import { ChatEntitlement, IChatEntitlementService } from '../../../../../services/chat/common/chatEntitlementService.js'; import { IChatSessionsService } from '../../../common/chatSessionsService.js'; import { AgentSessionProviders, AgentSessionTarget, getAgentSessionProvider, getAgentSessionProviderDescription, getAgentSessionProviderIcon, getAgentSessionProviderName, isFirstPartyAgentSessionProvider } from '../../agentSessions/agentSessions.js'; import { ChatInputPickerActionViewItem, IChatInputPickerOptions } from './chatInputPickerActionItem.js'; @@ -55,6 +57,7 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { @ICommandService protected readonly commandService: ICommandService, @IOpenerService protected readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, + @IChatEntitlementService protected readonly chatEntitlementService: IChatEntitlementService, ) { const actionProvider: IActionWidgetDropdownActionProvider = { @@ -67,17 +70,18 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { continue; } + const lockedForEntitlement = this._isLockedForEntitlement(sessionTypeItem.type); actions.push({ ...action, id: sessionTypeItem.commandId, label: sessionTypeItem.label, checked: currentType === sessionTypeItem.type, icon: this._getSessionIcon(sessionTypeItem), - enabled: this._isSessionTypeEnabled(sessionTypeItem.type), + enabled: lockedForEntitlement ? false : this._isSessionTypeEnabled(sessionTypeItem.type), category: this._getSessionCategory(sessionTypeItem), - description: this._getSessionDescription(sessionTypeItem), + description: lockedForEntitlement ? this._getUpgradeDescription() : this._getSessionDescription(sessionTypeItem), tooltip: '', - hover: { content: sessionTypeItem.hoverDescription }, + hover: { content: lockedForEntitlement ? this._getUpgradeHover() : sessionTypeItem.hoverDescription }, run: async () => { this._run(sessionTypeItem); }, @@ -196,6 +200,30 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { return !!this.chatSessionsService.getChatSessionContribution(type); } + /** + * Whether the given session type is locked behind a plan upgrade for the + * current user's entitlement. The cloud agent is not available to Copilot + * Free or Copilot Student (EDU) users, so it is shown greyed out with an + * Upgrade prompt instead of being selectable. + */ + protected _isLockedForEntitlement(type: AgentSessionTarget): boolean { + if (type !== AgentSessionProviders.Cloud) { + return false; + } + const entitlement = this.chatEntitlementService.entitlement; + return entitlement === ChatEntitlement.Free || entitlement === ChatEntitlement.EDU; + } + + private _getUpgradeDescription(): IMarkdownString { + return new MarkdownString(localize('chat.sessionTarget.upgradeLink', "[Upgrade](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }); + } + + private _getUpgradeHover(): MarkdownString { + const hover = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + hover.appendMarkdown(localize('chat.sessionTarget.upgradeHover', "[Upgrade to GitHub Copilot Pro](command:workbench.action.chat.upgradePlan \" \") to delegate work to the cloud agent.")); + return hover; + } + protected _getSessionCategory(sessionTypeItem: ISessionTypeItem) { // TODO: Remove hardcoded providers from core const knownType = getAgentSessionProvider(sessionTypeItem.type); From bd7b031ff1c92fdcd2a06dc913a309c6a5263c42 Mon Sep 17 00:00:00 2001 From: Courtney Webster <60238438+cwebster-99@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:58:21 -0500 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../widget/input/sessionTargetPickerActionItem.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts index 83144c0b6ad878..d40c496480bab5 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts @@ -215,14 +215,18 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { } private _getUpgradeDescription(): IMarkdownString { - return new MarkdownString(localize('chat.sessionTarget.upgradeLink', "[Upgrade](command:workbench.action.chat.upgradePlan \" \")"), { isTrusted: true }); + return new MarkdownString( + localize('chat.sessionTarget.upgradeLink', "[Upgrade](command:workbench.action.chat.upgradePlan)"), + { isTrusted: { enabledCommands: ['workbench.action.chat.upgradePlan'] } } + ); } private _getUpgradeHover(): MarkdownString { - const hover = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); - hover.appendMarkdown(localize('chat.sessionTarget.upgradeHover', "[Upgrade to GitHub Copilot Pro](command:workbench.action.chat.upgradePlan \" \") to delegate work to the cloud agent.")); + const hover = new MarkdownString('', { isTrusted: { enabledCommands: ['workbench.action.chat.upgradePlan'] }, supportThemeIcons: true }); + hover.appendMarkdown(localize('chat.sessionTarget.upgradeHover', "[Upgrade to GitHub Copilot Pro](command:workbench.action.chat.upgradePlan) to delegate work to the cloud agent.")); return hover; } + } protected _getSessionCategory(sessionTypeItem: ISessionTypeItem) { // TODO: Remove hardcoded providers from core