Skip to content

Commit 74054b8

Browse files
authored
Submit active session feedback action and CI check improvements (microsoft#303336)
submit and ci check primary actions
1 parent 582873e commit 74054b8

5 files changed

Lines changed: 333 additions & 70 deletions

File tree

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

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,78 @@
66
import './agentFeedbackEditorInputContribution.js';
77
import './agentFeedbackEditorWidgetContribution.js';
88
import './agentFeedbackOverviewRulerContribution.js';
9+
import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
10+
import { autorun, observableFromEvent } from '../../../../base/common/observable.js';
11+
import { localize } from '../../../../nls.js';
12+
import { MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js';
13+
import { IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
914
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1015
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
11-
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
16+
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
17+
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
1218
import { AgentFeedbackService, IAgentFeedbackService } from './agentFeedbackService.js';
1319
import { AgentFeedbackAttachmentContribution } from './agentFeedbackAttachment.js';
1420
import { AgentFeedbackAttachmentWidget } from './agentFeedbackAttachmentWidget.js';
1521
import { AgentFeedbackEditorOverlay } from './agentFeedbackEditorOverlay.js';
16-
import { registerAgentFeedbackEditorActions } from './agentFeedbackEditorActions.js';
22+
import { hasActiveSessionAgentFeedback, registerAgentFeedbackEditorActions, submitActiveSessionFeedbackActionId } from './agentFeedbackEditorActions.js';
1723
import { IChatAttachmentWidgetRegistry } from '../../../../workbench/contrib/chat/browser/attachments/chatAttachmentWidgetRegistry.js';
1824
import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
25+
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
26+
import { Codicon } from '../../../../base/common/codicons.js';
1927

28+
/**
29+
* Sets the `hasActiveSessionAgentFeedback` context key to true when the
30+
* currently active session has pending agent feedback items.
31+
*/
32+
class ActiveSessionFeedbackContextContribution extends Disposable implements IWorkbenchContribution {
33+
34+
static readonly ID = 'workbench.contrib.activeSessionFeedbackContext';
35+
36+
constructor(
37+
@IContextKeyService contextKeyService: IContextKeyService,
38+
@IAgentFeedbackService agentFeedbackService: IAgentFeedbackService,
39+
@ISessionsManagementService sessionManagementService: ISessionsManagementService,
40+
) {
41+
super();
42+
43+
const contextKey = hasActiveSessionAgentFeedback.bindTo(contextKeyService);
44+
const menuRegistration = this._register(new MutableDisposable());
45+
46+
const feedbackChanged = observableFromEvent(
47+
this,
48+
agentFeedbackService.onDidChangeFeedback,
49+
e => e,
50+
);
51+
52+
this._register(autorun(reader => {
53+
feedbackChanged.read(reader);
54+
const activeSession = sessionManagementService.activeSession.read(reader);
55+
menuRegistration.clear();
56+
if (!activeSession) {
57+
contextKey.set(false);
58+
return;
59+
}
60+
const feedback = agentFeedbackService.getFeedback(activeSession.resource);
61+
const count = feedback.length;
62+
contextKey.set(count > 0);
63+
64+
if (count > 0) {
65+
menuRegistration.value = MenuRegistry.appendMenuItem(MenuId.ChatEditingSessionApplySubmenu, {
66+
command: {
67+
id: submitActiveSessionFeedbackActionId,
68+
icon: Codicon.comment,
69+
title: localize('agentFeedback.submitFeedbackCount', "Submit Feedback ({0})", count),
70+
},
71+
group: 'navigation',
72+
order: 3,
73+
when: ContextKeyExpr.and(IsSessionsWindowContext, hasActiveSessionAgentFeedback),
74+
});
75+
}
76+
}));
77+
}
78+
}
79+
80+
registerWorkbenchContribution2(ActiveSessionFeedbackContextContribution.ID, ActiveSessionFeedbackContextContribution, WorkbenchPhase.AfterRestored);
2081
registerWorkbenchContribution2(AgentFeedbackEditorOverlay.ID, AgentFeedbackEditorOverlay, WorkbenchPhase.AfterRestored);
2182
registerWorkbenchContribution2(AgentFeedbackAttachmentContribution.ID, AgentFeedbackAttachmentContribution, WorkbenchPhase.AfterRestored);
2283

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { IChatEditingService } from '../../../../workbench/contrib/chat/common/e
2424
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
2525
import { ICodeReviewService } from '../../codeReview/browser/codeReviewService.js';
2626
import { getSessionEditorComments } from './sessionEditorComments.js';
27+
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
2728

2829
export const submitFeedbackActionId = 'agentFeedbackEditor.action.submit';
2930
export const navigatePreviousFeedbackActionId = 'agentFeedbackEditor.action.navigatePrevious';
@@ -32,6 +33,8 @@ export const clearAllFeedbackActionId = 'agentFeedbackEditor.action.clearAll';
3233
export const navigationBearingFakeActionId = 'agentFeedbackEditor.navigation.bearings';
3334
export const hasSessionEditorComments = new RawContextKey<boolean>('agentFeedbackEditor.hasSessionComments', false);
3435
export const hasSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasAgentFeedback', false);
36+
export const hasActiveSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasActiveSessionAgentFeedback', false);
37+
export const submitActiveSessionFeedbackActionId = 'agentFeedbackEditor.action.submitActiveSession';
3538

3639
abstract class AgentFeedbackEditorAction extends Action2 {
3740

@@ -190,8 +193,66 @@ class ClearAllFeedbackAction extends AgentFeedbackEditorAction {
190193
}
191194
}
192195

196+
class SubmitActiveSessionFeedbackAction extends Action2 {
197+
198+
static readonly ID = submitActiveSessionFeedbackActionId;
199+
200+
constructor() {
201+
super({
202+
id: SubmitActiveSessionFeedbackAction.ID,
203+
title: localize2('agentFeedback.submitFeedback', 'Submit Feedback'),
204+
icon: Codicon.comment,
205+
category: CHAT_CATEGORY,
206+
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, hasActiveSessionAgentFeedback),
207+
});
208+
}
209+
210+
override async run(accessor: ServicesAccessor): Promise<void> {
211+
const sessionManagementService = accessor.get(ISessionsManagementService);
212+
const agentFeedbackService = accessor.get(IAgentFeedbackService);
213+
const chatWidgetService = accessor.get(IChatWidgetService);
214+
const editorService = accessor.get(IEditorService);
215+
const logService = accessor.get(ILogService);
216+
217+
const activeSession = sessionManagementService.getActiveSession();
218+
if (!activeSession) {
219+
return;
220+
}
221+
222+
const sessionResource = activeSession.resource;
223+
const feedbackItems = agentFeedbackService.getFeedback(sessionResource);
224+
if (feedbackItems.length === 0) {
225+
return;
226+
}
227+
228+
const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);
229+
if (!widget) {
230+
logService.error('[AgentFeedback] Cannot submit feedback: no chat widget found for session', sessionResource.toString());
231+
return;
232+
}
233+
234+
// Close all editors belonging to the session resource
235+
const editorsToClose: IEditorIdentifier[] = [];
236+
for (const { editor, groupId } of editorService.getEditors(EditorsOrder.SEQUENTIAL)) {
237+
const candidates = getActiveResourceCandidates(editor);
238+
const belongsToSession = candidates.some(uri =>
239+
isEqual(agentFeedbackService.getMostRecentSessionForResource(uri), sessionResource)
240+
);
241+
if (belongsToSession) {
242+
editorsToClose.push({ editor, groupId });
243+
}
244+
}
245+
if (editorsToClose.length) {
246+
await editorService.closeEditors(editorsToClose);
247+
}
248+
249+
await widget.acceptInput('act on feedback');
250+
}
251+
}
252+
193253
export function registerAgentFeedbackEditorActions(): void {
194254
registerAction2(SubmitFeedbackAction);
255+
registerAction2(SubmitActiveSessionFeedbackAction);
195256
registerAction2(class extends NavigateFeedbackAction { constructor() { super(false); } });
196257
registerAction2(class extends NavigateFeedbackAction { constructor() { super(true); } });
197258
registerAction2(ClearAllFeedbackAction);

src/vs/sessions/contrib/changes/browser/changesView.contribution.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../work
1212
import { IViewContainersRegistry, ViewContainerLocation, IViewsRegistry, Extensions as ViewContainerExtensions, WindowVisibility } from '../../../../workbench/common/views.js';
1313
import { CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID, ChangesViewPane, ChangesViewPaneContainer } from './changesView.js';
1414
import './changesViewActions.js';
15+
import './fixCIChecksAction.js';
1516
import { ChangesViewController } from './changesViewController.js';
1617

1718
const changesViewIcon = registerIcon('changes-view-icon', Codicon.gitCompare, localize2('changesViewIcon', 'View icon for the Changes view.').value);

src/vs/sessions/contrib/changes/browser/ciStatusWidget.ts

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,10 @@ import { DEFAULT_LABELS_CONTAINER, IResourceLabel, ResourceLabels } from '../../
2323
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
2424
import { GitHubCheckConclusion, GitHubCheckStatus, GitHubCIOverallStatus, IGitHubCICheck } from '../../github/common/types.js';
2525
import { GitHubPullRequestCIModel } from '../../github/browser/models/githubPullRequestCIModel.js';
26+
import { CICheckGroup, buildFixChecksPrompt, getCheckGroup, getCheckStateLabel, getFailedChecks } from './fixCIChecksAction.js';
2627

2728
const $ = dom.$;
2829

29-
const enum CICheckGroup {
30-
Running,
31-
Pending,
32-
Failed,
33-
Successful,
34-
}
35-
3630
interface ICICheckListItem {
3731
readonly check: IGitHubCICheck;
3832
readonly group: CICheckGroup;
@@ -397,17 +391,6 @@ function compareChecks(a: IGitHubCICheck, b: IGitHubCICheck): number {
397391
return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
398392
}
399393

400-
function getCheckGroup(check: IGitHubCICheck): CICheckGroup {
401-
switch (check.status) {
402-
case GitHubCheckStatus.InProgress:
403-
return CICheckGroup.Running;
404-
case GitHubCheckStatus.Queued:
405-
return CICheckGroup.Pending;
406-
case GitHubCheckStatus.Completed:
407-
return isFailedConclusion(check.conclusion) ? CICheckGroup.Failed : CICheckGroup.Successful;
408-
}
409-
}
410-
411394
function getCheckCounts(checks: readonly IGitHubCICheck[]): ICICheckCounts {
412395
let running = 0;
413396
let pending = 0;
@@ -434,10 +417,6 @@ function getCheckCounts(checks: readonly IGitHubCICheck[]): ICICheckCounts {
434417
return { running, pending, failed, successful };
435418
}
436419

437-
function getFailedChecks(checks: readonly IGitHubCICheck[]): readonly IGitHubCICheck[] {
438-
return checks.filter(check => getCheckGroup(check) === CICheckGroup.Failed);
439-
}
440-
441420
function getChecksSummary(checks: readonly IGitHubCICheck[]): string {
442421
const counts = getCheckCounts(checks);
443422
const parts: string[] = [];
@@ -469,33 +448,6 @@ function getChecksSummary(checks: readonly IGitHubCICheck[]): string {
469448
return parts.join(', ');
470449
}
471450

472-
function buildFixChecksPrompt(failedChecks: ReadonlyArray<{ check: IGitHubCICheck; annotations: string }>): string {
473-
const sections = failedChecks.map(({ check, annotations }) => {
474-
const parts = [
475-
`Check: ${check.name}`,
476-
`Status: ${getCheckStateLabel(check)}`,
477-
`Conclusion: ${check.conclusion ?? 'unknown'}`,
478-
];
479-
480-
if (check.detailsUrl) {
481-
parts.push(`Details: ${check.detailsUrl}`);
482-
}
483-
484-
parts.push('', 'Annotations and output:', annotations || 'No output available for this check run.');
485-
return parts.join('\n');
486-
});
487-
488-
return [
489-
'Please fix the failed CI checks for this session immediately.',
490-
'Use the failed check information below, including annotations and check output, to identify the root causes and make the necessary code changes.',
491-
'Focus on resolving these CI failures. Avoid unrelated changes unless they are required to fix the checks.',
492-
'',
493-
'Failed CI checks:',
494-
'',
495-
sections.join('\n\n---\n\n'),
496-
].join('\n');
497-
}
498-
499451
function getHeaderIconAndClass(checks: readonly IGitHubCICheck[], overallStatus: GitHubCIOverallStatus): { icon: ThemeIcon; className: string } {
500452
const counts = getCheckCounts(checks);
501453
if (counts.running > 0) {
@@ -552,22 +504,3 @@ function getCheckStatusClass(check: IGitHubCICheck): string {
552504
return 'ci-status-success';
553505
}
554506
}
555-
556-
function getCheckStateLabel(check: IGitHubCICheck): string {
557-
switch (getCheckGroup(check)) {
558-
case CICheckGroup.Running:
559-
return localize('ci.runningState', "running");
560-
case CICheckGroup.Pending:
561-
return localize('ci.pendingState', "pending");
562-
case CICheckGroup.Failed:
563-
return localize('ci.failedState', "failed");
564-
case CICheckGroup.Successful:
565-
return localize('ci.successfulState', "successful");
566-
}
567-
}
568-
569-
function isFailedConclusion(conclusion: GitHubCheckConclusion | undefined): boolean {
570-
return conclusion === GitHubCheckConclusion.Failure
571-
|| conclusion === GitHubCheckConclusion.TimedOut
572-
|| conclusion === GitHubCheckConclusion.ActionRequired;
573-
}

0 commit comments

Comments
 (0)