@@ -800,6 +800,9 @@ export abstract class Protocol<ContextT extends BaseContext> {
800800 ) : Promise < SchemaOutput < T > > {
801801 const { relatedRequestId, resumptionToken, onresumptiontoken } = options ?? { } ;
802802
803+ let onAbort : ( ( ) => void ) | undefined ;
804+ let cleanupMessageId : number | undefined ;
805+
803806 // Send the request
804807 return new Promise < SchemaOutput < T > > ( ( resolve , reject ) => {
805808 const earlyReject = ( error : unknown ) => {
@@ -823,6 +826,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
823826 options ?. signal ?. throwIfAborted ( ) ;
824827
825828 const messageId = this . _requestMessageId ++ ;
829+ cleanupMessageId = messageId ;
826830 const jsonrpcRequest : JSONRPCRequest = {
827831 ...request ,
828832 jsonrpc : '2.0' ,
@@ -841,9 +845,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
841845 }
842846
843847 const cancel = ( reason : unknown ) => {
844- this . _responseHandlers . delete ( messageId ) ;
845848 this . _progressHandlers . delete ( messageId ) ;
846- this . _cleanupTimeout ( messageId ) ;
847849
848850 this . _transport
849851 ?. send (
@@ -885,9 +887,8 @@ export abstract class Protocol<ContextT extends BaseContext> {
885887 }
886888 } ) ;
887889
888- options ?. signal ?. addEventListener ( 'abort' , ( ) => {
889- cancel ( options ?. signal ?. reason ) ;
890- } ) ;
890+ onAbort = ( ) => cancel ( options ?. signal ?. reason ) ;
891+ options ?. signal ?. addEventListener ( 'abort' , onAbort , { once : true } ) ;
891892
892893 const timeout = options ?. timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC ;
893894 const timeoutHandler = ( ) => cancel ( new SdkError ( SdkErrorCode . RequestTimeout , 'Request timed out' , { timeout } ) ) ;
@@ -907,27 +908,38 @@ export abstract class Protocol<ContextT extends BaseContext> {
907908 let outboundQueued = false ;
908909 try {
909910 const taskResult = this . _taskManager . processOutboundRequest ( jsonrpcRequest , options , messageId , responseHandler , error => {
910- this . _cleanupTimeout ( messageId ) ;
911+ this . _progressHandlers . delete ( messageId ) ;
911912 reject ( error ) ;
912913 } ) ;
913914 if ( taskResult . queued ) {
914915 outboundQueued = true ;
915916 }
916917 } catch ( error ) {
917- this . _responseHandlers . delete ( messageId ) ;
918918 this . _progressHandlers . delete ( messageId ) ;
919- this . _cleanupTimeout ( messageId ) ;
920919 reject ( error ) ;
921920 return ;
922921 }
923922
924923 if ( ! outboundQueued ) {
925924 // No related task or no module - send through transport normally
926925 this . _transport . send ( jsonrpcRequest , { relatedRequestId, resumptionToken, onresumptiontoken } ) . catch ( error => {
927- this . _cleanupTimeout ( messageId ) ;
926+ this . _progressHandlers . delete ( messageId ) ;
928927 reject ( error ) ;
929928 } ) ;
930929 }
930+ } ) . finally ( ( ) => {
931+ // Per-request cleanup that must run on every exit path. Consolidated
932+ // here so new exit paths added to the promise body can't forget it.
933+ // _progressHandlers is NOT cleaned up here: _onresponse deletes it
934+ // conditionally (preserveProgress for task flows), and error paths
935+ // above delete it inline since no task exists in those cases.
936+ if ( onAbort ) {
937+ options ?. signal ?. removeEventListener ( 'abort' , onAbort ) ;
938+ }
939+ if ( cleanupMessageId !== undefined ) {
940+ this . _responseHandlers . delete ( cleanupMessageId ) ;
941+ this . _cleanupTimeout ( cleanupMessageId ) ;
942+ }
931943 } ) ;
932944 }
933945
0 commit comments