
    +jYG              	       r   U d Z ddlZddlZddlZddlZddlmZmZmZ ddl	m
Z
 ddlmZ ddlmZmZmZmZmZmZmZmZmZmZmZ ddlmZ dd	lmZ dd
lmZ ddlm Z  ddl!m"Z" ddl#m$Z$ ddl%m&Z&  ej'        e(          Z) G d deee e"e
          Z*da+ee*         e,d<   dee*         ddfdZ-dee*         fdZ.dS )u\   
LightClaw adapter — main coordinator class and module singleton.
Mirrors: src/gateway.ts
    N)DictListOptional)BasePlatformAdapter)PlatformConfig   )API_PATH_TICKETDEFAULT_API_BASE_URLDEFAULT_WS_BASE_URLEVENT_HANDSHAKEEVENT_HISTORY_REQUESTEVENT_MESSAGE_ACKEVENT_MESSAGE_PRIVATEEVENT_SESSIONS_REQUESTFileDownloadStatusKIND_FILE_DOWNLOADgenerate_msg_id)ReliableEmitter)NativeSocketMixin)InboundMixin)OutboundMixin)DownloadHandlerMixin)set_api_key_map)SessionUsageTrackerc                   l     e Zd ZdZdZdZdef fdZddZde	fdZ
dd	Zd
eddfdZdedefdZ xZS )LightClawAdapteru  
    Hermes gateway adapter for LightClaw-compatible servers.

    Connects via native WebSocket (aiohttp), receives message:private events,
    dispatches them to Hermes AIAgent, and sends responses back.

    Protocol:
    - Auth:      POST /cgi/ticket  → { code:0, data:{ client:{ extra:'{"botId":"..."}' }, ticket:"..." } }
    - Transport: Native WebSocket  wss://<domain>/ws/agent?ticket=<ticket>&enableMultiLogin=false
    - Framing:   Raw JSON  { "event": "<name>", "data": {...} }
    - Handshake: server sends { "event": "__handshake__", "data": { "id": "<socket_id>" } }
    - ACK:       server sends { "event": "message:ack", "data": { "relatedMsgId": "<msgId>" } }
    Tconfigc                    ddl m} t                                          | |d                     |j        pi }|                    d          rt          |d                   | _        n3t          j	        dd          
                                }|r|gng | _        t          j	        dt                    | _        t          j	        dt                    | _        d| _        i | _        i | _        i | _        i | _        i | _        i | _        i | _        i | _        i | _        i | _        i | _        t          j                            d	          pt          j                            d
          }|                    d          p3t          j	        d          pt          j                            |d          | _        tA          | j                  | _!        |                    d          pi | _"        d| _#        d| _$        d | _%        d | _&        d | _'        d | _(        tS          j*                    | _+        | ,                                 d S )Nr   )Platformlightclawbotapi_keysLIGHTCLAW_API_KEY LIGHTCLAW_WS_URLLIGHTCLAW_API_BASE_URLHERMES_HOMEz	~/.hermessessions_dirLIGHTCLAW_SESSIONS_DIRsessions)r'   agent_promptsF)-gateway.configr   super__init__extragetlist	_api_keysosgetenvstripr   _ws_base_urlr
   _api_base_url_bot_client_id_api_key_map
_round_ids_last_round_id_round_has_content_edit_snapshot_incoming_agent_ids_round_reply_to_round_usage_emitted_inbound_attachments_pending_file_paths_delivered_pathsenvironpath
expanduserjoin_sessions_dirr   _usage_tracker_agent_prompts_stopped
_socket_id_ws_session_connection_task	_reliableasyncioEvent_first_connect_event_init_pending_acks)selfr   r   r.   singlehermes_home	__class__s         ;/home/agentuser/.hermes/plugins/lightclawbot/src/adapter.pyr-   zLightClawAdapter.__init__B   s@   ++++++.!9!9:::"
 99Z   	8(,U:->(?(?DNNY2B77==??F)/7fXXRDN #%),>FY"Z"Z"$),DFZ"["[ +-,. +- /1 46 /1 46  02 57! 68!
 57  13 jnn]33Vrw7I7I+7V7VIIn%% 5y1225w||K44 	 4G+4
 4
 4
 /4ii.H.H.NB #("$"&"&8<8<8?! 	!!!!!    returnNc                   K   ddl }| j         t           }d}i }|                                4 d{V }| j        D ]}	 d| dd}|                    |||                    d          	          4 d{V 	 }|j        d
k    rBt          	                    dt          |j        |dd                    	 ddd          d{V  |
                                 d{V }	|	                    d          dk    rQt          	                    dt          |dd         |	                    d                     	 ddd          d{V  |	                    d          pi }
|
                    d          pi                     dd          }	 |rt          j        |          ni }|                    dd          }n# t          j        t          f$ r d}Y nw xY w|r|s|}t          |
                    d          pd                                          }|r-|||<   t                              d|dd         ||pd           n+|||<   t                              d|dd         |pd           ddd          d{V  n# 1 d{V swxY w Y   \# t$          $ r/}t          	                    d|dd         |           Y d}~d}~ww xY w	 ddd          d{V  n# 1 d{V swxY w Y   |st'          d          || _        || _        | j        r| j        d         nd}t-          ||           t                              d|t/          |                     dS )u  
        POST /cgi/ticket for each API key to get botClientId and the tenant's uin.

        Response shape::

            { code: 0,
              data: {
                id: "<uin>",                               # tenant user id
                client: { extra: '{"botId":"<botId>"}' },  # JSON string
                ticket: "..."
              }
            }

        Populates:
            self._bot_client_id   — any non-empty botId (first key wins)
            self._api_key_map     — { uin: apiKey, ... }  (plus apiKey→apiKey
                                     as a safety fallback when uin missing)

        And pushes the result into :mod:`.tenancy` so tool handlers and the
        download signal handler can resolve the correct apiKey per session.
        r   Nr#   zBearer channel)authorizationz	x-product   )total)headerstimeout   z$[lightclaw] %s HTTP %d for key ***%scodez&[lightclaw] %s error for key ***%s: %smessagedataclientr.   botIdidz1[lightclaw] Key ***%s mapped to uin=%s (botId=%s)?z8[lightclaw] Key ***%s mapped (botId=%s, no uin returned)z5[lightclaw] Identity resolve failed for key ***%s: %szCFailed to resolve botClientId from any API key via POST /cgi/ticketz.[lightclaw] Bot clientId: %s, %d key(s) mapped)aiohttpr6   r	   ClientSessionr1   postClientTimeoutstatusloggerwarningjsonr/   loadsJSONDecodeError	TypeErrorstrr4   info	ExceptionRuntimeErrorr7   r8   r   len)rT   rk   urlbot_client_idapi_key_mapsessionkeyr`   resprf   payload	extra_strparsedbiduinexcdefault_keys                    rX   _resolve_identityz"LightClawAdapter._resolve_identity   s     , 	#6_66&(((** =	 =	 =	 =	 =	 =	 =	g~ < <;)83)2 G  '||W ' 5 5B 5 ? ?  ,     1 1 1 1 1 1 1 1 ;#--"NN F /c"##h   %1 1 1 1 1 1 1 1 1 1 1 1 1 &*YY[[00000088F++q00"NN H /RSS488I;N;N   %!1 1 1 1 1 1 1 1 1 1 1 1 1$ #'((6"2"2"8b &-[[%:%:%@b$E$Egr$R$R	%>G%OTZ	%:%:%:RF%+ZZ%<%<CC $ 4i@ % % %"$CCC%  0} 0,/M
 "'++d"3"3"9r::@@BB /2K,"KK S #BCC#szc    03K,"KK Z #BCC#*  ]1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1d !   NNOBCC#       q<=	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	 =	~  	U   ,) ,0>AdnQ''r[111<3{++	
 	
 	
 	
 	
s   K(:J<<J8J	K(
A1J;JK(AJ.G ?J G	JG	BJ4J
JJJJK(
K#$KK(KK((
K25K2c                 $  K   | j         s4t                              d           |                     ddd           dS 	 dd l}nC# t
          $ r6 t                              d           |                     dd	d           Y dS w xY w	 |                                  d {V  nX# t          $ rK}t                              d
|           |                     dt          |          d           Y d }~dS d }~ww xY wdd l}d| _	        | j
                                         |                                | _        t          | j        | j        d          | _        t%          j        |                                           | _        	 t%          j        | j
                                        d           d {V  nH# t$          j        $ r6 t                              d           |                     ddd           Y dS w xY wt3          |            dS )Nz"[lightclaw] No API keys configuredno_api_keyszNo API keys configuredF)	retryabler   z![lightclaw] aiohttp not installedmissing_depszaiohttp not installedz*[lightclaw] Identity resolution failed: %sidentity_failedTz[lightclaw])ws_emitws_emit_with_timeoutprefixg      4@)ra   z4[lightclaw] Timed out waiting for initial connectionconnect_timeoutzInitial connection timeout)r1   rp   error_set_fatal_errorrk   ImportErrorr   rx   rv   rJ   rR   clearrl   rM   r   _ws_emit_ws_emit_with_timeoutrO   rP   create_task_connection_looprN   wait_forwaitTimeoutError_set_active_adapter)rT   rk   r   s      rX   connectzLightClawAdapter.connect%  s_     ~ 	LL=>>>!!-1IUZ![[[5	NNNN 	 	 	LL<===!!.2IUZ![[[55	
	((********** 	 	 	LLEsKKK!!"3SXX!NNN55555	
 	#(!'')))#*#8#8#:#:#2M!%!; $
 $
 $

 $+#6t7L7L7N7N#O#O	"4#<#A#A#C#CTRRRRRRRRRRR# 	 	 	LLOPPP!!"35Q]a!bbb55	
 	D!!!ts=   A <BBB# #
C8-A C33C83F9 9AG>=G>c                   K   d| _         |                     t          d                     | j        r | j                                         d | _        | j        rD| j                                         	 | j         d {V  n# t          j        $ r Y nw xY wd | _        | j	        rD| j	        j
        s8	 | j	                                         d {V  n# t          $ r Y nw xY wd | _	        | j        r2| j        j
        s&| j                                         d {V  d | _        t          d            |                                  d S )NTzadapter disconnect)rJ   _flush_pending_acksConnectionErrorrO   destroyrN   cancelrP   CancelledErrorrL   closedcloserx   rM   r   _mark_disconnected)rT   s    rX   
disconnectzLightClawAdapter.disconnectO  s       1E!F!FGGG> 	"N""$$$!DN  	)!((***+++++++++)   $(D!8 	DHO 	hnn&&&&&&&&&&   DH= 	!!5 	!-%%''''''''' DMD!!!!!!!!s$   4B BB2C 
CCrawc                 8  K   	 t          j        |          }n# t           j        t          f$ r Y dS w xY w|                    dd          }|                    d          pi }|t
          k    r=|                    dd          | _        t                              d| j                   dS |t          k    rJ|                    dd          }t          
                    d|           |r|                     |           dS |t          k    r|                    d	          t          k    rx|                    d
          pi                     d          pi }|                    d          t          j        k    r't!          j        |                     |                     dS t!          j        |                     |                     dS |t(          k    r|                     |           d{V  dS |t,          k    r|                     |           d{V  dS dS )z]
        Dispatch incoming WebSocket frames.
        Mirrors: src/socket/handlers.ts
        Neventr#   rf   ri   z#[lightclaw] Handshake, socket_id=%srelatedMsgIdz)[lightclaw] ACK received: relatedMsgId=%skindr.   transferDataro   )rr   rs   rt   ru   r/   r   rK   rp   rw   r   debug
_on_ws_ackr   r   r   REQrP   r   _handle_file_download_req_handle_incoming_messager   _handle_history_requestr   _handle_sessions_request)rT   r   msgr   rf   relatedtds          rX   _handle_rawzLightClawAdapter._handle_rawl  s(     
	*S//CC$i0 	 	 	FF	 $$%2O##"hhtR00DOKK=tOOOF%%%hh~r22GLLDgNNN )(((F)))
 xx#555hhw''-222>BBHb66(##'9'==='(F(Ft(L(LMMM 
  = =d C CDDDF)))..t444444444F***//555555555F +*s    33chat_idc                    K   d| dd|dS )NzLightClaw DM ()dm)nametyper    )rT   r   s     rX   get_chat_infozLightClawAdapter.get_chat_info  s!      3333TgVVVrY   )rZ   N)__name__
__module____qualname____doc__SUPPORTS_MESSAGE_EDITINGREQUIRES_EDIT_FINALIZEr   r-   r   boolr   r   rv   r   dictr   __classcell__)rW   s   @rX   r   r   '   s         "  $!l"~ l" l" l" l" l" l"dk
 k
 k
 k
b(t ( ( ( (T" " " ":2S 2T 2 2 2 2pW3 W4 W W W W W W W WrY   r   _active_adapteradapterrZ   c                 
    | a d S )Nr   )r   s    rX   r   r     s    OOOrY   c                      t           S )zFReturn the running LightClawAdapter singleton, or None if not started.r   r   rY   rX   get_active_adapterr     s    rY   )/r   rP   rr   loggingr2   typingr   r   r   gateway.platforms.baser   r+   r   r   r	   r
   r   r   r   r   r   r   r   r   r   socket.reliable_emitterr   socket.native_socketr   inboundr   outboundr   download_handlerr   tenancyr   usage_trackerr   	getLoggerr   rp   r   r   __annotations__r   r   r   rY   rX   <module>r      sC    
    				 ' ' ' ' ' ' ' ' ' ' 6 6 6 6 6 6 ) ) ) ) ) )                          5 4 4 4 4 4 3 3 3 3 3 3 ! ! ! ! ! ! # # # # # # 2 2 2 2 2 2 $ $ $ $ $ $ . . . . . .		8	$	$~W ~W ~W ~W ~W~W ~W ~WJ /3*+ 2 2 2*:!;     
H%56      rY   