@@ -758,6 +758,184 @@ describe("Connection", () => {
758758 } ) ;
759759 } ) ;
760760
761+ it ( "resolves closed promise when stream ends" , async ( ) => {
762+ const closeLog : string [ ] = [ ] ;
763+
764+ // Create simple client and agent
765+ class TestClient implements Client {
766+ async writeTextFile (
767+ _ : WriteTextFileRequest ,
768+ ) : Promise < WriteTextFileResponse > {
769+ return { } ;
770+ }
771+ async readTextFile (
772+ _ : ReadTextFileRequest ,
773+ ) : Promise < ReadTextFileResponse > {
774+ return { content : "test" } ;
775+ }
776+ async requestPermission (
777+ _ : RequestPermissionRequest ,
778+ ) : Promise < RequestPermissionResponse > {
779+ return {
780+ outcome : {
781+ outcome : "selected" ,
782+ optionId : "allow" ,
783+ } ,
784+ } ;
785+ }
786+ async sessionUpdate ( _ : SessionNotification ) : Promise < void > {
787+ // no-op
788+ }
789+ }
790+
791+ class TestAgent implements Agent {
792+ async initialize ( _ : InitializeRequest ) : Promise < InitializeResponse > {
793+ return {
794+ protocolVersion : PROTOCOL_VERSION ,
795+ agentCapabilities : { loadSession : false } ,
796+ } ;
797+ }
798+ async newSession ( _ : NewSessionRequest ) : Promise < NewSessionResponse > {
799+ return { sessionId : "test-session" } ;
800+ }
801+ async authenticate ( _ : AuthenticateRequest ) : Promise < void > {
802+ // no-op
803+ }
804+ async prompt ( _ : PromptRequest ) : Promise < PromptResponse > {
805+ return { stopReason : "end_turn" } ;
806+ }
807+ async cancel ( _ : CancelNotification ) : Promise < void > {
808+ // no-op
809+ }
810+ }
811+
812+ // Set up connections
813+ const agentConnection = new ClientSideConnection (
814+ ( ) => new TestClient ( ) ,
815+ ndJsonStream ( clientToAgent . writable , agentToClient . readable ) ,
816+ ) ;
817+
818+ const clientConnection = new AgentSideConnection (
819+ ( ) => new TestAgent ( ) ,
820+ ndJsonStream ( agentToClient . writable , clientToAgent . readable ) ,
821+ ) ;
822+
823+ // Listen for close via signal
824+ agentConnection . signal . addEventListener ( "abort" , ( ) => {
825+ closeLog . push ( "agent connection closed (signal)" ) ;
826+ } ) ;
827+
828+ clientConnection . signal . addEventListener ( "abort" , ( ) => {
829+ closeLog . push ( "client connection closed (signal)" ) ;
830+ } ) ;
831+
832+ // Verify connections are not closed yet
833+ expect ( agentConnection . signal . aborted ) . toBe ( false ) ;
834+ expect ( clientConnection . signal . aborted ) . toBe ( false ) ;
835+ expect ( closeLog ) . toHaveLength ( 0 ) ;
836+
837+ // Close the streams by closing the writable ends
838+ await clientToAgent . writable . close ( ) ;
839+ await agentToClient . writable . close ( ) ;
840+
841+ // Wait for closed promises to resolve
842+ await agentConnection . closed ;
843+ await clientConnection . closed ;
844+
845+ // Verify connections are now closed
846+ expect ( agentConnection . signal . aborted ) . toBe ( true ) ;
847+ expect ( clientConnection . signal . aborted ) . toBe ( true ) ;
848+ expect ( closeLog ) . toContain ( "agent connection closed (signal)" ) ;
849+ expect ( closeLog ) . toContain ( "client connection closed (signal)" ) ;
850+ } ) ;
851+
852+ it ( "supports removing signal event listeners" , async ( ) => {
853+ const closeLog : string [ ] = [ ] ;
854+
855+ // Create simple client and agent
856+ class TestClient implements Client {
857+ async writeTextFile (
858+ _ : WriteTextFileRequest ,
859+ ) : Promise < WriteTextFileResponse > {
860+ return { } ;
861+ }
862+ async readTextFile (
863+ _ : ReadTextFileRequest ,
864+ ) : Promise < ReadTextFileResponse > {
865+ return { content : "test" } ;
866+ }
867+ async requestPermission (
868+ _ : RequestPermissionRequest ,
869+ ) : Promise < RequestPermissionResponse > {
870+ return {
871+ outcome : {
872+ outcome : "selected" ,
873+ optionId : "allow" ,
874+ } ,
875+ } ;
876+ }
877+ async sessionUpdate ( _ : SessionNotification ) : Promise < void > {
878+ // no-op
879+ }
880+ }
881+
882+ class TestAgent implements Agent {
883+ async initialize ( _ : InitializeRequest ) : Promise < InitializeResponse > {
884+ return {
885+ protocolVersion : PROTOCOL_VERSION ,
886+ agentCapabilities : { loadSession : false } ,
887+ } ;
888+ }
889+ async newSession ( _ : NewSessionRequest ) : Promise < NewSessionResponse > {
890+ return { sessionId : "test-session" } ;
891+ }
892+ async authenticate ( _ : AuthenticateRequest ) : Promise < void > {
893+ // no-op
894+ }
895+ async prompt ( _ : PromptRequest ) : Promise < PromptResponse > {
896+ return { stopReason : "end_turn" } ;
897+ }
898+ async cancel ( _ : CancelNotification ) : Promise < void > {
899+ // no-op
900+ }
901+ }
902+
903+ // Set up connections
904+ const agentConnection = new ClientSideConnection (
905+ ( ) => new TestClient ( ) ,
906+ ndJsonStream ( clientToAgent . writable , agentToClient . readable ) ,
907+ ) ;
908+
909+ new AgentSideConnection (
910+ ( ) => new TestAgent ( ) ,
911+ ndJsonStream ( agentToClient . writable , clientToAgent . readable ) ,
912+ ) ;
913+
914+ // Register and then remove a listener
915+ const listener = ( ) => {
916+ closeLog . push ( "this should not be called" ) ;
917+ } ;
918+
919+ agentConnection . signal . addEventListener ( "abort" , listener ) ;
920+ agentConnection . signal . removeEventListener ( "abort" , listener ) ;
921+
922+ // Register another listener that should be called
923+ agentConnection . signal . addEventListener ( "abort" , ( ) => {
924+ closeLog . push ( "agent connection closed" ) ;
925+ } ) ;
926+
927+ // Close the streams
928+ await clientToAgent . writable . close ( ) ;
929+ await agentToClient . writable . close ( ) ;
930+
931+ // Wait for closed promise
932+ await agentConnection . closed ;
933+
934+ // Verify only the non-removed listener was called
935+ expect ( closeLog ) . toEqual ( [ "agent connection closed" ] ) ;
936+ expect ( closeLog ) . not . toContain ( "this should not be called" ) ;
937+ } ) ;
938+
761939 it ( "handles methods returning response objects with _meta or void" , async ( ) => {
762940 // Create client that returns both response objects and void
763941 class TestClient implements Client {
0 commit comments