Path: blob/main/src/vs/workbench/electron-browser/actions/openInAgentsAction.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/openInAgents.css';6import { $, append } from '../../../base/browser/dom.js';7import { getDefaultHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegateFactory.js';8import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../base/browser/ui/actionbar/actionViewItems.js';9import { IAction } from '../../../base/common/actions.js';10import { Disposable } from '../../../base/common/lifecycle.js';11import { isMacintosh, isWindows } from '../../../base/common/platform.js';12import { localize, localize2 } from '../../../nls.js';13import { Action2, MenuId, registerAction2 } from '../../../platform/actions/common/actions.js';14import { IActionViewItemService } from '../../../platform/actions/browser/actionViewItemService.js';15import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../platform/configuration/common/configurationRegistry.js';16import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';17import { IHoverService } from '../../../platform/hover/browser/hover.js';18import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';19import { INativeHostService } from '../../../platform/native/common/native.js';20import { IProductService } from '../../../platform/product/common/productService.js';21import { Registry } from '../../../platform/registry/common/platform.js';22import { ITelemetryService } from '../../../platform/telemetry/common/telemetry.js';23import { IWorkspaceContextService, WorkbenchState } from '../../../platform/workspace/common/workspace.js';24import { ToggleTitleBarConfigAction, TitleBarLeadingActionsGroup } from '../../browser/parts/titlebar/titlebarActions.js';25import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../common/contributions.js';26import { InEditorZenModeContext, IsAuxiliaryWindowContext, IsSessionsWindowContext } from '../../common/contextkeys.js';27import { workbenchConfigurationNodeBase } from '../../common/configuration.js';28import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';29import { ChatEntitlementContextKeys } from '../../services/chat/common/chatEntitlementService.js';3031const OpenInAgentsActionId = 'workbench.action.openInAgents';32const OpenInAgentsEnabledSetting = 'workbench.openInAgents.enabled';3334// Context key tracking the current product quality so we can hide the35// "Open in Agents" entry in stable builds for now.36const OpenInAgentsProductQualityContext = new RawContextKey<string>('openInAgentsProductQuality', '');3738type OpenInAgentsMode = 'siblingApp' | 'newWindow';3940type OpenInAgentsEvent = { mode: OpenInAgentsMode };41type OpenInAgentsClassification = {42owner: 'osortega';43comment: 'Tracks when the user opens the Agents application from the VS Code titlebar.';44mode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the Agents app was opened: siblingApp (launched separate Agents app) or newWindow (in-process agents window).' };45};4647const OpenInAgentsVisibility = ContextKeyExpr.and(48ContextKeyExpr.equals(`config.${OpenInAgentsEnabledSetting}`, true),49IsSessionsWindowContext.toNegated(),50IsAuxiliaryWindowContext.toNegated(),51InEditorZenModeContext.negate(),52// Hide whenever the user has signaled (or policy/workspace trust dictates)53// that AI features should not be shown in this window/workspace.54ChatEntitlementContextKeys.Setup.hidden.negate(),55ChatEntitlementContextKeys.Setup.disabled.negate(),56ChatEntitlementContextKeys.Setup.disabledInWorkspace.negate(),57ChatEntitlementContextKeys.Setup.untrusted.negate(),58// Hide in stable builds for now (insider, exploration and OSS dev are allowed).59ContextKeyExpr.notEquals(OpenInAgentsProductQualityContext.key, 'stable'),60);6162/**63* Action that opens the Agents application for the current workspace.64*65* In built builds where a sibling Agents app is registered (`darwinSiblingBundleIdentifier`66* / `win32SiblingExeBasename`), launches it via {@link INativeHostService.launchSiblingApp}67* with `--agents` and the current workspace folder/file. Otherwise falls back to opening68* a new in-process Agents window via {@link INativeHostService.openAgentsWindow}.69*/70class OpenInAgentsAction extends Action2 {7172constructor() {73super({74id: OpenInAgentsActionId,75title: localize2('openInAgents', "Open in Agents"),76f1: true,77precondition: OpenInAgentsVisibility,78menu: [{79// Render in the global titlebar tool bar in the dedicated leading80// slot so we appear before the layout controls (and stay visible81// when layout controls are toggled off).82id: MenuId.TitleBar,83group: TitleBarLeadingActionsGroup,84order: -1000,85when: OpenInAgentsVisibility,86}]87});88}8990override async run(accessor: ServicesAccessor): Promise<void> {91const nativeHostService = accessor.get(INativeHostService);92const productService = accessor.get(IProductService);93const environmentService = accessor.get(IWorkbenchEnvironmentService);94const workspaceContextService = accessor.get(IWorkspaceContextService);95const telemetryService = accessor.get(ITelemetryService);9697const args: string[] = ['--new-window'];9899const workspace = workspaceContextService.getWorkspace();100switch (workspaceContextService.getWorkbenchState()) {101case WorkbenchState.FOLDER:102if (workspace.folders.length > 0) {103args.push('--folder-uri', workspace.folders[0].uri.toString());104}105break;106case WorkbenchState.WORKSPACE:107if (workspace.configuration) {108args.push('--file-uri', workspace.configuration.toString());109}110break;111}112113const hasSibling = !!(114productService.darwinSiblingBundleIdentifier ||115productService.win32SiblingExeBasename116);117118// In built builds with a sibling Agents app available, launch it.119// Otherwise (dev / OSS / unsupported platform / no sibling), open a new agents window of120// the current Electron app. `launchSiblingApp` is only implemented for macOS/Windows121// (see `src/vs/platform/native/node/siblingApp.ts`), so gate on actual platform support.122const canLaunchSiblingApp = isMacintosh || isWindows;123const mode: OpenInAgentsMode = environmentService.isBuilt && hasSibling && canLaunchSiblingApp ? 'siblingApp' : 'newWindow';124telemetryService.publicLog2<OpenInAgentsEvent, OpenInAgentsClassification>('vscode.openInAgents', { mode });125126if (mode === 'siblingApp') {127await nativeHostService.launchSiblingApp(args);128} else {129await nativeHostService.openAgentsWindow({ forceNewWindow: true });130}131}132}133134/**135* Renders the "Open in Agents" titlebar entry as an icon-only button that136* expands to reveal a label on hover / keyboard focus.137*/138class OpenInAgentsTitleBarWidget extends BaseActionViewItem {139140constructor(141action: IAction,142options: IBaseActionViewItemOptions | undefined,143@IHoverService private readonly hoverService: IHoverService,144) {145super(undefined, action, options);146}147148override render(container: HTMLElement): void {149super.render(container);150151container.classList.add('open-in-agents-titlebar-widget');152container.setAttribute('role', 'button');153154const label = this.action.label || localize('openInAgentsLabel', "Open in Agents");155container.setAttribute('aria-label', label);156this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), container, label));157158const icon = append(container, $('span.open-in-agents-titlebar-widget-icon'));159icon.setAttribute('aria-hidden', 'true');160161const labelEl = append(container, $('span.open-in-agents-titlebar-widget-label'));162labelEl.textContent = label;163}164}165166class OpenInAgentsContribution extends Disposable implements IWorkbenchContribution {167168static readonly ID = 'workbench.contrib.openInAgents.desktop';169170constructor(171@IActionViewItemService actionViewItemService: IActionViewItemService,172@IInstantiationService instantiationService: IInstantiationService,173@IContextKeyService contextKeyService: IContextKeyService,174@IProductService productService: IProductService,175) {176super();177OpenInAgentsProductQualityContext.bindTo(contextKeyService).set(productService.quality ?? '');178this._register(actionViewItemService.register(MenuId.TitleBar, OpenInAgentsActionId, (action, options) => {179return instantiationService.createInstance(OpenInAgentsTitleBarWidget, action, options);180}, undefined));181}182}183184registerAction2(OpenInAgentsAction);185registerWorkbenchContribution2(OpenInAgentsContribution.ID, OpenInAgentsContribution, WorkbenchPhase.BlockRestore);186187// Toggle entry in titlebar context menu (right-click on titlebar)188registerAction2(class ToggleOpenInAgents extends ToggleTitleBarConfigAction {189constructor() {190super(191OpenInAgentsEnabledSetting,192localize('toggle.openInAgents', 'Open in Agents'),193localize('toggle.openInAgentsDescription', "Toggle visibility of the Open in Agents button in title bar"),1946,195);196}197});198199// Configuration setting backing the toggle.200Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({201...workbenchConfigurationNodeBase,202properties: {203[OpenInAgentsEnabledSetting]: {204type: 'boolean',205default: true,206markdownDescription: localize('openInAgentsEnabled', "Controls whether the Open in Agents button is shown in the title bar."),207}208}209});210211212