Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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