From f2e4ea6b2ece8fab9d977de3a4e19d4607c091ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:55:22 +0000 Subject: [PATCH 1/3] Initial plan From d226d16ae197453a5de8480aad2774c115d400d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:05:25 +0000 Subject: [PATCH 2/3] Initial analysis Agent-Logs-Url: https://github.com/microsoft/vscode-pull-request-github/sessions/2e5f5355-cccd-4ac6-b866-9d00dcd8b8b4 Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/@types/vscode.proposed.chatSessionsProvider.d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 06d0649b18..6a394c158b 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -594,8 +594,15 @@ declare module 'vscode' { /** * The initial option selections for the session, provided with the first request. * Contains the options the user selected (or defaults) before the session was created. + * + * @deprecated Use `inputState` instead */ readonly initialSessionOptions?: ReadonlyArray<{ optionId: string; value: string | ChatSessionProviderOptionItem }>; + + /** + * The current input state of the chat session. + */ + readonly inputState: ChatSessionInputState; } export interface ChatSessionCapabilities { @@ -692,6 +699,8 @@ declare module 'vscode' { * * These commands will be displayed at the bottom of the group. * + * For extensions using the legacy `commands` API, these commands are passed the sessionResource as the first argument. + * * For extensions that use the new `provideChatSessionInputState` API, these commands are passed a context object * `{ inputState: ChatSessionInputState; sessionResource: Uri | undefined }` that they can use to determine which session and options they are being invoked for. */ From 791e8c8074de176a1cd2deca7b5a88629fbf9e78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:08:47 +0000 Subject: [PATCH 3/3] Fix file-scoped comment failing with "Error: File has been deleted" When creating a file-scoped comment (range is undefined), the createReviewThread method was passing `line: 0` to the GitHub GraphQL API. The API does not accept line 0 and returns a null thread, which caused the misleading "File has been deleted" error. Fix: pass endLine directly (undefined for file-scoped comments) instead of converting undefined to 0. The line parameter is not required when subjectType is FILE. Agent-Logs-Url: https://github.com/microsoft/vscode-pull-request-github/sessions/2e5f5355-cccd-4ac6-b866-9d00dcd8b8b4 Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/pullRequestModel.ts | 2 +- src/test/view/reviewCommentController.test.ts | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index f37f2feda8..2a88e65034 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -751,7 +751,7 @@ export class PullRequestModel extends IssueModel implements IPullRe pullRequestId: this.graphNodeId, pullRequestReviewId: pendingReviewId, startLine: startLine === endLine ? undefined : startLine, - line: (endLine === undefined) ? 0 : endLine, + line: endLine, side, subjectType: (startLine === undefined || endLine === undefined) ? SubjectType.FILE : SubjectType.LINE } diff --git a/src/test/view/reviewCommentController.test.ts b/src/test/view/reviewCommentController.test.ts index 0dead8909e..1c1699e121 100644 --- a/src/test/view/reviewCommentController.test.ts +++ b/src/test/view/reviewCommentController.test.ts @@ -339,5 +339,106 @@ describe('ReviewCommentController', function () { assert.strictEqual(Object.keys(workspaceFileChangeCommentThreads)[0], fileName); assert.strictEqual(workspaceFileChangeCommentThreads[fileName].length, 1); }); + + it('creates a file-scoped comment on an empty thread', async function () { + const fileName = 'data/products.json'; + const uri = vscode.Uri.parse(`${repository.rootUri.toString()}/${fileName}`); + await activePullRequest.initializeReviewThreadCache(); + const localFileChanges = [createLocalFileChange(uri, fileName, repository.rootUri)]; + const reviewModel = new ReviewModel(); + reviewModel.localFileChanges = localFileChanges; + const reviewCommentController = new TestReviewCommentController( + reviewManager, + manager, + repository, + reviewModel, + gitApiImpl, + telemetry + ); + const thread = createGHPRCommentThread('review-1.2', uri); + thread.range = undefined as any; + + sinon.stub(activePullRequest, 'validateDraftMode').returns(Promise.resolve(false)); + sinon.stub(activePullRequest, 'getReviewThreads').returns(Promise.resolve([])); + sinon.stub(activePullRequest, 'getPendingReviewId').returns(Promise.resolve(undefined)); + + sinon.stub(manager, 'getCurrentUser').returns(Promise.resolve({ + login: 'rmacfarlane', + url: 'https://github.com/rmacfarlane', + id: '123', + accountType: AccountType.User + })); + + sinon.stub(vscode.workspace, 'getWorkspaceFolder').returns({ + uri: repository.rootUri, + name: '', + index: 0, + }); + + sinon.stub(vscode.workspace, 'asRelativePath').callsFake((pathOrUri: string | vscode.Uri): string => { + const path = pathOrUri.toString(); + return path.substring('/root/'.length); + }); + + sinon.stub(repository, 'diffWith').returns(Promise.resolve('')); + + await reviewCommentController.initialize(); + + githubRepo.queryProvider.expectGraphQLMutation( + { + mutation: schema.AddReviewThread, + variables: { + input: { + path: fileName, + body: 'file comment', + pullRequestId: activePullRequest.graphNodeId, + pullRequestReviewId: undefined, + startLine: undefined, + line: undefined, + side: 'RIGHT', + subjectType: 'FILE' + } + } + }, + { + data: { + addPullRequestReviewThread: { + thread: { + id: 2, + isResolved: false, + viewCanResolve: true, + path: fileName, + line: null, + startLine: null, + originalStartLine: null, + originalLine: null, + diffSide: 'RIGHT', + isOutdated: false, + subjectType: 'FILE', + comments: { + nodes: [ + { + databaseId: 2, + id: 2, + body: 'file comment', + commit: {}, + diffHunk: '', + reactionGroups: [], + author: {} + } + ] + } + } + } + } + } + ) + + const newReviewThreadPromise = asPromise(activePullRequest.onDidChangeReviewThreads); + await reviewCommentController.createOrReplyComment(thread, 'file comment', false); + await newReviewThreadPromise; + assert.strictEqual(thread.comments.length, 1); + assert.strictEqual(thread.comments[0].parent, thread); + }); }); });