From 19209a8c1f2dbdbb64ef28d8e9a8d75255b2baff Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 23 Jan 2026 09:17:25 +0100 Subject: [PATCH] Prototype agent sessions window (#289707) * prototype agent sessions window * prototype agent sessions window * polish defaults * apply template changes on startup * move under resources * some fixes and disable profile actions in agent sessions window * disabe manage profile actions for agent sessions workspace * fix fixing window title * disable when chat is hidden * simplify * feedback * fix tests * disable the action in stable * Update resources/profiles/agent-sessions.code-profile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/platform/userDataProfile/common/userDataProfileTemplateWatcher.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/platform/userDataProfile/common/userDataProfile.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Benjamin Pasero Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- build/gulpfile.vscode.ts | 3 + .../profiles/agent-sessions.code-profile | 19 +++ .../sharedProcess/sharedProcessMain.ts | 4 +- .../standalone/browser/standaloneServices.ts | 1 + .../environment/common/environment.ts | 4 + .../environment/common/environmentService.ts | 9 ++ .../userDataProfile/common/userDataProfile.ts | 108 +++++++++++++- .../common/userDataProfileIpc.ts | 5 + .../common/userDataProfileTemplateWatcher.ts | 138 ++++++++++++++++++ .../userDataProfile/node/userDataProfile.ts | 6 +- .../test/common/userDataSyncClient.ts | 1 + src/vs/workbench/browser/contextkeys.ts | 5 +- .../browser/parts/titlebar/titlebarPart.ts | 2 +- .../browser/parts/titlebar/windowTitle.ts | 13 +- src/vs/workbench/common/contextkeys.ts | 2 + .../actions/agentSessionsActions.ts | 51 +++++++ .../electron-browser/chat.contribution.ts | 2 + .../browser/userDataProfile.ts | 31 ++-- .../browser/userDataProfilesEditor.ts | 12 +- .../configuration/browser/configuration.ts | 17 ++- .../browser/configurationService.ts | 6 + .../environment/browser/environmentService.ts | 3 + .../environment/common/environmentService.ts | 1 + .../electron-browser/environmentService.ts | 8 +- 24 files changed, 422 insertions(+), 29 deletions(-) create mode 100644 resources/profiles/agent-sessions.code-profile create mode 100644 src/vs/platform/userDataProfile/common/userDataProfileTemplateWatcher.ts create mode 100644 src/vs/workbench/contrib/chat/electron-browser/actions/agentSessionsActions.ts diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index cb76ed614f4..147592d00a1 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -284,6 +284,8 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true }); + const profiles = gulp.src('resources/profiles/**', { base: '.', dot: true }); + const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const root = path.resolve(path.join(import.meta.dirname, '..')); const productionDependencies = getProductionDependencies(root); @@ -318,6 +320,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d license, api, telemetry, + profiles, sources, deps ); diff --git a/resources/profiles/agent-sessions.code-profile b/resources/profiles/agent-sessions.code-profile new file mode 100644 index 00000000000..e4a699d084c --- /dev/null +++ b/resources/profiles/agent-sessions.code-profile @@ -0,0 +1,19 @@ +{ + "name": "Agent Sessions", + "icon": "copilot", + "settings": { + "workbench.statusBar.visible": false, + "workbench.activityBar.location": "hidden", + "workbench.sideBar.location": "right", + "workbench.secondarySideBar.defaultVisibility": "maximized", + "workbench.secondarySideBar.restoreMaximized": true, + "chat.restoreLastPanelSession": true, + "workbench.startupEditor": "none", + "workbench.editor.restoreEditors": false, + "chat.agentsControl.enabled": true, + "files.autoSave": "afterDelay", + "chat.commandCenter.enabled": false, + "window.title": "Agent Sessions", + "workbench.editor.showTabs": "single" + } +} diff --git a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts index 89d97361485..4cdb288165e 100644 --- a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts @@ -132,6 +132,7 @@ import { McpManagementChannel } from '../../../platform/mcp/common/mcpManagement import { AllowedMcpServersService } from '../../../platform/mcp/common/allowedMcpServersService.js'; import { IMcpGalleryManifestService } from '../../../platform/mcp/common/mcpGalleryManifest.js'; import { McpGalleryManifestIPCService } from '../../../platform/mcp/common/mcpGalleryManifestServiceIpc.js'; +import { UserDataProfileTemplatesWatcher } from '../../../platform/userDataProfile/common/userDataProfileTemplateWatcher.js'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -197,7 +198,8 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { instantiationService.createInstance(LocalizationsUpdater), instantiationService.createInstance(ExtensionsContributions), instantiationService.createInstance(UserDataProfilesCleaner), - instantiationService.createInstance(DefaultExtensionsInitializer) + instantiationService.createInstance(DefaultExtensionsInitializer), + instantiationService.createInstance(UserDataProfileTemplatesWatcher) )); } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 33f4789da64..ddc44a1149a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -220,6 +220,7 @@ class StandaloneEnvironmentService implements IEnvironmentService { readonly keyboardLayoutResource: URI = URI.from({ scheme: 'monaco', authority: 'keyboardLayoutResource' }); readonly argvResource: URI = URI.from({ scheme: 'monaco', authority: 'argvResource' }); readonly untitledWorkspacesHome: URI = URI.from({ scheme: 'monaco', authority: 'untitledWorkspacesHome' }); + readonly builtinProfilesHome: URI = URI.from({ scheme: 'monaco', authority: 'builtinProfilesHome' }); readonly workspaceStorageHome: URI = URI.from({ scheme: 'monaco', authority: 'workspaceStorageHome' }); readonly localHistoryHome: URI = URI.from({ scheme: 'monaco', authority: 'localHistoryHome' }); readonly cacheHome: URI = URI.from({ scheme: 'monaco', authority: 'cacheHome' }); diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a14c835bf9..be0d2573d1d 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -58,6 +58,7 @@ export interface IEnvironmentService { workspaceStorageHome: URI; localHistoryHome: URI; cacheHome: URI; + builtinProfilesHome: URI; // --- settings sync userDataSyncHome: URI; @@ -88,6 +89,9 @@ export interface IEnvironmentService { disableExperiments: boolean; serviceMachineIdResource: URI; + // --- agent sessions workspace + agentSessionsWorkspace?: URI; + // --- Policy policyFile?: URI; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 535132f43a4..5a016b52bad 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -107,6 +107,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get untitledWorkspacesHome(): URI { return URI.file(join(this.userDataPath, 'Workspaces')); } + @memoize + get builtinProfilesHome(): URI { return joinPath(URI.file(this.appRoot), 'resources', 'profiles'); } + @memoize get builtinExtensionsPath(): string { const cliBuiltinExtensionsDir = this.args['builtin-extensions-dir']; @@ -117,6 +120,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return normalize(join(FileAccess.asFileUri('').fsPath, '..', 'extensions')); } + @memoize get extensionsDownloadLocation(): URI { const cliExtensionsDownloadDir = this.args['extensions-download-dir']; if (cliExtensionsDownloadDir) { @@ -252,6 +256,11 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return undefined; } + @memoize + get agentSessionsWorkspace(): URI { + return joinPath(this.appSettingsHome, 'agent-sessions.code-workspace'); + } + get editSessionId(): string | undefined { return this.args['editSessionId']; } get exportPolicyData(): string | undefined { diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 5d17d17ba03..2381fe4a1e0 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -20,6 +20,8 @@ import { Promises } from '../../../base/common/async.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { isString, Mutable } from '../../../base/common/types.js'; +import { ResourceMap } from '../../../base/common/map.js'; +import { parse } from '../../../base/common/json.js'; export const enum ProfileResourceType { Settings = 'settings', @@ -37,6 +39,14 @@ export const enum ProfileResourceType { */ export type UseDefaultProfileFlags = { [key in ProfileResourceType]?: boolean }; export type ProfileResourceTypeFlags = UseDefaultProfileFlags; +export type SettingValue = string | boolean | number | undefined | null | object; +export type ISettingsDictionary = Record; + +export interface ITemplateData { + readonly resource: URI; + readonly icon?: string; + readonly settings?: ISettingsDictionary; +} export interface IUserDataProfile { readonly id: string; @@ -56,6 +66,7 @@ export interface IUserDataProfile { readonly useDefaultFlags?: UseDefaultProfileFlags; readonly isTransient?: boolean; readonly workspaces?: readonly URI[]; + readonly templateData?: ITemplateData; } export function isUserDataProfile(thing: unknown): thing is IUserDataProfile { @@ -77,6 +88,13 @@ export function isUserDataProfile(thing: unknown): thing is IUserDataProfile { ); } +export interface ISystemProfileTemplate { + readonly name: string; + readonly icon?: string; + readonly settings?: ISettingsDictionary; + readonly globalState?: IStringDictionary; +} + export type DidChangeProfilesEvent = { readonly added: readonly IUserDataProfile[]; readonly removed: readonly IUserDataProfile[]; readonly updated: readonly IUserDataProfile[]; readonly all: readonly IUserDataProfile[] }; export type WillCreateProfileEvent = { @@ -94,9 +112,10 @@ export interface IUserDataProfileOptions { readonly useDefaultFlags?: UseDefaultProfileFlags; readonly transient?: boolean; readonly workspaces?: readonly URI[]; + readonly templateData?: ITemplateData; } -export interface IUserDataProfileUpdateOptions extends Omit { +export interface IUserDataProfileUpdateOptions extends Omit { readonly name?: string; readonly icon?: string | null; } @@ -113,6 +132,7 @@ export interface IUserDataProfilesService { readonly onDidResetWorkspaces: Event; + createSystemProfile(id: string): Promise; createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise; createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise; createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise; @@ -145,6 +165,10 @@ export function reviveProfile(profile: UriDto, scheme: string) useDefaultFlags: profile.useDefaultFlags, isTransient: profile.isTransient, workspaces: profile.workspaces?.map(w => URI.revive(w)), + templateData: profile.templateData ? { + ...profile.templateData, + resource: URI.revive(profile.templateData?.resource), + } : undefined, }; } @@ -167,6 +191,7 @@ export function toUserDataProfile(id: string, name: string, location: URI, profi useDefaultFlags: options?.useDefaultFlags, isTransient: options?.transient, workspaces: options?.workspaces, + templateData: options?.templateData, }; } @@ -180,6 +205,7 @@ export type StoredUserDataProfile = { location: URI; icon?: string; useDefaultFlags?: UseDefaultProfileFlags; + templateData?: ITemplateData; }; export type StoredProfileAssociations = { @@ -245,7 +271,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf this.logService.warn('Skipping the invalid stored profile', storedProfile.location || storedProfile.name); continue; } - profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { icon: storedProfile.icon, useDefaultFlags: storedProfile.useDefaultFlags }, defaultProfile)); + profiles.push(toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, this.profilesCacheHome, { icon: storedProfile.icon, useDefaultFlags: storedProfile.useDefaultFlags, templateData: storedProfile.templateData }, defaultProfile)); } } catch (error) { this.logService.error(error); @@ -310,6 +336,20 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf return profile; } + async createSystemProfile(id: string): Promise { + const existing = this.profiles.find(p => p.id === id); + if (existing) { + return existing; + } + + const systemProfileTemplate = await this.getSystemProfileTemplate(id); + if (!systemProfileTemplate) { + throw new Error(`System profile template '${id}' does not exist`); + } + + return this.doCreateProfile(id, systemProfileTemplate.name); + } + private async doCreateProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise { if (!isString(name) || !name) { throw new Error('Name of the profile is mandatory and must be of type `string`'); @@ -328,6 +368,20 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf if (URI.isUri(workspace)) { options = { ...options, workspaces: [workspace] }; } + + const systemProfileTemplate = await this.getSystemProfileTemplate(id); + if (systemProfileTemplate) { + options = { + ...options, + icon: options?.icon ?? systemProfileTemplate.icon, + templateData: { + resource: joinPath(this.environmentService.builtinProfilesHome, `${id}.code-profile`), + icon: systemProfileTemplate.icon, + settings: systemProfileTemplate.settings, + } + }; + } + const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), this.profilesCacheHome, options, this.defaultProfile); await this.fileService.createFolder(profile.location); @@ -366,6 +420,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf transient: options.transient ?? existing.isTransient, useDefaultFlags: options.useDefaultFlags ?? existing.useDefaultFlags, workspaces: options.workspaces ?? existing.workspaces, + templateData: existing.templateData ? { + ...existing.templateData, + ...options.templateData, + } : undefined, }, this.defaultProfile); } else if (options.workspaces) { profileToUpdate = existing; @@ -500,6 +558,50 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf : (this.profilesObject.emptyWindows.get(workspace) ?? this.transientProfilesObject.emptyWindows.get(workspace)); } + private getSystemProfileTemplate(id: string): Promise { + return this.getSystemProfileTemplates().then(templates => { + const resource = joinPath(this.environmentService.builtinProfilesHome, `${id}.code-profile`); + return templates.get(resource); + }); + } + + private systemProfilesTemplatesPromise: Promise> | undefined; + private async getSystemProfileTemplates(): Promise> { + if (!this.systemProfilesTemplatesPromise) { + this.systemProfilesTemplatesPromise = this.doGetSystemProfileTemplates(); + } + return this.systemProfilesTemplatesPromise; + } + + private async doGetSystemProfileTemplates(): Promise> { + const result = new ResourceMap(); + const profilesFolder = this.environmentService.builtinProfilesHome; + try { + const stat = await this.fileService.resolve(profilesFolder); + if (!stat.children?.length) { + return result; + } + for (const child of stat.children) { + if (child.isDirectory) { + continue; + } + if (this.uriIdentityService.extUri.extname(child.resource) !== '.code-profile') { + continue; + } + try { + const content = (await this.fileService.readFile(child.resource)).value.toString(); + const profile: ISystemProfileTemplate = parse(content); + result.set(child.resource, profile); + } catch (error) { + this.logService.error(`Error while reading system profile template from ${child.resource.toString()}`, error); + } + } + } catch (error) { + this.logService.error(`Error while reading system profile templates from ${profilesFolder.toString()}`, error); + } + return result; + } + protected getWorkspace(workspaceIdentifier: IAnyWorkspaceIdentifier): URI | string { if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { return workspaceIdentifier.uri; @@ -609,7 +711,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf continue; } if (!profile.isDefault) { - storedProfiles.push({ location: profile.location, name: profile.name, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags }); + storedProfiles.push({ location: profile.location, name: profile.name, icon: profile.icon, useDefaultFlags: profile.useDefaultFlags, templateData: profile.templateData }); } if (profile.workspaces) { for (const workspace of profile.workspaces) { diff --git a/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts index 00d9b2c5ed8..32a75a3b9df 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts @@ -94,6 +94,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf return reviveProfile(result, this.profilesHome.scheme); } + async createSystemProfile(id: string): Promise { + const result = await this.channel.call>('createSystemProfile', [id]); + return reviveProfile(result, this.profilesHome.scheme); + } + async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise { const result = await this.channel.call>('createTransientProfile', [workspaceIdentifier]); return reviveProfile(result, this.profilesHome.scheme); diff --git a/src/vs/platform/userDataProfile/common/userDataProfileTemplateWatcher.ts b/src/vs/platform/userDataProfile/common/userDataProfileTemplateWatcher.ts new file mode 100644 index 00000000000..9ef6b669524 --- /dev/null +++ b/src/vs/platform/userDataProfile/common/userDataProfileTemplateWatcher.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableMap, IDisposable } from '../../../base/common/lifecycle.js'; +import { IFileService } from '../../files/common/files.js'; +import { ILogService } from '../../log/common/log.js'; +import { IUserDataProfile, IUserDataProfilesService, IUserDataProfileUpdateOptions, ISystemProfileTemplate } from './userDataProfile.js'; +import { isEmptyObject, Mutable } from '../../../base/common/types.js'; +import { equals } from '../../../base/common/objects.js'; + +export class UserDataProfileTemplatesWatcher extends Disposable { + + private readonly templateWatchers = this._register(new DisposableMap()); + + constructor( + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + super(); + + // Watch template files for existing profiles + for (const profile of this.userDataProfilesService.profiles) { + this.watchProfileTemplate(profile); + } + + // Listen for profile changes to update watchers + this._register(this.userDataProfilesService.onDidChangeProfiles(e => { + // Stop watching removed profiles + for (const profile of e.removed) { + this.unwatchProfileTemplate(profile); + } + + // Start watching added profiles + for (const profile of e.added) { + this.watchProfileTemplate(profile); + } + + // Update watchers for updated profiles (templateResource might have changed) + for (const profile of e.updated) { + this.unwatchProfileTemplate(profile); + this.watchProfileTemplate(profile); + } + })); + } + + private async watchProfileTemplate(profile: IUserDataProfile): Promise { + const templateResource = profile.templateData?.resource; + + if (!templateResource) { + return; + } + + try { + await this.onDidChangeProfileTemplate(profile); // Apply any changes on startup + } catch (error) { + this.logService.error(`UserDataProfileTemplateService: Failed to apply template changes on startup for profile '${profile.name}'`, error); + } + + this.logService.trace(`UserDataProfileTemplateService: Watching template file for profile '${profile.name}'`, templateResource.toString()); + const watcher = this.fileService.createWatcher(templateResource, { recursive: false, excludes: [] }); + const disposable = watcher.onDidChange(async () => { + this.logService.trace(`UserDataProfileTemplateService: Template file changed for profile '${profile.name}'`, templateResource.toString()); + // Get the latest profile in case it was updated + const currentProfile = this.userDataProfilesService.profiles.find(p => p.id === profile.id); + if (currentProfile) { + try { + await this.onDidChangeProfileTemplate(currentProfile); + } catch (error) { + this.logService.error(`UserDataProfileTemplateService: Failed to apply template changes when template file changed for profile '${profile.name}'`, error); + } + } + }); + + this.templateWatchers.set(profile.id, { + dispose: () => { + disposable.dispose(); + watcher.dispose(); + } + }); + } + + private unwatchProfileTemplate(profile: IUserDataProfile): void { + this.templateWatchers.deleteAndDispose(profile.id); + } + + private async onDidChangeProfileTemplate(profile: IUserDataProfile): Promise { + if (!profile.templateData?.resource) { + return; + } + + this.logService.info(`UserDataProfileTemplateService: Template file changed for profile '${profile.name}', checking for changes...`); + + try { + const sourceTemplate = await this.resolveSourceTemplate(profile); + + if (!sourceTemplate) { + this.logService.warn(`UserDataProfileTemplateService: Could not resolve source template for profile '${profile.name}'`); + return; + } + + const profileUpdateOptions: Mutable = Object.create(null); + + if (sourceTemplate.icon !== profile.templateData?.icon && profile.templateData?.icon === profile.icon) { + profileUpdateOptions.icon = sourceTemplate.icon; + } + + if (!equals(sourceTemplate.settings, profile.templateData.settings)) { + this.logService.trace(`UserDataProfileTemplateService: Updating default settings for profile '${profile.name}'`); + profileUpdateOptions.templateData = { ...profile.templateData, settings: sourceTemplate.settings }; + } + + if (!isEmptyObject(profileUpdateOptions)) { + await this.userDataProfilesService.updateProfile(profile, profileUpdateOptions); + } + + this.logService.info(`UserDataProfileTemplateService: Successfully applied template changes to profile '${profile.name}'`); + } catch (error) { + this.logService.error(`UserDataProfileTemplateService: Failed to apply template changes to profile '${profile.name}'`, error); + } + } + + private async resolveSourceTemplate(profile: IUserDataProfile): Promise { + if (!profile.templateData?.resource) { + return null; + } + + try { + const content = await this.fileService.readFile(profile.templateData.resource); + return JSON.parse(content.value.toString()); + } catch (error) { + this.logService.error(`UserDataProfileTemplateService: Failed to resolve source template for profile '${profile.name}'`, error); + return null; + } + } +} diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts index c1db01f7506..26eddf0a5d4 100644 --- a/src/vs/platform/userDataProfile/node/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -29,7 +29,11 @@ export class UserDataProfilesReadonlyService extends BaseUserDataProfilesService protected override getStoredProfiles(): StoredUserDataProfile[] { const storedProfilesState = this.stateReadonlyService.getItem[]>(UserDataProfilesReadonlyService.PROFILES_KEY, []); - return storedProfilesState.map(p => ({ ...p, location: isString(p.location) ? this.uriIdentityService.extUri.joinPath(this.profilesHome, p.location) : URI.revive(p.location) })); + return storedProfilesState.map(p => ({ + ...p, + location: isString(p.location) ? this.uriIdentityService.extUri.joinPath(this.profilesHome, p.location) : URI.revive(p.location), + templateData: p.templateData ? { ...p.templateData, resource: URI.revive(p.templateData.resource) } : undefined + })); } protected override getStoredProfileAssociations(): StoredProfileAssociations { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 818f7d1f69b..437e1afc423 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -68,6 +68,7 @@ export class UserDataSyncClient extends Disposable { cacheHome: joinPath(userRoamingDataHome, 'cache'), argvResource: joinPath(userRoamingDataHome, 'argv.json'), sync: 'on', + builtinProfilesHome: joinPath(userRoamingDataHome, 'builtinProfiles') }); this.instantiationService.stub(IProductService, { diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 1da0210c75d..6e3d850fc4d 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,7 +6,7 @@ import { Disposable } from '../../base/common/lifecycle.js'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js'; import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js'; -import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext } from '../common/contextkeys.js'; +import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsAgentSessionsWorkspaceContext } from '../common/contextkeys.js'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { IWorkbenchEnvironmentService } from '../services/environment/common/environmentService.js'; @@ -47,6 +47,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private virtualWorkspaceContext: IContextKey; private temporaryWorkspaceContext: IContextKey; + private isAgentSessionsWorkspaceContext: IContextKey; private inAutomationContext: IContextKey; private inZenModeContext: IContextKey; @@ -93,6 +94,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.virtualWorkspaceContext = VirtualWorkspaceContext.bindTo(this.contextKeyService); this.temporaryWorkspaceContext = TemporaryWorkspaceContext.bindTo(this.contextKeyService); + this.isAgentSessionsWorkspaceContext = IsAgentSessionsWorkspaceContext.bindTo(this.contextKeyService); + this.isAgentSessionsWorkspaceContext.set(!!this.environmentService.agentSessionsWindow); this.updateWorkspaceContextKeys(); // Capabilities diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 743f9e6ee8b..5b17b596220 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -821,7 +821,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private get activityActionsEnabled(): boolean { const activityBarPosition = this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); - return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM); + return !this.isCompact && !this.isAuxiliary && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM || activityBarPosition === ActivityBarPosition.HIDDEN); } private get globalActionsEnabled(): boolean { diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 85a85ac0e0a..a182e124a2a 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -6,7 +6,9 @@ import { localize } from '../../../../nls.js'; import { dirname, basename } from '../../../../base/common/resources.js'; import { ITitleProperties, ITitleVariable } from './titlebarPart.js'; -import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js'; +import { IConfigurationService, IConfigurationChangeEvent, isConfigured } from '../../../../platform/configuration/common/configuration.js'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from '../../../common/editor.js'; @@ -412,6 +414,13 @@ export class WindowTitle extends Disposable { const title = this.configurationService.inspect(WindowSettingNames.title); const titleSeparator = this.configurationService.inspect(WindowSettingNames.titleSeparator); - return title.value !== title.defaultValue || titleSeparator.value !== titleSeparator.defaultValue; + if (isConfigured(title) || isConfigured(titleSeparator)) { + return true; + } + + // Check if the default value is overridden from the configuration registry + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + const configurationProperties = configurationRegistry.getConfigurationProperties(); + return title.defaultValue !== configurationProperties[WindowSettingNames.title]?.defaultDefaultValue; } } diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 3a687ad965a..b2de5c2f640 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -33,6 +33,8 @@ export const RemoteNameContext = new RawContextKey('remoteName', '', loc export const VirtualWorkspaceContext = new RawContextKey('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace is from a virtual file system or an empty string.")); export const TemporaryWorkspaceContext = new RawContextKey('temporaryWorkspace', false, localize('temporaryWorkspace', "The scheme of the current workspace is from a temporary file system.")); +export const IsAgentSessionsWorkspaceContext = new RawContextKey('isAgentSessionsWorkspace', false, localize('isAgentSessionsWorkspace', "Whether the current workspace is the agent sessions workspace.")); + export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access) export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); diff --git a/src/vs/workbench/contrib/chat/electron-browser/actions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/electron-browser/actions/agentSessionsActions.ts new file mode 100644 index 00000000000..e1a8af5253e --- /dev/null +++ b/src/vs/workbench/contrib/chat/electron-browser/actions/agentSessionsActions.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from '../../../../../base/common/buffer.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { localize2 } from '../../../../../nls.js'; +import { Action2 } from '../../../../../platform/actions/common/actions.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ProductQualityContext } from '../../../../../platform/contextkey/common/contextkeys.js'; +import { INativeEnvironmentService } from '../../../../../platform/environment/common/environment.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { INativeHostService } from '../../../../../platform/native/common/native.js'; +import { IUserDataProfilesService } from '../../../../../platform/userDataProfile/common/userDataProfile.js'; +import { ChatEntitlementContextKeys } from '../../../../services/chat/common/chatEntitlementService.js'; +import { CHAT_CATEGORY } from '../../browser/actions/chatActions.js'; + +export class OpenAgentSessionsWindowAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.openAgentSessionsWindow', + title: localize2('openAgentSessionsWindow', "Open Agent Sessions Window"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ChatEntitlementContextKeys.Setup.hidden.negate(), ProductQualityContext.notEqualsTo('stable')), + f1: true, + }); + } + + async run(accessor: ServicesAccessor) { + const environmentService = accessor.get(INativeEnvironmentService); + const nativeHostService = accessor.get(INativeHostService); + const userDataProfilesService = accessor.get(IUserDataProfilesService); + const fileService = accessor.get(IFileService); + + // Create workspace file if it doesn't exist + const workspaceUri = environmentService.agentSessionsWorkspace; + if (!workspaceUri) { + throw new Error('Agent Sessions workspace is not configured'); + } + + const workspaceExists = await fileService.exists(workspaceUri); + if (!workspaceExists) { + const emptyWorkspaceContent = JSON.stringify({ folders: [] }, null, '\t'); + await fileService.writeFile(workspaceUri, VSBuffer.fromString(emptyWorkspaceContent)); + } + + const profile = await userDataProfilesService.createSystemProfile('agent-sessions'); + await userDataProfilesService.updateProfile(profile, { workspaces: [workspaceUri] }); + await nativeHostService.openWindow([{ workspaceUri }], { forceNewWindow: true }); + } +} diff --git a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts index dc0d3a78777..2d754950fc6 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts @@ -32,6 +32,7 @@ import { registerChatDeveloperActions } from './actions/chatDeveloperActions.js' import { registerChatExportZipAction } from './actions/chatExportZip.js'; import { HoldToVoiceChatInChatViewAction, InlineVoiceChatAction, KeywordActivationContribution, QuickVoiceChatAction, ReadChatResponseAloud, StartVoiceChatAction, StopListeningAction, StopListeningAndSubmitAction, StopReadAloud, StopReadChatItemAloud, VoiceChatInChatViewAction } from './actions/voiceChatActions.js'; import { NativeBuiltinToolsContribution } from './builtInTools/tools.js'; +import { OpenAgentSessionsWindowAction } from './actions/agentSessionsActions.js'; class ChatCommandLineHandler extends Disposable { @@ -180,6 +181,7 @@ class ChatLifecycleHandler extends Disposable { } } +registerAction2(OpenAgentSessionsWindowAction); registerAction2(StartVoiceChatAction); registerAction2(VoiceChatInChatViewAction); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index f8ea86ce625..e1a10b4e535 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -58,7 +58,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IInstantiationService private readonly instantiationService: IInstantiationService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IURLService private readonly urlService: IURLService, - @IBrowserWorkbenchEnvironmentService environmentService: IBrowserWorkbenchEnvironmentService + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService ) { super(); @@ -76,22 +76,25 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1); this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1))); - this.registerEditor(); - this.registerActions(); + if (!this.environmentService.agentSessionsWindow) { - this._register(this.urlService.registerHandler(this)); + this.registerEditor(); + this.registerActions(); - if (isWeb) { - lifecycleService.when(LifecyclePhase.Eventually).then(() => userDataProfilesService.cleanUp()); + this._register(this.urlService.registerHandler(this)); + + if (isWeb) { + lifecycleService.when(LifecyclePhase.Eventually).then(() => userDataProfilesService.cleanUp()); + } + + this.reportWorkspaceProfileInfo(); + + if (environmentService.options?.profileToPreview) { + lifecycleService.when(LifecyclePhase.Restored).then(() => this.handleURL(URI.revive(environmentService.options!.profileToPreview!))); + } + + this.registerDropHandler(); } - - this.reportWorkspaceProfileInfo(); - - if (environmentService.options?.profileToPreview) { - lifecycleService.when(LifecyclePhase.Restored).then(() => this.handleURL(URI.revive(environmentService.options!.profileToPreview!))); - } - - this.registerDropHandler(); } async handleURL(uri: URI): Promise { diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index 486d93adc0d..adc6a1dfa27 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -69,6 +69,7 @@ import { normalizeDriveLetter } from '../../../../base/common/labels.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { DropdownMenuActionViewItem } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; const editIcon = registerIcon('profiles-editor-edit-folder', Codicon.edit, localize('editIcon', 'Icon for the edit folder icon in the profiles editor.')); const removeIcon = registerIcon('profiles-editor-remove-folder', Codicon.close, localize('removeIcon', 'Icon for the remove folder icon in the profiles editor.')); @@ -2119,14 +2120,17 @@ class ChangeProfileAction implements IAction { readonly id = 'changeProfile'; readonly label = 'Change Profile'; readonly class = ThemeIcon.asClassName(editIcon); - readonly enabled = true; + readonly enabled: boolean; readonly tooltip = localize('change profile', "Change Profile"); readonly checked = false; constructor( private readonly item: WorkspaceTableElement, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IEnvironmentService environmentService: IEnvironmentService, ) { + this.enabled = !uriIdentityService.extUri.isEqual(item.workspace, environmentService.agentSessionsWorkspace); } run(): void { } @@ -2163,6 +2167,7 @@ class WorkspaceUriActionsColumnRenderer implements ITableRenderer item.profileElement.updateWorkspaces([], [item.workspace]) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 0defa533935..cb8eaf810ce 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -14,7 +14,7 @@ import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, APPLY_ALL_PROFILES_SETTING, APPLICATION_SCOPES, MCP_CONFIGURATION_KEY } from '../common/configuration.js'; import { IStoredWorkspaceFolder } from '../../../../platform/workspaces/common/workspaces.js'; import { WorkbenchState, IWorkspaceFolder, IWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; -import { ConfigurationScope, Extensions, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { ConfigurationScope, Extensions, IConfigurationDefaults, IConfigurationRegistry, OVERRIDE_PROPERTY_REGEX } from '../../../../platform/configuration/common/configurationRegistry.js'; import { equals } from '../../../../base/common/objects.js'; import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; import { hash } from '../../../../base/common/hash.js'; @@ -23,11 +23,11 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IStringDictionary } from '../../../../base/common/collections.js'; import { joinPath } from '../../../../base/common/resources.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; -import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js'; import { isEmptyObject, isObject } from '../../../../base/common/types.js'; import { DefaultConfiguration as BaseDefaultConfiguration } from '../../../../platform/configuration/common/configurations.js'; import { IJSONEditingService } from '../common/jsonEditing.js'; import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js'; export class DefaultConfiguration extends BaseDefaultConfiguration { @@ -36,6 +36,7 @@ export class DefaultConfiguration extends BaseDefaultConfiguration { private readonly configurationRegistry = Registry.as(Extensions.Configuration); private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; + private profileDefaults: IConfigurationDefaults | undefined; constructor( private readonly configurationCache: IConfigurationCache, @@ -67,6 +68,18 @@ export class DefaultConfiguration extends BaseDefaultConfiguration { return !isEmptyObject(this.cachedConfigurationDefaultsOverrides); } + updateProfileDefaults(configurationDefaults: IStringDictionary | undefined): void { + if (this.profileDefaults) { + this.configurationRegistry.deregisterDefaultConfigurations([this.profileDefaults]); + } + if (configurationDefaults) { + this.profileDefaults = { overrides: configurationDefaults }; + this.configurationRegistry.registerDefaultConfigurations([this.profileDefaults]); + } else { + this.profileDefaults = undefined; + } + } + private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise | undefined; private initializeCachedConfigurationDefaultsOverrides(): Promise { if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index e9e0189165b..596dd1aa4ff 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -125,6 +125,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService, logService)); + if (this.userDataProfileService.currentProfile.templateData?.settings) { + this.defaultConfiguration.updateProfileDefaults(this.userDataProfileService.currentProfile.templateData?.settings); + } this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, ConfigurationModel.createEmptyModel(logService), ConfigurationModel.createEmptyModel(logService), ConfigurationModel.createEmptyModel(logService), ConfigurationModel.createEmptyModel(logService), new ResourceMap(), ConfigurationModel.createEmptyModel(logService), new ResourceMap(), this.workspace, logService); @@ -729,6 +732,9 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat promises.push(this.reloadApplicationConfiguration(true)); } } + if (!equals(e.previous.templateData?.settings, e.profile.templateData?.settings)) { + this.defaultConfiguration.updateProfileDefaults(e.profile.templateData?.settings); + } let [localUser, application] = await Promise.all(promises); application = application ?? this._configuration.applicationConfiguration; if (this.applicationConfiguration) { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index ec1a621d29e..9435617d2f6 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -138,6 +138,9 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi @memoize get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } + @memoize + get builtinProfilesHome(): URI { return joinPath(this.userRoamingDataHome, 'builtinProfiles'); } + @memoize get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 5312892fe6f..f3c7b9fc4d0 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -36,6 +36,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly skipWelcome: boolean; readonly disableWorkspaceTrust: boolean; readonly webviewExternalEndpoint: string; + readonly agentSessionsWindow?: boolean; // --- Development readonly debugRenderer: boolean; diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index 6cfa51701be..2489fff63b5 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -13,8 +13,9 @@ import { memoize } from '../../../../base/common/decorators.js'; import { URI } from '../../../../base/common/uri.js'; import { Schemas } from '../../../../base/common/network.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; -import { joinPath } from '../../../../base/common/resources.js'; +import { isEqual, joinPath } from '../../../../base/common/resources.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; +import { isWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; export const INativeWorkbenchEnvironmentService = refineServiceDecorator(IEnvironmentService); @@ -147,6 +148,11 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment @memoize get filesToWait(): IPathsToWaitFor | undefined { return this.configuration.filesToWait; } + @memoize + get agentSessionsWindow(): boolean | undefined { + return isWorkspaceIdentifier(this.configuration.workspace) && isEqual(this.configuration.workspace.configPath, this.agentSessionsWorkspace); + } + constructor( private readonly configuration: INativeWindowConfiguration, productService: IProductService