
    `gjJ                        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Zddl	Z	ddl
mZ ddlmZ  ej                  e      ZdZdZ eh d      Zd	Z	 dd
lmZ  G d d      ZddZddZddZy# e$ r eZY  w xY w)a  WebSocket transport for the tui_gateway JSON-RPC server.

Reuses :func:`tui_gateway.server.dispatch` verbatim so every RPC method, every
slash command, every approval/clarify/sudo flow, and every agent event flows
through the same handlers whether the client is Ink over stdio or an iOS /
web client over WebSocket.

Wire protocol
-------------
Identical to stdio: newline-delimited JSON-RPC in both directions. The server
emits a ``gateway.ready`` event immediately after connection accept, then
echoes responses/events for inbound requests. No framing differences.

Mounting
--------
    from fastapi import WebSocket
    from tui_gateway.ws import handle_ws

    @app.websocket("/api/ws")
    async def ws(ws: WebSocket):
        await handle_ws(ws)
    )annotationsN)Any)serverg      $@   >   message.deltathinking.deltareasoning.deltagL7A`?)WebSocketDisconnectc                  v    e Zd ZdZdd	 	 	 	 	 	 	 ddZedd       ZddZddZddZ	dd	Z
dd
ZddZddZy)WSTransporta  Per-connection WS transport.

    ``write`` is safe to call from any thread *other than* the event loop
    thread that owns the socket. Pool workers (the only real caller) run in
    their own threads, so marshalling onto the loop via
    :func:`asyncio.run_coroutine_threadsafe` + ``future.result()`` is correct
    and deadlock-free there.

    When called from the loop thread itself (e.g. by ``handle_ws`` for an
    inline response) the same call would deadlock: we'd schedule work onto
    the loop we're currently blocking. We detect that case and fire-and-
    forget instead. Callers that need to know when the bytes are on the wire
    should use :meth:`write_async` from the loop thread.
    unknownpeerc                   || _         || _        || _        d| _        t	        j
                         | _        g | _        d | _        d| _	        y )NF)
_ws_loop_peer_closed	threadingLock_token_lock_pending_tokens_token_flush_handle_token_flush_armed)selfwsloopr   s       A/root/.hermes/venv/lib/python3.12/site-packages/tui_gateway/ws.py__init__zWSTransport.__init__V   sI     


 %>>+*,?C "'    c                    t        | t              r| j                  d      nd}t        |t              sy|j                  d      t        v S )zATrue for high-frequency per-token frames eligible for coalescing.paramsNFtype)
isinstancedictget_STREAMING_EVENT_TYPES)objr"   s     r   _is_streaming_framezWSTransport._is_streaming_framej   s?     '1d&;"&$'zz&!%;;;r    c                   | j                   ryt        j                  |d      }	 t        j                         | j
                  u }| j                  |      ru| j                  5  | j                  j                  |       | j                  s,d| _        | j
                  j                  | j                         d d d        | j                    S ddlm} | j                  5  | j                  j                  |       | j                  }g | _	        |r4| j
                  j!                  | j#                  |             	 d d d        y || j#                  |      | j
                        }|d| _         	 d d d        y	 d d d        	 j%                  t&               | j                    S # t        $ r d}Y mw xY w# 1 sw Y   | j                    S xY w# 1 sw Y   WxY w# t(        j*                  j,                  $ r5 t.        j1                  dt&        | j2                         | j                    cY S t4        $ rF}d| _         t.        j1                  d| j2                  t7        |      j8                  |       Y d }~yd }~ww xY w)	NFensure_asciiTr   )safe_schedule_threadsafe)timeoutuB   ws write slow (loop stalled >%ss) peer=%s — frame left in flightz.ws write failed peer=%s error_type=%s error=%s)r   jsondumpsasyncioget_running_loopr   RuntimeErrorr)   r   r   appendr   call_soon_threadsafe_arm_token_flushagent.async_utilsr-   create_task_safe_send_manyresult_WS_WRITE_TIMEOUT_S
concurrentfuturesTimeoutError_logwarningr   	Exceptionr#   __name__)r   r(   lineon_loopr-   batchfutexcs           r   writezWSTransport.writer   s.   <<zz#E2	..0DJJ>G ##C(!! K$$++D1...2D+ JJ33D4I4IJK ||## 	? 	  ''-((E#%D 

&&t';';E'BC	 	 +$$U+TZZC {#	 	 		JJ2J3||##S  	G	K ||##	 	$ !!.. 	$ LLT#TZZ ||## 	DLLL@

DI.. 	sP    F% #AF7AG,G"G %F43F47GGAI:,I:4<I55I:c                |    | j                   ry| j                  j                  t        | j                        | _        y)zGArm the coalesce timer. Runs on the loop thread (call_soon_threadsafe).N)r   r   
call_later_TOKEN_COALESCE_S_flush_tokensr   )r   s    r   r6   zWSTransport._arm_token_flush   s.    <<#'::#8#8t11$
 r    c                .   | j                   5  d| _        d| _        | j                  r| j                  rg | _        	 ddd       y| j                  }g | _        | j
                  j                  | j                  |             ddd       y# 1 sw Y   yxY w)zSend buffered tokens as one batch. Runs on the loop thread (timer).

        The send is scheduled under the lock so its wire order is fixed relative
        to a concurrent non-streaming flush in :meth:`write`.
        NF)r   r   r   r   r   r   r8   r9   )r   rE   s     r   rL   zWSTransport._flush_tokens   s      	@'+D$&+D#''4<<')$	@ 	@ ((E#%D JJ""4#7#7#>?	@ 	@ 	@s   /B=BBc                @  K   | j                   ry| j                  5  | j                  }g | _        ddd       r| j                  |       d{    | j	                  t        j                  |d             d{    | j                    S # 1 sw Y   _xY w7 K7 w)zGSend from the owning event loop. Awaits until the frame is on the wire.FNr+   )r   r   r   r9   
_safe_sendr/   r0   )r   r(   pendings      r   write_asynczWSTransport.write_async   s     <<  	&**G#%D 	& &&w///oodjj5ABBB<<	& 	& 0Bs9   BBBB-B<B=BBBBc                   K   	 | j                   j                  |       d {    y 7 # t        $ rF}d| _        t        j                  d| j                  t        |      j                  |       Y d }~y d }~ww xY ww)NT-ws send failed peer=%s error_type=%s error=%s	r   	send_textrA   r   r?   r@   r   r#   rB   )r   rC   rG   s      r   rO   zWSTransport._safe_send   sb     	(($$T*** 	DLLL?

DI.. 	s6   A<* (* A<* 	A9<A4/A<4A99A<c                
  K   	 |D ]%  }| j                   j                  |       d{    ' y7 # t        $ rF}d| _        t        j                  d| j                  t        |      j                  |       Y d}~yd}~ww xY ww)zBSend a batch of pre-serialized frames in order on the loop thread.NTrS   rT   )r   linesrC   rG   s       r   r9   zWSTransport._safe_send_many   st     	 /hh((.../. 	DLLL?

DI.. 	s6   B#1 /1 B1 	B <A;6B;B  Bc                ^    d| _         | j                  }||j                          d | _        y y )NT)r   r   cancel)r   handles     r   closezWSTransport.close   s2     ))MMO'+D$ r    N)r   r   r   zasyncio.AbstractEventLoopr   strreturnNone)r(   r%   r]   bool)r]   r^   )rC   r\   r]   r^   )rW   z	list[str]r]   r^   )rB   
__module____qualname____doc__r   staticmethodr)   rH   r6   rL   rQ   rO   r9   r[    r    r   r   r   F   sq    ( (( ((
 ( 
(( < <EN
@  
,r    r   c                t    t        | dd      }|yt        |dd      xs d}t        |dd      }|| d| S |S )z?Return ``host:port`` when available, else a stable placeholder.clientNr   hostport:)getattr)r   rf   rg   rh   s       r   _ws_peer_labelrk      sT    R4(F~664(5ID664(D#/dV1TF9T9r    c                t   	 t        | dd      xs i }|j                  d      xs i j                  d      xs t        | dd      }||j                  d      nd}|0|j                  t        j
                  t        j                  d       yy# t        $ r }t        j                  d|       Y d}~yd}~ww xY w)u}  Disable Nagle so streamed JSON-RPC frames go out individually.

    Without it the kernel coalesces the small per-token frames, so a burst after
    the model's think-pause lands on the client in one tick and no client-side
    smoothing can recover the cadence. GUI/WS only; chat platforms don't hit
    this path. Best-effort — skip silently if the socket isn't reachable.
    scopeN
extensions	transportsocket   zws TCP_NODELAY skip: %s)
rj   r&   get_extra_info
setsockoptrp   IPPROTO_TCPTCP_NODELAYrA   r?   debug)r   rm   ro   sockrG   s        r   _disable_naglerx   	  s    3GT*0bYY|,277DfPRT_aeHf	5>5Jy''1PTOOF..0B0BAF  3

,c223s   B
B 	B7B22B7c                  K   t        |       }d}d}d}d}d}d}	 | j                          d{    d}t        |        t        j	                  d|       t        | t        j                         |      }ddlm	}  |t        d	       |j                  d
dddt        j                         idd       d{   }	|	sd}|dz  }t        j                  d|       	 d}
d}|B|j                          	 t        j                  t        j                   |d       d{   \  }
}	 | j                          d{    t        j	                  d|||||||
|	       y	 	 | j)                          d{   }|j/                         }|s-|dz  }	 t1        j2                  |      }t;        |t<              r|j?                  d&      nd}t;        |t<              r|j?                  d'      nd}	 t        j                  t        j@                  ||       d{   }|9|j                  |       d{   s d-}|dz  }t        j7                  d.|||       nd}
d}|B|j                          	 t        j                  t        j                   |d       d{   \  }
}	 | j                          d{    t        j	                  d|||||||
|	       y7 7 7 # t"        $ r t        j%                  d|       Y w xY w7 # t"        $ r"}t        j'                  d||       Y d}~d}~ww xY w7 # t*        $ r*}dt-        |dd       dt-        |dd       d}Y d}~d}~wt"        $ r d}t        j%                  d|       Y 7w xY w# t0        j4                  $ rv}|dz  }t        j7                  d||||dt8                |j                  d
d d!d"dd#       d{  7  }|s#d$}|dz  }t        j7                  d%|       Y d}~Y d}~d}~ww xY w7 # t"        $ rj |dz  }t        j%                  d(|||       |j                  d
d)d*d"||ndd#       d{  7  }|s"d+}|dz  }t        j7                  d,|||       Y 9Y 4w xY w7 f7 # t"        $ r t        j%                  d|       Y  w xY w7 # t"        $ r"}t        j'                  d||       Y d}~4d}~ww xY w# d}
d}|i|j                          	 t        j                  t        j                   |d       d{  7  \  }
}n%# t"        $ r t        j%                  d|       Y nw xY w	 | j                          d{  7   n-# t"        $ r!}t        j'                  d||       Y d}~nd}~ww xY wt        j	                  d|||||||
|	       w xY ww)/zFRun one WebSocket session. Wire-compatible with ``tui_gateway.entry``.Nr   not_connected	connectedzws accepted peer=%sr   )start_background_mcp_discoveryztui-ws-mcp-discovery)loggerthread_namez2.0eventzgateway.readyskin)r#   payload)jsonrpcmethodr"   ready_send_failedrq   z"ws ready frame send failed peer=%sws_disconnect)
end_reasonz$ws transport teardown failed peer=%sz ws close failed peer=%s error=%szws closed peer=%s reason=%s messages=%d parse_errors=%d dispatch_crashes=%d send_failures=%d reaped_sessions=%d detached_sessions=%dzclient_disconnect(code=codez,reason=reason)receive_failedzws receive failed peer=%sz3ws parse error peer=%s index=%d error=%s payload=%riDzparse error)r   message)r   erroridsend_failed_after_parse_errorz(ws parse-error reply send failed peer=%sr   r   z)ws dispatch crash peer=%s id=%s method=%sizinternal error send_failed_after_dispatch_crashz;ws dispatch-crash reply send failed peer=%s id=%s method=%ssend_failed_after_responsez/ws response send failed peer=%s id=%s method=%s)!rk   acceptrx   r?   infor   r1   r2   hermes_cli.mcp_startupr|   rQ   r   resolve_skinr   r[   	to_thread_close_sessions_for_transportrA   	exceptionrv   receive_text_WebSocketDisconnectrj   stripr/   loadsJSONDecodeErrorr@   _WS_LOG_PAYLOAD_PREVIEWr$   r%   r&   dispatch)r   r   ro   messagesparse_errorsdispatch_crashessend_failuresdisconnect_reasonr|   ready_okreaped_sessionsdetached_sessionsrG   rawrC   reqokreq_id
req_methodresps                       r   	handle_wsr     s<    "D$(IHLM'm
iik' 	r		'.G$<$<$>TJ	 	J&.	

 #.. !+ &(;(;(=>	
 	
  3QMJJ;TBz  OOM;B;L;L88.< 62!2	F((* 			[	
q OO-- 99;DMHjj&: '1d&;SWWT]F.8d.C*J$..vYOO6 i.C.CD.I(I(I$@!"E	 s v  OOM;B;L;L88.< 62!2	F((* 			[	
C 	2	
z6
  MEtLM  	FJJ94EE	Fi .' #C67 8%c8T:;1> "
  $4!:DA '' !I112 %00#(*0]!K"   (G%!Q&MLL!KTR+> P  A% ?	 %00#(*0=M!N(.(:f   (J%!Q&MLLU"	 34 )J:6
  MEtLM  	FJJ94EE	F7  OOM;B;L;L88.< 6 62!2
  MEtLM	F((* 	FJJ94EE	F		[	
sX  WS2 KB
S2 9K:#S2 W5)K KK 'L :L;L ?WS2 L7 2L43L7 7S2 N %AS2 ,(P PP S2 /R0%S2 W,)R RR S 1S2S 6WS2 S2 K L <W?L  WL 	L1L,&W,L11W4L7 7	N M%S2 %$N	S2 NS2 P#A P#O&$$PS2 PS2 P AR$Q'%&RS2 RS2 R R>:W=R>>WS 	S/S*$W*S//W2W
)T=3T64T=<W=UWUW#U=6U97U=<W=	V'V"W"V''!WW)r   r   r]   r\   )r   r   r]   r^   )rb   
__future__r   r1   concurrent.futuresr<   r/   loggingrp   r   typingr   tui_gatewayr   	getLoggerrB   r?   r;   r   	frozensetr'   rK   starlette.websocketsr
   r   ImportErrorrA   r   rk   rx   r   rd   r    r   <module>r      s   . #        w"
    # $    %P
v, v,r:3$w
s  %$%s   A- -A76A7