From 17c22d233fbe8e612370e0bfecd9c82bb2eb606f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:22:52 +0000 Subject: [PATCH 1/3] Initial plan From 0f102f5aee07733d4ba43a16bbcebab1c35976cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:43:55 +0000 Subject: [PATCH 2/3] Fix timeout issue: change resetTimeoutOnProgress default to true Co-authored-by: andrea-tomassi <4680279+andrea-tomassi@users.noreply.github.com> --- src/shared/protocol.test.ts | 18 +++++++++++++++++- src/shared/protocol.ts | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/shared/protocol.test.ts b/src/shared/protocol.test.ts index f4e74c8bb..483bb0760 100644 --- a/src/shared/protocol.test.ts +++ b/src/shared/protocol.test.ts @@ -82,6 +82,13 @@ describe("protocol tests", () => { }); describe("_meta preservation with onprogress", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.useRealTimers(); + }); + test("should preserve existing _meta when adding progressToken", async () => { await protocol.connect(transport); const request = { @@ -101,6 +108,8 @@ describe("protocol tests", () => { protocol.request(request, mockSchema, { onprogress: onProgressMock, + resetTimeoutOnProgress: false, + }); expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -133,6 +142,8 @@ describe("protocol tests", () => { protocol.request(request, mockSchema, { onprogress: onProgressMock, + resetTimeoutOnProgress: false, + }); expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({ @@ -163,7 +174,10 @@ describe("protocol tests", () => { result: z.string(), }); - protocol.request(request, mockSchema); + protocol.request(request, mockSchema, { + resetTimeoutOnProgress: false, + + }); expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({ method: "example", @@ -190,6 +204,8 @@ describe("protocol tests", () => { protocol.request(request, mockSchema, { onprogress: onProgressMock, + resetTimeoutOnProgress: false, + }); expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({ diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index 7df190ba1..142c634a9 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -83,7 +83,7 @@ export type RequestOptions = { /** * If true, receiving a progress notification will reset the request timeout. * This is useful for long-running operations that send periodic progress updates. - * Default: false + * Default: true */ resetTimeoutOnProgress?: boolean; @@ -628,7 +628,7 @@ export abstract class Protocol< { timeout } )); - this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? false); + this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? true); this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error) => { this._cleanupTimeout(messageId); From b711289249a80188b9e7beb0cae504ad616a4ada Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:46:04 +0000 Subject: [PATCH 3/3] Add test for default timeout reset behavior and complete implementation Co-authored-by: andrea-tomassi <4680279+andrea-tomassi@users.noreply.github.com> --- src/shared/protocol.test.ts | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/shared/protocol.test.ts b/src/shared/protocol.test.ts index 483bb0760..b1dc87b84 100644 --- a/src/shared/protocol.test.ts +++ b/src/shared/protocol.test.ts @@ -481,6 +481,58 @@ describe("protocol tests", () => { await Promise.resolve(); await expect(requestPromise).resolves.toEqual({ result: "success" }); }); + + test("should reset timeout by default when progress notification is received", async () => { + await protocol.connect(transport); + const request = { method: "example", params: {} }; + const mockSchema: ZodType<{ result: string }> = z.object({ + result: z.string(), + }); + const onProgressMock = jest.fn(); + // Don't specify resetTimeoutOnProgress, should default to true + const requestPromise = protocol.request(request, mockSchema, { + timeout: 1000, + onprogress: onProgressMock, + }); + + // Advance past most of the timeout period + jest.advanceTimersByTime(900); + + // Send progress notification - should reset timeout + if (transport.onmessage) { + transport.onmessage({ + jsonrpc: "2.0", + method: "notifications/progress", + params: { + progressToken: 0, + progress: 50, + total: 100, + }, + }); + } + await Promise.resolve(); + + expect(onProgressMock).toHaveBeenCalledWith({ + progress: 50, + total: 100, + }); + + // Advance another 900ms (would have timed out without reset) + jest.advanceTimersByTime(900); + + // Send final response + if (transport.onmessage) { + transport.onmessage({ + jsonrpc: "2.0", + id: 0, + result: { result: "completed" }, + }); + } + await Promise.resolve(); + + // Should complete successfully because timeout was reset + await expect(requestPromise).resolves.toEqual({ result: "completed" }); + }); }); describe("Debounced Notifications", () => {