
    |+jy/                        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
  ej        e          ZdZdZ	 ddlmZ n# e$ r eZY nw xY w G d	 d
          ZddZddZddZdS )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      $@   )WebSocketDisconnectc                  @    e Zd ZdZddddZddZddZddZddZdS )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peerwsr   loopasyncio.AbstractEventLoopr   strreturnNonec               >    || _         || _        || _        d| _        d S )NF)_ws_loop_peer_closed)selfr   r   r   s       -/usr/local/lib/hermes-agent/tui_gateway/ws.py__init__zWSTransport.__init__B   s$     

    objdictboolc                V   | j         rdS t          j        |d          }	 t          j                    | j        u }n# t          $ r d}Y nw xY w|r/| j                            |                     |                     dS 	 ddl	m
}  ||                     |          | j                  }|	d| _         dS |                    t                     | j          S # t          $ rF}d| _         t                              d| j        t#          |          j        |           Y d }~dS d }~ww xY w)NFensure_asciiTr   )safe_schedule_threadsafe)timeoutz.ws write failed peer=%s error_type=%s error=%s)r   jsondumpsasyncioget_running_loopr   RuntimeErrorcreate_task
_safe_sendagent.async_utilsr"   result_WS_WRITE_TIMEOUT_S	Exception_logwarningr   type__name__)r   r   lineon_loopr"   futexcs          r   writezWSTransport.writeN   s^   < 	5z#E222	.00DJ>GG 	 	 	GGG	  	J""4??4#8#89994	BBBBBB**4??4+@+@$*MMC{#uJJ2J333|## 	 	 	DLLL@
DII.   55555	s-   < A
A 3C 5"C 
D(";D##D(c                   K   | j         rdS |                     t          j        |d                     d{V  | j          S )zGSend from the owning event loop. Awaits until the frame is on the wire.Fr    N)r   r*   r$   r%   )r   r   s     r   write_asynczWSTransport.write_asyncn   sS      < 	5oodj5AAABBBBBBBBB<r   r3   c                   K   	 | j                             |           d {V  d S # t          $ rF}d| _        t                              d| j        t          |          j        |           Y d }~d S d }~ww xY w)NTz-ws send failed peer=%s error_type=%s error=%s)	r   	send_textr.   r   r/   r0   r   r1   r2   )r   r3   r6   s      r   r*   zWSTransport._safe_sendu   s      	($$T*********** 	 	 	DLLL?
DII.        	s    & 
A6;A11A6c                    d| _         d S )NT)r   )r   s    r   closezWSTransport.close   s    r   N)r   r   r   r   r   r   r   r   )r   r   r   r   )r3   r   r   r   )r   r   )	r2   
__module____qualname____doc__r   r7   r9   r*   r=    r   r   r	   r	   2   s         ( 
 
 
 
 
 
   @               r   r	   r   r   r   r   c                    t          | dd          }|dS t          |dd          pd}t          |dd          }|| d| n|S )z?Return ``host:port`` when available, else a stable placeholder.clientNr
   hostport:)getattr)r   rC   rD   rE   s       r   _ws_peer_labelrH      sb    R4((F~y664((5ID664((D#/dTT9r   r   c                   	 t          | dd          pi }|                    d          pi                     d          pt          | dd          }||                    d          nd}|-|                    t          j        t          j        d           dS dS # t          $ r&}t          	                    d|           Y d}~dS 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)
rG   getget_extra_info
setsockoptrM   IPPROTO_TCPTCP_NODELAYr.   r/   debug)r   rJ   rL   sockr6   s        r   _disable_naglerV      s    3GT**0bYY|,,277DDfPRT_aeHfHf	5>5Jy''111PTOOF.0BAFFFFF  3 3 3

,c2222222223s   BB 
C
$CC
c                N  K   t          |           }d}d}d}d}d}d}	 |                                  d{V  d}t          |            t                              d|           t          | t          j                    |          }|                    ddd	d
t          j
                    idd           d{V }|sd}|dz  }t                              d|           	 d}	d}
|k|                                 	 t          j        t          j        |d           d{V \  }	}
n+# t          $ r t                              d|           Y nw xY w	 |                                  d{V  n3# t          $ r&}t                              d||           Y d}~nd}~ww xY wt                              d|||||||	|
	  	         dS 	 	 |                                  d{V }nh# t&          $ r2}dt)          |dd           dt)          |dd           d}Y d}~nAd}~wt          $ r! d}t                              d|           Y nw xY w|                                }|s|dz  }	 t-          j        |          }n# t,          j        $ r}|dz  }t                              d||||dt4                              |                    dddd dd!           d{V }|s(d"}|dz  }t                              d#|           Y d}~nSY d}~Hd}~ww xY wt7          |t8                    r|                    d$          nd}t7          |t8                    r|                    d%          nd}	 t          j        t          j        ||           d{V }n# t          $ rt |dz  }t                              d&|||           |                    dd'd(d ||ndd!           d{V }|s&d)}|dz  }t                              d*|||           Y nKY Mw xY w|@|                    |           d{V s%d+}|dz  }t                              d,|||           nd}	d}
|k|                                 	 t          j        t          j        |d           d{V \  }	}
n+# t          $ r t                              d|           Y nw xY w	 |                                  d{V  n3# t          $ r&}t                              d||           Y d}~nd}~ww xY wt                              d|||||||	|
	  	         dS # d}	d}
|k|                                 	 t          j        t          j        |d           d{V \  }	}
n+# t          $ r t                              d|           Y nw xY w	 |                                  d{V  n3# t          $ r&}t                              d||           Y d}~nd}~ww xY wt                              d|||||||	|
	  	         w xY w)-zFRun one WebSocket session. Wire-compatible with ``tui_gateway.entry``.Nr   not_connected	connectedzws accepted peer=%sr   z2.0eventzgateway.readyskin)r1   payload)jsonrpcmethodparamsready_send_failedrN   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=%dTz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)rc   message)r]   erroridsend_failed_after_parse_errorz(ws parse-error reply send failed peer=%sri   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)rH   acceptrV   r/   infor	   r&   r'   r9   r   resolve_skinrh   r=   	to_thread_close_sessions_for_transportr.   	exceptionrT   receive_text_WebSocketDisconnectrG   stripr$   loadsJSONDecodeErrorr0   _WS_LOG_PAYLOAD_PREVIEW
isinstancer   rO   dispatch)r   r   rL   messagesparse_errorsdispatch_crashessend_failuresdisconnect_reasonready_okreaped_sessionsdetached_sessionsr6   rawr3   reqokreq_id
req_methodresps                      r   	handle_wsr      s	     "D$(IHLM']
iikk' 	r		'...G$<$>$>TJJJ	".. !+ &(;(=(=>  	
 	
 	
 	
 	
 	
 	
 	
  	 3QMJJ;TBBBz  OOM;B;L8.< < < 6 6 6 6 6 62!2!2
  M M MEtLLLLLM	F((** 	F 	F 	FJJ94EEEEEEEE	F		[	
 	
 	
 	
 	
qY	OO--------'   >#C66> >%c8T::> > > "
    $4!:DAAA
 99;;D MHj&&'   !I1112   %00#(*0]!K!K"          (G%!Q&MLL!KTRRREEEE+8 '1d&;&;ESWWT]]]F.8d.C.CM***J$.vYOOOOOOOO    A% ?	   %00#(*0=M!N!N(.(:ff          	(J%!Q&MLLU"	   E34 i.C.CD.I.I(I(I(I(I(I(I$@!"E	   sY	v  OOM;B;L8.< < < 6 6 6 6 6 62!2!2
  M M MEtLLLLLM	F((** 	F 	F 	FJJ94EEEEEEEE	F		[	
 	
 	
 	
 	
;  OOM;B;L8.< < < 6 6 6 6 6 62!2!2
  M M MEtLLLLLM	F((** 	F 	F 	FJJ94EEEEEEEE	F		[	
 	
 	
 	
s<  CU  >*D) )%EEE0 0
F :FF U  	G$ #U  $
I	.'HU  *I	U  I		U  )I> =U  >LA6LU  LAU  0&N U  A8PU  PAU  7*R" "%S
	S
S) )
T3TT X$*VX$%V/,X$.V//X$3WX$
W>W94X$9W>>&X$)r   r   r   r   )r   r   r   r   )r@   
__future__r   r&   r$   loggingrM   typingr   tui_gatewayr   	getLoggerr2   r/   r-   rx   starlette.websocketsr   rt   ImportErrorr.   r	   rH   rV   r   rA   r   r   <module>r      sI   . # " " " " "                w""
   %PPPPPPP % % %$%N N N N N N N Nb: : : :3 3 3 3$g
 g
 g
 g
 g
 g
s   A A
A