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 <benjamin.pasero@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sandeep Somavarapu
2026-01-23 09:17:25 +01:00
committed by GitHub
parent 4adf8989a8
commit 19209a8c1f
24 changed files with 422 additions and 29 deletions

View File

@@ -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
);

View File

@@ -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"
}
}

View File

@@ -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)
));
}

View File

@@ -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' });

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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<string, SettingValue>;
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<string>;
}
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<IUserDataProfileOptions, 'icon'> {
export interface IUserDataProfileUpdateOptions extends Omit<IUserDataProfileOptions, 'icon' | 'defaultSettings'> {
readonly name?: string;
readonly icon?: string | null;
}
@@ -113,6 +132,7 @@ export interface IUserDataProfilesService {
readonly onDidResetWorkspaces: Event<void>;
createSystemProfile(id: string): Promise<IUserDataProfile>;
createNamedProfile(name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
createProfile(id: string, name: string, options?: IUserDataProfileOptions, workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile>;
@@ -145,6 +165,10 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, 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<IUserDataProfile> {
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<IUserDataProfile> {
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<ISystemProfileTemplate | undefined> {
return this.getSystemProfileTemplates().then(templates => {
const resource = joinPath(this.environmentService.builtinProfilesHome, `${id}.code-profile`);
return templates.get(resource);
});
}
private systemProfilesTemplatesPromise: Promise<ResourceMap<ISystemProfileTemplate>> | undefined;
private async getSystemProfileTemplates(): Promise<ResourceMap<ISystemProfileTemplate>> {
if (!this.systemProfilesTemplatesPromise) {
this.systemProfilesTemplatesPromise = this.doGetSystemProfileTemplates();
}
return this.systemProfilesTemplatesPromise;
}
private async doGetSystemProfileTemplates(): Promise<ResourceMap<ISystemProfileTemplate>> {
const result = new ResourceMap<ISystemProfileTemplate>();
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) {

View File

@@ -94,6 +94,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return reviveProfile(result, this.profilesHome.scheme);
}
async createSystemProfile(id: string): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createSystemProfile', [id]);
return reviveProfile(result, this.profilesHome.scheme);
}
async createTransientProfile(workspaceIdentifier?: IAnyWorkspaceIdentifier): Promise<IUserDataProfile> {
const result = await this.channel.call<UriDto<IUserDataProfile>>('createTransientProfile', [workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);

View File

@@ -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<string, IDisposable>());
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<void> {
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<void> {
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<IUserDataProfileUpdateOptions> = 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<ISystemProfileTemplate | null> {
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;
}
}
}

View File

@@ -29,7 +29,11 @@ export class UserDataProfilesReadonlyService extends BaseUserDataProfilesService
protected override getStoredProfiles(): StoredUserDataProfile[] {
const storedProfilesState = this.stateReadonlyService.getItem<UriDto<StoredUserDataProfileState>[]>(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 {

View File

@@ -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, {

View File

@@ -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<string>;
private temporaryWorkspaceContext: IContextKey<boolean>;
private isAgentSessionsWorkspaceContext: IContextKey<boolean>;
private inAutomationContext: IContextKey<boolean>;
private inZenModeContext: IContextKey<boolean>;
@@ -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

View File

@@ -821,7 +821,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
private get activityActionsEnabled(): boolean {
const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(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 {

View File

@@ -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<string>(WindowSettingNames.title);
const titleSeparator = this.configurationService.inspect<string>(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<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const configurationProperties = configurationRegistry.getConfigurationProperties();
return title.defaultValue !== configurationProperties[WindowSettingNames.title]?.defaultDefaultValue;
}
}

View File

@@ -33,6 +33,8 @@ export const RemoteNameContext = new RawContextKey<string>('remoteName', '', loc
export const VirtualWorkspaceContext = new RawContextKey<string>('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace is from a virtual file system or an empty string."));
export const TemporaryWorkspaceContext = new RawContextKey<boolean>('temporaryWorkspace', false, localize('temporaryWorkspace', "The scheme of the current workspace is from a temporary file system."));
export const IsAgentSessionsWorkspaceContext = new RawContextKey<boolean>('isAgentSessionsWorkspace', false, localize('isAgentSessionsWorkspace', "Whether the current workspace is the agent sessions workspace."));
export const HasWebFileSystemAccess = new RawContextKey<boolean>('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access)
export const EmbedderIdentifierContext = new RawContextKey<string | undefined>('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined'));

View File

@@ -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 });
}
}

View File

@@ -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);

View File

@@ -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<boolean> {

View File

@@ -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<WorkspaceTable
@IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
) {
}
@@ -2189,7 +2194,7 @@ class WorkspaceUriActionsColumnRenderer implements ITableRenderer<WorkspaceTable
templateData.actionBar.clear();
const actions: IAction[] = [];
actions.push(this.createOpenAction(item));
actions.push(new ChangeProfileAction(item, this.userDataProfilesService));
actions.push(new ChangeProfileAction(item, this.userDataProfilesService, this.uriIdentityService, this.environmentService));
actions.push(this.createDeleteAction(item));
templateData.actionBar.push(actions, { icon: true });
}
@@ -2206,10 +2211,11 @@ class WorkspaceUriActionsColumnRenderer implements ITableRenderer<WorkspaceTable
}
private createDeleteAction(item: WorkspaceTableElement): IAction {
const isAgentSessionsWorkspace = this.uriIdentityService.extUri.isEqual(item.workspace, this.environmentService.agentSessionsWorkspace);
return {
label: '',
class: ThemeIcon.asClassName(removeIcon),
enabled: this.userDataProfileManagementService.getDefaultProfileToUse().id !== item.profileElement.profile.id,
enabled: this.userDataProfileManagementService.getDefaultProfileToUse().id !== item.profileElement.profile.id && !isAgentSessionsWorkspace,
id: 'deleteTrustedUri',
tooltip: localize('deleteTrustedUri', "Delete Path"),
run: () => item.profileElement.updateWorkspaces([], [item.workspace])

View File

@@ -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<IConfigurationRegistry>(Extensions.Configuration);
private cachedConfigurationDefaultsOverrides: IStringDictionary<unknown> = {};
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<unknown> | 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<void> | undefined;
private initializeCachedConfigurationDefaultsOverrides(): Promise<void> {
if (!this.initiaizeCachedConfigurationDefaultsOverridesPromise) {

View File

@@ -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<ConfigurationModel>(), 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) {

View File

@@ -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'); }

View File

@@ -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;

View File

@@ -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, INativeWorkbenchEnvironmentService>(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