Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/vs/platform/native/common/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export interface INativeZipOptions {

export interface IOpenAgentsWindowOptions {
readonly folderUri?: UriComponents;
/** Use the invoking editor's folder only for a fresh composer, without replacing an existing session or user choice. */
readonly folderUriIsDefault?: boolean;
readonly sessionResource?: UriComponents;
readonly source?: AgentsWindowOpenSource;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
context: OpenContext.API,
contextWindowId: windowId,
cli: this.environmentMainService.args,
}, options?.folderUri ? URI.revive(options.folderUri) : undefined, options?.sessionResource ? URI.revive(options.sessionResource) : undefined, options?.source);
}, options?.folderUri ? URI.revive(options.folderUri) : undefined, options?.sessionResource ? URI.revive(options.sessionResource) : undefined, options?.source, options?.folderUriIsDefault);
if (windows.length > 0) {
windows[0].focus();
}
Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/windows/electron-main/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface IWindowsMainService {
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): Promise<ICodeWindow[]>;
openExistingWindow(window: ICodeWindow, openConfig: IOpenConfiguration): void;

openAgentsWindow(openConfig: IOpenConfiguration, folderUri?: URI, sessionResource?: URI, source?: AgentsWindowOpenSource): Promise<ICodeWindow[]>;
openAgentsWindow(openConfig: IOpenConfiguration, folderUri?: URI, sessionResource?: URI, source?: AgentsWindowOpenSource, folderUriIsDefault?: boolean): Promise<ICodeWindow[]>;

sendToFocused(channel: string, ...args: unknown[]): void;
sendToOpeningWindow(channel: string, ...args: unknown[]): void;
Expand Down
9 changes: 3 additions & 6 deletions src/vs/platform/windows/electron-main/windowsMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,19 +292,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
this.handleChatRequest(openConfig, [window]);
}

async openAgentsWindow(openConfig: IOpenConfiguration, folderUri?: URI, sessionResource?: URI, source?: AgentsWindowOpenSource): Promise<ICodeWindow[]> {
async openAgentsWindow(openConfig: IOpenConfiguration, folderUri?: URI, sessionResource?: URI, source?: AgentsWindowOpenSource, folderUriIsDefault = false): Promise<ICodeWindow[]> {
this.logService.trace('windowsManager#openAgentsWindow');

// Open in a new browser window with the agent sessions workspace
const windows = await this.open(await this.ensureAgentsWindow(openConfig));

// Single IPC carrying the folder to pre-select and an optional existing-
// session resource to open. The handler in the agents window sequences
// them (folder → open session) so the session-open doesn't race the
// folder-resolve.
// Existing-session intent takes precedence over explicit or inferred workspace selection.
if (windows.length > 0) {
const openSource = source ?? (openConfig.cli.agents ? AgentsWindowOpenSource.CommandLine : AgentsWindowOpenSource.Unknown);
windows[0].sendWhenReady('vscode:selectAgentsFolder', CancellationToken.None, folderUri?.toJSON(), sessionResource?.toJSON(), openSource);
windows[0].sendWhenReady('vscode:selectAgentsFolder', CancellationToken.None, folderUri?.toJSON(), sessionResource?.toJSON(), openSource, folderUriIsDefault);
}

return windows;
Expand Down
6 changes: 3 additions & 3 deletions src/vs/sessions/browser/parts/chatGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ChatCompositeBar, IChatCompositeBarDelegate } from './chatCompositeBar.
import { type IRemoteHostUnavailableEmptyStateContent, RemoteHostUnavailableEmptyState } from './remoteHostUnavailableEmptyState.js';
import { SessionRemoteConnection } from './sessionRemoteConnection.js';
import { ISessionReadOnlyBannerContent, SessionReadOnlyBanner } from './sessionReadOnlyBanner.js';
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';

/**
* The data + callbacks a {@link ChatGroupView} needs from its owning
Expand Down Expand Up @@ -358,8 +358,8 @@ export class ChatGroupView extends Disposable implements ISerializableView {
return this._currentView.value?.submitInput() ?? Promise.resolve(false);
}

selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
this._currentView.value?.selectWorkspace(folderUri, options);
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
return this._currentView.value?.selectWorkspace(folderUri, options) ?? 'notReady';
}

selectNoWorkspace(): void {
Expand Down
6 changes: 3 additions & 3 deletions src/vs/sessions/browser/parts/chatGroupsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { agentsPanelBorder } from '../../common/theme.js';
import { IChat } from '../../services/sessions/common/session.js';
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
import { IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
import { IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';
import { ChatGroupView, IChatGroupContext } from './chatGroupView.js';
import { ChatDropZone, ChatGroupDropTarget, IChatGroupDropTargetDelegate } from './chatGroupDropTarget.js';
import { IDraggedSessionChat, isSessionChatDrag } from '../dnd.js';
Expand Down Expand Up @@ -799,8 +799,8 @@ export class ChatGroupsView extends Themable {
return this._activeGroup?.view.submitInput() ?? Promise.resolve(false);
}

selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
this._activeGroup?.view.selectWorkspace(folderUri, options);
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
return this._activeGroup?.view.selectWorkspace(folderUri, options) ?? 'notReady';
}

selectNoWorkspace(): void {
Expand Down
15 changes: 10 additions & 5 deletions src/vs/sessions/browser/parts/chatView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { URI } from '../../../base/common/uri.js';
import { defaultProgressBarStyles } from '../../../platform/theme/browser/defaultStyles.js';
import { IProgressScope, ScopedProgressIndicator } from '../../../workbench/services/progress/browser/progressIndicator.js';
import { IChat, ISession } from '../../services/sessions/common/session.js';
import { WorkspaceSelectionOrigin } from '../../common/workspaceSelection.js';

/**
* Discriminates between concrete {@link AbstractChatView} subclasses without
Expand All @@ -29,8 +30,13 @@ export interface IChatViewOptions {
export interface ISelectWorkspaceOptions {
readonly providerId?: string;
readonly preferDevContainer?: boolean;
readonly selectionOrigin?: WorkspaceSelectionOrigin;
/** Only replace an automatic default in a fresh, empty composer. */
readonly isDefault?: boolean;
}

export type WorkspaceSelectionResult = 'applied' | 'notReady' | 'preserved';

/**
* Base class for a view that lives inside the {@link SessionsPart} internal grid.
* Each instance occupies a single grid leaf. Subclasses populate {@link element}
Expand Down Expand Up @@ -90,12 +96,11 @@ export abstract class AbstractChatView extends Disposable implements ISerializab
}

/**
* Select a workspace folder in this view's workspace picker. The default
* implementation is a no-op; subclasses that host a workspace picker
* (e.g. `NewChatView`) override this to forward the selection.
* Select a workspace folder, acknowledging application or preservation of an existing choice.
* Views without a ready workspace picker return notReady.
*/
selectWorkspace(_folderUri: URI, _options?: ISelectWorkspaceOptions): void {
// no-op by default
selectWorkspace(_folderUri: URI, _options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
return 'notReady';
}

selectNoWorkspace(): void {
Expand Down
6 changes: 3 additions & 3 deletions src/vs/sessions/browser/parts/sessionView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { IContextKey, IContextKeyService } from '../../../platform/contextkey/co
import { IThemeService } from '../../../platform/theme/common/themeService.js';
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
import { IChat } from '../../services/sessions/common/session.js';
import { AbstractChatView, IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
import { AbstractChatView, IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';
import { ChatGroupsView } from './chatGroupsView.js';
import { SessionHeader, SessionViewFloatingToolbar } from './sessionHeader.js';
import { ISessionContext, SessionContext } from '../../services/sessions/browser/sessionContext.js';
Expand Down Expand Up @@ -282,9 +282,9 @@ export class SessionView extends Disposable implements ISerializableView {
return this._currentSession;
}

selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
const standaloneView = this._standaloneView.value;
standaloneView ? standaloneView.selectWorkspace(folderUri, options) : this._groupsView.selectWorkspace(folderUri, options);
return standaloneView ? standaloneView.selectWorkspace(folderUri, options) : this._groupsView.selectWorkspace(folderUri, options);
}

selectNoWorkspace(): void {
Expand Down
36 changes: 36 additions & 0 deletions src/vs/sessions/common/workspaceSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { URI } from '../../base/common/uri.js';

export const enum WorkspaceSelectionOrigin {
None = 'none',
CheckedWorkspace = 'checkedWorkspace',
AgentsRecent = 'agentsRecent',
VSCodeRecent = 'vscodeRecent',
VSCodeWorkspace = 'vscodeWorkspace',
ExistingSessions = 'existingSessions',
WindowOpen = 'windowOpen',
WindowContext = 'windowContext',
RestoredDraft = 'restoredDraft',
SessionSync = 'sessionSync',
Programmatic = 'programmatic',
User = 'user',
}

export type WorkspaceHistoryLoadState = 'loading' | 'loaded' | 'error';
export type WorkspaceSessionFallbackState = 'idle' | 'pending' | 'completed' | 'error' | 'disabled';
export type WorkspaceArgumentKind = 'none' | 'local' | 'devContainer' | 'remote' | 'other';

/** Selection and lookup state at the instant it is read, not a guarantee that a session can run. */
export interface IWorkspaceSelectionSnapshot {
/** For local comparisons only; never include this URI in telemetry. */
readonly folderUri: URI | undefined;
readonly origin: WorkspaceSelectionOrigin;
readonly state: 'none' | 'noWorkspace' | 'selected' | 'unresolved';
readonly historyState: WorkspaceHistoryLoadState;
readonly sessionFallbackState: WorkspaceSessionFallbackState;
readonly registeredProviderCount: number;
}
15 changes: 15 additions & 0 deletions src/vs/sessions/contrib/chat/browser/agentsWindowOpenIntent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Schemas } from '../../../../base/common/network.js';
import { URI } from '../../../../base/common/uri.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { DevContainerAgentHostEnabledSettingId } from '../../../common/devContainerAgentHostService.js';
import { WorkspaceArgumentKind } from '../../../common/workspaceSelection.js';

const DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX = 'dev-container+';

Expand All @@ -16,6 +17,20 @@ export interface IAgentsWindowFolderIntent {
readonly preferDevContainer: boolean;
}

/** Classifies the original argument without exposing its path or remote authority. */
export function getAgentsWindowWorkspaceArgumentKind(workspaceUri: URI | undefined): WorkspaceArgumentKind {
if (!workspaceUri) {
return 'none';
}
if (workspaceUri.scheme === Schemas.file) {
return 'local';
}
if (workspaceUri.scheme === Schemas.vscodeRemote) {
return workspaceUri.authority.startsWith(DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX) ? 'devContainer' : 'remote';
}
return 'other';
}

export function resolveAgentsWindowFolderIntent(workspaceUri: URI | undefined, configurationService: IConfigurationService): IAgentsWindowFolderIntent {
if (workspaceUri?.scheme === Schemas.file) {
return { folderUri: workspaceUri, preferDevContainer: false };
Expand Down
Loading