Skip to content

Commit 4660585

Browse files
authored
refactor(copilotcli): move worktree properties and metadata tracking to session request lifecycle (#308960)
refactor: move worktree properties and metadata tracking to session request lifecycle
1 parent f836075 commit 4660585

File tree

5 files changed

+178
-123
lines changed

5 files changed

+178
-123
lines changed

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessionInitializer.ts

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,21 @@
66
import type { SweCustomAgent } from '@github/copilot/sdk';
77
import * as l10n from '@vscode/l10n';
88
import * as vscode from 'vscode';
9+
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
910
import { ILogService } from '../../../platform/log/common/logService';
1011
import { IPromptsService, ParsedPromptFile } from '../../../platform/promptFiles/common/promptsService';
1112
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
1213
import { createServiceIdentifier } from '../../../util/common/services';
1314
import { DisposableStore, IReference } from '../../../util/vs/base/common/lifecycle';
1415
import { URI } from '../../../util/vs/base/common/uri';
1516
import { ChatVariablesCollection, extractDebugTargetSessionIds, isPromptFile } from '../../prompt/common/chatVariablesCollection';
16-
import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';
17-
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
18-
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
1917
import { FolderRepositoryInfo, IFolderRepositoryManager, IsolationMode } from '../common/folderRepositoryManager';
20-
import { SessionIdForCLI } from '../copilotcli/common/utils';
2118
import { emptyWorkspaceInfo, getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../common/workspaceInfo';
19+
import { SessionIdForCLI } from '../copilotcli/common/utils';
2220
import { COPILOT_CLI_REASONING_EFFORT_PROPERTY, ICopilotCLIAgents, ICopilotCLIModels } from '../copilotcli/node/copilotCli';
2321
import { ICopilotCLISession } from '../copilotcli/node/copilotcliSession';
2422
import { ICopilotCLISessionService } from '../copilotcli/node/copilotcliSessionService';
2523
import { buildMcpServerMappings, McpServerMappings } from '../copilotcli/node/mcpHandler';
26-
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
2724

2825
function isReasoningEffortFeatureEnabled(configurationService: IConfigurationService): boolean {
2926
return configurationService.getConfig(ConfigKey.Advanced.CLIThinkingEffortEnabled);
@@ -85,13 +82,10 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
8582
constructor(
8683
@ICopilotCLISessionService private readonly sessionService: ICopilotCLISessionService,
8784
@IFolderRepositoryManager private readonly folderRepositoryManager: IFolderRepositoryManager,
88-
@IChatSessionWorktreeService private readonly worktreeService: IChatSessionWorktreeService,
89-
@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,
9085
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
9186
@ICopilotCLIModels private readonly copilotCLIModels: ICopilotCLIModels,
9287
@ICopilotCLIAgents private readonly copilotCLIAgents: ICopilotCLIAgents,
9388
@IPromptsService private readonly promptsService: IPromptsService,
94-
@IChatSessionMetadataStore private readonly chatSessionMetadataStore: IChatSessionMetadataStore,
9589
@ILogService private readonly logService: ILogService,
9690
@IConfigurationService private readonly configurationService: IConfigurationService,
9791
) { }
@@ -129,15 +123,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
129123
return { session: undefined, isNewSession, model, agent, trusted };
130124
}
131125
this.logService.info(`Using Copilot CLI session: ${session.object.sessionId} (isNewSession: ${isNewSession}, isolationEnabled: ${isIsolationEnabled(workspaceInfo)}, workingDirectory: ${workingDirectory}, worktreePath: ${worktreeProperties?.worktreePath})`);
132-
if (isNewSession) {
133-
if (worktreeProperties) {
134-
void this.worktreeService.setWorktreeProperties(session.object.sessionId, worktreeProperties);
135-
}
136-
this.finalizeSessionCreation(session.object.sessionId, session.object.workspace);
137-
}
138-
139-
const modeInstructions = this.createModeInstructions(request);
140-
this.chatSessionMetadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agent?.name ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));
141126

142127
disposables.add(session);
143128
disposables.add(session.object.attachStream(stream));
@@ -198,14 +183,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
198183
]);
199184

200185
const session = await this.sessionService.createSession({ workspace, agent, model: model?.model, reasoningEffort: model?.reasoningEffort, mcpServerMappings: options.mcpServerMappings }, token);
201-
const worktreeProperties = workspace.worktreeProperties;
202-
if (worktreeProperties) {
203-
void this.worktreeService.setWorktreeProperties(session.object.sessionId, worktreeProperties);
204-
}
205-
this.finalizeSessionCreation(session.object.sessionId, workspace);
206-
207-
const modeInstructions = this.createModeInstructions(request);
208-
this.chatSessionMetadataStore.updateRequestDetails(session.object.sessionId, [{ vscodeRequestId: request.id, agentId: agent?.name ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));
209186

210187
return { session, model, agent };
211188
}
@@ -255,23 +232,6 @@ export class CopilotCLIChatSessionInitializer implements ICopilotCLIChatSessionI
255232
return undefined;
256233
}
257234

258-
private finalizeSessionCreation(sessionId: string, workspace: IWorkspaceInfo): void {
259-
const workingDirectory = getWorkingDirectory(workspace);
260-
if (workingDirectory && !isIsolationEnabled(workspace)) {
261-
void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);
262-
}
263-
}
264-
265-
private createModeInstructions(request: vscode.ChatRequest): StoredModeInstructions | undefined {
266-
return request.modeInstructions2 ? {
267-
uri: request.modeInstructions2.uri?.toString(),
268-
name: request.modeInstructions2.name,
269-
content: request.modeInstructions2.content,
270-
metadata: request.modeInstructions2.metadata,
271-
isBuiltin: request.modeInstructions2.isBuiltin,
272-
} : undefined;
273-
}
274-
275235
private async getPromptInfoFromRequest(request: vscode.ChatRequest, token: vscode.CancellationToken): Promise<ParsedPromptFile | undefined> {
276236
const promptFile = new ChatVariablesCollection(request.references).find(isPromptFile);
277237
if (!promptFile || !URI.isUri(promptFile.reference.value)) {

extensions/copilot/src/extension/chatSessions/vscode-node/copilotCLIChatSessions.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5-
import type { Attachment, SessionOptions } from '@github/copilot/sdk';
5+
import type { Attachment, SessionOptions, SweCustomAgent } from '@github/copilot/sdk';
66
import * as l10n from '@vscode/l10n';
77
import * as vscode from 'vscode';
88
import { ChatExtendedRequestHandler, ChatRequestTurn2, Uri } from 'vscode';
@@ -47,10 +47,10 @@ import { ICopilotCLIChatSessionInitializer, SessionInitOptions } from './copilot
4747
import { convertReferenceToVariable } from './copilotCLIPromptReferences';
4848
import { ICopilotCLITerminalIntegration, TerminalOpenLocation } from './copilotCLITerminalIntegration';
4949
import { CopilotCloudSessionsProvider } from './copilotCloudSessionsProvider';
50+
import { UNTRUSTED_FOLDER_MESSAGE } from './folderRepositoryManagerImpl';
5051
import { IPullRequestDetectionService } from './pullRequestDetectionService';
5152
import { getSelectedSessionOptions, ISessionOptionGroupBuilder, OPEN_REPOSITORY_COMMAND_ID, toRepositoryOptionItem, toWorkspaceFolderOptionItem } from './sessionOptionGroupBuilder';
5253
import { ISessionRequestLifecycle } from './sessionRequestLifecycle';
53-
import { UNTRUSTED_FOLDER_MESSAGE } from './folderRepositoryManagerImpl';
5454

5555
/**
5656
* ODO:
@@ -580,7 +580,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
580580
return this.handleRequest.bind(this);
581581
}
582582

583-
private readonly contextForRequest = new Map<string, { prompt: string; attachments: Attachment[] }>();
583+
private readonly contextForRequest = new Map<string, { prompt: string; attachments: Attachment[]; agent?: string }>();
584584

585585
/**
586586
* Outer request handler that supports *yielding* for session steering.
@@ -733,14 +733,14 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
733733
const selectedOptions = getSelectedSessionOptions(chatSessionContext.inputState);
734734
const sessionResult = await this.getOrCreateSession(request, chatSessionContext.chatSessionItem.resource, { ...selectedOptions, newBranch: branchNamePromise, stream }, disposables, token);
735735
({ session } = sessionResult);
736-
const { model } = sessionResult;
736+
const { model, agent } = sessionResult;
737737
if (!session || token.isCancellationRequested) {
738738
return {};
739739
}
740740

741741
sdkSessionId = session.object.sessionId;
742742

743-
await this.sessionRequestLifecycle.startRequest(sdkSessionId, request, context.history.length === 0);
743+
await this.sessionRequestLifecycle.startRequest(sdkSessionId, request, context.history.length === 0, session.object.workspace, agent?.name ?? this.contextForRequest.get(session.object.sessionId)?.agent);
744744

745745
if (request.command === 'delegate') {
746746
await this.handleDelegationToCloud(session.object, request, context, stream, token);
@@ -769,18 +769,18 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
769769
}
770770
}
771771

772-
private async getOrCreateSession(request: vscode.ChatRequest, chatResource: vscode.Uri, options: SessionInitOptions, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; isNewSession: boolean; model: { model: string; reasoningEffort?: string } | undefined; trusted: boolean }> {
772+
private async getOrCreateSession(request: vscode.ChatRequest, chatResource: vscode.Uri, options: SessionInitOptions, disposables: DisposableStore, token: vscode.CancellationToken): Promise<{ session: IReference<ICopilotCLISession> | undefined; isNewSession: boolean; model: { model: string; reasoningEffort?: string } | undefined; agent: SweCustomAgent | undefined; trusted: boolean }> {
773773
const result = await this.sessionInitializer.getOrCreateSession(request, chatResource, options, disposables, token);
774-
const { session, isNewSession, model, trusted } = result;
774+
const { session, isNewSession, model, agent, trusted } = result;
775775
if (!session || token.isCancellationRequested) {
776-
return { session: undefined, isNewSession, model, trusted };
776+
return { session: undefined, isNewSession, model, agent, trusted };
777777
}
778778

779779
if (isNewSession) {
780780
this.sessionItemProvider.refreshSession({ reason: 'update', sessionId: session.object.sessionId });
781781
}
782782

783-
return { session, isNewSession, model, trusted };
783+
return { session, isNewSession, model, agent, trusted };
784784
}
785785

786786
private async handleDelegationToCloud(session: ICopilotCLISession, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
@@ -835,7 +835,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
835835
const { prompt, attachments, references } = await this.promptResolver.resolvePrompt(request, await requestPromptPromise, (otherReferences || []).concat([]), workspaceInfo, [], token);
836836

837837
const mcpServerMappings = buildMcpServerMappings(request.tools);
838-
const { session, model } = await this.sessionInitializer.createDelegatedSession(request, workspaceInfo, { mcpServerMappings }, token);
838+
const { session, model, agent } = await this.sessionInitializer.createDelegatedSession(request, workspaceInfo, { mcpServerMappings }, token);
839+
839840
if (summary) {
840841
const summaryRef = await this.chatDelegationSummaryService.trackSummaryUsage(session.object.sessionId, summary);
841842
if (summaryRef) {
@@ -844,7 +845,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
844845
}
845846

846847
try {
847-
this.contextForRequest.set(session.object.sessionId, { prompt, attachments });
848+
this.contextForRequest.set(session.object.sessionId, { prompt, attachments, agent: agent?.name });
848849
// this.sessionItemProvider.notifySessionsChange();
849850
// TODO @DonJayamanne I don't think we need to refresh the list of session here just yet, or perhaps we do,
850851
// Same as getOrCreate session, we need a dummy title or the initial prompt to show in the sessions list.

extensions/copilot/src/extension/chatSessions/vscode-node/sessionRequestLifecycle.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7+
import { ILogService } from '../../../platform/log/common/logService';
78
import { createServiceIdentifier } from '../../../util/common/services';
89
import { Disposable } from '../../../util/vs/base/common/lifecycle';
10+
import { IChatSessionMetadataStore, StoredModeInstructions } from '../common/chatSessionMetadataStore';
911
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
1012
import { IChatSessionWorktreeCheckpointService } from '../common/chatSessionWorktreeCheckpointService';
1113
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
@@ -17,9 +19,10 @@ export interface ISessionRequestLifecycle {
1719

1820
/**
1921
* Begin tracking a request for a session. Creates a baseline checkpoint
20-
* if this is the first request in the session.
22+
* if this is the first request in the session. Records request details
23+
* (agent, mode instructions) in the metadata store.
2124
*/
22-
startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean): Promise<void>;
25+
startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void>;
2326

2427
/**
2528
* Finalize a request: commit worktree changes, create checkpoints, detect
@@ -59,18 +62,39 @@ export class SessionRequestLifecycle extends Disposable implements ISessionReque
5962
@IChatSessionWorktreeCheckpointService private readonly checkpointService: IChatSessionWorktreeCheckpointService,
6063
@IChatSessionWorkspaceFolderService private readonly workspaceFolderService: IChatSessionWorkspaceFolderService,
6164
@IPullRequestDetectionService private readonly prDetectionService: IPullRequestDetectionService,
65+
@IChatSessionMetadataStore private readonly metadataStore: IChatSessionMetadataStore,
66+
@ILogService private readonly logService: ILogService,
6267
) {
6368
super();
6469
}
6570

66-
async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean): Promise<void> {
71+
async startRequest(sessionId: string, request: vscode.ChatRequest, isFirstRequest: boolean, workspace: IWorkspaceInfo, agentName?: string): Promise<void> {
6772
if (isFirstRequest) {
68-
await this.checkpointService.handleRequest(sessionId);
73+
if (workspace.worktreeProperties) {
74+
void this.worktreeService.setWorktreeProperties(sessionId, workspace.worktreeProperties);
75+
}
76+
const workingDirectory = getWorkingDirectory(workspace);
77+
if (workingDirectory && !isIsolationEnabled(workspace)) {
78+
void this.workspaceFolderService.trackSessionWorkspaceFolder(sessionId, workingDirectory.fsPath, workspace.repositoryProperties);
79+
}
6980
}
7081

82+
const modeInstructions: StoredModeInstructions | undefined = request.modeInstructions2 ? {
83+
uri: request.modeInstructions2.uri?.toString(),
84+
name: request.modeInstructions2.name,
85+
content: request.modeInstructions2.content,
86+
metadata: request.modeInstructions2.metadata,
87+
isBuiltin: request.modeInstructions2.isBuiltin,
88+
} : undefined;
89+
this.metadataStore.updateRequestDetails(sessionId, [{ vscodeRequestId: request.id, agentId: agentName ?? '', modeInstructions }]).catch(ex => this.logService.error(ex, 'Failed to update request details'));
90+
7191
const requests = this.pendingRequestBySession.get(sessionId) ?? new Set<vscode.ChatRequest>();
7292
requests.add(request);
7393
this.pendingRequestBySession.set(sessionId, requests);
94+
95+
if (isFirstRequest) {
96+
await this.checkpointService.handleRequest(sessionId);
97+
}
7498
}
7599

76100
async endRequest(sessionId: string, request: vscode.ChatRequest, session: SessionCompletionInfo, token: vscode.CancellationToken): Promise<void> {

0 commit comments

Comments
 (0)