
    `gj-                    p   U d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m
Z
mZmZmZ  ej                  e      Ze G d d             Z ej$                         Zi Zded	<   i Zd
ed<   	 	 	 	 	 	 	 	 	 	 ddZddZddZdd	 	 	 	 	 d dZd!dZd"dZd#dZd$dZd%dZd&dZ i Z!ded<   d'dZ"d(dZ#d)dZ$y)*u  Gateway-side clarify primitive (blocking event-based queue).

The ``clarify`` tool needs to ask the user a question and block the agent
thread until they respond.  In CLI mode this is trivial — ``input()`` is
synchronous.  In gateway mode the agent runs on a worker thread while the
event loop handles the user's reply, so we need a thread-safe primitive
that:

  * stores a pending clarify request (with a generated ``clarify_id``),
  * blocks the agent thread on an ``Event``,
  * resolves the wait when the gateway's button-callback or text-intercept
    fires ``resolve_gateway_clarify(clarify_id, response)``,
  * supports timeouts so a user who never responds does NOT hang the agent
    thread forever (which would also pin the gateway's running-agent guard).

State is module-level (same shape as ``tools.approval``) so platform
adapters can call ``resolve_gateway_clarify`` without holding a back-
reference to the ``GatewayRunner`` instance.

Two delivery paths from the adapter:

  1. **Button UI** — adapters override ``send_clarify`` to render inline
     buttons (e.g. Telegram ``InlineKeyboardMarkup``).  The button
     callback resolves with the chosen string.  A final "Other (type
     answer)" button enters text-capture mode for free-form responses.

  2. **Text fallback** — adapters without rich UI render a numbered list.
     The user replies with a number ("2") or with free text; the gateway's
     ``_handle_message`` intercepts the reply and resolves directly.
    )annotationsN)	dataclassfield)CallableDictListOptionalc                      e Zd ZU dZded<   ded<   ded<   ded<    eej                        Zd	ed
<   dZ	ded<   dZ
ded<   ddZy)_ClarifyEntryz5One pending clarify request inside a gateway session.str
clarify_idsession_keyquestionOptional[List[str]]choices)default_factoryzthreading.EventeventNOptional[str]responseFboolawaiting_textc                    | j                   | j                  | j                  | j                  rt	        | j                        dS d dS )N)r   r   r   r   )r   r   r   r   list)selfs    H/root/.hermes/venv/lib/python3.12/site-packages/tools/clarify_gateway.py	signaturez_ClarifyEntry.signature:   sF    //++-1\\tDLL)	
 	
 @D	
 	
    )returnzDict[str, object])__name__
__module____qualname____doc____annotations__r   	threadingEventr   r   r   r    r   r   r   r   /   sG    ?OM  "9??CE?C"Hm"M4
r   r   zDict[str, _ClarifyEntry]_entrieszDict[str, List[str]]_session_indexc           	         t        | |||rt        |      ndt        |             }t        5  |t        | <   t
        j                  |g       j                  |        ddd       |S # 1 sw Y   |S xY w)zRegister a pending clarify request and return the entry.

    The caller (gateway clarify_callback) will then send the prompt to the
    user and block on ``wait_for_response(clarify_id, timeout)``.
    N)r   r   r   r   r   )r   r   r   _lockr'   r(   
setdefaultappend)r   r   r   r   entrys        r   registerr.   N   sw     !(Wdw-'E 
 F$!!+r299*EF LF Ls   /A''A1c                   t         5  t        j                  |       }ddd       y	 ddlm} t        j                         t        |d      z   }t        j                         t        j                         d}	 |t        j                         z
  }|dk  rn3|j                  j                  t        d|            rn|	 ||d       Pt         5  t        j                  | d       t        j                  |j                        }|r7| |v r3|j                  |        |s t        j                  |j                  d       ddd       |j                   S # 1 sw Y   8xY w# t
        $ r d}Y ?w xY w# 1 sw Y   |j                   S xY w)	u  Block on the entry's event until resolved or timeout fires.

    Polls in 1-second slices so the agent's inactivity heartbeat keeps
    firing — without this, ``Event.wait(timeout=600)`` blocks the thread
    for 10 minutes with zero activity touches and the gateway's inactivity
    watchdog kills the agent while the user is still typing.

    Returns the resolved response string, or ``None`` on timeout.
    Nr   )touch_activity_if_dueg        )
last_touchstartg      ?)timeoutz!waiting for user clarify response)r*   r'   gettools.environments.baser0   	Exceptiontime	monotonicmaxr   waitminpopr(   r   remover   )r   r3   r-   r0   deadlineactivity_state	remainingidss           r   wait_for_responserB   g   sU    
 )Z()}%A ~~#gs"33H$(NN$4t~~?OPN
t~~//	>;;CY$78 ,!.2UV  
 <Z&  !2!23:$JJz"""5#4#4d;< >>=) )  % $%< >>s)   EE  A/E2E E/.E/2Fc                    t         5  t        j                  |       }|
	 ddd       y	 ddd       |t        |      nd_        |j
                  j                          y# 1 sw Y   8xY w)zUnblock the agent thread waiting on ``clarify_id``.

    Returns True if an entry was found and resolved, False otherwise
    (already resolved, expired, or never existed).
    NF T)r*   r'   r4   r   r   r   set)r   r   r-   s      r   resolve_gateway_clarifyrF      sd     
 Z(=  '/&:S]EN	KKOO s   A!!A*Finclude_choice_promptsc                   t         5  t        j                  |       xs g }|D ]5  }t        j                  |      }||s|j                  s*|c cddd       S  	 ddd       y# 1 sw Y   yxY w)a  Return the oldest pending clarify entry for a session, or None.

    By default this only returns entries awaiting free-form text (open-ended
    clarifies, or a multi-choice clarify after the user picked ``Other``).
    Gateways may pass ``include_choice_prompts=True`` when the user has typed
    directly in response to an active multi-choice prompt; in that case the
    oldest unresolved clarify is returned so the text can resolve it instead
    of being queued as an unrelated follow-up turn.
    N)r*   r(   r4   r'   r   )r   rH   rA   cidr-   s        r   get_pending_for_sessionrK      su     
   -3 	CLL%E}%)<)< 	   s   AA%A%A%%A.c                   t        |      j                         }| j                  r	 t        |      dz
  }d|cxk  rt        | j                        k  rn n| j                  |   S | j                  D ]U  }|j                         t        |      j                         j                         k(  s<t        |      j                         c S  |S # t        $ r d}Y w xY w)zNMap typed choice replies to canonical choice text, otherwise keep custom text.   r   )r   stripr   int
ValueErrorlencasefold)r-   r   textidxchoices        r   _coerce_text_responserW      s    x= D}}	d)a-C (c%--((==%%mm 	+F}}#f+"3"3"5">">"@@6{((**	+ K  	C	s   C CCc                b    t        | d      }|yt        |j                  t        ||            S )zFResolve the oldest pending clarify in ``session_key`` from typed text.TrG   F)rK   rF   r   rW   )r   r   r-   s      r   !resolve_text_response_for_sessionrY      s6    #KME}"eX. r   c                    t         5  t        j                  |       }|
	 ddd       yd|_        	 ddd       y# 1 sw Y   yxY w)zFlip an entry into text-capture mode (user picked the 'Other' button).

    Returns True if the entry exists and was flipped, False otherwise.
    NFT)r*   r'   r4   r   )r   r-   s     r   mark_awaiting_textr[      sI    
 
 Z(=  #  s   ::Ac                    t         5  t        j                  |       xs g }t        d |D              cddd       S # 1 sw Y   yxY w)zEReturn True when this session has at least one pending clarify entry.c              3  J   K   | ]  }t         j                  |      d u  y wN)r'   r4   ).0rJ   s     r   	<genexpr>zhas_pending.<locals>.<genexpr>   s     @S8<<$D0@s   !#N)r*   r(   r4   any)r   rA   s     r   has_pendingrb      sA    	 A  -3@C@@A A As	   +<Ac                6   t         5  t        t        j                  | g       xs g       }|D cg c]  }t        j                  |d       }}ddd       d}D ]+  }|d|_        |j                  j                          |dz  }- |S c c}w # 1 sw Y   BxY w)a  Resolve and drop every pending clarify for a session.

    Used by session-boundary cleanup (e.g. ``/new``, gateway shutdown,
    cached-agent eviction) so blocked agent threads don't hang past the
    end of their session.  Returns the number of entries cancelled.
    Nr   rD   rM   )r*   r   r(   r<   r'   r   r   rE   )r   rA   rJ   entries	cancelledr-   s         r   clear_sessionrf      s     
 ;>%%k26<"=69:s8<<T*::; I 	=
 Q		  ;; ;s   (BB
B
BBc                     	 ddl m}   |        xs i }|j                  di       xs i }t        |j                  dd            S # t        $ r Y yw xY w)u;  Read the clarify response timeout (seconds) from config.

    Defaults to 3600 (1 hour) — long enough that a user who steps away
    (meeting, AFK, slow to read) still finds a live entry when they tap
    the button, short enough that a genuinely abandoned prompt eventually
    unblocks the agent thread instead of pinning the running-agent guard
    forever.  The old 600s default evicted the entry mid-think, so a late
    tap landed on a dead entry and the agent hung on ``running: clarify``
    (#32762).

    Reads ``agent.clarify_timeout`` from config.yaml.
    r   )load_configagentclarify_timeouti  )hermes_cli.configrh   r4   rP   r6   )rh   cfg	agent_cfgs      r   get_clarify_timeoutrn   	  sT    1m!rGGGR(.B	9==!2D9:: s   AA 	AAz*Dict[str, Callable[[_ClarifyEntry], None]]_notify_cbsc                L    t         5  |t        | <   ddd       y# 1 sw Y   yxY w)zDRegister a per-session notify callback used by ``clarify_callback``.N)r*   ro   )r   cbs     r   register_notifyrr   *  s$    	 &#%K & & &s   
#c                |    t         5  t        j                  | d       ddd       t        |        y# 1 sw Y   xY w)zLDrop the per-session notify callback and cancel any pending clarify entries.N)r*   ro   r<   rf   r   s    r   unregister_notifyru   0  s2    	 +T*+ +	+ +s   2;c                d    t         5  t        j                  |       cd d d        S # 1 sw Y   y xY wr^   )r*   ro   r4   rt   s    r   
get_notifyrw   9  s&    	 ,{+, , ,s   &/)
r   r   r   r   r   r   r   r   r   r   )r   r   r3   floatr   r   )r   r   r   r   r   r   )r   r   rH   r   r   zOptional[_ClarifyEntry])r-   r   r   r   r   r   )r   r   r   r   r   r   )r   r   r   r   )r   r   r   r   )r   r   r   rP   )r   rP   )r   r   rq   zCallable[[_ClarifyEntry], None]r   None)r   r   r   ry   )r   r   r   z)Optional[Callable[[_ClarifyEntry], None]])%r"   
__future__r   loggingr$   r7   dataclassesr   r   typingr   r   r   r	   	getLoggerr   loggerr   RLockr*   r'   r#   r(   r.   rB   rF   rK   rW   rY   r[   rb   rf   rn   ro   rr   ru   rw   r&   r   r   <module>r      s  > #    ( 1 1			8	$ 
 
 
& 		%'
" '')$ )  !	
 2(^$ $) ! 	2 
A8< ;=7 <&,r   