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
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 } from './chatView.js';
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions } from './chatView.js';

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

selectWorkspace(folderUri: URI, providerId?: string): void {
this._currentView.value?.selectWorkspace(folderUri, providerId);
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
this._currentView.value?.selectWorkspace(folderUri, options);
}

prefillInput(text: string): 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 } from './chatView.js';
import { IChatViewOptions, ISelectWorkspaceOptions } 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 @@ -805,8 +805,8 @@ export class ChatGroupsView extends Themable {
return this._activeGroup?.view.submitInput() ?? Promise.resolve(false);
}

selectWorkspace(folderUri: URI, providerId?: string): void {
this._activeGroup?.view.selectWorkspace(folderUri, providerId);
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
this._activeGroup?.view.selectWorkspace(folderUri, options);
}

prefillInput(text: string): void {
Expand Down
7 changes: 6 additions & 1 deletion src/vs/sessions/browser/parts/chatView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export type ChatViewKind = 'newSession' | 'newChatInSession' | 'chat';
export interface IChatViewOptions {
}

export interface ISelectWorkspaceOptions {
readonly providerId?: string;
readonly preferDevContainer?: boolean;
}

/**
* 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 @@ -81,7 +86,7 @@ export abstract class AbstractChatView extends Disposable implements ISerializab
* implementation is a no-op; subclasses that host a workspace picker
* (e.g. `NewChatView`) override this to forward the selection.
*/
selectWorkspace(_folderUri: URI, _providerId?: string): void {
selectWorkspace(_folderUri: URI, _options?: ISelectWorkspaceOptions): void {
// no-op by default
}

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 } from './chatView.js';
import { AbstractChatView, IChatViewOptions, ISelectWorkspaceOptions } 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 @@ -293,9 +293,9 @@ export class SessionView extends Disposable implements ISerializableView {
return this._currentSession;
}

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

/** Opens the given chat in a group beside the active one ("open to the side"). */
Expand Down
2 changes: 2 additions & 0 deletions src/vs/sessions/common/agentHostSessionsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ export interface IAgentHostSessionsProvider extends ISessionsProvider {
isDevContainerEnabled?(sessionId: string): boolean;
/** Set whether this draft should run on a Dev Container Agent Host. */
setDevContainerEnabled?(sessionId: string, enabled: boolean): void;
/** Enable Dev Container execution once availability resolves for this draft. */
preferDevContainer?(sessionId: string): void;

// -- Dynamic Session Config --

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

import { decodeHex } from '../../../../base/common/buffer.js';
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';

const DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX = 'dev-container+';

export interface IAgentsWindowFolderIntent {
readonly folderUri: URI | undefined;
readonly preferDevContainer: boolean;
}

export function resolveAgentsWindowFolderIntent(workspaceUri: URI | undefined, configurationService: IConfigurationService): IAgentsWindowFolderIntent {
if (workspaceUri?.scheme === Schemas.file) {
return { folderUri: workspaceUri, preferDevContainer: false };
}
if (workspaceUri?.scheme !== Schemas.vscodeRemote || !workspaceUri.authority.startsWith(DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX)) {
return { folderUri: undefined, preferDevContainer: false };
}
try {
return {
folderUri: URI.file(decodeHex(workspaceUri.authority.slice(DEV_CONTAINER_REMOTE_AUTHORITY_PREFIX.length)).toString()),
preferDevContainer: configurationService.getValue<boolean>(DevContainerAgentHostEnabledSettingId) === true,
};
} catch (error) {
if (error instanceof SyntaxError) {
return { folderUri: undefined, preferDevContainer: false };
}
throw error;
}
}
6 changes: 3 additions & 3 deletions src/vs/sessions/contrib/chat/browser/chatView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { IChatModel } from '../../../../workbench/contrib/chat/common/model/chat
import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js';
import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js';
import { IChatSessionsService, localChatSessionType } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
import { AbstractChatView, ChatViewKind, IChatViewOptions } from '../../../browser/parts/chatView.js';
import { AbstractChatView, ChatViewKind, IChatViewOptions, ISelectWorkspaceOptions } from '../../../browser/parts/chatView.js';
import { ChatInteractivity, getSessionStatusMessage, IChat, isActiveSessionStatus, ISession, SessionStatus } from '../../../services/sessions/common/session.js';
import { IChatViewFactory } from '../../../services/chatView/browser/chatViewFactory.js';
import { NewChatWidget } from './newChatWidget.js';
Expand Down Expand Up @@ -104,9 +104,9 @@ export class NewChatView extends AbstractChatView {
this._widget.focusInput();
}

override selectWorkspace(folderUri: URI, providerId?: string): void {
override selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
if (this._widget instanceof NewChatWidget) {
this._widget.selectWorkspace(folderUri, providerId);
this._widget.selectWorkspace(folderUri, options);
}
}

Expand Down
29 changes: 26 additions & 3 deletions src/vs/sessions/contrib/chat/browser/newChatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import { localize } from '../../../../nls.js';
import { IActiveSession, ICreateNewSessionOptions, ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
import { ISession, SESSION_WORKSPACE_GROUP_GITHUB } from '../../../services/sessions/common/session.js';
import { IOpenNewSessionResult, ISessionsService } from '../../../services/sessions/browser/sessionsService.js';
import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js';
import { isAllowSignedOutWhenUsableEnabled, shouldShowGitHubWorkspaceGroupSignIn } from '../../../browser/sessionsAuthGate.js';
import { AGENTIC_SIGN_IN_COMMAND_ID } from '../../../common/sessionCommands.js';
import { isAgentHostProvider } from '../../../common/agentHostSessionsProvider.js';
import { IAquariumService, IMountedToggleHandle } from '../../aquarium/browser/aquariumOverlay.js';
import { IWorkspacePickerNoWorkspaceOption, IWorkspacePickerTrigger, WorkspacePicker } from './sessionWorkspacePicker.js';
import { WebWorkspacePicker } from './webWorkspacePicker.js';
Expand All @@ -35,7 +37,7 @@ import { NewChatInputWidget } from './newChatInput.js';
import { NoAgentHostEmptyState } from './noAgentHostEmptyState.js';
import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
import { IAgentHostFilterService } from '../../../services/agentHostFilter/common/agentHostFilter.js';
import { IChatViewOptions } from '../../../browser/parts/chatView.js';
import { IChatViewOptions, ISelectWorkspaceOptions } from '../../../browser/parts/chatView.js';
import { SessionWorkspacePickerVisibleContext } from '../../../common/contextkeys.js';
import { AGENT_FEEDBACK_NEW_SESSION_RESOURCE, AgentFeedbackState, IAgentFeedback, IAgentFeedbackService } from '../../agentFeedback/browser/agentFeedbackService.js';
import { buildNewSessionPrompt } from '../../agentFeedback/browser/agentFeedbackAttachmentEntry.js';
Expand Down Expand Up @@ -70,6 +72,7 @@ export class NewChatWidget extends Disposable {
/** Recreates the draft once a better/late-registering provider can serve the folder (see {@link _createNewSession}). */
private readonly _pendingPreferredUpgrade = new MutableDisposable<IDisposable>();
private readonly _newSessionCreation = new MutableDisposable<IDisposable>();
private _preferredDevContainerFolderUri: URI | undefined;

/**
* The currently mounted no-agent-host empty state, if any. Set by
Expand Down Expand Up @@ -114,6 +117,7 @@ export class NewChatWidget extends Disposable {
@ILogService private readonly logService: ILogService,
@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,
@ISessionsService private readonly sessionsService: ISessionsService,
@ISessionsProvidersService private readonly sessionsProvidersService: ISessionsProvidersService,
@IAquariumService private readonly aquariumService: IAquariumService,
@IAgentHostFilterService private readonly agentHostFilterService: IAgentHostFilterService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
Expand Down Expand Up @@ -618,13 +622,16 @@ export class NewChatWidget extends Disposable {
} else {
return result;
}
this._applyPreferredDevContainer(result.session, folderUri);
if (result.trustDeclined) {
this._preferredDevContainerFolderUri = undefined;
// The user explicitly declined trust: don't schedule a retry, which
// would silently recreate (and possibly re-prompt) the draft once a
// provider registers/changes without any further user action.
this._pendingPreferredUpgrade.clear();
return result;
}

// Keep the draft in sync with late-registering providers. Agent hosts
// connect lazily, so there is no timeout — the listener lives until the
// draft is sent or replaced. We watch when:
Expand All @@ -640,6 +647,18 @@ export class NewChatWidget extends Disposable {
return result;
}

private _applyPreferredDevContainer(session: ISession | undefined, folderUri: URI): void {
if (!session || !this._preferredDevContainerFolderUri || !this.uriIdentityService.extUri.isEqual(this._preferredDevContainerFolderUri, folderUri)) {
return;
}
const provider = this.sessionsProvidersService.getProvider(session.providerId);
if (!provider || !isAgentHostProvider(provider) || !provider.preferDevContainer) {
return;
}
provider.preferDevContainer(session.sessionId);
this._preferredDevContainerFolderUri = undefined;
}

private async _createSessionNow(folderUri: URI, userPick: IPreferredSessionType | undefined, token: CancellationToken): Promise<IOpenNewSessionResult> {
// Prefer the user's explicit pick when its provider can serve the
// folder; otherwise fall back to the preferred (first) session type.
Expand Down Expand Up @@ -997,6 +1016,9 @@ export class NewChatWidget extends Disposable {
private async _onWorkspaceSelected(folderUri: URI | undefined): Promise<void> {
// Cancel any in-flight upgrade for a previous selection.
this._pendingPreferredUpgrade.clear();
if (!folderUri || !this._preferredDevContainerFolderUri || !this.uriIdentityService.extUri.isEqual(this._preferredDevContainerFolderUri, folderUri)) {
this._preferredDevContainerFolderUri = undefined;
}
const currentFolderUri = this._session.get()?.workspace.get()?.folders[0]?.root;
const refreshingPromptOptions = !!currentFolderUri
&& (!folderUri || !this.uriIdentityService.extUri.isEqual(currentFolderUri, folderUri))
Expand Down Expand Up @@ -1054,8 +1076,9 @@ export class NewChatWidget extends Disposable {
this._newChatInput.attach(uris);
}

selectWorkspace(folderUri: URI, providerId?: string): void {
this._workspacePicker.setSelectedWorkspace(folderUri, { providerId });
selectWorkspace(folderUri: URI, options?: ISelectWorkspaceOptions): void {
this._preferredDevContainerFolderUri = options?.preferDevContainer ? folderUri : undefined;
this._workspacePicker.setSelectedWorkspace(folderUri, { providerId: options?.providerId });
}
}

Expand Down
Loading