Explore to support desktop notifications when in agent mode (#251621) (#255643)

This commit is contained in:
Benjamin Pasero 2025-07-14 13:45:53 +02:00 committed by GitHub
parent 6a2aa04439
commit b50ac5ac92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 90 additions and 32 deletions

View File

@ -1,8 +0,0 @@
---
description: 'Save learnings from conversation'
tools: ['codebase', 'editFiles', 'githubRepo', 'runCommands', 'search', 'searchResults', 'usages']
---
# Learn mode instructions
Please take a moment and deeply reflect on all the steps you took and think if there would have been a piece of information which would have allowed you to work faster (take less steps).
The file .github/copilot-instructions.md has been already provided to you. Edit the file such that it would contain information which would have made you work faster. Please don't make this too specific to this task, but rather something that is generic but useful enought to ensure speed-ups in the future for other tasks.

View File

@ -108,8 +108,9 @@ function f(x: number, y: string): void { }
### Code Quality
- All files must include Microsoft copyright header
- Prefer async/await over Promises, handle cancellation with `CancellationToken`
- Prefer `async` and `await` over `Promise` and `then` calls
- All user facing messages must be localized using the applicable localization framework (for example `nls.localize()` method)
- Don't add tests to the wrong test suite (e.g., adding to end of file instead of inside relevant suite)
- Look for existing test patterns before creating new structures
- Use `describe` and `test` consistently with existing patterns
- If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task

View File

@ -2,28 +2,26 @@
applyTo: '**/*.ts'
---
# VS Code Copilot Development Instructions
# VS Code Copilot Development Instructions for TypeScript
## ⚠️ MANDATORY COMPILATION CHECK
You MUST check compilation output before running ANY script or declaring work complete!
**CRITICAL: You MUST check compilation output before running ANY script or declaring work complete!**
### Before running any command:
1. **ALWAYS** check the "Core - Build" task output for compilation errors
2. **ALWAYS** check the "Ext - Build" task output for compilation errors
3. **NEVER** run tests if there are compilation errors
4. **FIX** all compilation errors before moving forward
## Scripts
- Use `npm install` to install dependencies if you changed `package.json`
- Use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep <pattern>` to filter tests)
- Use `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests
- Use `npm run valid-layers-check` to check for layering issues
## TypeScript compilation steps
## Compilation Tasks
Typescript compilation errors can be found by running the "Core - Build" and "Ext - Build" tasks:
- **Core - Build**: Compiles the main VS Code TypeScript sources
- **Ext - Build**: Compiles the built-in extensions
- These background tasks may already be running from previous development sessions
- If not already running, start them to get real-time compilation feedback
- The tasks provide incremental compilation, so they will automatically recompile when files change
## TypeScript validation steps
- Use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep <pattern>` to filter tests)
- Use `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests
- Use `npm run valid-layers-check` to check for layering issues

10
.github/prompts/implement.prompt.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
mode: agent
description: 'Implement the solution for a problem.'
tools: ['changes', 'codebase', 'editFiles', 'fetch', 'findTestFiles', 'openSimpleBrowser', 'problems', 'readNotebookCellOutput', 'runCommands', 'runNotebooks', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure', 'usages', 'vscodeAPI']
---
Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally.
Focus on understanding the problem requirements and implementing the correct algorithm. Tests are there to verify correctness, not to define the solution. Provide a principled implementation that follows best practices and software design principles.
If the task is unreasonable or infeasible, or if any of the tests are incorrect, please tell me. The solution should be robust, maintainable, and extendable.

View File

@ -1,11 +1,10 @@
---
mode: agent
description: 'Plan the solution for a problem.'
tools: ['codebase', 'fetch', 'findTestFiles', 'githubRepo', 'get_issue', 'get_issue_comments', 'get_me', 'search', 'searchResults', 'usages', 'vscodeAPI']
---
# Planning mode instructions
You are an expert software engineer tasked with fixing a bug or adding a new feature in the codebase.
Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to:
* Understand the context of the bug by reading the issue description and comments.
* Understand the context of the bug or feature by reading the issue description and comments.
* Understand the codebase by reading the relevant instruction files.
* If its a bug, then identify the root cause of the bug, and explain this to the user.
@ -17,4 +16,4 @@ Ensure the plan consists of a Markdown document that has the following sections:
* Requirements: A list of requirements to resolve the bug or add the new feature.
* Implementation Steps: A detailed list of steps to implement the bug fix or new feature.
Remember, do not make any code edits, just generate a plan.
Remember, do not make any code edits, just generate a plan. Use thinking and reasoning skills to outline the steps needed to achieve the desired outcome.

View File

@ -1589,6 +1589,35 @@ export function triggerUpload(): Promise<FileList | undefined> {
});
}
export interface INotification extends IDisposable {
readonly onClick: event.Event<void>;
}
export async function triggerNotification(message: string, options?: { detail?: string; sticky?: boolean }): Promise<INotification | undefined> {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
return;
}
const disposables = new DisposableStore();
const notification = new Notification(message, {
body: options?.detail,
requireInteraction: options?.sticky
});
const onClick = new event.Emitter<void>();
disposables.add(addDisposableListener(notification, 'click', () => onClick.fire()));
disposables.add(addDisposableListener(notification, 'close', () => disposables.dispose()));
disposables.add(toDisposable(() => notification.close()));
return {
onClick: onClick.event,
dispose: () => disposables.dispose()
};
}
export enum DetectedFullscreenMode {
/**

View File

@ -168,7 +168,7 @@ export class CodeApplication extends Disposable {
const isUrlFromWindow = (requestingUrl?: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}`);
const isUrlFromWebview = (requestingUrl: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeWebview}://`);
const alwaysAllowedPermissions = new Set(['pointerLock']);
const alwaysAllowedPermissions = new Set(['pointerLock', 'notifications']);
const allowedPermissionsInWebview = new Set([
...alwaysAllowedPermissions,

View File

@ -11,7 +11,7 @@ import { Event } from '../../../../base/common/event.js';
import { MarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
import { isLinux, isMacintosh } from '../../../../base/common/platform.js';
import { isMacintosh } from '../../../../base/common/platform.js';
import { assertDefined } from '../../../../base/common/types.js';
import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js';
import * as nls from '../../../../nls.js';
@ -214,8 +214,7 @@ configurationRegistry.registerConfiguration({
},
'chat.notifyWindowOnConfirmation': {
type: 'boolean',
included: !isLinux, // Linux does not have a mechanism for this
description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether the Copilot window should notify the user when a confirmation is needed."),
description: nls.localize('chat.notifyWindowOnConfirmation', "Controls whether the Copilot window should notify the user when a confirmation is needed while the window is not in focus. This includes a window badge as well as notification toast."),
default: true,
},
'chat.tools.autoApprove': {

View File

@ -8,8 +8,9 @@ import { Button, ButtonWithDropdown, IButton, IButtonOptions } from '../../../..
import { Action } from '../../../../../base/common/actions.js';
import { Emitter, Event } from '../../../../../base/common/event.js';
import { IMarkdownString, MarkdownString } from '../../../../../base/common/htmlContent.js';
import { Disposable, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js';
import { IMarkdownRenderResult, MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
import { localize } from '../../../../../nls.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
@ -17,6 +18,8 @@ import { FocusMode } from '../../../../../platform/native/common/native.js';
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
import { IHostService } from '../../../../services/host/browser/host.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
import { showChatView } from '../chat.js';
import './media/chatConfirmationWidget.css';
export interface IChatConfirmationButton {
@ -114,6 +117,8 @@ abstract class BaseChatConfirmationWidget extends Disposable {
private readonly messageElement: HTMLElement;
protected readonly markdownRenderer: MarkdownRenderer;
private readonly notification = this._register(new MutableDisposable<DisposableStore>());
constructor(
title: string | IMarkdownString,
subtitle: string | IMarkdownString | undefined,
@ -122,6 +127,7 @@ abstract class BaseChatConfirmationWidget extends Disposable {
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IHostService private readonly _hostService: IHostService,
@IViewsService private readonly _viewsService: IViewsService
) {
super();
@ -183,10 +189,32 @@ abstract class BaseChatConfirmationWidget extends Disposable {
if (this.showingButtons && this._configurationService.getValue<boolean>('chat.notifyWindowOnConfirmation')) {
const targetWindow = dom.getWindow(listContainer);
if (!targetWindow.document.hasFocus()) {
this._hostService.focus(targetWindow, { mode: FocusMode.Notify });
this.notifyConfirmationNeeded(targetWindow);
}
}
}
private async notifyConfirmationNeeded(targetWindow: Window): Promise<void> {
// Focus Window
this._hostService.focus(targetWindow, { mode: FocusMode.Notify });
// Notify
const notification = await dom.triggerNotification(localize('notificationTitle', "Chat: Confirmation Required"),
{
detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed"),
sticky: true
}
);
if (notification) {
this.notification.value = new DisposableStore();
this.notification.value.add(notification);
this.notification.value.add(Event.once(notification.onClick)(() => {
showChatView(this._viewsService);
}));
}
}
}
export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
@ -202,8 +230,9 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IHostService hostService: IHostService,
@IViewsService viewsService: IViewsService
) {
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService);
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService, viewsService);
this.updateMessage(message);
}
@ -229,8 +258,9 @@ export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget {
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IHostService hostService: IHostService,
@IViewsService viewsService: IViewsService
) {
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService);
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService, viewsService);
this.renderMessage(messageElement, container);
}
}