From 90b637eb1f7f1165275ee0b17925d237ad4cbe69 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 15 Jul 2026 16:04:37 -0700 Subject: [PATCH 1/2] activation guard for copilot --- extensions/copilot/package.json | 2 +- .../browser/copilotActivation.ts | 54 +++++++++++++++++++ .../browser/positronAssistant.contribution.ts | 6 +++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index dbce0cbc6a0a..9885cdf356fa 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -78,7 +78,7 @@ } ], "activationEvents": [ - "onStartupFinished", + "onCopilotEnabled", "onLanguageModelChat:copilot", "onUri", "onFileSystem:ccreq", diff --git a/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts b/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts new file mode 100644 index 000000000000..44f789ffe887 --- /dev/null +++ b/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2026 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { ChatConfiguration } from '../../chat/common/constants.js'; + +/** + * Custom activation event fired when Positron's AI features are enabled. + * + * The bundled Copilot extension is heavyweight, so it declares this as an + * activation event instead of `onStartupFinished`. That keeps it unloaded (and + * off the memory budget) for users who have turned AI off, and activates it + * eagerly for everyone else. + */ +export const COPILOT_ACTIVATION_EVENT = 'onCopilotEnabled'; + +/** + * Fires {@link COPILOT_ACTIVATION_EVENT} while AI features are enabled -- that + * is, whenever `chat.disableAIFeatures` is not `true` -- both at startup and + * when the user changes the setting at runtime. + * + * VS Code cannot unload an already-activated extension without a reload, so + * turning AI features off at runtime does not deactivate Copilot here; its + * individual features are each gated on their own preconditions and go quiet on + * their own. + */ +export class CopilotActivationContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IExtensionService private readonly _extensionService: IExtensionService, + ) { + super(); + + this._update(); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ChatConfiguration.AIDisabled)) { + this._update(); + } + })); + } + + private _update(): void { + // `chat.disableAIFeatures` is inverted: `true` means AI is off. Activate + // Copilot for every other value (including the unset default). + if (this._configurationService.getValue(ChatConfiguration.AIDisabled) !== true) { + this._extensionService.activateByEvent(COPILOT_ACTIVATION_EVENT); + } + } +} diff --git a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts index c535ed290b0a..60498bda71e4 100644 --- a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts +++ b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts @@ -24,6 +24,7 @@ import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../service import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { NextEditSuggestionsStatusBarEntry } from './nextEditSuggestionsStatusBar.js'; import { CommitMessageMenuContribution, registerCommitMessageGeneration } from './commitMessageAction.js'; +import { CopilotActivationContribution } from './copilotActivation.js'; // Register the `ai.enabled` main switch for Positron's AI features. import '../common/positronAIConfiguration.js'; @@ -121,3 +122,8 @@ class PositronAssistantContribution extends Disposable implements IWorkbenchCont Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PositronAssistantContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NextEditSuggestionsStatusBarEntry, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(CommitMessageMenuContribution, LifecyclePhase.Restored); + +// Activate the bundled Copilot extension via `onCopilotEnabled` while AI +// features are enabled. Registered at `Eventually` to preserve the deferred +// timing the extension had under `onStartupFinished`. +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(CopilotActivationContribution, LifecyclePhase.Eventually); From 7ff04264d29be009afde4212aa6cb99cbe070f88 Mon Sep 17 00:00:00 2001 From: Jonathan McPherson Date: Wed, 15 Jul 2026 16:19:38 -0700 Subject: [PATCH 2/2] add an ai activation event too --- extensions/positron-assistant/package.json | 3 +- .../browser/aiExtensionActivation.ts | 75 +++++++++++++++++++ .../browser/copilotActivation.ts | 54 ------------- .../browser/positronAssistant.contribution.ts | 11 +-- 4 files changed, 83 insertions(+), 60 deletions(-) create mode 100644 src/vs/workbench/contrib/positronAssistant/browser/aiExtensionActivation.ts delete mode 100644 src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts diff --git a/extensions/positron-assistant/package.json b/extensions/positron-assistant/package.json index f5336d5ff1fb..0156630a4650 100644 --- a/extensions/positron-assistant/package.json +++ b/extensions/positron-assistant/package.json @@ -12,7 +12,8 @@ "Other" ], "activationEvents": [ - "onStartupFinished" + "onAiEnabled", + "onCopilotEnabled" ], "main": "./out/extension.js", "enabledApiProposals": [ diff --git a/src/vs/workbench/contrib/positronAssistant/browser/aiExtensionActivation.ts b/src/vs/workbench/contrib/positronAssistant/browser/aiExtensionActivation.ts new file mode 100644 index 000000000000..efeacde4dbcf --- /dev/null +++ b/src/vs/workbench/contrib/positronAssistant/browser/aiExtensionActivation.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2026 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { ChatConfiguration } from '../../chat/common/constants.js'; +import { AI_ENABLED_KEY } from '../common/positronAIConfiguration.js'; + +/** + * Custom activation event fired while Positron's AI features are enabled, i.e. + * while `ai.enabled` (the main AI switch) is `true`. + * + * Heavyweight AI extensions (e.g. the bundled `positron-assistant`) declare this + * as an activation event instead of `onStartupFinished`, so they stay unloaded + * (and off the memory budget) when AI is turned off, and activate eagerly when + * it's on. + */ +export const AI_ENABLED_ACTIVATION_EVENT = 'onAiEnabled'; + +/** + * Custom activation event fired while chat/Copilot is enabled, i.e. while + * `chat.disableAIFeatures` is not `true`. + * + * The bundled Copilot extension declares this instead of `onStartupFinished`. + * `positron-assistant` declares it too: it registers the default chat + * participants the Copilot chat UI depends on, but is not a declared extension + * dependency of Copilot, so it must listen for this event to come up alongside + * it. + */ +export const COPILOT_ACTIVATION_EVENT = 'onCopilotEnabled'; + +/** + * Fires the custom AI activation events while their backing settings are on, + * both at startup and when the user changes either setting at runtime: + * + * - {@link AI_ENABLED_ACTIVATION_EVENT} while `ai.enabled` is `true`. + * - {@link COPILOT_ACTIVATION_EVENT} while `chat.disableAIFeatures` is not `true`. + * + * VS Code cannot unload an already-activated extension without a reload, so + * turning a setting off at runtime does not deactivate anything here; the AI + * extensions' individual features are each gated on their own preconditions and + * go quiet on their own. See `.claude/rules/ai-gating.md`. + */ +export class AiExtensionActivationContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IExtensionService private readonly _extensionService: IExtensionService, + ) { + super(); + + this._update(); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(AI_ENABLED_KEY) || e.affectsConfiguration(ChatConfiguration.AIDisabled)) { + this._update(); + } + })); + } + + private _update(): void { + // `ai.enabled` defaults to `true`; activate only when it reads exactly + // `true` (an unset value already reads through as the `true` default). + if (this._configurationService.getValue(AI_ENABLED_KEY) === true) { + this._extensionService.activateByEvent(AI_ENABLED_ACTIVATION_EVENT); + } + // `chat.disableAIFeatures` is inverted: `true` means AI is off. Activate + // for every other value (including the unset default). + if (this._configurationService.getValue(ChatConfiguration.AIDisabled) !== true) { + this._extensionService.activateByEvent(COPILOT_ACTIVATION_EVENT); + } + } +} diff --git a/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts b/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts deleted file mode 100644 index 44f789ffe887..000000000000 --- a/src/vs/workbench/contrib/positronAssistant/browser/copilotActivation.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2026 Posit Software, PBC. All rights reserved. - * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { ChatConfiguration } from '../../chat/common/constants.js'; - -/** - * Custom activation event fired when Positron's AI features are enabled. - * - * The bundled Copilot extension is heavyweight, so it declares this as an - * activation event instead of `onStartupFinished`. That keeps it unloaded (and - * off the memory budget) for users who have turned AI off, and activates it - * eagerly for everyone else. - */ -export const COPILOT_ACTIVATION_EVENT = 'onCopilotEnabled'; - -/** - * Fires {@link COPILOT_ACTIVATION_EVENT} while AI features are enabled -- that - * is, whenever `chat.disableAIFeatures` is not `true` -- both at startup and - * when the user changes the setting at runtime. - * - * VS Code cannot unload an already-activated extension without a reload, so - * turning AI features off at runtime does not deactivate Copilot here; its - * individual features are each gated on their own preconditions and go quiet on - * their own. - */ -export class CopilotActivationContribution extends Disposable implements IWorkbenchContribution { - constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IExtensionService private readonly _extensionService: IExtensionService, - ) { - super(); - - this._update(); - this._register(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ChatConfiguration.AIDisabled)) { - this._update(); - } - })); - } - - private _update(): void { - // `chat.disableAIFeatures` is inverted: `true` means AI is off. Activate - // Copilot for every other value (including the unset default). - if (this._configurationService.getValue(ChatConfiguration.AIDisabled) !== true) { - this._extensionService.activateByEvent(COPILOT_ACTIVATION_EVENT); - } - } -} diff --git a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts index 60498bda71e4..9c49bc2ed1b4 100644 --- a/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts +++ b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts @@ -24,7 +24,7 @@ import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../service import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { NextEditSuggestionsStatusBarEntry } from './nextEditSuggestionsStatusBar.js'; import { CommitMessageMenuContribution, registerCommitMessageGeneration } from './commitMessageAction.js'; -import { CopilotActivationContribution } from './copilotActivation.js'; +import { AiExtensionActivationContribution } from './aiExtensionActivation.js'; // Register the `ai.enabled` main switch for Positron's AI features. import '../common/positronAIConfiguration.js'; @@ -123,7 +123,8 @@ Registry.as(WorkbenchExtensions.Workbench).regi Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NextEditSuggestionsStatusBarEntry, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(CommitMessageMenuContribution, LifecyclePhase.Restored); -// Activate the bundled Copilot extension via `onCopilotEnabled` while AI -// features are enabled. Registered at `Eventually` to preserve the deferred -// timing the extension had under `onStartupFinished`. -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(CopilotActivationContribution, LifecyclePhase.Eventually); +// Fire the custom AI activation events (`onAiEnabled`, `onCopilotEnabled`) so +// the bundled AI extensions activate lazily while their backing settings are +// on. Registered at `Eventually` to preserve the deferred timing those +// extensions had under `onStartupFinished`. +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AiExtensionActivationContribution, LifecyclePhase.Eventually);