-
Notifications
You must be signed in to change notification settings - Fork 37.3k
Experimenting welcome and picker #286854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Experimenting welcome and picker #286854
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces two main experimental features: a searchable picker UI component for chat session option groups with many items (e.g., repository selection), and an agent sessions start page that provides a welcome/landing page for agent sessions.
Key changes:
- Adds a
searchableproperty to option groups in the Chat Sessions Provider API to enable QuickPick-based selection for large item lists - Implements
SearchableOptionPickerActionItemas an alternative to the dropdown-basedChatSessionPickerActionItem - Creates a new "Agent Sessions Start Page" editor pane with session history, search, and an embedded chat input widget
- Modifies the logic for showing session option pickers to display them when items are available, not just when a value is already set
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts | Adds searchable property to ChatSessionProviderOptionGroup interface for QuickPick UI |
| src/vs/workbench/contrib/chat/common/chatSessionsService.ts | Adds searchable property to internal option group interface |
| src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts | Updates picker creation logic to support searchable option groups and show pickers when items are available |
| src/vs/workbench/contrib/chat/browser/chatSessions/searchableOptionPickerActionItem.ts | New action item component implementing QuickPick-based option selection for searchable groups |
| src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts | Adds hardcoded default icon and label for repository use case |
| src/vs/workbench/contrib/chat/browser/chatSessions/media/chatSessionPickerActionItem.css | Adds styling for searchable picker with commented-out experimental rules |
| src/vs/workbench/contrib/chat/browser/chat.contribution.ts | Registers the new agent sessions start page contribution |
| src/vs/workbench/contrib/chat/browser/agentSessionsStartPage/*.ts | New editor pane, input, and contribution files for the agent sessions start page |
| src/vs/workbench/contrib/chat/browser/agentSessionsStartPage/media/agentSessionsStartPage.css | Complete styling for the start page UI with collapsible sections and session cards |
| src/vs/platform/actionWidget/browser/actionWidget.css | Increases global min-width for all action widgets from 100px to 160px |
| src/vs/base/common/network.ts | Adds new URI scheme for the agent sessions start page |
Comments suppressed due to low confidence (1)
src/vs/workbench/contrib/chat/browser/agentSessionsStartPage/agentSessionsStartPage.ts:116
- The
renderPage()method referencesthis.tabsContainerwhich is never initialized (it's declared at line 42 but never assigned a value). This will cause the code to attempt operations onundefined. Either the tabs functionality should be fully implemented, or this dead code should be removed.
private renderPage(): void {
// Update dynamic parts only (tabs and sessions)
if (this.tabsContainer) {
dom.clearNode(this.tabsContainer);
this.renderTabs(this.tabsContainer);
}
this.renderSessions();
}
| /* .chat-searchable-option-picker-item .chat-session-option-picker > .codicon:first-child { | ||
| margin-right: 0px; | ||
| } */ |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out CSS rules should either be removed or have a clear explanation for why they're being kept. The commented rules on lines 51-53 appear to be styling experiments that should either be implemented or deleted.
| /* .chat-searchable-option-picker-item .chat-session-option-picker > .codicon:first-child { | |
| margin-right: 0px; | |
| } */ |
| // const tabsConfig: Array<{ id: 'agents' | 'code' | 'learn'; label: string }> = [ | ||
| // { id: 'agents', label: 'AGENTS' }, | ||
| // { id: 'code', label: 'CODE' }, | ||
| // { id: 'learn', label: 'LEARN' }, | ||
| // ]; | ||
|
|
||
| // for (const tab of tabsConfig) { | ||
| // const tabButton = dom.append(container, dom.$('button.tab')); | ||
| // tabButton.textContent = tab.label; | ||
| // if (tab.id === this.selectedTab) { | ||
| // tabButton.classList.add('active'); | ||
| // } | ||
| // tabButton.onclick = () => { | ||
| // this.selectedTab = tab.id; | ||
| // // Re-render tabs to update active state | ||
| // dom.clearNode(container); | ||
| // this.renderTabs(container); | ||
| // }; | ||
| // } |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extensive commented-out code (lines 119-137 and related fields) should be removed unless there's a specific reason to keep it. If this represents future work for implementing tabs functionality, it should be tracked in an issue rather than left in the codebase. The commented field at line 48 and related code in setInput (lines 432-434) should also be removed.
This issue also appears in the following locations of the same file:
- line 109
| // const tabsConfig: Array<{ id: 'agents' | 'code' | 'learn'; label: string }> = [ | |
| // { id: 'agents', label: 'AGENTS' }, | |
| // { id: 'code', label: 'CODE' }, | |
| // { id: 'learn', label: 'LEARN' }, | |
| // ]; | |
| // for (const tab of tabsConfig) { | |
| // const tabButton = dom.append(container, dom.$('button.tab')); | |
| // tabButton.textContent = tab.label; | |
| // if (tab.id === this.selectedTab) { | |
| // tabButton.classList.add('active'); | |
| // } | |
| // tabButton.onclick = () => { | |
| // this.selectedTab = tab.id; | |
| // // Re-render tabs to update active state | |
| // dom.clearNode(container); | |
| // this.renderTabs(container); | |
| // }; | |
| // } |
| export interface AgentSessionsStartEditorOptions extends IEditorOptions { | ||
| selectedTab?: 'agents' | 'code' | 'learn'; | ||
| } |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The selectedTab property is defined in the input but never actually used in any meaningful way. The commented code at line 48 and lines 432-434 suggests this was intended to be implemented but wasn't. Either fully implement tab functionality or remove the unused selectedTab property from AgentSessionsStartEditorOptions and AgentSessionsStartInput to avoid confusion.
| const searchableDelegate: ISearchableOptionPickerDelegate = { | ||
| getCurrentOption: () => this.getCurrentOptionForGroup(optionGroup.id), | ||
| onDidChangeOption: this.getOrCreateOptionEmitter(optionGroup.id).event, | ||
| setOption: (option: IChatSessionProviderOptionItem) => { | ||
| const ctx = resolveChatSessionContext(); | ||
| if (!ctx) { | ||
| return; | ||
| } | ||
| this.getOrCreateOptionEmitter(optionGroup.id).fire(option); | ||
| this.chatSessionsService.notifySessionOptionsChange( | ||
| ctx.chatSessionResource, | ||
| [{ optionId: optionGroup.id, value: option }] | ||
| ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); | ||
| }, | ||
| getOptionGroup: () => { | ||
| const ctx = resolveChatSessionContext(); | ||
| if (!ctx) { | ||
| return undefined; | ||
| } | ||
| const groups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); | ||
| return groups?.find(g => g.id === optionGroup.id); | ||
| } | ||
| this.getOrCreateOptionEmitter(optionGroup.id).fire(option); | ||
| this.chatSessionsService.notifySessionOptionsChange( | ||
| ctx.chatSessionResource, | ||
| [{ optionId: optionGroup.id, value: option }] | ||
| ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); | ||
| }, | ||
| getAllOptions: () => { | ||
| const ctx = resolveChatSessionContext(); | ||
| if (!ctx) { | ||
| return []; | ||
| }; | ||
|
|
||
| const widget = this.instantiationService.createInstance(SearchableOptionPickerActionItem, action, initialState, searchableDelegate, {}); | ||
| this.chatSessionPickerWidgets.set(optionGroup.id, widget); | ||
| widgets.push(widget); | ||
| } else { | ||
| // Create delegate for standard dropdown picker | ||
| const itemDelegate: IChatSessionPickerDelegate = { | ||
| getCurrentOption: () => this.getCurrentOptionForGroup(optionGroup.id), | ||
| onDidChangeOption: this.getOrCreateOptionEmitter(optionGroup.id).event, | ||
| setOption: (option: IChatSessionProviderOptionItem) => { | ||
| const ctx = resolveChatSessionContext(); | ||
| if (!ctx) { | ||
| return; | ||
| } | ||
| this.getOrCreateOptionEmitter(optionGroup.id).fire(option); | ||
| this.chatSessionsService.notifySessionOptionsChange( | ||
| ctx.chatSessionResource, | ||
| [{ optionId: optionGroup.id, value: option }] | ||
| ).catch(err => this.logService.error(`Failed to notify extension of ${optionGroup.id} change:`, err)); | ||
| }, | ||
| getAllOptions: () => { | ||
| const ctx = resolveChatSessionContext(); | ||
| if (!ctx) { | ||
| return []; | ||
| } | ||
| const groups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); | ||
| const group = groups?.find(g => g.id === optionGroup.id); | ||
| return group?.items ?? []; | ||
| } | ||
| const groups = this.chatSessionsService.getOptionGroupsForSessionType(ctx.chatSessionType); | ||
| const group = groups?.find(g => g.id === optionGroup.id); | ||
| return group?.items ?? []; | ||
| } | ||
| }; | ||
| }; |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Significant code duplication between the searchableDelegate (lines 737-759) and itemDelegate (lines 766-789) objects. The getCurrentOption, onDidChangeOption, and setOption implementations are identical. Consider extracting these common properties into a shared factory function or base delegate object to improve maintainability and reduce the risk of inconsistencies.
| this.labelElement.tabIndex = 0; | ||
| this.labelElement.role = 'button'; | ||
| this.labelElement.setAttribute('aria-haspopup', 'true'); | ||
| this.labelElement.setAttribute('aria-expanded', 'false'); |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aria-expanded attribute is set to 'false' at line 74 but is never updated to 'true' when the QuickPick is shown. For proper accessibility, this attribute should be updated in the showQuickPick method to indicate when the picker is open, though the QuickPick itself handles its own accessibility, so this may be acceptable as-is given the QuickPick is a separate UI element.
| this.labelElement.setAttribute('aria-expanded', 'false'); |
| case 'subagents': | ||
| return Codicon.copilot; | ||
| default: | ||
| return Codicon.sourceControl; |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default case in the switch statement (line 155) returns Codicon.sourceControl, which means any unrecognized option group will get a source control icon. This hardcoded fallback is inappropriate - if the option group ID doesn't match known cases, it should return undefined instead of defaulting to a potentially misleading icon.
| return Codicon.sourceControl; | |
| return undefined; |
| font-size: 13px; | ||
| border-radius: 0; | ||
| min-width: 100px; | ||
| min-width: 160px; |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The min-width change from 100px to 160px affects all action widgets globally, not just the chat session pickers. This could have unintended layout effects on other parts of the application that use the action widget. Consider whether this change should be scoped specifically to chat session pickers using a more specific CSS selector, or if there's a documented reason why all action widgets need to be wider.
| min-width: 160px; | |
| min-width: 100px; |
| } else { | ||
| domChildren.push(...renderLabelWithIcons(`$(source-control)`)); | ||
| } | ||
| domChildren.push(dom.$('span.chat-session-option-label', undefined, this.currentOption?.name ?? localize('chat.sessionPicker.label', "Pick Option"))); | ||
| domChildren.push(dom.$('span.chat-session-option-label', undefined, this.currentOption?.name ?? localize('chat.sessionPicker.label', "Repository"))); |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded default icon and label ("Repository") suggest this code is tied to a specific use case rather than being generic. Since this is supposed to be a generic picker for any option group, these defaults should be removed or made more generic. The icon should only be shown when explicitly provided by the option, and the label should fall back to a generic message like "Pick Option" (which already exists in the code) or the option group name.
See below for a potential fix:
}
domChildren.push(dom.$('span.chat-session-option-label', undefined, this.currentOption?.name ?? localize('chat.sessionPicker.label', "Pick Option")));
No description provided.