
    $j.                    (   U d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m	Z	m
Z
 ddlmZmZmZ ddlZerddlmZ ddlmZ dd	lmZ  ej        e          Zd
ZdZdZdZdZd'dZd(dZ d)dZ! e"            Z#de$d <   i Z%d!e$d"<   d(d#Z&d*d$Z'd+d&Z(dS ),a  Distributed notification queue for background task events (SEP-1686).

Enables distributed Docket workers to send MCP notifications to clients
without holding session references. Workers push to a Redis queue,
the MCP server process subscribes and forwards to the client's session.

Pattern: Fire-and-forward with retry
- One queue per session_id
- LPUSH/BRPOP for reliable ordered delivery
- Retry up to 3 times on delivery failure, then discard
- TTL-based expiration for stale messages

Note: Docket's execution.subscribe() handles task state/progress events via
Redis Pub/Sub. This module handles elicitation-specific notifications that
require reliable delivery (input_required prompts, cancel signals).
    )annotationsN)suppress)datetimetimezone)TYPE_CHECKINGAnycast)Docket)ServerSession)FastMCPz"fastmcp:notifications:{session_id}z)fastmcp:notifications:{session_id}:activei,        
session_idstrnotificationdict[str, Any]docketr
   returnNonec                  K   |                     t                              |                     }t          j        |dt          j        t          j                  	                                d          }|
                                4 d{V }|                    ||           d{V  |                    |t                     d{V  ddd          d{V  dS # 1 d{V swxY w Y   dS )a~  Push notification to session's queue (called from Docket worker).

    Used for elicitation-specific notifications (input_required, cancel)
    that need reliable delivery across distributed processes.

    Args:
        session_id: Target session's identifier
        notification: MCP notification dict (method, params, _meta)
        docket: Docket instance for Redis access
    r   r   )r   attemptenqueued_atN)keyNOTIFICATION_QUEUE_KEYformatjsondumpsr   nowr   utc	isoformatredislpushexpireNOTIFICATION_TTL_SECONDS)r   r   r   r   messager"   s         j/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/fastmcp/server/tasks/notifications.pypush_notificationr(   0   s      **+22j2II
J
JCj(#<55??AA	
 	
 G ||~~ : : : : : : :kk#w'''''''''ll3 8999999999: : : : : : : : : : : : : : : : : : : : : : : : : : : : : :s   >C""
C,/C,sessionr   fastmcpr   c                  K   |                     t                              |                     }|                     t                              |                     }t                              d|            	 	 |                                4 d{V }|                    |dt          dz             d{V  t          t          |                    |gt                               d{V }|s	 ddd          d{V  |\  }}	t          j        |	          }
|
d	         }|
                    d
d          }	 t          ||| ||           d{V  t                              d| |dz              n# t           $ r}|t"          dz
  k     ri|dz   |
d
<   t%          |          |
d<   |                    |t          j        |
                     d{V  t                              d| |dz   |           n"t                              d| t"          |           Y d}~nd}~ww xY wddd          d{V  n# 1 d{V swxY w Y   ny# t,          j        $ r t                              d|            Y dS t           $ r@}t                              d| |           t-          j        d           d{V  Y d}~nd}~ww xY wM)a~  Subscribe to notification queue and forward to session.

    Runs in the MCP server process. Bridges distributed workers to clients.

    This loop:
    1. Maintains a heartbeat (active subscriber marker for debugging)
    2. Blocks on BRPOP waiting for notifications
    3. Forwards notifications to the client's session
    4. Retries failed deliveries, then discards (no dead-letter queue)

    Args:
        session_id: Session identifier to subscribe to
        session: MCP ServerSession for sending notifications
        docket: Docket instance for Redis access
        fastmcp: FastMCP server instance (for elicitation relay)
    r   z/Starting notification subscriber for session %sTN1   )ex)timeoutr   r   r   z1Delivered notification to session %s (attempt %d)   
last_errorz5Requeued notification for session %s (attempt %d): %sz<Discarding notification for session %s after %d attempts: %sz0Notification subscriber cancelled for session %sz0Notification subscriber error for session %s: %s)r   r   r   NOTIFICATION_ACTIVE_KEYloggerdebugr"   setSUBSCRIBER_TIMEOUT_SECONDSr	   r   brpopr   loadsget_send_mcp_notification	ExceptionMAX_DELIVERY_ATTEMPTSr   r#   r   warningasyncioCancelledErrorsleep)r   r)   r   r*   	queue_key
active_keyr"   result_message_bytesr&   notification_dictr   
send_errores                  r'   notification_subscriber_looprI   L   s     , 

188J8OOPPI3::j:QQRRJ
LLBJOOO;#:	#||~~ / / / / / / /ii
C4NQR4RiSSSSSSSSS  $i[:TUU           / / / / / / / / / / / / / $* =*]33$+N$;!!++i330!2J         LLK"!   
 !   !6!:::-4q[	*03J-#kk)TZ5H5HIIIIIIIIIS&#aK&	    Z&1&	  7/ / / / / / / / / / / / / / / / / / / / / / / / / / /b % 	 	 	LLKZXXXEE 	# 	# 	#LLBJPQ   -""""""""""""""		#o;#sz   <I A H;6I 7H; 8E98H;9
H&BH!H;!H&&H;)I ;
II I	I *K:	K6J>>KrF   c           	       K   |                     dd          }|dk    rt          d|           t          j        j                            d|                     di           |                     d          d          }t          j                            |          }|                     |           d{V  |                     di           }|                     d          d	k    r|                     di           }	|	                     d
i           }
|
                     d          }|r|                     d          }|st          	                    d           dS d|
vrt          	                    d|           dS |
d         }ddl
m} t          j         || ||||          d|dd                    }t                              |           |                    t          j                   dS dS dS )a0  Reconstruct MCP notification from dict and send to session.

    For input_required notifications with elicitation metadata, also sends
    a standard elicitation/create request to the client and relays the
    response back to the worker via Redis.

    Args:
        session: MCP ServerSession
        notification_dict: Notification as dict (method, params, _meta)
        session_id: Session identifier (for elicitation relay)
        docket: Docket instance (for notification delivery)
        fastmcp: FastMCP server instance (for elicitation relay)
    methodznotifications/tasks/statusz0Unsupported notification method for subscriber: params_meta)rK   rL   rM   Nstatusinput_requiredz$io.modelcontextprotocol/related-taskelicitationtaskIdz:input_required notification missing taskId, skipping relay
task_scopez_input_required notification for task %s missing task_scope metadata, skipping elicitation relayr   )relay_elicitationzelicitation-relay-   name)r9   
ValueErrormcptypesTaskStatusNotificationmodel_validateServerNotificationsend_notificationr3   r=    fastmcp.server.tasks.elicitationrS   r>   create_task_background_tasksaddadd_done_callbackdiscard)r)   rF   r   r   r*   rK   r   server_notificationrL   metarelated_taskrP   task_idrR   rS   tasks                   r'   r:   r:      sI     ( ""8-IJJF---TFTTUUU93BB2'++Hb99&**733	
 	
 L )66|DD

#
#$7
8
88888888 ""8R00Fzz(/// $$Wb11xx FKK"&&}55 	>jj**G P   <//;  
 %l3JJJJJJJ&!!':wWUU7'"1"+77  D !!$'''""#4#<=====5 0/	> 	>    zset[asyncio.Task[None]]r`   z@dict[str, tuple[asyncio.Task[None], weakref.ref[ServerSession]]]_active_subscribersc                "  K   | t           v rt           |          \  }}|                                s |            dS |                                sM|                                 t          t          j                  5  | d{V  ddd           n# 1 swxY w Y   t           | = t	          j        t          | |||          d| dd                    }|t          j	        |          ft           | <   t                              d|            dS )a  Start notification subscriber if not already running (idempotent).

    Subscriber is created on first task submission and cleaned up on disconnect.
    Safe to call multiple times for the same session.

    Args:
        session_id: Session identifier
        session: MCP ServerSession
        docket: Docket instance
        fastmcp: FastMCP server instance (for elicitation relay)
    Nznotification-subscriber-rT   rU   z.Started notification subscriber for session %s)rj   donecancelr   r>   r?   r_   rI   weakrefrefr3   r4   )r   r)   r   r*   rh   session_refs         r'   ensure_subscriber_runningrq      sU     $ (((/
;kyy{{ 	{{}}8F yy{{ 	KKMMM'011  






              
+ $Z&'JJ8
2A288  D (,W[-A-A&B
#
LLA:NNNNNs   =	BBBc                R  K   | t           vrdS t                               |           \  }}|                                sM|                                 t	          t
          j                  5  | d{V  ddd           n# 1 swxY w Y   t                              d|            dS )zStop notification subscriber for a session.

    Called when session disconnects. Pending messages remain in queue
    for delivery if client reconnects (with TTL expiration).

    Args:
        session_id: Session identifier
    Nz.Stopped notification subscriber for session %s)	rj   poprl   rm   r   r>   r?   r3   r4   )r   rh   rD   s      r'   stop_subscriberrt     s       ,,,!%%j11GD!99;; g,-- 	 	JJJJJJJ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
LLA:NNNNNs   ,	BBBintc                 *    t          t                    S )z2Get number of active subscribers (for monitoring).)lenrj    ri   r'   get_subscriber_country   2  s    "###ri   )r   r   r   r   r   r
   r   r   )
r   r   r)   r   r   r
   r*   r   r   r   )r)   r   rF   r   r   r   r   r
   r*   r   r   r   )r   r   r   r   )r   ru   ))__doc__
__future__r   r>   r   loggingrn   
contextlibr   r   r   typingr   r   r	   	mcp.typesrX   r   r
   mcp.server.sessionr   fastmcp.server.serverr   	getLogger__name__r3   r   r2   r%   r<   r6   r(   rI   r:   r5   r`   __annotations__rj   rq   rt   ry   rx   ri   r'   <module>r      s    " # " " " " "           ' ' ' ' ' ' ' ' + + + + + + + + + +     .000000------		8	$	$ > E     : : : :8V# V# V# V#r@> @> @> @>P .1SUU  2 2 2 2      
%O %O %O %OPO O O O($ $ $ $ $ $ri   