It all depends on your custom plumbing and protocol as to whether a module is aware of another.
For instance, consider a module that does nothing but communicate with a set of services. Consider a view module that has need of that service module's functionality.
You could plumb it so that the shell has the reference to both modules, and the view module sends messages to the shell, and the shell rebroadcasts them to the service module, and also forwards the responses back to the view module. That makes the Shell the middleman for all transactions between the view and service modules, simply due to brute force plumbing and protocol. The message name constants must be shared by the shell, the view module and the service module.
Imagine instead that the Shell creates the view module and the view module sends the shell a request to have a service module connected. The shell instantiates and plumbs the service module bidirectionally to the view module and sends the view module a message with the name of the pipe to the service module.
From that point on the view module sends messages to the service module on that pipe and the service module responds on stdout which comes into the view's stdin. The message constants need only be shared between the view and service modules and the shell does not need to participate in ongoing communications. And the view module need not reference the service module to get the constants, you put protocol constants in separate classes in a library and have every module include the constants it needs for the protocols it will participate in.
There are many approaches to the plumbing. Here is a deeplink into the MultiCore presentation describing various arrangements: http://puremvc.tv/#P002/T235