diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index dbce0cbc6a0..9885cdf356f 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/aiExtensionActivation.ts b/src/vs/workbench/contrib/positronAssistant/browser/aiExtensionActivation.ts new file mode 100644 index 00000000000..efeacde4dbc --- /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/positronAssistant.contribution.ts b/src/vs/workbench/contrib/positronAssistant/browser/positronAssistant.contribution.ts index cde1683e0ea..f8adbf3f644 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 { AiExtensionActivationContribution } from './aiExtensionActivation.js'; import { PositronAssistantToolsContribution } from './tools/positronAssistantTools.js'; // Register the `ai.enabled` main switch for Positron's AI features. @@ -122,4 +123,12 @@ 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); + +// 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); + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PositronAssistantToolsContribution, LifecyclePhase.Restored); +