
    +jL                        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
mZmZ ddlmZmZmZmZmZ ddlmZmZmZ ddlmZmZmZ dd	lmZmZ  ej        e          Z  G d
 d          Z!ddlm"Z# g Z$ e%            Z& e#j'                    D ]5\  Z(Z)e)e&vr,e$*                    e(e)f           e&+                    e)           6[&dS )u6  
LightClaw inbound message handler — processes incoming message:private events,
downloads/decodes file attachments, and dispatches MessageEvents.

Mirrors: src/inbound.ts + src/media.ts (processFiles inline in inbound.ts).

Attachment pipeline (aligned 1:1 with TS inbound.ts L106-172):

    for each file:
      ① source detection
         - file.bytes is a data URL  → parse_data_url → (buf, mime)
         - file.uri  is a cloud URI  → download_file_from_server(buf, ctype)
         - neither                   → skip with warn

      ② save to local media dir (size/format check)

      ③ warm upload to /drive/save:
         - data-URL source: POST /drive/save (server-side content review),
           failures are logged and ignored
         - uri source: skip upload (already on server)

         ★ The resulting public URL is ONLY logged — the URL pushed into
           `public_urls` is ALWAYS `localfile://<saved_path>`.  This is
           intentional and mirrors TS behaviour precisely.  The real
           delivery path back to the client is the on-demand file:download
           signal handshake (see download_handler.py), not the inbound
           public URL.

      ④ record `{name, mimeType, url: localfile://...}` into
         adapter._inbound_attachments[chat_id] for history persistence.
    N)ListOptionalTuple)MessageEventMessageTypeget_image_cache_dir   )CHANNEL_KEYDEFAULT_AGENT_IDEVENT_MESSAGE_PRIVATELOCALFILE_SCHEMEMEDIA_MAX_BYTES)download_file_from_serverget_file_download_urlupload_file_to_server)format_file_sizeguess_mime_by_extparse_data_url)resolve_effective_api_keyset_session_api_keyc                       e Zd ZdZdeddfdZdedededee	e         e	e         ef         fd	Z
d
edededefdZdededee         fdZdeddfdZdeddfdZdS )InboundMixinaE  
    Mixin providing inbound message handling, file processing,
    and history/sessions stub responses.
    Mirrors: inbound.ts (handleIncomingMessage) + media.ts (processFiles)

    Requires (set by LightClawAdapter):
        self._bot_client_id: str
        self._api_keys: list[str]
        self._api_base_url: str
        self._session: aiohttp.ClientSession
        self._inbound_attachments: Dict[str, list]
        self.build_source(...)
        self.handle_message(event)
        self._fire_and_forget(event, data)
        self._generate_msg_id() or generate_msg_id()
    datareturnNc                   K   |                     dd          }|                     dd          }|                     dd          }|                     dd          }|                     d          pg }|                     d          pt          }|| j        k    rd	S |r|dk    rd	S |r|                                s|sd	S t                              d
|||||pdd	d         t          |                     || j        |<   |t          k    rdt           d| d| }ndt           d| }t          |          }	t          ||	           | j                             |          }
|
r3|                     t          |                     |dd|
|                     |                     |           | j                            |d	           | j                            |d	           || j        |<   | j                            |           t-          | di                               |d	           t-          | di                               |d	           |                     |          }|                     t          |                     |dd||                     t                              d||           |                     |d|||t          k    r|nd	          }|                     |||	           d	{V \  }}}d	}|r*t-          | dd	          pi }|                     |          pd	}t5          |pd|z   t6          j        |||||          }|                     |           d	{V  d	S )zn
        Handle a message:private inbound event.
        Mirrors: handleIncomingMessage in inbound.ts
        from contentmsgIdkindtextfilesagentIdNzO[lightclaw] Incoming: from=%s msgId=%s kind=%s agentId=%s content='%s' files=%d<   agent:main::dm::	sender_idtyping_stop)r    msg_idagent_id_pending_file_paths_delivered_pathstyping_startz2[lightclaw] typing_start sent: to=%s roundMsgId=%sdm)chat_id	chat_typeuser_id	user_name	thread_id_agent_prompts)r!   message_typesource
message_id
media_urlsmedia_typeschannel_prompt)getr   _bot_client_idstriploggerinfolen_incoming_agent_idsr
   r   r   
_round_ids_fire_and_forgetr   _build_message_clear_round_id_last_round_idpop_round_usage_emitted_round_reply_to_usage_trackersnapshot_baselinegetattr_get_or_create_round_idbuild_source_process_filesr   r   TEXThandle_message)selfr   senderr   r+   r    r"   r,   session_keyeffective_key	old_roundround_msg_idr8   r:   r;   attachment_descagent_promptagent_promptsevents                      ;/home/agentuser/.hermes/plugins/lightclawbot/src/inbound.py_handle_incoming_messagez%InboundMixin._handle_incoming_messageQ   s     
 88FB''88Ir**88GR((88FF++88G$$*88I&&:*: T(((F 	DFNNF 	GMMOO 	U 	F]FD(W]CRC,@#e**	
 	
 	
 ,4 ( '''LLLLL(LLKKAAAAAK1FCCCK777 O''//	 	!!%##FB]+4x $ I I   	V$$$ 	--- 	!%%fd333'-V$--f555+R0044VTBBB("--11&$???33F;;!'3h   H H	
 	
 	

 	H&R^___"" #+.>">">hhD # 
 
 :>9L9L6=:
 :
 4
 4
 4
 4
 4
 4
0
K  	?#D*:DAAGRM(,,X66>$L-R?2$)!#'
 
 
 !!%(((((((((((    r"   rU   api_keyc                   K   g }g }g }d}|D ]}	 |                     d          pd}	|                     d          pd}
d}d}|                     d          r=t          |d                   }|r
|\  }}|p|
}
nt                              d	|	           |                     d
          rhd}	 t	          |d
         || j                   d{V \  }}}|r|dk    r|}
nP# t          $ r&}t                              d|	|           Y d}~d}~ww xY wt                              d|	           "|&t          |          t          k    r1t                              d|	t          t          |                     o| 	                    ||
|	          }|
                    |           |
                    |
           |sp	 t          ||| j                   d{V \  }}t                              d||           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wt           | }|
                    |	|
|d           t          t          |                    }|
                    d          r|d|	 d| dz  }n|d|	 d| d| dz  }t                              d||
t          t          |                               # t          $ r:}t                              d|                     d          |           Y d}~d}~ww xY w|rOt!          | dd          }t#          |t$                    r)|                    |g                               |           |||fS )aM  
        Ingest `files[]` from an inbound `message:private` payload.

        Returns (local_paths, mime_types, description_text).

        * local_paths / mime_types: parallel arrays passed to the agent via
          `MessageEvent.media_urls / media_types`.  These point to on-disk
          copies and are what vision/audio tools actually read.

        * description_text: trailing block appended to the user text so
          small models that don't inspect `media_urls` still know files
          were attached (TS `attachmentDescription`).

        Side effects:
          - Warm-uploads data-URL files to /drive/save for server-side
            content review (failures are logged, never fatal).
          - Populates self._inbound_attachments[sender] with
            `{name, mimeType, url=localfile://...}` entries for history.
        r   namefilemimeTypezapplication/octet-streamNFbytesz1[lightclaw] file %s: malformed data URL, skippinguriTra   sessionz1[lightclaw] file %s: download from uri failed: %sz7[lightclaw] file %s has neither bytes nor uri, skippingz7[lightclaw] file %s exceeds %d bytes (got %d), skippingu*   [lightclaw] inbound warm upload: %s → %sz4[lightclaw] inbound warm upload failed (ignored): %s)rc   re   url)zimage/zaudio/u   
用户发送了文件: z ()u   
[用户发送了文件: u   )，已保存到: ]z#[lightclaw] file saved: %s (%s, %s)z-[lightclaw] file processing failed for %s: %s_inbound_attachments)r=   r   r@   warningr   _session	ExceptionrB   r   _save_media_bufferappendr   rA   r   r   
startswithrN   
isinstancedict
setdefaultextend)rT   r"   rU   ra   local_pathslocal_typesctx_attachmentsdescfrc   mimebufis_uri_sourceparsedparsed_mime_ctypeexc
saved_pathwarm_url
upload_err	local_urisize_strinbounds                           r^   rQ   zInboundMixin._process_files   s     , $&#%&( f	 f	AeuuV}}.uuZ((F,F'+ % 55>> +AgJ77F !+1([*2dOQU   !UU5\\ $(M!.GeHgt}/ / / ) ) ) ) ) )Q ! )U.H%H%H#(D$ ! ! !O #   !! NNQSW   ;s88o--NNQos3xx    "44S$EE
"":...""4((( % ,A&- - - ' ' ' ' ' '8 H&    %   R&         0===	&& d9( (    ,CHH55??#788 KKKKKKKDD7T 7 7X 7 7)37 7 7D 9&6s3xx&@&@   
    CEE&MM3         	Gd$:DAAG'4(( G""62..55oFFFK--s   BKK-1CK
D)D
K
DK/K3AK<AK <G=<K=
H,H'"K'H,,B+K
L#/LLbufferr}   	file_namec                    t           j                            |pd          d                                         }|st          D ]\  }}||k    r|} n|sd}t                      }t          j                    j        dd         }t          |d| | z            }	t          |	d          5 }
|
                    |           ddd           n# 1 swxY w Y   |	S )a  Persist *buffer* to the framework media cache dir.

        The filename is sanitised by keeping the original extension (if
        any, otherwise derived from *mime*) and prefixing with a short
        random token to avoid collisions.  Returns the absolute path.
        r   r	   z.binN   lc_wb)ospathsplitextlower_MIME_REVERSE_LOOKUPr   uuiduuid4hexstropenwrite)rT   r   r}   r   extem	cache_dirtokenr   fps              r^   rq   zInboundMixin._save_media_bufferO  s+    gyB//288:: 	,  199CE   	C'))	
 !$91U1C11122$ 	HHV	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   ,CCCrg   c                    K   t          |          }	 t          ||| j                   d{V \  }}}|S # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)zDownload a file from server URI or HTTP URL.

        Thin wrapper kept for callers that pre-date the file_storage
        module; delegates to :func:`download_file_from_server`.
        r(   rh   Nz.[lightclaw] _download_file fallback failed: %s)r   r   ro   rp   r@   rn   )rT   rg   rU   ra   r~   r   r   s          r^   _download_filezInboundMixin._download_filen  s       ,f===	7Wdm        ICA J 	 	 	NNKSQQQ44444	s   "7 
A'A""A'c           
      B  K   ddl m}m}m}m} ddlm} |                    dd          }|r|| j        k    rdS |                    d          p|}|                    d          pd	}	|                    d
d          }
||k    rd| d| d| }nd| d| } ||t          | dd          |	|
          }t          | dd          }t                              d||||t          |                      |            }|                     ||| j        ||||d           dS )u   Return real history messages from the SQLite session store.

        Mirrors: handlers.ts → EVENT_HISTORY_REQUEST handler.
        r	   )EVENT_HISTORY_RESPONSEr   r
   generate_msg_id)read_session_historyr   r   Nr#   limit   chatOnlyTr%   r&   r'   _sessions_dir)r   	chat_onlyzW[lightclaw] History request: userId=%s agentId=%s sessionKey=%s sessionsDir=%s found=%d)r   r   to
sessionKeymessagesr#   )configr   r   r
   r   historyr   r=   r>   rN   r@   rA   rB   rE   )rT   r   r   r   r
   r   r   r3   r,   r   r   rV   r   sessions_dirr+   s                  r^   _handle_history_requestz$InboundMixin._handle_history_request  s     
 	cbbbbbbbbbbb111111((62&& 	'T%888F88I&&:*:!!(SHHZ..	 '''MMMMM8MMKKBBBBBK''D/400	
 
 
 t_d;;&X{L#h--	
 	
 	
 !""4 -!%""7
 7
 	 	 	 	 	r`   c                    K   ddl m}m} ddlm}  |t          | dd                    } |            }|                     ||                    d          ||d           dS )ux   Return all sessions from sessions.json index.

        Mirrors: handlers.ts → EVENT_SESSIONS_REQUEST handler.
        r	   )EVENT_SESSIONS_RESPONSEr   )list_sessionsr   N	requestId)r   sessionsr   )r   r   r   r   r   rN   rE   r=   )rT   r   r   r   r   r   r+   s          r^   _handle_sessions_requestz%InboundMixin._handle_sessions_request  s      
 	EDDDDDDD****** =!E!EFF ""5+..!8
 8
 	 	 	 	 	r`   )__name__
__module____qualname____doc__ru   r_   listr   r   r   rQ   rf   rq   r   r   r   r    r`   r^   r   r   ;   s>        *i)4 i)D i) i) i) i)^I.I.#&I.14I.	tCy$s)S(	)I. I. I. I.^#&36	   > S Xe_    (0$ 04 0 0 0 0d4 D      r`   r   )	_MIME_MAP),r   base64loggingr   r   typingr   r   r   gateway.platforms.baser   r   r   r   r
   r   r   r   r   file_storager   r   r   mediar   r   r   tenancyr   r   	getLoggerr   r@   r   r   _FORWARD_MIME_MAPr   set_seen_mimesitems_ext_mrr   addr   r`   r^   <module>r      s   @   				  ( ( ( ( ( ( ( ( ( ( Q Q Q Q Q Q Q Q Q Q                      
 G F F F F F F F F F C C C C C C C C		8	$	$H H H H H H H Hb 3 2 2 2 2 2 cee'!'))  HD"	##T2J///KKr`   