Skip to content

Commit 5559183

Browse files
authored
Merge pull request #335480 from microsoft/agents/workspace-preselection-telemetry
sessions: improve workspace preselection and telemetry
2 parents 0ebeee3 + e083d5f commit 5559183

44 files changed

Lines changed: 2991 additions & 210 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/vs/platform/native/common/native.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ export interface INativeZipOptions {
8282

8383
export interface IOpenAgentsWindowOptions {
8484
readonly folderUri?: UriComponents;
85+
/** Use the invoking editor's folder only for a fresh composer, without replacing an existing session or user choice. */
86+
readonly folderUriIsDefault?: boolean;
8587
readonly sessionResource?: UriComponents;
8688
readonly source?: AgentsWindowOpenSource;
8789
}

src/vs/platform/native/electron-main/nativeHostMainService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
320320
context: OpenContext.API,
321321
contextWindowId: windowId,
322322
cli: this.environmentMainService.args,
323-
}, options?.folderUri ? URI.revive(options.folderUri) : undefined, options?.sessionResource ? URI.revive(options.sessionResource) : undefined, options?.source);
323+
}, options?.folderUri ? URI.revive(options.folderUri) : undefined, options?.sessionResource ? URI.revive(options.sessionResource) : undefined, options?.source, options?.folderUriIsDefault);
324324
if (windows.length > 0) {
325325
windows[0].focus();
326326
}

src/vs/platform/windows/electron-main/windows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export interface IWindowsMainService {
4141
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): Promise<ICodeWindow[]>;
4242
openExistingWindow(window: ICodeWindow, openConfig: IOpenConfiguration): void;
4343

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

4646
sendToFocused(channel: string, ...args: unknown[]): void;
4747
sendToOpeningWindow(channel: string, ...args: unknown[]): void;

src/vs/platform/windows/electron-main/windowsMainService.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,19 +292,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
292292
this.handleChatRequest(openConfig, [window]);
293293
}
294294

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

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

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

310307
return windows;

src/vs/sessions/browser/parts/chatGroupView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { ChatCompositeBar, IChatCompositeBarDelegate } from './chatCompositeBar.
2525
import { type IRemoteHostUnavailableEmptyStateContent, RemoteHostUnavailableEmptyState } from './remoteHostUnavailableEmptyState.js';
2626
import { SessionRemoteConnection } from './sessionRemoteConnection.js';
2727
import { ISessionReadOnlyBannerContent, SessionReadOnlyBanner } from './sessionReadOnlyBanner.js';
28-
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
28+
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';
2929

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

361-
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
362-
this._currentView.value?.selectWorkspace(folderUri, options);
361+
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
362+
return this._currentView.value?.selectWorkspace(folderUri, options) ?? 'notReady';
363363
}
364364

365365
selectNoWorkspace(): void {

src/vs/sessions/browser/parts/chatGroupsView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { agentsPanelBorder } from '../../common/theme.js';
1919
import { IChat } from '../../services/sessions/common/session.js';
2020
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
2121
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
22-
import { IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
22+
import { IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';
2323
import { ChatGroupView, IChatGroupContext } from './chatGroupView.js';
2424
import { ChatDropZone, ChatGroupDropTarget, IChatGroupDropTargetDelegate } from './chatGroupDropTarget.js';
2525
import { IDraggedSessionChat, isSessionChatDrag } from '../dnd.js';
@@ -799,8 +799,8 @@ export class ChatGroupsView extends Themable {
799799
return this._activeGroup?.view.submitInput() ?? Promise.resolve(false);
800800
}
801801

802-
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
803-
this._activeGroup?.view.selectWorkspace(folderUri, options);
802+
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
803+
return this._activeGroup?.view.selectWorkspace(folderUri, options) ?? 'notReady';
804804
}
805805

806806
selectNoWorkspace(): void {

src/vs/sessions/browser/parts/chatView.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { URI } from '../../../base/common/uri.js';
1313
import { defaultProgressBarStyles } from '../../../platform/theme/browser/defaultStyles.js';
1414
import { IProgressScope, ScopedProgressIndicator } from '../../../workbench/services/progress/browser/progressIndicator.js';
1515
import { IChat, ISession } from '../../services/sessions/common/session.js';
16+
import { WorkspaceSelectionOrigin } from '../../common/workspaceSelection.js';
1617

1718
/**
1819
* Discriminates between concrete {@link AbstractChatView} subclasses without
@@ -29,8 +30,13 @@ export interface IChatViewOptions {
2930
export interface ISelectWorkspaceOptions {
3031
readonly providerId?: string;
3132
readonly preferDevContainer?: boolean;
33+
readonly selectionOrigin?: WorkspaceSelectionOrigin;
34+
/** Only replace an automatic default in a fresh, empty composer. */
35+
readonly isDefault?: boolean;
3236
}
3337

38+
export type WorkspaceSelectionResult = 'applied' | 'notReady' | 'preserved';
39+
3440
/**
3541
* Base class for a view that lives inside the {@link SessionsPart} internal grid.
3642
* Each instance occupies a single grid leaf. Subclasses populate {@link element}
@@ -90,12 +96,11 @@ export abstract class AbstractChatView extends Disposable implements ISerializab
9096
}
9197

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

101106
selectNoWorkspace(): void {

src/vs/sessions/browser/parts/sessionView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IContextKey, IContextKeyService } from '../../../platform/contextkey/co
1515
import { IThemeService } from '../../../platform/theme/common/themeService.js';
1616
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
1717
import { IChat } from '../../services/sessions/common/session.js';
18-
import { AbstractChatView, IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';
18+
import { AbstractChatView, IChatViewOptions, ISelectWorkspaceOptions, WorkspaceSelectionResult } from './chatView.js';
1919
import { ChatGroupsView } from './chatGroupsView.js';
2020
import { SessionHeader, SessionViewFloatingToolbar } from './sessionHeader.js';
2121
import { ISessionContext, SessionContext } from '../../services/sessions/browser/sessionContext.js';
@@ -282,9 +282,9 @@ export class SessionView extends Disposable implements ISerializableView {
282282
return this._currentSession;
283283
}
284284

285-
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
285+
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): WorkspaceSelectionResult {
286286
const standaloneView = this._standaloneView.value;
287-
standaloneView ? standaloneView.selectWorkspace(folderUri, options) : this._groupsView.selectWorkspace(folderUri, options);
287+
return standaloneView ? standaloneView.selectWorkspace(folderUri, options) : this._groupsView.selectWorkspace(folderUri, options);
288288
}
289289

290290
selectNoWorkspace(): void {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { URI } from '../../base/common/uri.js';
7+
8+
export const enum WorkspaceSelectionOrigin {
9+
None = 'none',
10+
CheckedWorkspace = 'checkedWorkspace',
11+
AgentsRecent = 'agentsRecent',
12+
VSCodeRecent = 'vscodeRecent',
13+
VSCodeWorkspace = 'vscodeWorkspace',
14+
ExistingSessions = 'existingSessions',
15+
WindowOpen = 'windowOpen',
16+
WindowContext = 'windowContext',
17+
RestoredDraft = 'restoredDraft',
18+
SessionSync = 'sessionSync',
19+
Programmatic = 'programmatic',
20+
User = 'user',
21+
}
22+
23+
export type WorkspaceHistoryLoadState = 'loading' | 'loaded' | 'error';
24+
export type WorkspaceSessionFallbackState = 'idle' | 'pending' | 'completed' | 'error' | 'disabled';
25+
export type WorkspaceArgumentKind = 'none' | 'local' | 'devContainer' | 'remote' | 'other';
26+
27+
/** Selection and lookup state at the instant it is read, not a guarantee that a session can run. */
28+
export interface IWorkspaceSelectionSnapshot {
29+
/** For local comparisons only; never include this URI in telemetry. */
30+
readonly folderUri: URI | undefined;
31+
readonly origin: WorkspaceSelectionOrigin;
32+
readonly state: 'none' | 'noWorkspace' | 'selected' | 'unresolved';
33+
readonly historyState: WorkspaceHistoryLoadState;
34+
readonly sessionFallbackState: WorkspaceSessionFallbackState;
35+
readonly registeredProviderCount: number;
36+
}

src/vs/sessions/contrib/chat/browser/agentsWindowOpenIntent.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Schemas } from '../../../../base/common/network.js';
88
import { URI } from '../../../../base/common/uri.js';
99
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
1010
import { DevContainerAgentHostEnabledSettingId } from '../../../common/devContainerAgentHostService.js';
11+
import { WorkspaceArgumentKind } from '../../../common/workspaceSelection.js';
1112

1213
const DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX = 'dev-container+';
1314

@@ -16,6 +17,20 @@ export interface IAgentsWindowFolderIntent {
1617
readonly preferDevContainer: boolean;
1718
}
1819

20+
/** Classifies the original argument without exposing its path or remote authority. */
21+
export function getAgentsWindowWorkspaceArgumentKind(workspaceUri: URI | undefined): WorkspaceArgumentKind {
22+
if (!workspaceUri) {
23+
return 'none';
24+
}
25+
if (workspaceUri.scheme === Schemas.file) {
26+
return 'local';
27+
}
28+
if (workspaceUri.scheme === Schemas.vscodeRemote) {
29+
return workspaceUri.authority.startsWith(DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX) ? 'devContainer' : 'remote';
30+
}
31+
return 'other';
32+
}
33+
1934
export function resolveAgentsWindowFolderIntent(workspaceUri: URI | undefined, configurationService: IConfigurationService): IAgentsWindowFolderIntent {
2035
if (workspaceUri?.scheme === Schemas.file) {
2136
return { folderUri: workspaceUri, preferDevContainer: false };

0 commit comments

Comments
 (0)