diff --git a/package.json b/package.json index 121adffe67..46d74bf1e5 100644 --- a/package.json +++ b/package.json @@ -297,6 +297,12 @@ "default": "off", "description": "%githubPullRequests.notifications.description%" }, + "githubPullRequests.notificationsRefreshInterval": { + "type": "number", + "default": 600, + "minimum": 60, + "description": "%githubPullRequests.notificationsRefreshInterval.description%" + }, "githubPullRequests.fileListLayout": { "type": "string", "enum": [ diff --git a/package.nls.json b/package.nls.json index 73c02b7684..4f05c33e21 100644 --- a/package.nls.json +++ b/package.nls.json @@ -35,6 +35,7 @@ "githubPullRequests.queries.createdByMe": "Created By Me", "githubPullRequests.defaultMergeMethod.description": "The method to use when merging pull requests.", "githubPullRequests.notifications.description": "If GitHub notifications should be shown to the user.", + "githubPullRequests.notificationsRefreshInterval.description": "The interval in seconds at which to refresh GitHub notifications. Default is 600 seconds (10 minutes). Minimum is 60 seconds.", "githubPullRequests.fileListLayout.description": "The layout to use when displaying changed files list.", "githubPullRequests.hideViewedFiles.description": "Hide files that have been marked as viewed in the pull request changes tree.", "githubPullRequests.defaultDeletionMethod.selectLocalBranch.description": "When true, the option to delete the local branch will be selected by default when deleting a branch from a pull request.", diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 71520fa1ec..aa7001a3d2 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -105,6 +105,7 @@ declare module 'vscode' { isComplete?: boolean; toolSpecificData?: ChatTerminalToolInvocationData; fromSubAgent?: boolean; + presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); } diff --git a/src/common/settingKeys.ts b/src/common/settingKeys.ts index 1376c4c7bc..a98e7e87c2 100644 --- a/src/common/settingKeys.ts +++ b/src/common/settingKeys.ts @@ -62,6 +62,7 @@ export const EXPERIMENTAL_CHAT = 'experimental.chat'; export const EXPERIMENTAL_USE_QUICK_CHAT = 'experimental.useQuickChat'; export const EXPERIMENTAL_NOTIFICATIONS_PAGE_SIZE = 'experimental.notificationsViewPageSize'; export const EXPERIMENTAL_NOTIFICATIONS_SCORE = 'experimental.notificationsScore'; +export const NOTIFICATIONS_REFRESH_INTERVAL = 'notificationsRefreshInterval'; export const WEBVIEW_REFRESH_INTERVAL = 'webviewRefreshInterval'; // git diff --git a/src/notifications/notificationsManager.ts b/src/notifications/notificationsManager.ts index 86fdfcaeab..2269782d63 100644 --- a/src/notifications/notificationsManager.ts +++ b/src/notifications/notificationsManager.ts @@ -9,7 +9,7 @@ import { NotificationsProvider } from './notificationsProvider'; import { commands, contexts } from '../common/executeCommands'; import { Disposable } from '../common/lifecycle'; import Logger from '../common/logger'; -import { NOTIFICATION_SETTING, NotificationVariants, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; +import { NOTIFICATION_SETTING, NOTIFICATIONS_REFRESH_INTERVAL, NotificationVariants, PR_SETTINGS_NAMESPACE } from '../common/settingKeys'; import { EventType, TimelineEvent } from '../common/timelineEvent'; import { toNotificationUri } from '../common/uri'; import { CredentialStore } from '../github/credentials'; @@ -47,8 +47,8 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP private _fetchNotifications: boolean = false; private _notifications = new Map(); - private _pollingDuration: number = 60; // Default polling duration private _pollingHandler: NodeJS.Timeout | null; + private _pollingDisposable: vscode.Disposable | null; private _pollingLastModified: string; private _sortingMethod: NotificationsSortMethod = NotificationsSortMethod.Timestamp; @@ -70,6 +70,12 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP this._startPolling(); } } + if (e.affectsConfiguration(`${PR_SETTINGS_NAMESPACE}.${NOTIFICATIONS_REFRESH_INTERVAL}`)) { + if (this.isPRNotificationsOn()) { + // Restart polling with new interval (stopPolling is called inside startPolling) + this._startPolling(); + } + } })); this._register(PullRequestOverviewPanel.onVisible(e => { this.markPrNotificationsAsRead(e); @@ -195,7 +201,7 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP } public async getNotifications(): Promise { - let pollInterval = this._pollingDuration; + let pollInterval = this._getRefreshInterval(); let lastModified = this._pollingLastModified; if (this._fetchNotifications) { // Get raw notifications @@ -412,6 +418,10 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP return (vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(NOTIFICATION_SETTING) === 'pullRequests'); } + private _getRefreshInterval(): number { + return vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get(NOTIFICATIONS_REFRESH_INTERVAL, 600); + } + private async _pollForNewNotifications() { this._pageCount = 1; this._dateTime = new Date(); @@ -423,16 +433,8 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP return; } - // Adapt polling interval if it has changed. - if (response.pollInterval !== this._pollingDuration) { - this._pollingDuration = response.pollInterval; - if (this._pollingHandler && this.isPRNotificationsOn()) { - Logger.appendLine('Notifications: Clearing interval', NotificationsManager.ID); - clearInterval(this._pollingHandler); - Logger.appendLine(`Notifications: Starting new polling interval with ${this._pollingDuration}`, NotificationsManager.ID); - this._startPolling(); - } - } + // Use the user-configured interval instead of GitHub's poll interval to respect user preferences. + // This allows users to control notification refresh frequency for managing notification volume and API rate limits. if (response.lastModified !== this._pollingLastModified) { this._pollingLastModified = response.lastModified; this._onDidChangeTreeData.fire(); @@ -440,19 +442,42 @@ export class NotificationsManager extends Disposable implements vscode.TreeDataP // this._onDidChangeNotifications.fire(oldPRNodesToUpdate); } + private _stopPolling() { + if (this._pollingDisposable) { + this._pollingDisposable.dispose(); + this._pollingDisposable = null; + } + if (this._pollingHandler) { + Logger.appendLine('Notifications: Clearing interval', NotificationsManager.ID); + clearInterval(this._pollingHandler); + this._pollingHandler = null; + } + } + private _startPolling() { if (!this.isPRNotificationsOn()) { return; } + // Stop any existing polling to prevent resource leaks + this._stopPolling(); + + const refreshInterval = this._getRefreshInterval(); + Logger.appendLine(`Notifications: Starting polling with interval ${refreshInterval} seconds`, NotificationsManager.ID); this._pollForNewNotifications(); this._pollingHandler = setInterval( function (notificationProvider: NotificationsManager) { notificationProvider._pollForNewNotifications(); }, - this._pollingDuration * 1000, + refreshInterval * 1000, this ); - this._register({ dispose: () => clearInterval(this._pollingHandler!) }); + this._pollingDisposable = this._register({ + dispose: () => { + if (this._pollingHandler) { + clearInterval(this._pollingHandler); + } + } + }); } private _findNotificationKeyForIssueModel(issueModel: IssueModel | PullRequestModel | { owner: string; repo: string; number: number }): string | undefined {