44//! Ports the logic from server/src/ipc.ts to Rust with cross-platform support.
55
66use crate :: types:: {
7- GetSelectionResult , IPCMessage , IPCMessageType , IPCResponse , LogLevel , LogParams ,
8- PresentReviewParams , PresentReviewResult ,
7+ GetSelectionResult , GoodbyePayload , IPCMessage , IPCMessageType , LogLevel , LogParams ,
8+ PoloPayload , PresentReviewParams , PresentReviewResult , ResponsePayload ,
99} ;
1010use futures:: FutureExt ;
1111use serde_json;
@@ -73,7 +73,7 @@ struct IPCCommunicatorInner {
7373 /// Tracks outgoing requests awaiting responses from VSCode extension
7474 /// Key: unique message ID (UUID), Value: channel to send response back to caller
7575 /// Enables concurrent request/response handling with proper correlation
76- pending_requests : HashMap < String , oneshot:: Sender < IPCResponse > > ,
76+ pending_requests : HashMap < String , oneshot:: Sender < ResponsePayload > > ,
7777
7878 /// Flag to track if we have an active connection and reader task
7979 /// When true, ensure_connection() is a no-op
@@ -258,12 +258,96 @@ impl IPCCommunicator {
258258 }
259259 }
260260
261+ /// Send Marco discovery message. In normal workflow,
262+ /// this is actually sent by the *extension* to broadcast
263+ /// "who's out there?" -- but we include it for testing purposes.
264+ pub async fn send_marco ( & self ) -> Result < ( ) > {
265+ if self . test_mode {
266+ info ! ( "Marco discovery message sent (test mode)" ) ;
267+ return Ok ( ( ) ) ;
268+ }
269+
270+ let message = IPCMessage {
271+ message_type : IPCMessageType :: Marco ,
272+ payload : serde_json:: json!( { } ) ,
273+ id : Uuid :: new_v4 ( ) . to_string ( ) ,
274+ } ;
275+
276+ debug ! ( "Sending Marco discovery message" ) ;
277+ self . send_message_without_reply ( message) . await
278+ }
279+
280+ /// Send Polo discovery message (MCP server announces presence with shell PID)
281+ pub async fn send_polo ( & self , terminal_shell_pid : u32 ) -> Result < ( ) > {
282+ if self . test_mode {
283+ info ! (
284+ "Polo discovery message sent (test mode) with shell PID: {}" ,
285+ terminal_shell_pid
286+ ) ;
287+ return Ok ( ( ) ) ;
288+ }
289+
290+ let payload = PoloPayload { terminal_shell_pid } ;
291+ let message = IPCMessage {
292+ message_type : IPCMessageType :: Polo ,
293+ payload : serde_json:: to_value ( payload) ?,
294+ id : Uuid :: new_v4 ( ) . to_string ( ) ,
295+ } ;
296+
297+ debug ! (
298+ "Sending Polo discovery message with shell PID: {}" ,
299+ terminal_shell_pid
300+ ) ;
301+ self . send_message_without_reply ( message) . await
302+ }
303+
304+ /// Send Goodbye discovery message (MCP server announces departure with shell PID)
305+ pub async fn send_goodbye ( & self , terminal_shell_pid : u32 ) -> Result < ( ) > {
306+ if self . test_mode {
307+ info ! (
308+ "Goodbye discovery message sent (test mode) with shell PID: {}" ,
309+ terminal_shell_pid
310+ ) ;
311+ return Ok ( ( ) ) ;
312+ }
313+
314+ let payload = GoodbyePayload { terminal_shell_pid } ;
315+ let message = IPCMessage {
316+ message_type : IPCMessageType :: Goodbye ,
317+ payload : serde_json:: to_value ( payload) ?,
318+ id : Uuid :: new_v4 ( ) . to_string ( ) ,
319+ } ;
320+
321+ debug ! (
322+ "Sending Goodbye discovery message with shell PID: {}" ,
323+ terminal_shell_pid
324+ ) ;
325+ self . send_message_without_reply ( message) . await
326+ }
327+
328+ /// Gracefully shutdown the IPC communicator, sending Goodbye discovery message
329+ pub async fn shutdown ( & self ) -> Result < ( ) > {
330+ if self . test_mode {
331+ info ! ( "IPC shutdown (test mode)" ) ;
332+ return Ok ( ( ) ) ;
333+ }
334+
335+ let shell_pid = {
336+ let inner_guard = self . inner . lock ( ) . await ;
337+ inner_guard. terminal_shell_pid
338+ } ;
339+
340+ self . send_goodbye ( shell_pid) . await ?;
341+ info ! ( "Sent Goodbye discovery message during shutdown" ) ;
342+ Ok ( ( ) )
343+ }
344+
261345 /// Sends an IPC message and waits for a response from VSCode extension
262346 ///
263347 /// Sets up response correlation using the message UUID and waits up to 5 seconds
264348 /// for the background reader task to deliver the matching response.
265349 /// Uses the underlying `write_message` primitive to send the data.
266- async fn send_message_with_reply ( & self , message : IPCMessage ) -> Result < IPCResponse > {
350+ async fn send_message_with_reply ( & self , message : IPCMessage ) -> Result < ResponsePayload > {
267351 debug ! (
268352 "Sending IPC message with ID: {} (PID: {})" ,
269353 message. id,
@@ -512,7 +596,7 @@ impl IPCCommunicator {
512596 }
513597 } ;
514598
515- Self :: handle_response_message ( & inner, & message_str) . await ;
599+ Self :: handle_incoming_message ( & inner, & message_str) . await ;
516600 }
517601 Err ( e) => {
518602 error ! ( "Error reading from IPC connection: {}" , e) ;
@@ -535,39 +619,81 @@ impl IPCCommunicator {
535619 . boxed ( )
536620 }
537621
538- /// Processes incoming response messages from VSCode extension
539- /// Matches responses to pending requests by ID and sends results back to callers
540- async fn handle_response_message ( inner : & Arc < Mutex < IPCCommunicatorInner > > , message_str : & str ) {
622+ /// Processes incoming messages from the daemon
623+ /// Handles both responses to our requests and incoming messages (like Marco)
624+ async fn handle_incoming_message ( inner : & Arc < Mutex < IPCCommunicatorInner > > , message_str : & str ) {
541625 debug ! (
542- "Received IPC response (PID: {}): {}" ,
626+ "Received IPC message (PID: {}): {}" ,
543627 std:: process:: id( ) ,
544628 message_str
545629 ) ;
546630
547- // Parse the response message
548- let response : IPCResponse = match serde_json:: from_str ( message_str) {
549- Ok ( r ) => r ,
631+ // Parse as unified IPCMessage
632+ let message : IPCMessage = match serde_json:: from_str ( message_str) {
633+ Ok ( msg ) => msg ,
550634 Err ( e) => {
551635 error ! (
552- "Failed to parse IPC response : {} - Message: {}" ,
636+ "Failed to parse incoming message : {} - Message: {}" ,
553637 e, message_str
554638 ) ;
555639 return ;
556640 }
557641 } ;
558642
559- // Find the pending request and send the response
560- let mut inner_guard = inner. lock ( ) . await ;
561- if let Some ( sender) = inner_guard. pending_requests . remove ( & response. id ) {
562- if let Err ( _) = sender. send ( response) {
563- warn ! ( "Failed to send response to caller - receiver dropped" ) ;
643+ match message. message_type {
644+ IPCMessageType :: Response => {
645+ // Handle response to our request
646+ let response_payload: ResponsePayload =
647+ match serde_json:: from_value ( message. payload ) {
648+ Ok ( payload) => payload,
649+ Err ( e) => {
650+ error ! ( "Failed to parse response payload: {}" , e) ;
651+ return ;
652+ }
653+ } ;
654+
655+ let mut inner_guard = inner. lock ( ) . await ;
656+ if let Some ( sender) = inner_guard. pending_requests . remove ( & message. id ) {
657+ if let Err ( _) = sender. send ( response_payload) {
658+ warn ! ( "Failed to send response to caller - receiver dropped" ) ;
659+ }
660+ } else {
661+ // Every message (including the ones we send...) gets rebroadcast to everyone,
662+ // so this is (hopefully) to some other MCP server. Just ignore it.
663+ debug ! (
664+ "Received response for unknown request ID: {} (PID: {})" ,
665+ message. id,
666+ std:: process:: id( )
667+ ) ;
668+ }
669+ }
670+ IPCMessageType :: Marco => {
671+ info ! ( "Received Marco discovery message, responding with Polo" ) ;
672+
673+ // Get shell PID from inner state
674+ let shell_pid = {
675+ let inner_guard = inner. lock ( ) . await ;
676+ inner_guard. terminal_shell_pid
677+ } ;
678+
679+ // Create a temporary IPCCommunicator to send Polo response
680+ let temp_communicator = IPCCommunicator {
681+ inner : Arc :: clone ( inner) ,
682+ test_mode : false ,
683+ } ;
684+
685+ if let Err ( e) = temp_communicator. send_polo ( shell_pid) . await {
686+ error ! ( "Failed to send Polo response to Marco: {}" , e) ;
687+ }
688+ }
689+ _ => {
690+ // Every message (including the ones we send...) gets rebroadcast to everyone,
691+ // so we can just ignore anything else.
692+ debug ! (
693+ "Received unhandled message type: {:?}" ,
694+ message. message_type
695+ ) ;
564696 }
565- } else {
566- warn ! (
567- "Received response for unknown request ID: {} (PID: {})" ,
568- response. id,
569- std:: process:: id( )
570- ) ;
571697 }
572698 }
573699}
0 commit comments