diff --git a/packages/core-internal/src/shared/protocol.ts b/packages/core-internal/src/shared/protocol.ts index ffab6e8df8..3f5ef4a2a1 100644 --- a/packages/core-internal/src/shared/protocol.ts +++ b/packages/core-internal/src/shared/protocol.ts @@ -1610,7 +1610,8 @@ export abstract class Protocol { const debouncedMethods = this._options?.debouncedNotificationMethods ?? []; // A notification can only be debounced if it's in the list AND it's "simple" // (i.e., has no parameters and no related request ID that could be lost). - const canDebounce = debouncedMethods.includes(notification.method) && !notification.params && !options?.relatedRequestId; + const hasRelatedRequestId = options?.relatedRequestId !== undefined; + const canDebounce = debouncedMethods.includes(notification.method) && !notification.params && !hasRelatedRequestId; if (canDebounce) { // If a notification of this type is already scheduled, do nothing. diff --git a/packages/core-internal/test/shared/protocol.test.ts b/packages/core-internal/test/shared/protocol.test.ts index 2ecdc40adc..496bffa01b 100644 --- a/packages/core-internal/test/shared/protocol.test.ts +++ b/packages/core-internal/test/shared/protocol.test.ts @@ -656,6 +656,29 @@ describe('protocol tests', () => { expect(sendSpy).toHaveBeenCalledWith(expect.any(Object), { relatedRequestId: 'req-2' }); }); + it('should NOT debounce a notification that has relatedRequestId 0', async () => { + protocol = new TestProtocolImpl({ debouncedNotificationMethods: ['test/debounced_with_zero'] }); + await protocol.connect(transport); + + await protocol.notification({ method: 'test/debounced_with_zero' }, { relatedRequestId: 0 }); + await protocol.notification({ method: 'test/debounced_with_zero' }, { relatedRequestId: 0 }); + + expect(sendSpy).toHaveBeenCalledTimes(2); + expect(sendSpy).toHaveBeenNthCalledWith(1, expect.any(Object), { relatedRequestId: 0 }); + expect(sendSpy).toHaveBeenNthCalledWith(2, expect.any(Object), { relatedRequestId: 0 }); + }); + + it('should reject debounced-method notifications when transport send fails with relatedRequestId 0', async () => { + protocol = new TestProtocolImpl({ debouncedNotificationMethods: ['test/debounced_with_zero'] }); + await protocol.connect(transport); + + sendSpy.mockRejectedValueOnce(new Error('send failed')); + + await expect(protocol.notification({ method: 'test/debounced_with_zero' }, { relatedRequestId: 0 })).rejects.toThrow( + 'send failed' + ); + }); + it('should clear pending debounced notifications on connection close', async () => { // ARRANGE protocol = new TestProtocolImpl({ debouncedNotificationMethods: ['test/debounced'] });