Conversation
…nd update item structure
…in SessionsManagementService
…-02-26T22-26-52 # Conflicts: # src/vs/sessions/contrib/chat/browser/newChatViewPane.ts
There was a problem hiding this comment.
Pull request overview
This PR adds a mode picker to the Sessions app, allowing users to select between the default "Agent" mode and any custom agents when starting a new local/Background session. The mode picker filters custom agents based on the session type's target and integrates with the existing chat mode infrastructure.
Changes:
- Adds a new
ModePickerwidget for selecting chat modes in the Sessions app - Extends
INewSessioninterface to track the selected mode viamodeIdproperty - Updates
SessionsManagementServiceto resolve mode information and pass it to chat requests
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/vs/sessions/contrib/chat/browser/modePicker.ts | New self-contained mode picker widget that displays Agent and filtered custom modes for local sessions |
| src/vs/sessions/contrib/chat/browser/newChatViewPane.ts | Integrates ModePicker into the new chat widget, shows/hides based on session type, syncs mode changes to session |
| src/vs/sessions/contrib/chat/browser/newSession.ts | Adds modeId property to INewSession interface with implementations in LocalNewSession (stores) and RemoteNewSession (no-op) |
| src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts | Resolves mode from session's modeId, constructs modeInstructions with tool references, sets mode on input model for UI consistency |
Comments suppressed due to low confidence (1)
src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts:309
- Non-null assertion operator (!) is used on resolvedMode at line 309 without a null check. While rawModeInstructions being truthy implies resolvedMode exists, this assumption is fragile. If the conditional logic changes, this could cause a runtime error. Consider using optional chaining instead: resolvedMode?.name.get().
name: resolvedMode!.name.get(),
| private _updateTriggerLabel(): void { | ||
| if (!this._triggerElement) { | ||
| return; | ||
| } | ||
|
|
||
| dom.clearNode(this._triggerElement); | ||
|
|
||
| const icon = this._selectedMode.icon.get(); | ||
| if (icon) { | ||
| dom.append(this._triggerElement, renderIcon(icon)); | ||
| } | ||
|
|
||
| const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); | ||
| labelSpan.textContent = this._selectedMode.label.get(); | ||
| dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); | ||
| } |
There was a problem hiding this comment.
The ModePicker does not visually indicate when no custom modes are available, unlike the CloudModelPicker which adds a 'disabled' class (see modelPicker.ts line 202). While the picker will show only the default Agent mode, it would be better UX to add visual feedback when the picker is effectively showing only one option. Consider adding disabled styling when modes.length is 1.
| if (modes.length === 0) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The early return when modes.length is 0 prevents the picker from being shown, but this condition should never be true since _getAvailableModes always includes at least ChatMode.Agent (line 109). This check is defensive but may hide bugs where the default Agent mode is unexpectedly missing. Consider removing this check or adding logging to detect if this condition ever occurs.
| if (modes.length === 0) { | |
| return; | |
| } |
| } | ||
|
|
||
| setModeId(_modeId: string | undefined): void { | ||
| // No-op for remote sessions |
There was a problem hiding this comment.
The setModeId method is a no-op for RemoteNewSession (line 241-243), but there's no documentation explaining why remote sessions don't support mode selection. Consider adding a comment explaining the reasoning, such as whether mode selection is not supported by the remote session provider or if modes are determined server-side.
| // No-op for remote sessions | |
| // Intentionally a no-op: remote sessions do not support client-side mode selection. | |
| // Any mode or behavior differences are determined by the remote session provider/server. |
|
|
||
| const trigger = dom.append(slot, dom.$('a.action-label')); | ||
| trigger.tabIndex = 0; | ||
| trigger.role = 'button'; |
There was a problem hiding this comment.
The trigger element has tabIndex and role set for keyboard accessibility (lines 73-74), but there's no aria-label or aria-describedby attribute to provide context for screen reader users. Consider adding an aria-label such as "Select chat mode" to improve accessibility, similar to how other pickers in the codebase provide accessible labels.
| trigger.role = 'button'; | |
| trigger.role = 'button'; | |
| trigger.setAttribute('aria-label', localize('sessions.modePicker.ariaLabel', "Select chat mode")); |
| @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, | ||
| @ICommandService private readonly commandService: ICommandService, | ||
| ) { | ||
| super(); |
There was a problem hiding this comment.
The ModePicker does not listen to changes in available modes. If custom agents are added, removed, or modified after the picker is created, the picker will not update its list of available modes until the user reopens the picker. Consider adding a listener to chatModeService.onDidChangeChatModes to refresh the available modes dynamically.
| super(); | |
| super(); | |
| this._register(this.chatModeService.onDidChangeChatModes(() => { | |
| // Refresh the trigger label when available chat modes change | |
| if (this._triggerElement) { | |
| this._updateTriggerLabel(); | |
| } | |
| })); |
| // Set the selected mode on the input model so the mode picker reflects it | ||
| if (session.modeId) { | ||
| const resolvedMode = this.chatModeService.findModeById(session.modeId); | ||
| if (resolvedMode) { | ||
| model.inputModel.setState({ | ||
| mode: { id: resolvedMode.id, kind: resolvedMode.kind } | ||
| }); | ||
| } |
There was a problem hiding this comment.
The mode resolution logic is duplicated: it's first resolved at lines 302-313 to build sendOptions, and then resolved again at lines 360-365 to set the input model state. This could lead to inconsistencies if the mode is deleted between the two calls. Consider consolidating this logic by reusing the resolvedMode variable from the first resolution, or move the input model state setting earlier to avoid redundant lookups.
This issue also appears on line 309 of the same file.
| private _getAvailableModes(): IChatMode[] { | ||
| const customAgentTarget = this.chatSessionsService.getCustomAgentTargetForSessionType(AgentSessionProviders.Background); | ||
| const effectiveTarget = customAgentTarget && customAgentTarget !== Target.Undefined ? customAgentTarget : Target.GitHubCopilot; | ||
| const modes = this.chatModeService.getModes(); | ||
|
|
||
| // Always include the default Agent mode | ||
| const result: IChatMode[] = [ChatMode.Agent]; | ||
|
|
||
| // Add custom modes matching the target | ||
| for (const mode of modes.custom) { | ||
| const target = mode.target.get(); | ||
| if (target === effectiveTarget || target === Target.Undefined) { | ||
| result.push(mode); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } |
There was a problem hiding this comment.
The mode filtering logic does not check the visibility.userInvocable property of custom modes. According to the codebase conventions (see chatModes.ts line 116 and line 162), modes with userInvocable set to false should not be shown in user-facing UI. The picker should filter out modes where visibility.userInvocable is false to prevent users from selecting modes that are only intended for agent invocation.
Mode picker in Sessions app