-
Notifications
You must be signed in to change notification settings - Fork 38.2k
[WIP] poc: branch-associated Chat Sessions #267139
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
Changes from all commits
d955d9c
7e48c17
e863092
3011fd4
a620243
280ff42
9cd38ae
76fd76f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,7 @@ export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); | |
| export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`; | ||
| export const ACTION_ID_NEW_EDIT_SESSION = `workbench.action.chat.newEditSession`; | ||
| export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; | ||
| export const CHAT_OPEN_HISTORICAL_SESSION_ACTION_ID = 'workbench.action.chat.openHistoricalSession'; | ||
| export const CHAT_SETUP_ACTION_ID = 'workbench.action.chat.triggerSetup'; | ||
| const TOGGLE_CHAT_ACTION_ID = 'workbench.action.chat.toggle'; | ||
| const CHAT_CLEAR_HISTORY_ACTION_ID = 'workbench.action.chat.clearHistory'; | ||
|
|
@@ -579,7 +580,7 @@ export function registerChatActions() { | |
| separator, | ||
| { | ||
| label: i.title, | ||
| description: i.isActive ? `(${localize('currentChatLabel', 'current')})` : '', | ||
| description: i.isActive ? `(${i.createdOnBranch} - ${localize('currentChatLabel', 'current')})` : `${i.createdOnBranch}`, | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use last active branch instead of createdOnBranch? |
||
| chat: i, | ||
| buttons: i.isActive ? [renameButton] : [ | ||
| renameButton, | ||
|
|
@@ -1232,7 +1233,7 @@ export function registerChatActions() { | |
| const suggestCtrl = SuggestController.get(widget.inputEditor); | ||
| if (suggestCtrl) { | ||
| const curText = widget.inputEditor.getValue(); | ||
| const newValue = curText ? `@ ${curText}` : '@'; | ||
| const newValue = curText ? `@${curText}` : '@'; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert. this seems unrelated. |
||
| if (!curText.startsWith('@')) { | ||
| widget.inputEditor.setValue(newValue); | ||
| } | ||
|
|
@@ -1402,7 +1403,7 @@ export function registerChatActions() { | |
|
|
||
| override async run(accessor: ServicesAccessor): Promise<void> { | ||
| const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); | ||
| extensionsWorkbenchService.openSearch(`@feature:${CopilotUsageExtensionFeatureId}`); | ||
| extensionsWorkbenchService.openSearch(`@feature: ${CopilotUsageExtensionFeatureId}`); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert. unrelated. |
||
| } | ||
| }); | ||
|
|
||
|
|
@@ -1984,3 +1985,42 @@ registerAction2(class EditToolApproval extends Action2 { | |
| } | ||
| } | ||
| }); | ||
|
|
||
| registerAction2(class OpenHistoricalChatSessionAction extends Action2 { | ||
| constructor() { | ||
| super({ | ||
| id: CHAT_OPEN_HISTORICAL_SESSION_ACTION_ID, | ||
| title: localize2('openHistoricalChatSession', "Open Historical Chat Session"), | ||
| category: CHAT_CATEGORY, | ||
| f1: true, | ||
| precondition: ChatContextKeys.enabled | ||
| }); | ||
| } | ||
|
|
||
| async run(accessor: ServicesAccessor, options?: { sessionId?: string }): Promise<void> { | ||
| const viewsService = accessor.get(IViewsService); | ||
| const editorService = accessor.get(IEditorService); | ||
|
|
||
| if (!options?.sessionId) { | ||
| console.log('No sessionId provided to openHistoricalSession command'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const sessionId = options.sessionId; | ||
| console.log(`Opening historical chat session with sessionId: ${sessionId}`); | ||
|
|
||
| // Try to open in chat view first | ||
| const view = await viewsService.openView<ChatViewPane>(ChatViewId); | ||
| if (view) { | ||
| await view.loadSession(sessionId); | ||
| } else { | ||
| // Fallback to opening in editor | ||
| const options: IChatEditorOptions = { target: { sessionId }, pinned: true }; | ||
| await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options }, ACTIVE_GROUP); | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to open historical chat session:', error); | ||
| } | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ import { StopWatch } from '../../../../base/common/stopwatch.js'; | |
| import { URI } from '../../../../base/common/uri.js'; | ||
| import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js'; | ||
| import { localize } from '../../../../nls.js'; | ||
| import { ICommandService } from '../../../../platform/commands/common/commands.js'; | ||
| import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; | ||
| import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; | ||
| import { ILogService } from '../../../../platform/log/common/log.js'; | ||
|
|
@@ -33,6 +34,7 @@ import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChat | |
| import { ChatRequestTelemetry, ChatServiceTelemetry } from './chatServiceTelemetry.js'; | ||
| import { IChatSessionsService } from './chatSessionsService.js'; | ||
| import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js'; | ||
| import { IGitStatus } from './gitStatusService.js'; | ||
| import { IChatSlashCommandService } from './chatSlashCommands.js'; | ||
| import { IChatTransferService } from './chatTransferService.js'; | ||
| import { ChatSessionUri } from './chatUri.js'; | ||
|
|
@@ -118,6 +120,8 @@ export class ChatService extends Disposable implements IChatService { | |
| @IChatTransferService private readonly chatTransferService: IChatTransferService, | ||
| @IChatSessionsService private readonly chatSessionService: IChatSessionsService, | ||
| @IMcpService private readonly mcpService: IMcpService, | ||
| @IGitStatus private readonly gitStatus: IGitStatus, | ||
| @ICommandService private readonly commandService: ICommandService, | ||
| ) { | ||
| super(); | ||
|
|
||
|
|
@@ -160,6 +164,18 @@ export class ChatService extends Disposable implements IChatService { | |
| const models = this._sessionModels.observable.read(reader).values(); | ||
| return Array.from(models).some(model => model.requestInProgressObs.read(reader)); | ||
| }); | ||
|
|
||
| // Listen for branch changes | ||
| this._register(this.gitStatus.onChangedBranch(async branch => { | ||
| console.log('Branch changed to:', branch); | ||
| const history = await this.getHistory(); | ||
| const sessionId = history.find(h => h.lastUsedOnBranch === branch)?.sessionId; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to check ordering on this. Ensure |
||
| if (sessionId) { | ||
| this.commandService.executeCommand('workbench.action.chat.openHistoricalSession', { sessionId }); | ||
| } else { | ||
| this.commandService.executeCommand('workbench.action.chat.newChat'); | ||
| } | ||
| })); | ||
| } | ||
|
|
||
| isEnabled(location: ChatAgentLocation): boolean { | ||
|
|
@@ -303,6 +319,8 @@ export class ChatService extends Disposable implements IChatService { | |
| title, | ||
| lastMessageDate: session.lastMessageDate, | ||
| isActive: true, | ||
| createdOnBranch: session.createdOnBranch, | ||
| lastUsedOnBranch: session.lastUsedOnBranch, | ||
| } satisfies IChatDetail; | ||
| }); | ||
|
|
||
|
|
@@ -331,12 +349,22 @@ export class ChatService extends Disposable implements IChatService { | |
|
|
||
| private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, location: ChatAgentLocation, isGlobalEditingSession: boolean, token: CancellationToken, inputType?: string): ChatModel { | ||
| const model = this.instantiationService.createInstance(ChatModel, someSessionHistory, { initialLocation: location, inputType }); | ||
| // Initialize branch metadata on creation | ||
| const branch = this.gitStatus.getCurrentBranch(); | ||
| if (branch) { | ||
| if (!model.createdOnBranch) { | ||
| model.createdOnBranch = branch; | ||
| } | ||
| model.lastUsedOnBranch = branch; | ||
| } | ||
| if (location === ChatAgentLocation.Chat) { | ||
| model.startEditingSession(isGlobalEditingSession); | ||
| } | ||
|
|
||
| this._sessionModels.set(model.sessionId, model); | ||
| this.initializeSession(model, token); | ||
| // Persist immediately to capture branch info | ||
| this._chatSessionStore.storeSessions([model]); | ||
| return model; | ||
| } | ||
|
|
||
|
|
@@ -349,6 +377,19 @@ export class ChatService extends Disposable implements IChatService { | |
| this.activateDefaultAgent(model.initialLocation).catch(e => this.logService.error(e)); | ||
| } | ||
|
|
||
| /** | ||
| * Update session metadata to reflect that it has been actively used (selected, restored, or loaded). | ||
| * Captures the current branch (best effort) for lastUsedOnBranch and persists the session metadata. | ||
| */ | ||
| private _markSessionUsed(model: ChatModel): void { | ||
| const branch = this.gitStatus.getCurrentBranch(); | ||
| if (branch) { | ||
| model.lastUsedOnBranch = branch; | ||
| } | ||
| // Persist metadata update only (non-blocking) | ||
| this._chatSessionStore.storeSessions([model]); | ||
| } | ||
|
|
||
| async activateDefaultAgent(location: ChatAgentLocation): Promise<void> { | ||
| await this.extensionService.whenInstalledExtensionsRegistered(); | ||
|
|
||
|
|
@@ -382,6 +423,7 @@ export class ChatService extends Disposable implements IChatService { | |
| this.trace('getOrRestoreSession', `sessionId: ${sessionId}`); | ||
| const model = this._sessionModels.get(sessionId); | ||
| if (model) { | ||
| this._markSessionUsed(model); | ||
| return model; | ||
| } | ||
|
|
||
|
|
@@ -403,6 +445,7 @@ export class ChatService extends Disposable implements IChatService { | |
| this._transferredSessionData = undefined; | ||
| } | ||
|
|
||
| this._markSessionUsed(session); | ||
| return session; | ||
| } | ||
|
|
||
|
|
@@ -443,7 +486,9 @@ export class ChatService extends Disposable implements IChatService { | |
| } | ||
|
|
||
| loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined { | ||
| return this._startSession(data, data.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); | ||
| const model = this._startSession(data, data.initialLocation ?? ChatAgentLocation.Chat, true, CancellationToken.None); | ||
| this._markSessionUsed(model); | ||
| return model; | ||
| } | ||
|
|
||
| async loadSessionForResource(resource: URI, location: ChatAgentLocation, token: CancellationToken): Promise<IChatModel | undefined> { | ||
|
|
@@ -466,6 +511,7 @@ export class ChatService extends Disposable implements IChatService { | |
| const content = await this.chatSessionService.provideChatSessionContent(chatSessionType, parsed.sessionId, CancellationToken.None); | ||
|
|
||
| const model = this._startSession(undefined, location, true, CancellationToken.None, chatSessionType); | ||
| this._markSessionUsed(model); | ||
| if (!this._contentProviderSessionModels.has(chatSessionType)) { | ||
| this._contentProviderSessionModels.set(chatSessionType, new Map()); | ||
| } | ||
|
|
||
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.