Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,8 @@ export class CopilotCLIChatSessionContentProvider extends Disposable implements
const WAIT_FOR_NEW_SESSION_TO_GET_USED = 5 * 60 * 1000; // 5 minutes

export class CopilotCLIChatSessionParticipant extends Disposable {
private CLI_INCLUDE_CHANGES = vscode.l10n.t('Include Changes');
private CLI_MOVE_CHANGES = vscode.l10n.t('Move Changes');
private CLI_COPY_CHANGES = vscode.l10n.t('Copy Changes');
private CLI_SKIP_CHANGES = vscode.l10n.t('Skip Changes');
private CLI_CANCEL = vscode.l10n.t('Cancel');
private readonly untitledSessionIdMapping = new Map<string, string>();
Expand Down Expand Up @@ -521,11 +522,10 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
});

const confirmationResults = this.getAcceptedRejectedConfirmationData(request);
if (!chatSessionContext) {
// Invoked from a 'normal' chat or 'cloud button' without CLI session context
// Or cases such as delegating from Regular chat to CLI chat
// Handle confirmation data
return await this.handlePushConfirmationData(request, context, stream, token);
// Check if it was delegated from chat or cloud button or if it's the first iteration
const response = await this.generateConfirmationResponseIfNeeded(request, context, stream, token);
if (!chatSessionContext || chatSessionContext.isUntitled) {
return response || {};
}

const isUntitled = chatSessionContext.isUntitled;
Expand Down Expand Up @@ -730,7 +730,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
}

// Get model from request.
const preferredModelInRequest = preferModelInRequest && request?.model.id ? await this.copilotCLIModels.resolveModel(request.model.id) : undefined;
const preferredModelInRequest = preferModelInRequest && request?.model?.id ? await this.copilotCLIModels.resolveModel(request.model.id) : undefined;
if (preferredModelInRequest) {
return preferredModelInRequest;
}
Expand Down Expand Up @@ -788,12 +788,12 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
return {};
}

private async handlePushConfirmationData(
private async generateConfirmationResponseIfNeeded(
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
token: vscode.CancellationToken
): Promise<vscode.ChatResult | void> {
) {
// Check if this is a confirmation response
const confirmationResults = this.getAcceptedRejectedConfirmationData(request);
if (confirmationResults.length > 0) {
Expand All @@ -811,15 +811,25 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
if (!hasUncommittedChanges) {
// No uncommitted changes, create worktree and proceed
return await this.createCLISessionAndSubmitRequest(request, undefined, request.references, context, undefined, true, stream, token);
} else {
return this.generateUncommittedChangesConfirmation(request, context, stream, token);
}
}

private generateUncommittedChangesConfirmation(
request: vscode.ChatRequest,
context: vscode.ChatContext,
stream: vscode.ChatResponseStream,
token: vscode.CancellationToken
): vscode.ChatResult | void {
const message =
vscode.l10n.t('Background Agent will work in an isolated worktree to implement your requested changes.')
+ '\n\n'
+ vscode.l10n.t('This workspace has uncommitted changes. Should these changes be included in the new worktree?');

const buttons = [
this.CLI_INCLUDE_CHANGES,
this.CLI_COPY_CHANGES,
this.CLI_MOVE_CHANGES,
this.CLI_SKIP_CHANGES,
this.CLI_CANCEL
];
Expand Down Expand Up @@ -861,10 +871,11 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
return {};
}

const includeChanges = selection.includes(this.CLI_INCLUDE_CHANGES.toUpperCase());
const moveChanges = selection === this.CLI_MOVE_CHANGES.toUpperCase();
const copyChanges = selection === this.CLI_COPY_CHANGES.toUpperCase();
const prompt = uncommittedChangesData.metadata.prompt;

if (includeChanges && this.worktreeManager.isSupported()) {
if ((moveChanges || copyChanges) && this.worktreeManager.isSupported()) {
// Create worktree first
stream.progress(vscode.l10n.t('Creating worktree...'));
const worktreePathValue = await this.worktreeManager.createWorktree(stream);
Expand Down Expand Up @@ -906,7 +917,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable {
} else {
await this.gitService.migrateChanges(worktreeRepo.rootUri, activeRepository.rootUri, {
confirmation: false,
deleteFromSource: true,
deleteFromSource: moveChanges,
untracked: true
});
stream.markdown(vscode.l10n.t('Changes migrated to worktree.'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
override getWorktreePath = vi.fn((_id: string) => undefined);
override getIsolationPreference = vi.fn(() => false);
override getDefaultIsolationPreference = vi.fn(() => false);
override isSupported = vi.fn(() => false);
}

class FakeModels implements ICopilotCLIModels {
Expand Down Expand Up @@ -248,7 +249,7 @@

await participant.createHandler()(request, context, stream, token);

expect(cliSessions.length).toBe(1);

Check failure on line 252 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Linux)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > reuses existing session (non-untitled) and does not create new one

AssertionError: expected 2 to be 1 // Object.is equality - Expected + Received - 1 + 2 ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:252:30

Check failure on line 252 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > reuses existing session (non-untitled) and does not create new one

AssertionError: expected 2 to be 1 // Object.is equality - Expected + Received - 1 + 2 ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:252:30
expect(cliSessions[0].sessionId).toBe(sessionId);
expect(cliSessions[0].requests.length).toBe(1);
expect(cliSessions[0].requests[0]).toEqual({ prompt: 'Continue', attachments: [], modelId: 'base', token });
Expand Down Expand Up @@ -285,10 +286,23 @@
expect(cloudProvider.delegate).toHaveBeenCalled();
});

it('handles /delegate command for new session', async () => {
it('handles /delegate command for new untitled session with uncomitted changes', async () => {
expect(manager.sessions.size).toBe(0);
git.activeRepository = { get: () => ({ changes: { indexChanges: [{ path: 'file.ts' }] } }) } as unknown as IGitService['activeRepository'];
const request = new TestChatRequest('/delegate Build feature');
const context = { chatSessionContext: undefined } as vscode.ChatContext;
const stream = new MockChatResponseStream();
const token = disposables.add(new CancellationTokenSource()).token;

await participant.createHandler()(request, context, stream, token);

expect(manager.sessions.size).toBe(0);
});

it('handles /delegate command for new session without uncommitted changes', async () => {
expect(manager.sessions.size).toBe(0);
git.activeRepository = { get: () => ({ changes: { indexChanges: [], workingTree: [] } }) } as unknown as IGitService['activeRepository'];
const request = new TestChatRequest('/delegate Build feature');
const context = createChatContext('existing-delegate', true);
const stream = new MockChatResponseStream();
const token = disposables.add(new CancellationTokenSource()).token;
Expand All @@ -297,7 +311,7 @@

expect(manager.sessions.size).toBe(1);
const sdkSession = Array.from(manager.sessions.values())[0];
expect(cloudProvider.delegate).toHaveBeenCalled();

Check failure on line 314 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Linux)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > handles /delegate command for new session without uncommitted changes

AssertionError: expected "spy" to be called at least once ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:314:34

Check failure on line 314 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > handles /delegate command for new session without uncommitted changes

AssertionError: expected "spy" to be called at least once ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:314:34
// PR metadata recorded
expect(sdkSession.emittedEvents.length).toBe(2);
expect(sdkSession.emittedEvents[0].event).toBe('user.message');
Expand Down Expand Up @@ -342,7 +356,7 @@
await participant.createHandler()(request, context, stream, token);

// Should NOT call session.handleRequest, instead record push messages
expect(cliSessions.length).toBe(1);

Check failure on line 359 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Linux)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > handleConfirmationData accepts uncommitted-changes and records push

AssertionError: expected 2 to be 1 // Object.is equality - Expected + Received - 1 + 2 ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:359:30

Check failure on line 359 in src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts > CopilotCLIChatSessionParticipant.handleRequest > handleConfirmationData accepts uncommitted-changes and records push

AssertionError: expected 2 to be 1 // Object.is equality - Expected + Received - 1 + 2 ❯ src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts:359:30
expect(cliSessions[0].requests.length).toBe(0);
expect(sdkSession.emittedEvents.length).toBe(2);
expect(sdkSession.emittedEvents[0].event).toBe('user.message');
Expand Down
Loading