@@ -909,4 +909,158 @@ describe('Agency Full Integration', () => {
909909 ) . not . toThrow ( ) ;
910910 } ) ;
911911 } ) ;
912+
913+ // ---------------------------------------------------------------------------
914+ // Task 4: listen() — voice WebSocket transport
915+ // ---------------------------------------------------------------------------
916+
917+ describe ( 'listen() — voice transport' , ( ) => {
918+ it ( 'listen() is present when voice.enabled is true' , ( ) => {
919+ const team = agency ( {
920+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
921+ strategy : 'sequential' ,
922+ voice : { enabled : true } ,
923+ } ) ;
924+
925+ expect ( typeof team . listen ) . toBe ( 'function' ) ;
926+ } ) ;
927+
928+ it ( 'listen() is absent when voice is not configured' , ( ) => {
929+ const team = agency ( {
930+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
931+ strategy : 'sequential' ,
932+ } ) ;
933+
934+ expect ( team . listen ) . toBeUndefined ( ) ;
935+ } ) ;
936+
937+ it ( 'listen() is absent when voice.enabled is false' , ( ) => {
938+ const team = agency ( {
939+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
940+ strategy : 'sequential' ,
941+ voice : { enabled : false } ,
942+ } ) ;
943+
944+ expect ( team . listen ) . toBeUndefined ( ) ;
945+ } ) ;
946+
947+ it ( 'listen() returns port, url, and close function' , async ( ) => {
948+ /**
949+ * This test actually binds a WebSocket server on an OS-assigned port.
950+ * The `ws` package must be resolvable in the test environment.
951+ * If `ws` is not available, the test is skipped gracefully.
952+ */
953+ const team = agency ( {
954+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
955+ strategy : 'sequential' ,
956+ voice : { enabled : true } ,
957+ } ) ;
958+
959+ let result : { port : number ; url : string ; close : ( ) => Promise < void > } | undefined ;
960+ try {
961+ result = await team . listen ! ( ) ;
962+ } catch ( err ) {
963+ const msg = err instanceof Error ? err . message : String ( err ) ;
964+ if ( msg . includes ( 'ws package' ) ) {
965+ // ws not installed in this environment — skip.
966+ return ;
967+ }
968+ throw err ;
969+ }
970+
971+ expect ( typeof result . port ) . toBe ( 'number' ) ;
972+ expect ( result . port ) . toBeGreaterThan ( 0 ) ;
973+ expect ( result . url ) . toMatch ( / ^ w s : \/ \/ 1 2 7 \. 0 \. 0 \. 1 : \d + $ / ) ;
974+ expect ( typeof result . close ) . toBe ( 'function' ) ;
975+
976+ // Clean up the server.
977+ await result . close ( ) ;
978+ } ) ;
979+
980+ it ( 'listen() with explicit port binds to that port' , async ( ) => {
981+ const team = agency ( {
982+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
983+ strategy : 'sequential' ,
984+ voice : { enabled : true } ,
985+ } ) ;
986+
987+ let result : { port : number ; url : string ; close : ( ) => Promise < void > } | undefined ;
988+ try {
989+ result = await team . listen ! ( { port : 0 } ) ;
990+ } catch ( err ) {
991+ const msg = err instanceof Error ? err . message : String ( err ) ;
992+ if ( msg . includes ( 'ws package' ) ) return ;
993+ throw err ;
994+ }
995+
996+ expect ( result . port ) . toBeGreaterThan ( 0 ) ;
997+ await result . close ( ) ;
998+ } ) ;
999+ } ) ;
1000+
1001+ // ---------------------------------------------------------------------------
1002+ // Task 4: connect() — channel adapter wiring
1003+ // ---------------------------------------------------------------------------
1004+
1005+ describe ( 'connect() — channel wiring' , ( ) => {
1006+ it ( 'connect() is present when channels are configured' , ( ) => {
1007+ const team = agency ( {
1008+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
1009+ strategy : 'sequential' ,
1010+ channels : { discord : { token : 'abc123' } } ,
1011+ } ) ;
1012+
1013+ expect ( typeof team . connect ) . toBe ( 'function' ) ;
1014+ } ) ;
1015+
1016+ it ( 'connect() is absent when channels are not configured' , ( ) => {
1017+ const team = agency ( {
1018+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
1019+ strategy : 'sequential' ,
1020+ } ) ;
1021+
1022+ expect ( team . connect ) . toBeUndefined ( ) ;
1023+ } ) ;
1024+
1025+ it ( 'connect() is absent when channels object is empty' , ( ) => {
1026+ const team = agency ( {
1027+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
1028+ strategy : 'sequential' ,
1029+ channels : { } ,
1030+ } ) ;
1031+
1032+ expect ( team . connect ) . toBeUndefined ( ) ;
1033+ } ) ;
1034+
1035+ it ( 'connect() resolves without throwing for multiple channels' , async ( ) => {
1036+ const team = agency ( {
1037+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
1038+ strategy : 'sequential' ,
1039+ channels : {
1040+ discord : { token : 'tok1' } ,
1041+ telegram : { token : 'tok2' } ,
1042+ } ,
1043+ } ) ;
1044+
1045+ await expect ( team . connect ! ( ) ) . resolves . toBeUndefined ( ) ;
1046+ } ) ;
1047+
1048+ it ( 'connect() logs each configured channel without throwing' , async ( ) => {
1049+ const consoleSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } ) ;
1050+
1051+ const team = agency ( {
1052+ agents : { worker : mockAgentConfig ( 'worker' ) } ,
1053+ strategy : 'sequential' ,
1054+ channels : { slack : { webhookUrl : 'https://hooks.slack.com/...' } } ,
1055+ } ) ;
1056+
1057+ await team . connect ! ( ) ;
1058+
1059+ expect ( consoleSpy ) . toHaveBeenCalledWith (
1060+ expect . stringContaining ( 'slack' ) ,
1061+ ) ;
1062+
1063+ consoleSpy . mockRestore ( ) ;
1064+ } ) ;
1065+ } ) ;
9121066} ) ;
0 commit comments