Skip to content

Commit 2ae61ac

Browse files
benibenjCopilotdmitrivMS
authored
Agents: support multiple pull requests per session (#333189)
* sessions: support multiple pull requests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: cover multi-PR review loading Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: address multi-PR review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: register banner fixture services Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: register toolbar fixture providers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: align input banner split buttons Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dmitriy Vasyura <dmitriv@microsoft.com>
1 parent a5a7103 commit 2ae61ac

41 files changed

Lines changed: 1916 additions & 498 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/vs/platform/agentHost/common/meta/agentFeedbackAnnotations.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ export type AgentFeedbackKindValue = 'user' | 'codeReview' | 'prReview';
7171
*/
7272
export type AgentFeedbackStateValue = 'created' | 'accepted' | 'submitted' | 'resolved';
7373

74+
/** Pull request that originated a PR-review feedback item. */
75+
export interface IFeedbackPullRequest {
76+
readonly owner: string;
77+
readonly repo: string;
78+
readonly number: number;
79+
}
80+
7481
/**
7582
* Feedback semantics carried in an annotation's {@link Annotation._meta}.
7683
*
7784
* The optional client-only fields ({@link suggestion}, {@link codeSelection},
78-
* {@link diffHunks}, {@link sourcePRReviewCommentId}) are populated when a
85+
* {@link diffHunks}, {@link sourcePRReviewCommentId}, {@link sourcePullRequest}) are populated when a
7986
* feedback item is converted from a code- or PR-review comment on the client;
8087
* server tools only ever write {@link kind} / {@link state} /
8188
* {@link sessionResource}. {@link suggestion} is typed loosely here because
@@ -89,6 +96,7 @@ export interface IFeedbackAnnotationMeta {
8996
readonly codeSelection?: string;
9097
readonly diffHunks?: string;
9198
readonly sourcePRReviewCommentId?: string;
99+
readonly sourcePullRequest?: IFeedbackPullRequest;
92100
/**
93101
* Transient marker set by the client when the user reveals this comment to
94102
* the agent via the `viewUnreviewedComments` tool. The marker persists until
@@ -106,6 +114,14 @@ function isAgentFeedbackStateValue(value: unknown): value is AgentFeedbackStateV
106114
return value === 'created' || value === 'accepted' || value === 'submitted' || value === 'resolved';
107115
}
108116

117+
function isFeedbackPullRequest(value: unknown): value is IFeedbackPullRequest {
118+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
119+
return false;
120+
}
121+
const candidate = value as Partial<IFeedbackPullRequest>;
122+
return typeof candidate.owner === 'string' && typeof candidate.repo === 'string' && typeof candidate.number === 'number';
123+
}
124+
109125
/**
110126
* Who wrote a specific {@link AnnotationEntry} within a feedback comment.
111127
*
@@ -190,6 +206,7 @@ export function readFeedbackAnnotationMeta(annotation: Annotation): IFeedbackAnn
190206
if (typeof raw['codeSelection'] === 'string') { result.codeSelection = raw['codeSelection']; }
191207
if (typeof raw['diffHunks'] === 'string') { result.diffHunks = raw['diffHunks']; }
192208
if (typeof raw['sourcePRReviewCommentId'] === 'string') { result.sourcePRReviewCommentId = raw['sourcePRReviewCommentId']; }
209+
if (isFeedbackPullRequest(raw['sourcePullRequest'])) { result.sourcePullRequest = raw['sourcePullRequest']; }
193210
if (typeof raw['pendingAgentReveal'] === 'boolean') { result.pendingAgentReveal = raw['pendingAgentReveal']; }
194211
return result;
195212
}

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachmentEntry.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function createAgentFeedbackVariableEntry(sessionResource: URI, feedbackI
4747
codeSelection: f.codeSelection,
4848
diffHunks: f.diffHunks,
4949
sourcePRReviewCommentId: f.sourcePRReviewCommentId,
50+
sourcePullRequest: f.sourcePullRequest,
5051
replies: f.replies?.map(reply => reply.text),
5152
})),
5253
value: buildAgentFeedbackValue(feedbackItems),
@@ -69,7 +70,10 @@ export function buildAgentFeedbackValue(feedbackItems: readonly IAgentFeedback[]
6970

7071
let part = `[${fileName}:${lineRef}]`;
7172
if (item.sourcePRReviewCommentId) {
72-
part += `\n(PR review comment, thread ID: ${item.sourcePRReviewCommentId} — resolve this thread when addressed)`;
73+
const pullRequest = item.sourcePullRequest
74+
? ` on ${item.sourcePullRequest.owner}/${item.sourcePullRequest.repo}#${item.sourcePullRequest.number}`
75+
: '';
76+
part += `\n(PR review comment${pullRequest}, thread ID: ${item.sourcePRReviewCommentId} — resolve this thread when addressed)`;
7377
}
7478
if (item.codeSelection) {
7579
part += `\nSelection:\n\`\`\`\n${item.codeSelection}\n\`\`\``;

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackEditorWidget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,8 @@ export class AgentFeedbackEditorWidget extends Disposable implements IOverlayWid
755755
createAgentFeedbackContext(this._editor, this._codeEditorService, comment.resourceUri, comment.range),
756756
comment.sourceId,
757757
AgentFeedbackKind.PRReview,
758+
undefined,
759+
comment.sourcePullRequest,
758760
);
759761
this._agentFeedbackService.addReply(this._sessionResource, feedback.id, replyText);
760762
this._agentFeedbackService.setNavigationAnchor(this._sessionResource, toSessionEditorCommentId(SessionEditorCommentSource.AgentFeedback, feedback.id));
@@ -826,6 +828,8 @@ export class AgentFeedbackEditorWidget extends Disposable implements IOverlayWid
826828
createAgentFeedbackContext(this._editor, this._codeEditorService, comment.resourceUri, comment.range),
827829
comment.sourceId,
828830
AgentFeedbackKind.PRReview,
831+
undefined,
832+
comment.sourcePullRequest,
829833
);
830834
this._agentFeedbackService.setNavigationAnchor(this._sessionResource, toSessionEditorCommentId(SessionEditorCommentSource.AgentFeedback, feedback.id));
831835
this._codeReviewService.markPRReviewCommentConverted(this._sessionResource, comment.sourceId);

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackItemsBackend.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { IAgentSubscription } from '../../../../platform/agentHost/common/state/
1212
import { ActionType } from '../../../../platform/agentHost/common/state/protocol/common/actions.js';
1313
import { Annotation, AnnotationEntry, AnnotationsState, StateComponents, StringOrMarkdown } from '../../../../platform/agentHost/common/state/sessionState.js';
1414
import { TextRange } from '../../../../platform/agentHost/common/state/protocol/common/state.js';
15-
import { authorForFeedbackKind, feedbackAnnotationEntryMeta, FEEDBACK_ANNOTATION_META_KEY, readFeedbackAnnotationMeta, resolveFeedbackEntryAuthor, type AgentFeedbackKindValue, type AgentFeedbackStateValue, type IFeedbackAnnotationMeta } from '../../../../platform/agentHost/common/meta/agentFeedbackAnnotations.js';
15+
import { authorForFeedbackKind, feedbackAnnotationEntryMeta, FEEDBACK_ANNOTATION_META_KEY, readFeedbackAnnotationMeta, resolveFeedbackEntryAuthor, type AgentFeedbackKindValue, type AgentFeedbackStateValue, type IFeedbackAnnotationMeta, type IFeedbackPullRequest } from '../../../../platform/agentHost/common/meta/agentFeedbackAnnotations.js';
1616
import { ICodeReviewSuggestion } from '../../codeReview/browser/codeReviewService.js';
1717
import { IAgentHostSessionsProvider, isAgentHostProviderId } from '../../../common/agentHostSessionsProvider.js';
1818
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
@@ -173,6 +173,7 @@ interface IFeedbackMetaView {
173173
readonly codeSelection?: string;
174174
readonly diffHunks?: string;
175175
readonly sourcePRReviewCommentId?: string;
176+
readonly sourcePullRequest?: IFeedbackPullRequest;
176177
readonly pendingAgentReveal?: boolean;
177178
}
178179

@@ -218,6 +219,7 @@ function readFeedbackMeta(annotation: Annotation): IFeedbackMetaView | undefined
218219
codeSelection: base.codeSelection,
219220
diffHunks: base.diffHunks,
220221
sourcePRReviewCommentId: base.sourcePRReviewCommentId,
222+
sourcePullRequest: base.sourcePullRequest,
221223
pendingAgentReveal: base.pendingAgentReveal,
222224
};
223225
}
@@ -263,6 +265,7 @@ function feedbackToAnnotation(feedback: IAgentFeedback, connection: IAgentConnec
263265
codeSelection: feedback.codeSelection,
264266
diffHunks: feedback.diffHunks,
265267
sourcePRReviewCommentId: feedback.sourcePRReviewCommentId,
268+
sourcePullRequest: feedback.sourcePullRequest,
266269
pendingAgentReveal: feedback.pendingAgentReveal,
267270
};
268271
return {
@@ -301,6 +304,7 @@ function annotationToFeedback(annotation: Annotation, sessionResource: URI, conn
301304
diffHunks: meta?.diffHunks,
302305
kind: meta?.kind ?? AgentFeedbackKind.UserReview,
303306
sourcePRReviewCommentId: meta?.sourcePRReviewCommentId,
307+
sourcePullRequest: meta?.sourcePullRequest,
304308
replies: replies.length ? replies : undefined,
305309
state: annotation.resolved ? AgentFeedbackState.Resolved : (meta?.state ?? AgentFeedbackState.Accepted),
306310
pendingAgentReveal: meta?.pendingAgentReveal,

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackModel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import type { URI } from '../../../../base/common/uri.js';
77
import type { IRange } from '../../../../editor/common/core/range.js';
8-
import type { AgentFeedbackAuthorValue } from '../../../../platform/agentHost/common/meta/agentFeedbackAnnotations.js';
8+
import type { AgentFeedbackAuthorValue, IFeedbackPullRequest } from '../../../../platform/agentHost/common/meta/agentFeedbackAnnotations.js';
99
import type { ICodeReviewSuggestion } from '../../codeReview/browser/codeReviewService.js';
1010

1111
/**
@@ -83,6 +83,8 @@ export interface IAgentFeedback {
8383
readonly kind: AgentFeedbackKind;
8484
/** When this feedback was converted from a PR review comment, the original thread ID. */
8585
readonly sourcePRReviewCommentId?: string;
86+
/** Pull request that originated this PR review comment. */
87+
readonly sourcePullRequest?: IFeedbackPullRequest;
8688
/**
8789
* Additional comment messages that belong to the same thread as this feedback,
8890
* talking about the same code region. The first {@link text} is the initial

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackPRReviewSeeder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ export class AgentFeedbackPRReviewSeederContribution extends Disposable implemen
125125
comment.id,
126126
AgentFeedbackKind.PRReview,
127127
AgentFeedbackState.Created,
128+
{
129+
owner: comment.pullRequest.owner,
130+
repo: comment.pullRequest.repo,
131+
number: comment.pullRequest.number,
132+
},
128133
);
129134
}
130135
}

src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackPRThreadResolver.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Disposable } from '../../../../base/common/lifecycle.js';
77
import { URI } from '../../../../base/common/uri.js';
8+
import { IFeedbackPullRequest } from '../../../../platform/agentHost/common/meta/agentFeedbackAnnotations.js';
89
import { ILogService } from '../../../../platform/log/common/log.js';
910
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
1011
import { ICodeReviewService } from '../../codeReview/browser/codeReviewService.js';
@@ -13,6 +14,7 @@ import { AgentFeedbackKind, AgentFeedbackState, IAgentFeedbackChangeEvent, IAgen
1314
interface ISeenPRComment {
1415
readonly state: AgentFeedbackState;
1516
readonly threadId: string;
17+
readonly pullRequest: IFeedbackPullRequest | undefined;
1618
}
1719

1820
/**
@@ -65,19 +67,19 @@ export class AgentFeedbackPRThreadResolverContribution extends Disposable implem
6567
const key = e.sessionResource.toString();
6668
const previous = this._seenBySession.get(key) ?? new Map<string, ISeenPRComment>();
6769
const next = new Map<string, ISeenPRComment>();
68-
const threadsToResolve = new Set<string>();
70+
const threadsToResolve = new Map<string, IFeedbackPullRequest | undefined>();
6971

7072
for (const item of e.feedbackItems) {
7173
if (item.kind !== AgentFeedbackKind.PRReview || !item.sourcePRReviewCommentId) {
7274
continue;
7375
}
7476
const threadId = item.sourcePRReviewCommentId;
75-
next.set(item.id, { state: item.state, threadId });
77+
next.set(item.id, { state: item.state, threadId, pullRequest: item.sourcePullRequest });
7678

7779
const before = previous.get(item.id);
7880
// Resolve transition (requires a prior non-resolved observation).
7981
if (item.state === AgentFeedbackState.Resolved && before && before.state !== AgentFeedbackState.Resolved) {
80-
threadsToResolve.add(threadId);
82+
threadsToResolve.set(threadId, item.sourcePullRequest);
8183
}
8284
}
8385

@@ -87,7 +89,7 @@ export class AgentFeedbackPRThreadResolverContribution extends Disposable implem
8789
continue;
8890
}
8991
if (before.state === AgentFeedbackState.Submitted || before.state === AgentFeedbackState.Resolved) {
90-
threadsToResolve.add(before.threadId);
92+
threadsToResolve.set(before.threadId, before.pullRequest);
9193
}
9294
}
9395

@@ -102,17 +104,17 @@ export class AgentFeedbackPRThreadResolverContribution extends Disposable implem
102104
requested = new Set<string>();
103105
this._requestedBySession.set(key, requested);
104106
}
105-
for (const threadId of threadsToResolve) {
107+
for (const [threadId, pullRequest] of threadsToResolve) {
106108
if (requested.has(threadId)) {
107109
continue;
108110
}
109111
requested.add(threadId);
110-
this._resolveThread(e.sessionResource, threadId);
112+
this._resolveThread(e.sessionResource, threadId, pullRequest);
111113
}
112114
}
113115

114-
private _resolveThread(sessionResource: URI, threadId: string): void {
115-
this._codeReviewService.resolvePRReviewThread(sessionResource, threadId)
116+
private _resolveThread(sessionResource: URI, threadId: string, pullRequest: IFeedbackPullRequest | undefined): void {
117+
this._codeReviewService.resolvePRReviewThread(sessionResource, threadId, pullRequest)
116118
.catch(err => this._logService.warn('[AgentFeedback] Failed to resolve PR review thread on GitHub', threadId, err));
117119
}
118120
}

0 commit comments

Comments
 (0)