
    PL
j                   L   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	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mZmZ ddlmZ ddlmZmZ ddlmZmZmZmZmZmZm Z  ddl!Z!ddl"Z"	 ddl#Z#ddl$Z#dZ%n# e&$ r d	Z%dZ#Y nw xY wdd
l'm(Z(m)Z) ddl*m+Z+m,Z,m-Z-m.Z.m/Z/m0Z0 ddl1m2Z2 ddl3m4Z5m6Z6m7Z7m8Z8m9Z9m:Z:m;Z; ddl<m=Z=m>Z>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOmPZPmQZQmRZR ddlSmTZT  e	jU        eV          ZW	 ddlXmYZZ n# e&$ r dZZY nw xY weZZ[eZZ\ e]eD          Z^e!j_        Z`dZadZbdZcdZddZedZfdZgh dZhdZih dZjh dZkdZldZmdZndZodZp ejq        d           Zr esd!d"h          Zt ejq        d#          Zud$Zvd%Zw G d& d'          Zx G d( d)          Zydd*lmzZzm{Z| ez G d+ d,                      Z} G d- d.e          Z~ G d/ d0          Z G d1 d2e~          Z G d3 d4e~          Z G d5 d6e~          Z G d7 d8e~          Z G d9 d:e~          Z G d; d<e~          Z G d= d>          Z G d? d@e~          Z G dA dBe~          Z G dC dDe~          Z G dE dFe~          Z G dG dHe~          Z G dI dJe~          Z G dK dLe~          Z G dM dNe~          Z G dO dPe~          Z G dQ dRe~          Z G dS dTe~          Z G dU dVe~          Z G dW dX          Z G dY dZ          Z G d[ d\e          Z G d] d^e          Z G d_ d`e          Z G da dbe          Z G dc dde          Z G de dfe          Z G dg dh          Z G di dj          Z G dk dl          Z G dm dn          Z G do dp          Z G dq dre+          ZdduZ	 ddd~ZdS )aQ  
Yuanbao platform adapter.

Connects to the Yuanbao WebSocket gateway, handles authentication (AUTH_BIND),
heartbeat, reconnection, message receive (T05) and send (T06).

Configuration in config.yaml (or via env vars):
    platforms:
      yuanbao:
        extra:
          app_id: "..."              # or YUANBAO_APP_ID
          app_secret: "..."          # or YUANBAO_APP_SECRET
          bot_id: "..."              # or YUANBAO_BOT_ID  (optional, returned by sign-token)
          ws_url: "wss://..."        # or YUANBAO_WS_URL
          api_domain: "https://..."  # or YUANBAO_API_DOMAIN
    )annotationsN)datetimetimezone	timedelta)Path)ABCabstractmethod)AnyCallableClassVarDictListOptionalTupleTF)PlatformPlatformConfig)BasePlatformAdapterMessageEventMessageType
SendResultcache_document_from_bytescache_image_from_bytes)MessageDeduplicator)download_urlget_cos_credentialsupload_to_cosbuild_image_msg_bodybuild_file_msg_bodyguess_mime_typemd5_hex)CMD_TYPE_fields_to_dict_get_string_get_varint_parse_fieldsWS_HEARTBEAT_RUNNINGWS_HEARTBEAT_FINISHHERMES_INSTANCE_IDdecode_conn_msgdecode_inbound_pushdecode_query_group_info_rsp decode_get_group_member_list_rspencode_auth_bindencode_pingencode_push_ackencode_send_c2c_messageencode_send_group_messageencode_send_private_heartbeatencode_send_group_heartbeatencode_query_group_infoencode_get_group_member_listnext_seq_no)build_session_key)__version__z0.0.0z0wss://bot-wss.yuanbao.tencent.com/wss/connectionzhttps://bot.yuanbao.tencent.comg      >@      .@      $@d   >                  >         >                @     r@g      ^@u?   任务有点复杂，正在努力处理中，请耐心等待...zB\[(image|voice|video|file(?::[^|\]]*)?)\|ybres:([A-Za-z0-9_\-]+)\]imagefilez\s*\(\d+/\d+\)$2      c                  <   e Zd ZdZedd            Zedd            Ze	 ddd            Zedd            Zedd            Z	ed d            Z
e	 	 d!d"d            Zed#d            Zed$d            Zed%d            Zed%d            Zed&d            ZdS )'MarkdownProcessoraa  Encapsulates all Markdown-related utilities for the Yuanbao platform.

    Provides static methods for:
    - Fence detection and streaming merge
    - Table row detection and sanitization
    - Paragraph-boundary splitting
    - Atomic-block extraction and chunk splitting
    - Outer markdown fence stripping
    - Markdown hint prompt generation
    textstrreturnboolc                j    d}|                      d          D ]}|                    d          r| }|S )a  
        Detect whether the text has unclosed code block fences.

        Scan line by line, toggling in/out state when encountering a line starting with ```.
        An odd number of toggles indicates an unclosed fence.

        Args:
            text: Markdown text to check

        Returns:
            Returns True if the text ends with an unclosed fence, otherwise False
        F
```)split
startswith)rQ   in_fencelines      =/home/kuhnn/.hermes/hermes-agent/gateway/platforms/yuanbao.pyhas_unclosed_fencez$MarkdownProcessor.has_unclosed_fence   sE     JJt$$ 	( 	(Du%% ('<    c                    |                                  }|sdS |                    d          d                                         }|                    d          o|                    d          S )z
        Detect whether the text ends with a table row (last non-empty line starts and ends with |).

        Args:
            text: Text to check

        Returns:
            Returns True if the last non-empty line is a table row
        FrV   |)rstriprX   striprY   endswith)rQ   trimmed	last_lines      r\   ends_with_table_rowz%MarkdownProcessor.ends_with_table_row   sg     ++-- 	5MM$''+1133	##C((DY-?-?-D-DDr^   N	max_charsintlen_fnOptional[Callable[[str], int]]tuple[str, str]c                   |pt           } ||           |k    r| dfS |t           u r| d|         }nQdt          |           }}||k     r0||z   dz   dz  } || d|                   |k    r|}n|dz
  }||k     0| d|         }|                    d          }|dk    r| d|dz            | |dz   d         fS t          j        d          }	d}
|	                    |          D ]}|                                }
|
dk    r| d|
         | |
d         fS |                    d	          }|dk    r| d|dz            | |dz   d         fS t          |          }| d|         | |d         fS )
a  
        Find the nearest paragraph boundary split point within max_chars, return (head, tail).

        Split priority:
        1. Blank line (paragraph boundary)
        2. Newline after period/question mark/exclamation mark (Chinese and English)
        3. Last newline
        4. Force split at max_chars

        Args:
            text: Text to split
            max_chars: Maximum character count limit
            len_fn: Optional custom length function (e.g. UTF-16 length); defaults to built-in len

        Returns:
            (head, tail) tuple, head is the front part, tail is the back part, satisfying head + tail == text
         Nr      rB   

u   [。！？.!?]\nr`   rV   )lenrfindrecompilefinditerend)rQ   rh   rj   _lenwindowlohimidpossentence_end_rebest_posmcuts                r\   split_at_paragraph_boundaryz-MarkdownProcessor.split_at_paragraph_boundary   s   . }4::""8O
 3;;*9*%FFD		Br''Bw{q(4TcT
##y00BBqB r'' #2#YF ll6""77q>4a>11 *%899 ))&11 	 	AuuwwHHa<<		?DO33 ll4  77q>4a>11 &kkDSDz4:%%r^   c                P    |                                                      d          S )zDDetermine whether an atomic block is a code block (starts with ```).rW   )lstriprY   rQ   s    r\   is_fence_atomzMarkdownProcessor.is_fence_atom  s      {{}}''...r^   c                    |                      d          d                                         }|                    d          o|                    d          S )zHDetermine whether an atomic block is a table (first line starts with |).rV   r   ra   )rX   rc   rY   rd   )rQ   
first_lines     r\   is_table_atomzMarkdownProcessor.is_table_atom  sM     ZZ%%a(..00
$$S))Fj.A.A#.F.FFr^   	list[str]c                   |                      d          }g g d}dd}dfd	}|D ]}|rJ                    |           |                    d
          rt                    dk    rd} |             O|                    d
          r" |             d}                    |            ||          r3r |d                   s
 |                                 |           |                                dk    r |             r |d                   r
 |                                 |            |             S )a  
        Split text into a list of "atomic blocks", each being an indivisible logical unit:

        - Code block (fence): from opening ``` to closing ``` (including fence lines)
        - Table: consecutive |...| lines forming a whole segment
        - Normal paragraph: plain text segments separated by blank lines

        Blank lines serve as separators and are not included in any atomic block.

        Args:
            text: Markdown text to split

        Returns:
            List of atomic block strings (all non-empty)
        rV   Fr[   rR   rS   rT   c                ~    |                                  }|                    d          o|                    d          S )Nra   )rc   rY   rd   )r[   strippeds     r\   _is_table_linez:MarkdownProcessor.split_into_atoms.<locals>._is_table_line<  s6    zz||H&&s++F0A0A#0F0FFr^   Nonec                     rTd                               } |                                 r                    |                                             d S d S )NrV   )joinrc   appendclear)atomatomscurrent_liness    r\   _flush_currentz:MarkdownProcessor.split_into_atoms.<locals>._flush_current@  s`     &yy//::<< 'LL&&&##%%%%%	& &r^   rW   ro   Tr`   rn   )r[   rR   rS   rT   rS   r   )rX   r   rY   rq   rc   )rQ   linesrZ   r   r   r[   r   r   s         @@r\   split_into_atomsz"MarkdownProcessor.split_into_atoms%  s   " 

4  #%	G 	G 	G 	G	& 	& 	& 	& 	& 	& 	&  	+ 	+D +$$T***??5)) %c-.@.@1.D.D$H"N$$$'' +   $$T****%% 	+  %b8I)J)J %"N$$$$$T****##      %^^M"4E%F%F %"N$$$$$T****r^     c                   |pt           }|sg S  ||          |k    r|gS |                     |          }g t                      }g d}dfd}|D ]}	 ||	          }
rdnd}||z   |
z   }||k    rr |             g d}d}sh|
|k    rb|                     |	          s|                     |	          r8|                    t                                                   |	                               |	           |||
z   z  } |             g }t                    D ]\  }} ||          |k    r|                    |           *||v r|                    |           D|                     |          r|                    |           o|} ||          |k    rW| 	                    |||          \  }}|s|d|         ||d         }}|r|                    |            ||          |k    W|r|                    |           t          |          dk    rR|d         g}|dd         D ]<}|d	         }|d
z   |z   } ||          |k    r||d	<   '|                    |           =|}d |D             S )a  
        Split Markdown text into multiple chunks by max_chars.

        Guarantees:
        - Each chunk <= max_chars characters (unless a single code block/table itself exceeds the limit)
        - Code blocks (```...```) are not split in the middle
        - Table rows are not split in the middle (tables output as atomic blocks)
        - Split at paragraph boundaries (blank lines, after periods, etc.)
        - Small trailing/leading chunks are merged with neighbours when possible

        Args:
            text: Markdown text to split
            max_chars: Max characters per chunk, default 4000
            len_fn: Optional custom length function (e.g. UTF-16 length); defaults to built-in len

        Returns:
            List of text chunks after splitting (non-empty)
        r   rS   r   c                 `    r*                      d                                         d S d S )Nrp   )r   r   )chunkscurrent_partss   r\   _flush_partsz;MarkdownProcessor.chunk_markdown_text.<locals>._flush_parts  s9     :fkk-8899999: :r^   rB   rj   Nro   r`   rp   c                    g | ]}||S  r   .0cs     r\   
<listcomp>z9MarkdownProcessor.chunk_markdown_text.<locals>.<listcomp>  s    '''aQ''''r^   r   )
rq   r   setr   r   addr   	enumerater]   r   )clsrQ   rh   rj   rw   r   indivisible_setcurrent_lenr   r   atom_lensep_lenprojected_lenresultidxchunk	remainingheadmergedprevcombinedr   r   s                        @@r\   chunk_markdown_textz%MarkdownProcessor.chunk_markdown_textb  sf   2 } 	I4::""6M $$T** $'EE#%	: 	: 	: 	: 	: 	: 	:  	. 	.DtDzzH(/aaaG''1H<My((]( "!  9,,**400 -474E4Ed4K4K -##CKK000d###  &&&7X--KK #F++ 	) 	)JCtE{{i''e$$$o%%e$$$%%e,, e$$$I$y//I--"%"A"Ay #B # #i  S&/

&;Yyzz=R)D (MM$''' $y//I--  )i((( v;;??!'F ) )bz&=504>>Y..!)F2JJMM%((((F''6''''r^   
prev_chunk
next_chunkc                   |                                 }|                                }|                    d          s|                    d          rdS |                     |          r]|r-|                    d          d                                         nd}|                    d          r|                    d          rdS dS )u'  
        Infer the separator to use between two split chunks.

        Rules (aligned with TS markdown-stream.ts):
        - Previous chunk ends with code fence or next chunk starts with fence → single newline '\n'
        - Previous chunk ends with table row and next chunk starts with table row → single newline '\n' (continued table)
        - Otherwise → double newline '\n\n' (paragraph separator)

        Args:
            prev_chunk: Previous chunk
            next_chunk: Next chunk

        Returns:
            '\n' or '\n\n'
        rW   rV   r   rn   ra   rp   )rb   r   rd   rY   rg   rX   rc   )r   r   r   prev_trimmednext_trimmedr   s         r\   infer_block_separatorz'MarkdownProcessor.infer_block_separator  s    " "((**!((**   '' 	<+B+B5+I+I 	4 "":.. 	@LT++D11!4::<<<RTJ$$S)) j.A.A#.F.F tvr^   r   c                   |sg S g }d}|t          |          k     r||         }|                     |          rv|dz   t          |          k     r`|                     |||dz                      }||z   ||dz            z   }|dz  }|                     |          r|dz   t          |          k     `|                    |           |dz  }|t          |          k     |S )aM  
        Stream-aware fence-conscious chunk merging.

        When streaming output produces multiple chunks truncated in the middle of a fence,
        attempt to merge adjacent chunks to complete the fence.

        Rules:
        - If chunk i has an unclosed fence and chunk i+1 starts with ```,
            merge i+1 into i (until the fence is closed or no more chunks).
        - Use infer_block_separator to infer the separator during merging.

        Args:
            chunks: Original chunk list

        Returns:
            Merged chunk list (length <= original length)
        r   ro   )rq   r]   r   r   )r   r   r   icurrentseps         r\   merge_block_streaming_fencesz.MarkdownProcessor.merge_block_streaming_fences  s    &  	I#f++ooQiG((11 a!ec&kk6I6I//AGG!C-&Q-7Q ((11 a!ec&kk6I6I MM'"""FA #f++oo r^   c                X   | s| S |                      d          }t          |          dk     r| S |d                                         }|d                                         }t          j        d|t          j                  s| S |dk    r| S d                    |dd                   }|S )a  
        Strip outer Markdown fence.

        When AI reply is entirely wrapped in ```markdown\n...\n```, remove the outer fence,
        keeping the content. Only strip when the first line is ```markdown (case-insensitive) and the last line is ```.

        Args:
            text: Text to process

        Returns:
            Text with outer fence stripped (returns original if no match)
        rV      r   r`   z^```(?:markdown|md)?\s*$rW   ro   )rX   rq   rc   rs   match
IGNORECASEr   )rQ   r   r   rf   inners        r\   strip_outer_markdown_fencez,MarkdownProcessor.strip_outer_markdown_fence  s      	K

4  u::>>K1X^^%%
"IOO%%	 x3ZOO 	K K 		%"+&&r^   c                f   d| vr| S |                      d          }g }|D ]}|                                }|                    d          r|                    d          rt	          j        d|          rJ|                     d          }d                    d |D                       }|                    |           |dk    s,|                    dd                                          dk    r|                    |           |                    |           d                    |          S )a  
        Table output sanitization.

        Handle common formatting issues in AI-generated Markdown tables:
        1. Remove extra whitespace before/after table rows
        2. Ensure separator rows (|---|---|) are correctly formatted
        3. Remove empty table rows

        Args:
            text: Markdown text containing tables

        Returns:
            Sanitized text
        ra   rV   z^\|[\s\-:]+(\|[\s\-:]+)+\|$c              3  j   K   | ].}|                                 r|                                 n|V  /d S Nrc   )r   cells     r\   	<genexpr>z<MarkdownProcessor.sanitize_markdown_table.<locals>.<genexpr>^  sO       * *  )-

>

$* * * * * *r^   z||rn   )	rX   rc   rY   rd   rs   r   r   r   replace)rQ   r   result_linesr[   r   cells
normalizeds          r\   sanitize_markdown_tablez)MarkdownProcessor.sanitize_markdown_table@  sS     d??K

4  "$ 	* 	*Dzz||H ""3'' *H,=,=c,B,B *8:HEE 2$NN3//E!$ * *$)* * * " "J !''
3333%%)9)9#r)B)B)H)H)J)Jb)P)P ''1111##D))))yy&&&r^   c                     	 dS )z
        Markdown rendering hint (appended to system prompt).

        Tell AI that Yuanbao platform supports Markdown rendering, including:
        - Code blocks (```lang)
        - Tables (| col | col |)
        - Bold/italic
        a  The current platform supports Markdown rendering. You can use the following formats:
- Code blocks: ```language\ncode\n```
- Tables: | col1 | col2 |\n|---|---|\n| val1 | val2 |
- Bold: **text** / Italic: *text*
Please use Markdown formatting when appropriate to improve readability.r   r   r^   r\   markdown_hint_system_promptz-MarkdownProcessor.markdown_hint_system_prompto  s    V	
 	
r^   )rQ   rR   rS   rT   r   )rQ   rR   rh   ri   rj   rk   rS   rl   )rQ   rR   rS   r   r   N)rQ   rR   rh   ri   rj   rk   rS   r   )r   rR   r   rR   rS   rR   )r   r   rS   r   rQ   rR   rS   rR   rS   rR   )__name__
__module____qualname____doc__staticmethodr]   rg   r   r   r   r   classmethodr   r   r   r   r   r   r   r^   r\   rP   rP      s       	 	    \* E E E \E$  26=& =& =& =& \=&B / / / \/ G G G \G
 8 8 8 \8x  15	k( k( k( k( [k(^    [B ! ! ! [!J ! ! ! \!J *' *' *' \*'\ 
 
 
 \
 
 
r^   rP   c                  ,   e Zd ZU dZdZdZdZdZdZdZ	i Z
ded	<   i Zd
ed<   ed%d            Zed&d            Zed'd            Zed(d            Zed)d            Zed*d            Ze	 d+d,d!            Ze	 d+d,d"            Ze	 d+d,d#            Zd$S )-SignManagera  Encapsulates all sign-token related logic for the Yuanbao platform.

    Manages token acquisition, caching, signature computation, and
    automatic retry.  All state (cache, locks) is kept as class-level
    attributes so that a single shared client serves the whole process.
    z/api/v5/robotLogic/sign-tokenis'  r   g      ?<   r:   zdict[str, dict[str, Any]]_cachezdict[str, asyncio.Lock]_locksapp_keyrR   rS   asyncio.Lockc                d    || j         vrt          j                    | j         |<   | j         |         S )zReturn (creating if needed) the per-app_key refresh lock.

        Must only be called from within a running event loop (async context).
        )r   asyncioLock)r   r   s     r\   get_refresh_lockzSignManager.get_refresh_lock  s0     #*$$"),..CJwz'""r^   nonce	timestamp
app_secretc                    | |z   |z   |z   }t          j        |                                |                                t          j                                                  S )zCompute HMAC-SHA256 signature (aligned with TypeScript original).

        plain     = nonce + timestamp + app_key + app_secret
        signature = HMAC-SHA256(key=app_secret, msg=plain).hexdigest()
        )hmacnewencodehashlibsha256	hexdigest)r   r   r   r   plains        r\   compute_signaturezSignManager.compute_signature  sN     	!G+j8x
))++U\\^^W^LLVVXXXr^   c                     t          j        t          t          d                              } |                     d          S )zlBuild Beijing-time ISO-8601 timestamp (no milliseconds).

        Format: 2006-01-02T15:04:05+08:00
           )hourstzz%Y-%m-%dT%H:%M:%S+08:00)r   nowr   r   strftime)bjtimes    r\   build_timestampzSignManager.build_timestamp  s<     )!*<*<*<!=!=>>>8999r^   entrydict[str, Any]rT   c                L    |d         t          j                     z
  | j        k    S )zEDetermine whether the cache entry is valid (not expired with margin).	expire_ts)timeCACHE_REFRESH_MARGIN_S)r   r   s     r\   is_cache_validzSignManager.is_cache_valid  s"     [!DIKK/#2LLLr^   r   c                8    | j                                          dS )z;Clear all per-app_key refresh locks (called on disconnect).N)r   r   r   s    r\   clear_lockszSignManager.clear_locks  s     	
r^   ri   c                    t          j                     fd| j                                        D             }|D ]}| j                            |d           t	          |          S )zRemove all expired entries from the token cache.

        Returns the number of entries purged.  Called lazily from
        ``get_token()`` so that stale app_key entries don't accumulate
        indefinitely in long-running processes.
        c                T    g | ]$\  }}|                     d d          z
  dk    "|%S )r  r   get)r   kvr   s      r\   r   z-SignManager.purge_expired.<locals>.<listcomp>  sE     
 
 
!QQUU;***Q.. ...r^   N)r  r   itemspoprq   )r   expired_keysr  r   s      @r\   purge_expiredzSignManager.purge_expired  s~     ikk
 
 
 
***,,
 
 
  	$ 	$AJNN1d####<   r^   rn   
api_domain	route_envc                  K   |                     d           | j         }t          j        | j                  4 d{V }t          | j        dz             D ]P}t          j        d          }| 	                                }	| 
                    ||	||          }
|||
|	d}dt          t          t          t          d}|r||d	<   t                              d
||dk    rd| d| j         dnd           |                    |||           d{V }|j        dk    r)|j        }t)          d|j         d|dd                    	 |                                }n%# t,          $ r}t/          d|           |d}~ww xY w|                    d          }|dk    r|                    d          }t3          |t4                    st/          d|           t                              d|                    d                     |c cddd          d{V  S || j        k    rW|| j        k     rLt                              d|| j        |dz   | j                   t=          j        | j                   d{V  '|                    dd          }t)          d| d|           	 ddd          d{V  n# 1 d{V swxY w Y   t)          d          )zHSend sign-ticket HTTP request with auto-retry (up to MAX_RETRIES times)./timeoutNro      )r   r   	signaturer   application/json)Content-TypezX-AppVersionzX-OperationSystemzX-Instance-IdzX-Bot-VersionzX-Route-EnvzSign token request: url=%s%sr   z (retry )rn   )jsonheaders   zSign token API returned : z!Sign token response parse error: codedataz*Sign token response missing 'data' field: zSign token success: bot_id=%sbot_idz>Sign token retryable: code=%s, retrying in %ss (attempt=%d/%d)msgzSign token error: code=, msg=z'Sign token failed: max retries exceeded) rb   
TOKEN_PATHhttpxAsyncClientHTTP_TIMEOUT_SrangeMAX_RETRIESsecrets	token_hexr   r   _APP_VERSION_OPERATION_SYSTEM_YUANBAO_INSTANCE_ID_BOT_VERSIONloggerinfopoststatus_coderQ   RuntimeErrorr  	Exception
ValueErrorr  
isinstancedictRETRYABLE_CODEwarningRETRY_DELAY_Sr   sleep)r   r   r   r  r  urlclientattemptr   r   r  payloadr   responsebodyresult_dataexcr#  r$  r&  s                       r\   fetchzSignManager.fetch  sK      ""3''999$S-?@@@ <	P <	P <	P <	P <	P <	P <	PF 1!455 ;P ;P)"--//11	11%GZXX	  '"!*!*	  %7$0):%9%1   7-6GM*2?F{{;w;;;;;;PR   "(Sw!P!PPPPPPP'3..#=D&'f(BV'f'fZ^_c`c_cZd'f'fgggY2:--//KK  Y Y Y$%N%N%NOOUXXY #v..199&??622D%dD11 e()cVa)c)cdddKK ?(ASASTTTKK]<	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P` 3---'CO2K2KNNX)!   "-(9:::::::::!ooeR00"#NT#N#N#N#NOOOw;P<	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P <	P| DEEEs?   DJ>	EJ>
F (E;;F  BJ>BJ>>
KKc           	       K   |                                   | j                            |          }|rh|                     |          rSt	          |d         t          j                    z
            }t                              d|           t          |          S | 	                    |          4 d{V  | j                            |          }|r6|                     |          r!t          |          cddd          d{V  S | 
                    ||||           d{V }|                    dd          }|dk    rt          j                    |z   nt          j                    dz   }	|                    dd          |                    d	d          ||                    d
d          |                    dd          |	d| j        |<   ddd          d{V  n# 1 d{V swxY w Y   t          | j        |                   S )zGet WS auth token (with cache).

        Return directly on cache hit without re-requesting; treat as expiring
        60 seconds before actual expiry, triggering refresh.
        r  z"Using cached token (%ds remaining)Ndurationr     tokenrn   r%  productsourcerM  r%  rK  rN  rO  r  )r  r   r  r  ri   r  r4  r5  r<  r   rI  )
r   r   r   r  r  cachedremainr$  rK  r  s
             r\   	get_tokenzSignManager.get_token*  s      	(( 	 c((00 	 ,ty{{:;;FKK<fEEE<<''00 	 	 	 	 	 	 	 	Z^^G,,F $#,,V44 $F||	 	 	 	 	 	 	 	 	 	 	 	 	 	
 7J
INNNNNNNND HHZ33H2:Q,,	h..DIKKRVDVI '2..((8R00$88Ir22((8R00&# #CJw	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	& CJw'(((s   6A G"	CG""
G,/G,c           	       K   t                               d|dd                    |                     |          4 d{V  | j                            |d           |                     ||||           d{V }|                    dd          }|dk    rt          j                    |z   nt          j                    dz   }|                    dd          |                    d	d          ||                    d
d          |                    dd          |d| j        |<   ddd          d{V  n# 1 d{V swxY w Y   t          | j        |                   S )z.Force refresh token (clear cache and re-sign).zC[force-refresh] Clearing cache and re-signing token: app_key=****%sNrK  r   rL  rM  rn   r%  rN  rO  rP  )	r4  r>  r   r   r  rI  r  r  r<  )r   r   r   r  r  r$  rK  r  s           r\   force_refreshzSignManager.force_refreshW  s      	\^efhfifi^jkkk''00 	 	 	 	 	 	 	 	JNN7D)))7J
INNNNNNNND HHZ33H2:Q,,	h..DIKKRVDVI '2..((8R00$88Ir22((8R00&# #CJw	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	  CJw'(((s   C#D66
E E N)r   rR   rS   r   )
r   rR   r   rR   r   rR   r   rR   rS   rR   r   )r   r  rS   rT   r   rS   ri   rn   )
r   rR   r   rR   r  rR   r  rR   rS   r  )r   r   r   r   r(  r=  r-  r?  r  r+  r   __annotations__r   r   r   r   r   r   r  r	  r  rI  rS  rV  r   r^   r\   r   r     s          1JNKM   N
 )+F**** ')F(((( # # # [# Y Y Y \Y : : : \: M M M [M    [ ! ! ! [!$  GF GF GF GF [GFV  () () () () [()X  ) ) ) ) [) ) )r^   r   )	dataclassfieldc                     e Zd ZU dZded<    ee          Zded<   dZded	<   d
Z	ded<   d
Z
ded<   d
Zded<   d
Zded<   d
Zded<    ee          Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<   d
Zded<    ee          Zded<   dZded<   dZded<   dZded<   dZded<   dZded<    ee          Zded <    ee          Zded!<    ee          Zded"<    ee          Zded#<   dZded$<   dS )%InboundContextzMutable context flowing through the inbound middleware pipeline.

    Each middleware reads/writes fields on this context.  The pipeline
    engine passes it to every middleware in registration order.
    r
   adapter)default_factorylist
raw_framesNOptional[dict]pushrn   rR   decoded_viafrom_account
group_code
group_namesender_nicknamemsg_bodymsg_idcloud_custom_datachat_id	chat_type	chat_nameraw_text
media_refsOptional[str]owner_commandzOptional[Any]rO  msg_typereply_to_message_idreply_to_textquote_media_refs
media_urlsmedia_types	link_urlschannel_prompt) r   r   r   r   rY  dc_fieldr`  ra  rc  rd  re  rf  rg  rh  ri  rj  rk  rl  rm  rn  ro  rp  rr  rO  rs  rt  ru  rv  rw  rx  ry  rz  r   r^   r\   r]  r]  v  sD          LLLx555J5555  DK LJJOXd333H3333F GII Hx555J5555 $(M'''' !F     #H"""" *.----#'M''''%Xd;;;;;;;  x555J5555 666K6666 ht444I4444 %)N((((((r^   r]  c                  J    e Zd ZU dZdZded<   edd            ZddZddZ	dS )InboundMiddlewarea  Abstract base class for all inbound pipeline middlewares.

    Subclasses must:
      - Set ``name`` as a class-level attribute (used for pipeline registration
        and dynamic insertion/removal).
      - Implement ``async handle(ctx, next_fn)`` containing the middleware logic.

    Convention:
      - Call ``await next_fn()`` to pass control to the next middleware.
      - Return without calling ``next_fn`` to **stop** the pipeline.
    rn   rR   namectxr]  next_fnr   rS   r   c                
   K   dS )zEProcess *ctx* and optionally call *next_fn* to continue the pipeline.Nr   selfr  r  s      r\   handlezInboundMiddleware.handle  
        r^   c                >   K   |                      ||           d{V S )zFAllow middleware instances to be called directly (duck-typing compat).N)r  r  s      r\   __call__zInboundMiddleware.__call__  s,      [[g.........r^   c                2    d| j         j         d| j        dS )N<z name=>)	__class__r   r~  r  s    r\   __repr__zInboundMiddleware.__repr__  s"    @4>*@@$)@@@@r^   N)r  r]  r  r   rS   r   r   )
r   r   r   r   r~  rY  r	   r  r  r  r   r^   r\   r}  r}    s         
 
 DNNNNT T T ^T/ / / /A A A A A Ar^   r}  c                  x    e Zd ZdZddZedd            ZdddZdddZdddZ	ddZ
edd            ZddZdS )InboundPipelinea  Onion-model middleware pipeline engine for inbound message processing.

    Inspired by OpenClaw's MessagePipeline (extensions/yuanbao/src/business/
    pipeline/engine.ts).  Supports named middlewares, conditional guards
    (``when``), and ``use_before`` / ``use_after`` / ``remove`` for dynamic
    composition.

    Accepts both ``InboundMiddleware`` instances (OOP style) and plain
    ``async def(ctx, next_fn)`` callables (functional style) for flexibility.
    rS   r   c                    g | _         d S r   _middlewaresr  s    r\   __init__zInboundPipeline.__init__  s    "$r^   Nc                F    t          | t                    r	| j        | fS | |fS )zHNormalize (name, handler) or (InboundMiddleware,) into (name, callable).)r;  r}  r~  )
name_or_mwhandlers     r\   
_normalizezInboundPipeline._normalize  s/     j"344 	/?J..7""r^   'InboundPipeline'c                r    |                      ||          \  }}| j                            |||f           | S )u   Append a middleware to the end of the pipeline.

        Accepts either:
          - ``pipeline.use(SomeMiddleware())``  — OOP style
          - ``pipeline.use("name", some_fn)``   — functional style
        )r  r  r   )r  r  r  whenr~  hs         r\   usezInboundPipeline.use  s=     //*g66a  $4111r^   targetrR   c                   |                      ||          \  }}t          fdt          | j                  D             d          }|||f}|| j                            |           n| j                            ||           | S )zEInsert a middleware before *target* (by name).  Appends if not found.c              3  6   K   | ]\  }\  }}}|k    |V  d S r   r   r   r   n_r  s       r\   r   z-InboundPipeline.use_before.<locals>.<genexpr>  1      VV,!YaA!v++A++++VVr^   Nr  nextr   r  r   insert	r  r  r  r  r  r~  r  r   r   s	    `       r\   
use_beforezInboundPipeline.use_before  s    //*g66aVVVV)D4E*F*FVVVX\]]q$;$$U++++$$S%000r^   c                   |                      ||          \  }}t          fdt          | j                  D             d          }|||f}|| j                            |           n| j                            |dz   |           | S )zDInsert a middleware after *target* (by name).  Appends if not found.c              3  6   K   | ]\  }\  }}}|k    |V  d S r   r   r  s       r\   r   z,InboundPipeline.use_after.<locals>.<genexpr>  r  r^   Nro   r  r  s	    `       r\   	use_afterzInboundPipeline.use_after  s    //*g66aVVVV)D4E*F*FVVVX\]]q$;$$U++++$$S1We444r^   r~  c                8    fd| j         D             | _         | S )zRemove a middleware by name.c                .    g | ]\  }}}|k    |||fS r   r   )r   r  r  wr~  s       r\   r   z*InboundPipeline.remove.<locals>.<listcomp>	  s+    UUU71a1PT99aAY999r^   r  )r  r~  s    `r\   removezInboundPipeline.remove  s'    UUUUd6GUUUr^   r`  c                $    d | j         D             S )zAReturn ordered list of registered middleware names (for testing).c                    g | ]\  }}}|	S r   r   )r   r  r  s      r\   r   z4InboundPipeline.middleware_names.<locals>.<listcomp>  s    333gaA333r^   r  r  s    r\   middleware_namesz InboundPipeline.middleware_names  s     43!23333r^   r  r]  c                V   K   | j         ddfd              d{V  dS )zKRun all middlewares in order.  Each middleware receives ``(ctx, next_fn)``.r   rS   r   c                    K   t                    k     ra         \  } }}dz  | |          s2	  |           d {V  n,# t          $ r t                              d| d            w xY wd S d S )Nro   z'[InboundPipeline] middleware [%s] errorTexc_info)rq   r9  r4  error)r~  r  when_fnchainr  indexr  s      r\   r  z(InboundPipeline.execute.<locals>.next_fn  s      #e**$$).u&gw
&wws||&!'#w//////////    LL!JD[_L```  %$s   A
 
)A3Nr   r  )r  r  r  r  r  s    `@@@r\   executezInboundPipeline.execute  sa      !	 	 	 	 	 	 	 	 	  giir^   r   r   NN)rS   r  )r  rR   rS   r  )r~  rR   rS   r  )rS   r`  r  r]  rS   r   )r   r   r   r   r  r   r  r  r  r  r  propertyr  r  r   r^   r\   r  r    s        	 	% % % %
 # # # \#	 	 	 	 		 	 	 	 		 	 	 	 	   
 4 4 4 X4     r^   r  c                  V    e Zd ZdZdZedd            Zedd
            ZddZddZ	dS )DecodeMiddlewarezDecode raw inbound frames from JSON or Protobuf into ctx.push.

    Encapsulates JSON push parsing (aligned with TS decodeFromContent)
    and Protobuf decoding via ``decode_inbound_push``.
    decoderaw_bodyr`  rS   c                   g }| pg D ]}t          |t                    s|                    d          p|                    dd          }|                    d          p|                    di           }t          |t                    r*	 t	          j        |          }n# t          $ r d|i}Y nw xY w|                    ||pi d           |S )zNormalize raw JSON msg_body array to [{"msg_type": str, "msg_content": dict}].

        Compatible with both PascalCase (MsgType/MsgContent) and
        snake_case (msg_type/msg_content) naming.
        rs  MsgTypern   msg_content
MsgContentrQ   rs  r  )r;  r<  r  rR   r  loadsr9  r   )r  r   itemrs  r  s        r\   convert_json_msg_bodyz&DecodeMiddleware.convert_json_msg_body4  s     N 
	T 
	TDdD)) xx
++Ftxx	2/F/FH((=11OTXXlB5O5OK+s++ 88"&*["9"9KK  8 8 8#);"7KKK8MMx@QrRRSSSSs   B  B10B1raw_jsonr<  dict | Nonec                   | sdS |                      dd          p|                      dd          }|                      dd          p+|                      dd          p|                      dd          }|                      dg           p|                      d	g           }t                              |          }|s|s|                      d
          sdS |                      d
d          ||                      dd          p|                      dd          |                      dd          p|                      dd          ||                      dd          |                      dd          p|                      dd          |                      dd          p+|                      dd          p|                      dd          ||                      dd          p|                      dd          |                      dd          p|                      dd          |                      d          pdt          |                      d          t                    r+|                      d          pi                      dd          nddS )a  Convert JSON-format push to a dict with the same structure as
        ``decode_inbound_push``.

        Supports standard callback format (callback_command + from_account +
        msg_body) and legacy format fields (GroupId, MsgSeq, MsgKey, MsgBody,
        etc.).
        Nre  rn   From_Accountrf  GroupIdgroup_idri  MsgBodycallback_command
to_account
To_Accountrh  	nick_namerg  msg_seqr   MsgSeqrj  msg_keyMsgKeyrk  CloudCustomDatabot_owner_id
botOwnerIdrecall_msg_seq_listlog_exttrace_id)r  re  r  rh  rf  rg  r  rj  ri  rk  r  r  r  )r  r  r  r;  r<  )r  re  rf  msg_body_rawri  s        r\   parse_json_pushz DecodeMiddleware.parse_json_pushI  s     	4 LL,, 0||NB// 	
 LLr** ,||Ir**,||J++ 	 LLR(( +||Ir** 	 $99,GG  	H 	X\\BT5U5U 	4 !)-? D D(",,|R88ZHLLWY<Z<Z'||,=rBBchllS^`bFcFc$",,|R88||Iq11NX\\(A5N5Nll8R00mHLLB4O4OmS[S_S_`hjlSmSm !).A2!F!F!m(,,WhjlJmJm$LL<<^\[]@^@^#+<<0E#F#F#N$OYZbZfZfgpZqZqswOxOx  Ai006B;;JKKK  A
 
 	
r^   r$  bytestuplec                0   	 t          j        |                    d                    }n# t          $ r d}Y nw xY wt	          |t
                    r|                     |          }|r|dfS n)	 t          |          }n# t          $ r d}Y nw xY w|r|dfS dS )zFDecode a single raw frame into (push_dict, decoded_via) or (None, '').utf-8Nr  protobufNrn   )r  r  r  r9  r;  r<  r  r*   )r  r^  r$  	conn_jsonrc  s        r\   _decode_singlezDecodeMiddleware._decode_single}  s    	
4;;w#7#788II 	 	 	III	 i&& 
	(''	22D $V|#$*400    (Z''xs   '* 99.A> >BBr  r]  r   c                .  K   |j         }|sd S d }d}|D ]}|                     |j        |          \  }}|sEt                              d|j        j        |r|                                d d         nd           h|9|}|}t                              d|j        j        |t          |                     |                    dg           }	|	rZddd	id
}
|                    dg           |
gz   |	z   |d<   t                              d|j        j        t          |	                     |sd S ||_	        ||_
        t                              d|j        j        |j
        |j	                            dd          |j	                            dd          |j	                            dd          d |j	                            dg           D                        t                              d|j        j        |j	                    |             d {V  d S )Nrn   z;[%s] Push decoded but no valid message. raw hex(first64)=%s   z(empty)z#[%s] Frame decoded (via=%s): len=%dri  TIMTextElemrQ   rV   r  z;[%s] Merged %d extra msg_body elements from aggregated pushzC[%s] Push decoded (via=%s): from=%s group=%s msg_id=%s msg_types=%sre  rf  rj  c                :    g | ]}|                     d d          S )rs  rn   r  r   es     r\   r   z+DecodeMiddleware.handle.<locals>.<listcomp>  s&    IIIqQUU:r""IIIr^   z[%s] Push payload: %s)ra  r  r^  r4  r5  r~  hexrq   r  rc  rd  debug)r  r  r  	data_listmerged_pushrd  r$  rc  via
extra_body_seps              r\   r  zDecodeMiddleware.handle  s/     N	 	F 	 	D++CK>>ID# MK$$&Mdhhjj#&6&6I   ""!5K$c3t99    "XXj"55
 (5vtnUUD.9ooj".M.MQUPV.VYc.cK
+KKU(#j//  
  	F%QKcoHLL,,HLLr**HLL2&&IICHLLR,H,HIII	
 	
 	
 	,ck.>IIIgiir^   N)r  r`  rS   r`  )r  r<  rS   r  )r$  r  rS   r  r  )
r   r   r   r   r~  r   r  r  r  r  r   r^   r\   r  r  )  s          D    \( /
 /
 /
 \/
f   *4 4 4 4 4 4r^   r  c                      e Zd ZdZdZd	dZdS )
ExtractFieldsMiddlewarez8Extract common fields from ctx.push into ctx attributes.zextract-fieldsr  r]  rS   r   c                  K   |j         }|                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dg           |_        |                    dd          |_        |                    dd          |_         |             d {V  d S )	Nre  rn   rf  rg  rh  ri  rj  rk  )	rc  r  re  rf  rg  rh  ri  rj  rk  )r  r  r  rc  s       r\   r  zExtractFieldsMiddleware.handle  s      x88NB77,33,33"hh'8"==xx
B//XXh++
 $)<b A Agiir^   Nr  r   r   r   r   r~  r  r   r^   r\   r  r    s3        BBD	 	 	 	 	 	r^   r  c                      e Zd ZdZdZd	dZdS )
DedupMiddlewarezInbound message deduplication.dedupr  r]  rS   r   c                   K   |j         rQ|j        j                            |j                   r-t                              d|j        j        |j                    d S  |             d {V  d S )Nz)[%s] Duplicate message ignored: msg_id=%s)rj  r^  _dedupis_duplicater4  r  r~  r  s      r\   r  zDedupMiddleware.handle  sl      : 	#+,99#*EE 	LLDckFVX[XbcccFgiir^   Nr  r  r   r^   r\   r  r    s3        ((D     r^   r  c                      e Zd ZdZdZ eddh          ZdZdd
Ze	dd            Z
ddZe	dd            Zedd            Zed d            Ze	 d!d"d            ZdS )#RecallGuardMiddlewareu5  Intercept Group.CallbackAfterRecallMsg / C2C.CallbackAfterMsgWithDraw.

    Branch A: message in transcript (observed, not yet consumed) → redact content
    Branch B: message not in transcript → append system note
    Branch C: message currently being processed → silent interrupt + delayed redact
    recall_guardGroup.CallbackAfterRecallMsgzC2C.CallbackAfterMsgWithDrawzM[This message was recalled/withdrawn by the sender; original content removed]r  r]  rS   r   c                   K   |j         pi                     dd          }|| j        vr |             d {V  d S |                     ||           d S )Nr  rn   )rc  r  _RECALL_COMMANDS_handle_recall)r  r  r  cmds       r\   r  zRecallGuardMiddleware.handle  sh      x~2""#5r::d+++'))OOOOOOOFC%%%%%r^   rf  rR   re  c                \    |                      |rd| nd| |rdnd|pd |rdnd           S )Ngroup:direct:groupdmmain)rl  rm  user_id	thread_id)build_source)r^  rf  re  s      r\   _build_sourcez#RecallGuardMiddleware._build_source  s^    ##.8V*j***>V>V>V!+5gg (D *4ff	 $ 
 
 	
r^   r  c                
   |j         }|j        pi }|dk    r|                    d          pg }n8|                    d          pd}|                    d          }|s|r||dgng }|s"t                              d|j                   d S |                    d          pd                                }|                    d	          pd                                }	|D ]}
|
                    d          p#t          |
                    d          pd          }|s>|                     ||          }|| 	                    |||||	           p|j
                            |          }|                     ||||	|           d S )
Nr  r  rj  rn   r  )rj  r  z2[%s] Recall callback with empty seq_list, skippingrf  re  )r^  rc  r  r4  r  r~  rc   rR   _find_processing_session_interrupt_for_recall_msg_content_cache_patch_transcript)r  r  r  r^  rc  seq_listr{   seqrf  re  	seq_entryrecalled_id
matched_skrecalled_contents                 r\   r  z$RecallGuardMiddleware._handle_recall  s   +x~2000xx 566<"HH((8$$*C((9%%C=@PCP337788bH 	LLMw|\\\Fhh|,,299;;
006B==??! 
	i 
	iI#--11XSy9Q9Q9WUW5X5XK 66wLLJ%**7JZYeffff#*#=#A#A+#N#N &&wZWghhhh
	i 
	ir^   r  rq  c                j    | j                                         D ]\  }}||k    r|| j        v r|c S d S r   )_processing_msg_idsr  _active_sessions)r^  r  skr{   s       r\   r  z.RecallGuardMiddleware._find_processing_session&  sK    288:: 	 	GBk!!bG,D&D&D			tr^   session_keyc           	        |rd| nd| }d| d| d}t          |t          j        |                     |||          d          }||j        |<   |j                            |          }	|	|	                                 t          	                    d|j
        ||d d	                    |j                            |d
          }
|
r|                     |||
||           d S d S )Nzgroup zdirect chat with u_   [CRITICAL — MESSAGE RECALLED] The user message that triggered your current task (message_id="z") in ud   has been recalled/withdrawn by the sender. IGNORE any prior system note asking you to finish processing tool results — the original request is void. Do NOT continue the task, do NOT call more tools, do NOT reference the recalled content. Reply only with a brief acknowledgment such as "The message has been recalled." in the language the user was using.T)rQ   message_typerO  internalz+[%s] Recall interrupt: msg_id=%s session=%s   rn   )r   r   TEXTr  _pending_messagesr   r  r   r4  r5  r~  _processing_msg_texts_schedule_content_redact)r   r^  r"  r  rf  re  whererecall_textsynth_eventactive_eventrecalled_texts              r\   r  z+RecallGuardMiddleware._interrupt_for_recall-  s=    *4[%%%%9[\9[9[	,/:	, 	,CH	, 	, 	, 	 #$)$$Wj,GG	
 
 
 2=!+./33K@@#A7<Q\^ijmkmjm^nooo  599+rJJ 	h((+}jZfggggg	h 	hr^   r/  c                     d fd}t          j         |                      }j                            |           |                    j        j                   d S )NrS   r   c            	     2  K   t          dd           } | sd S 	 |                                         	                    j        }n# t          $ r Y d S w xY wt          d          D ]}t          j        d           d {V  	 |                     |          }n# t          $ r Y @w xY w|D ]}|	                    d          dk    r|	                    d          
k    rj
        |d<   	 |                     ||           t                              dj        d d                    n8# t          $ r+}t                              dj        |           Y d }~nd }~ww xY w  d S t                              d	j        d d                    d S )
N_session_storer&  g      ?roleusercontentz[%s] Recall redact: session %sz[%s] Recall redact failed: %sz?[%s] Recall redact: content not found after polling, session %s)getattrget_or_create_sessionr  
session_idr9  r,  r   r@  load_transcriptr  	_REDACTEDrewrite_transcriptr4  r5  r~  r>  r  )storesidr  
transcriptr   rH  r^  r   re  rf  r/  r"  s         r\   _redactz?RecallGuardMiddleware._schedule_content_redact.<locals>._redactV  s     G%5t<<E 11%%gz<HH       2YY  mC(((((((((!&!6!6s!;!;JJ    H'  Eyy((F22uyy7K7K}7\7\+.=i(_!44S*EEE"KK(H',XcdgegdgXhiiii( _ _ _"NN+JGLZ]^^^^^^^^_ LLZ\c\hjuvywyvyjz{{{{{s;   /A
 

AAB
B+*B+/?D//
E$9!EE$r   )r   create_task_background_tasksr   add_done_callbackdiscard)r   r^  r"  r/  rf  re  r?  tasks   ``````  r\   r*  z.RecallGuardMiddleware._schedule_content_redactS  s    	| 	| 	| 	| 	| 	| 	| 	| 	| 	| 	|: "7799--!%%d+++w8@AAAAAr^   Nr  c                x   t          |dd           }|sd S 	 |                    |                     |||                    j        }n9# t          $ r,}t
                              d|j        |           Y d }~d S d }~ww xY wg }		 |                    |          }
|
	                                rt          |
dd          5 }|D ]V}|                                }|r>	 |	                    t          j        |                     A# t          j        $ r Y Rw xY wW	 d d d            n# 1 swxY w Y   n9# t          $ r,}t
                              d|j        |           Y d }~d S d }~ww xY wd }|	D ]}|                    d          |k    r|} n |=|r;|	D ]8}|                    d          d	k    r|                    d
          |k    r|} n9|}| j        |d
<   	 |                    ||	           t
                              d|j        |           n8# t          $ r+}t
                              d|j        |           Y d }~nd }~ww xY wd S |                    |dd| dt+          j        t.          j                                                  d           t
                              d|j        |           d S )Nr2  z*[%s] Recall: failed to resolve session: %srr  encodingz*[%s] Recall: failed to load transcript: %s
message_idr3  r4  r5  z*[%s] Recall: redacted msg_id=%s (branch A)z*[%s] Recall: rewrite_transcript failed: %ssystemz[recall] message_id="z2" has been recalled; do not quote or reference it.r   )r3  r5  r   z1[%s] Recall: system note for msg_id=%s (branch B))r6  r7  r  r8  r9  r4  r>  r~  get_transcript_pathexistsopenrc   r   r  r  JSONDecodeErrorr  r:  r;  r5  append_to_transcriptr   r   r   utc	isoformat)r   r^  r  rf  re  r  r<  r=  rH  r>  pathfr[   r  r   s                  r\   r  z'RecallGuardMiddleware._patch_transcripty  s    !1488 	F	--c.?.?Ua.b.bccnCC 	 	 	NNGWZ[[[FFFFF	
 
	,,S11D{{}} %$g666 %! ! % %#zz|| %% * 1 1$*T2B2B C C C C#'#7 % % % $%%%% % % % % % % % % % % % % % %  	 	 	NNGWZ[[[FFFFF	  	 	Eyy&&+55 6 >.>#  99V$$..599Y3G3GK[3[3["FE #F9`((j999H',Xcdddd ` ` `KW\[^________`F 	""3n{nnn!666@@BB)
 )
 	 	 	
 	GWbcccccs   /A 
A=!A88A=;D1 >D%'DD%DD%DD%D1 %D))D1 ,D)-D1 1
E';!E""E'7H 
I!IIr  )rf  rR   re  rR   )r  r]  r  rR   rS   r   )r  rR   rS   rq  )
r"  rR   r  rR   rf  rR   re  rR   rS   r   )
r"  rR   r/  rR   rf  rR   re  rR   rS   r   r   )
r  rR   rf  rR   re  rR   r  rq  rS   r   )r   r   r   r   r~  	frozensetr  r:  r  r   r  r  r  r   r  r*  r  r   r^   r\   r  r    s0         D y&&"   `I& & & & 
 
 
 \
i i i i@    \ #h #h #h [#hJ !B !B !B [!BJ OS8d 8d 8d 8d [8d 8d 8dr^   r  c                  6    e Zd ZdZdZedd	            ZddZdS )SkipSelfMiddlewarezFilter out bot's own messages.z	skip-selfre  rR   r%  rq  rS   rT   c                    | r|sdS | |k    S )z2Detect whether the message is from the bot itself.Fr   )re  r%  s     r\   _is_self_referencez%SkipSelfMiddleware._is_self_reference  s#      	6 	5v%%r^   r  r]  r   c                   K   |                      |j        |j        j                  r-t                              d|j        j        |j                   d S  |             d {V  d S )Nz'[%s] Ignoring self-sent message from %s)rX  re  r^  _bot_idr4  r  r~  r  s      r\   r  zSkipSelfMiddleware.handle  sf      ""3#3S[5HII 	LLBCKDTVYVfgggFgiir^   N)re  rR   r%  rq  rS   rT   r  )r   r   r   r   r~  r   rX  r  r   r^   r\   rV  rV    sQ        ((D& & & \&     r^   rV  c                      e Zd ZdZdZd	dZdS )
ChatRoutingMiddlewarez9Determine chat_id, chat_type, chat_name from push fields.zchat-routingr  r]  rS   r   c                   K   |j         r*d|j          |_        d|_        |j        p|j         |_        n)d|j         |_        d|_        |j        p|j        |_         |             d {V  d S )Nr
  r  r  r  )rf  rl  rm  rg  rn  re  rh  r  s      r\   r  zChatRoutingMiddleware.handle  s      > 	D33>33CK#CMN<cnCMM6C$466CK CM/C33CCMgiir^   Nr  r  r   r^   r\   r\  r\    s3        CCD	 	 	 	 	 	r^   r\  c                  Z    e Zd ZdZdd
ZddZddZedd            Zedd            Z	dS )AccessPolicyzPlatform-level DM / Group access control policy.

    Encapsulates the allow/deny logic so that both inbound middleware
    and outbound ``send_dm`` can share the same rules without reaching
    into adapter internals.
    	dm_policyrR   dm_allow_fromr   group_policygroup_allow_fromrS   r   c                >    || _         || _        || _        || _        d S r   )
_dm_policy_dm_allow_from_group_policy_group_allow_from)r  r`  ra  rb  rc  s        r\   r  zAccessPolicy.__init__  s)     $+)!1r^   	sender_idrT   c                l    | j         dk    rdS | j         dk    r|                                | j        v S dS )z?Platform-level DM inbound filter (open / allowlist / disabled).disabledF	allowlistT)re  rc   rf  )r  ri  s     r\   is_dm_allowedzAccessPolicy.is_dm_allowed  s>    ?j((5?k))??$$(;;;tr^   rf  c                l    | j         dk    rdS | j         dk    r|                                | j        v S dS )zGPlatform-level group chat inbound filter (open / allowlist / disabled).rk  Frl  T)rg  rc   rh  r  rf  s     r\   is_group_allowedzAccessPolicy.is_group_allowed  sB    ++5,,##%%)???tr^   c                    | j         S r   )re  r  s    r\   r`  zAccessPolicy.dm_policy  s
    r^   c                    | j         S r   )rg  r  s    r\   rb  zAccessPolicy.group_policy  s    !!r^   N)
r`  rR   ra  r   rb  rR   rc  r   rS   r   )ri  rR   rS   rT   )rf  rR   rS   rT   r   )
r   r   r   r   r  rm  rp  r  r`  rb  r   r^   r\   r_  r_    s         
2 
2 
2 
2          X " " " X" " "r^   r_  c                      e Zd ZdZdZd	dZdS )
AccessGuardMiddlewarez.Platform-level DM/Group access control filter.zaccess-guardr  r]  rS   r   c                  K   |j         }|j        }|j        dk    rI|                    |j                  s.t
                              d|j        |j        |j                   d S nS|j        dk    rH|	                    |j
                  s.t
                              d|j        |j
        |j                   d S  |             d {V  d S )Nr  z'[%s] DM from %s blocked by dm_policy=%sr  z([%s] Group %s blocked by group_policy=%s)r^  _access_policyrm  rm  re  r4  r  r~  r`  rp  rf  rb  )r  r  r  r^  policys        r\   r  zAccessGuardMiddleware.handle  s      +&5=D  ''(899 =L#"2F4D    ]g%%**3>:: >L#.&2E   giir^   Nr  r  r   r^   r\   rt  rt    s3        88D     r^   rt  c                      e Zd ZdZdZd	dZdS )
AutoSetHomeMiddlewarea  Auto-designate the first inbound conversation as Yuanbao home channel.

    Triggers when no home channel is configured, or when an existing group-chat
    home is superseded by the first DM (direct > group upgrade).
    Silent: writes config.yaml and env, no user-facing message.
    zauto-sethomer  r]  rS   r   c                  K   |j         }|j        s_t          j        dd          }| p|                    d          o
|j        dk    }|j        dk    rd|_        |r	 ddlm} ddlm	} dd l
} |            }	|	d	z  }
i }|
                                r@t          |
d
          5 }|                    |          pi }d d d            n# 1 swxY w Y   |j        |d<    ||
|           t          |j                  t          j        d<   t"                              d|j        |j        |j                   n8# t*          $ r+}t"                              d|j        |           Y d }~nd }~ww xY w |             d {V  d S )NYUANBAO_HOME_CHANNELrn   r
  r  Tr   )get_hermes_home)atomic_yaml_writezconfig.yamlr  rG  z=[%s] Auto-sethome: designated %s (%s) as Yuanbao home channelz[%s] Auto-sethome failed: %s)r^  _auto_sethome_doneosgetenvrY   rm  hermes_constantsr|  utilsr}  yamlrL  rM  	safe_loadrl  rR   environr4  r5  r~  rn  r9  r>  )r  r  r  r^  	_cur_home_should_setr|  r}  r  _homeconfig_pathuser_configrS  r  s                 r\   r  zAutoSetHomeMiddleware.handle)  s2     +) 	T	"8"==I N((22Ls}7L  }$$-1* TT@@@@@@777777KKK+O--E"'-"7K(*K"))++ B!+@@@ BA*...*;*;*ArKB B B B B B B B B B B B B B B:=+K 67%%k;???9<S[9I9IBJ56KKWck3=   
 ! T T TNN#A7<QRSSSSSSSSTgiis>    AD9 &C
>D9 
CD9 CA&D9 9
E.!E))E.Nr  r  r   r^   r\   ry  ry    s9          D           r^   ry  c                      e Zd ZdZdZdZedd            Zedd
            Zedd            Z	e
dd            Zedd            Zedd            Zed d            Zd!dZdS )"ExtractContentMiddlewarez.Extract raw text and media refs from msg_body.zextract-content  customr<  rS   rR   c                   |                      dd          }|                      dd          }|r	d| d| dnd| d}|g}t          j        }dD ]j}|                      |          }|rQt          |t                    r<t          |          |k    r|d|         d	z   n|}|                    d
|             nk|r|                    d           d                    |          S )zAFormat elem_type 1010 (share card) into bracket-placeholder text.titlern   linkz[share_card: z | ])card_content
wechat_desNz...(truncated)z	Preview: z[visit link for full content]rV   )r  r  _CARD_CONTENT_MAX_LENGTHr;  rR   rq   r   r   )	r  r  r  headerr   max_lenr[  valpreviews	            r\   _format_shared_linkz,ExtractContentMiddleware._format_shared_linkS  s    

7B''zz&"%%6:X22242222@XPU@X@X@X*C3 	 	E**U##C z#s++ >A#hh>P>P#hwh-*:::VY222333 	:LL8999yyr^   rq  c                2   |                      d          }|sdS 	 t          j        |          }t          |t                    r|                     d          nd}n# t          j        t          f$ r d}Y nw xY w|rt          |t                    sdS d| dS )zNFormat elem_type 1007 (link understanding card) into bracket-placeholder text.r5  Nr  z[link: z | visit link for full content])r  r  r  r;  r<  rN  	TypeErrorrR   )r  r5  parsedr  s       r\   _format_link_understandingz3ExtractContentMiddleware._format_link_understandinge  s     **Y'' 	4	Z((F)3FD)A)AK6::f%%%tDD$i0 	 	 	DDD	 	:dC00 	4>>>>>s   A A A76A7rA  c                ^   | sdS 	 t           j                            t           j                            |           j                  }|                    d          p|                    d          pg }|r't          |d                                                   ndS # t          $ r Y dS w xY w)zExtract resourceId from Yuanbao resource URL query parameters.

        Args:
            url: Resource URL (e.g., https://...?resourceId=abc123)

        Returns:
            Resource ID string, or empty string if not found
        rn   
resourceId
resourceidr   )	urllibparseparse_qsurlparsequeryr  rR   rc   r9  )rA  r  idss      r\   _parse_resource_idz+ExtractContentMiddleware._parse_resource_idt  s      	2	L))&,*?*?*D*D*JKKE))L))JUYY|-D-DJC*-53s1v;;$$&&&25 	 	 	22	s   BB 
B,+B,ri  r`  c                    g }|D ]`}|                     dd          }|                     di           }|dk    r.|                     dd          }|r|                    |           c|dk    r|                     d          }t          |t                    sg }d}t	          |          d	k    r$t          |d	         t
                    r	|d	         }n6t	          |          d
k    r#t          |d
         t
                    r|d
         }t          |pi                      d          pd                                          }	|                     |	          }
|                    |
rd|
 dnd           q|dk    r|                     d|                     d|                     dd                              }t          |                     d          pd                                          }|                     |          }
|
r&|                    |r	d| d|
 dnd|
 d           (|                    |rd| dnd           G|dk    rjt          |                     d          pd                                          }|                     |          }
|                    |
rd|
 dnd           |dk    rjt          |                     d          pd                                          }|                     |          }
|                    |
rd|
 dnd           '|dk    rw|                     dd          }|rG	 t          j
        |          }t          |t
                    s|                    d            |                     d!          }|d"k    r*|                    |                     dd#                     n|d$k    r)|                    |                     |                     n^|d%k    rC|                     |          }|r|                    |           n+|                    d            n|                    d            \# t          j        t          f$ r |                    |           Y w xY w|                    d            |d&k    r|                     dd          }d}|ra	 t          j
        |          }|                     d'          pd                                }n"# t          j        t          t          f$ r Y nw xY w|                    |rd(| dnd)           E|r|                    d*| d           b|rd+                    |          ndS ),a  Extract plain text content from MsgBody.

        - TIMTextElem      -> text field
        - TIMImageElem     -> "[image]"
        - TIMFileElem      -> "[file: {filename}]"
        - TIMSoundElem     -> "[voice]"
        - TIMVideoFileElem -> "[video]"
        - TIMFaceElem      -> "[emoji: {name}]" or "[emoji]"
        - TIMCustomElem    -> try to extract data field, otherwise "[custom message]"
        - Multiple elems joined with spaces
        rs  rn   r  r  rQ   TIMImageElemimage_info_arrayNro   r   rA  z[image|ybres:r  [image]TIMFileElem	file_namefileNamefilenamez[file:|ybres:z[file|ybres:[file: [file]TIMSoundElemz[voice|ybres:[voice]TIMVideoFileElemz[video|ybres:[video]TIMCustomElemr$  z[unsupported message type]	elem_type  z	[mention]    TIMFaceElemr~  z[emoji: z[emoji][ )r  r   r;  r`  rq   r<  rR   rc   r  r  r  r  r  rN  r  AttributeErrorr   )r   ri  partselemr  r5  rQ   r  
image_info	image_urlridr  file_url	sound_url	video_urldata_valr  ctyperaw_data	face_name	face_datas                        r\   _extract_textz&ExtractContentMiddleware._extract_text  s     L	/ L	/D!XXj"55I HH]B77GM)){{62.. 'LL&&&n,,#*;;/A#B#B !"2D99 *')$!
'((1,,<LQ<OQU1V1V,!1!!4JJ)**Q..:>Nq>QSW3X3X.!1!!4J!1r 6 6u = = CDDJJLL	,,Y77sI3S3333	JJJJm++";;{GKK
GKKXbdfLgLg4h4hiiw{{5117R88>>@@,,X66 RLLX!h!A(!A!A3!A!A!A!AShbeShShShiiiiLL(!P!68!6!6!6!6QQQQn,,E 2 2 8b99??AA	,,Y77sI3S3333	JJJJ000E 2 2 8b99??AA	,,Y77sI3S3333	JJJJo--";;vr22 ?/!%H!5!5)&$77 %!LL)EFFF$ &

; 7 7 D==!LLFK)H)HIIII"d]]!LL)@)@)H)HIIII"d]]#&#A#A&#I#ID# K %T 2 2 2 2 %-I J J J J!LL)EFFF 0)< / / /X...../ LL!=>>>>m++";;vr22	 $(Jx$8$8	%.]]6%:%:%@b$G$G$I$I		 0)^L   	P4	4444yQQQQ /----..."'/sxxR/s+   >Q!CQ!!+RR=T		T('T(rQ   c                r    |                                  } |                     d          rd| dd         z   } | S )zNormalize input text: strip whitespace and convert full-width slash
        (Chinese input method) to ASCII slash so commands are recognized correctly.
           ／r  ro   Nrc   rY   r   s    r\   _rewrite_slash_commandz/ExtractContentMiddleware._rewrite_slash_command  s;    
 zz||??8$$ 	"abb>Dr^   List[Dict[str, str]]c                   g }| pg D ]S}t          |t                    s|                    dd          }|                    di           pi }t          |t                    s]|dk    r|                    d          }t          |t                    sg }d}t	          |          dk    r$t          |d         t                    r	|d         }n6t	          |          dk    r#t          |d         t                    r|d         }t          |pi                     d	          pd                                          }|r|                    d
|d           R|dk    rt          |                    d	          pd                                          }t          |                    d          pd                                          pkt          |                    d          pd                                          p5t          |                    d          pd                                          }	|r!d|d}
|	r|	|
d<   |                    |
           U|S )zExtract inbound image/file references from TIM msg_body.

        Return example:
          [{"kind": "image", "url": "https://..."}, {"kind": "file", "url": "...", "name": "a.pdf"}]
        rs  rn   r  r  r  Nro   r   rA  rK   )kindrA  r  r  r  r  rL   r~  )r;  r<  r  r`  rq   rR   rc   r   )ri  refsr  rs  r5  r  r  r  r  r  refs              r\   _extract_inbound_media_refsz4ExtractContentMiddleware._extract_inbound_media_refs  sl    &(N "	% "	%DdD)) xx
B//Hhh}b117RGgt,, >))#*;;/A#B#B !"2D99 *')$!
'((1,,<LQ<OQU1V1V,!1!!4JJ)**Q..:>Nq>QSW3X3X.!1!!4J!1r 6 6u = = CDDJJLL	 EKK C CDDD=((w{{5117R88>>@@K006B77==?? B7;;z228b99??AAB7;;z228b99??AA 
  %39(*K*KC  0&/FKK$$$r^   c                t   g }| pg D ]}t          |t                    r|                    d          dk    r2|                    d          pi                     dd          }|s`	 t          j        |          }n# t          j        t          f$ r Y w xY wt          |t                    s|                    d          }|dk    rC|                    d          }|r*t          |t                    r|                    |           |d	k    r|                    d
          }|r	 t          j        |          }t          |t                    r|                    d          nd}|r*t          |t                    r|                    |           # t          j        t          f$ r Y w xY w|S )zTExtract link URLs from share-card (1010) and link-understanding (1007) custom elems.rs  r  r  r$  rn   r  r  r  r  r5  N)	r;  r<  r  r  r  rN  r  rR   r   )	ri  urlsr  data_strr  r  r  r5  r  s	            r\   _extract_link_urlsz+ExtractContentMiddleware._extract_link_urls  s    N 	 	DdD)) TXXj-A-A_-T-T//52::62FFH H--()4   fd++ JJ{++E}}zz&)) &JtS11 &KK%%%$ **Y// !%G!4!45?5M5MWvzz&111SW .JtS$9$9 . KK--- 0)<   s%   (A==BB+A,FF32F3r  r]  r   c                
  K   |                      |                     |j                            |_        |                     |j                  |_        |                     |j                  |_         |             d {V  d S r   )r  r  ri  ro  r  rp  r  ry  r  s      r\   r  zExtractContentMiddleware.handle=  st      2243E3Ecl3S3STT99#,GG//==giir^   N)r  r<  rS   rR   )r  r<  rS   rq  rA  rR   rS   rR   )ri  r`  rS   rR   r   )ri  r`  rS   r  )ri  r`  rS   r`  r  )r   r   r   r   r~  r  r   r  r  r  r   r  r  r  r  r  r   r^   r\   r  r  L  s       88D#      \ " ? ? ? \?    \$ [0 [0 [0 [[0z    \ * * * \*X    \@     r^   r  c                  ^    e Zd ZU dZdZ eh d          Zded<   eddd            Z	ddZ
dS )PlaceholderFilterMiddlewarez>Skip pure placeholder messages (e.g. '[image]' with no media).zplaceholder-filter>      [图片]   [文件]   [视频]   [语音]r  r  r  r  rT  SKIPPABLE_PLACEHOLDERSr   rQ   rR   media_countri   rS   rT   c                L    |dk    rdS |                                 }|| j        v S )zEDetect whether the message is a pure placeholder (should be skipped).r   F)rc   r  )r   rQ   r  r   s       r\   is_skippable_placeholderz4PlaceholderFilterMiddleware.is_skippable_placeholderM  s-     ??5::<<3555r^   r  r]  r   c                   K   |                      |j        t          |j                            r-t                              d|j        j        |j                   d S  |             d {V  d S )Nz%[%s] Skipping placeholder message: %r)r  ro  rq   rp  r4  r  r^  r~  r  s      r\   r  z"PlaceholderFilterMiddleware.handleU  sj      ((s3>7J7JKK 	LL@#+BRTWT`aaaFgiir^   N)r   )rQ   rR   r  ri   rS   rT   r  )r   r   r   r   r~  rT  r  rY  r   r  r  r   r^   r\   r  r  C  s         HHD(1	 3 3 3 ) )    
 6 6 6 6 [6     r^   r  c                  t    e Zd ZU dZdZ eh d          Zded<   edd	            Z	e
dd            ZddZdS )OwnerCommandMiddlewarezDetect bot-owner slash commands in group chat.

    Identifies in-group allowlisted slash commands and determines sender identity.
    Owner commands skip @Bot detection; non-owner attempts are rejected.
    zowner-command>   /q/bg/btw/new/deny/stop/undo/queue/reset/retry/approve/backgroundrT  	ALLOWLISTrQ   rR   rS   c                r    |                                  } |                     d          rd| dd         z   } | S )z?Normalize full-width slash to ASCII slash and strip whitespace.r  r  ro   Nr  r   s    r\   r  z-OwnerCommandMiddleware._rewrite_slash_commandl  s;     zz||??8$$ 	"abb>Dr^   rc  r<  ri  r`  rm  re  )Tuple[Optional[str], Optional[str], bool]c               6   |dk    s| j         sdS d |pg D             }t          |          dk    rdS |d                             d          pi                     dd          }|                     |          }|                    d	          sdS |                    d
          d                                         }|| j         vrdS t          |pi                     d          pd                                          }	t          |	          o|	|k    }
|||
fS )a2  Identify allowlisted slash commands and determine sender identity.

        Returns (cmd, cmd_line, is_owner):
          - (None, None, False): Not an allowlisted command
          - (cmd, cmd_line, True): Owner match
          - (cmd, cmd_line, False): Allowlisted command but sender is not owner
        r  )NNFc                D    g | ]}|                     d           dk    |S )rs  r  r  r  s     r\   r   z@OwnerCommandMiddleware._detect_owner_command.<locals>.<listcomp>  s9     
 
 
uuZ  M11 111r^   ro   r   r  rQ   rn   r  )maxsplitr  )
r  rq   r  r  rY   rX   lowerrR   rc   rT   )r   rc  ri  rm  re  
text_elemsrQ   cmd_liner  owner_idis_owners              r\   _detect_owner_commandz,OwnerCommandMiddleware._detect_owner_commandt  s=     s}$$
 
 B
 
 

 z??a$$1!!-006B;;FBGG--d33""3'' 	%$$nnan((+1133cm##$$ 
''77=2>>DDFF>>>h,&>Hh&&r^   r  r]  r   c           
       K   |j         }|                     |j        |j        |j        |j                  \  }}}|rz|sxt                              d|j        |j	        |j        |           |
                    t          j        |                    |j	        d| d          d|                      d S |r?|r=|r;t                              d|j        |j	        |j        |           ||_        ||_         |             d {V  d S )N)rc  ri  rm  re  z;[%s] Reject non-owner slash command: chat=%s from=%s cmd=%su   ⚠️ z6 is only available to the creator in private chat modezyuanbao-owner-cmd-denial-r~  z4[%s] Bot owner slash command: chat=%s from=%s cmd=%s)r^  r  rc  ri  rm  re  r4  r5  r~  rl  _track_taskr   r@  sendrr  ro  )r  r  r  r^  matched_cmdr   r  s          r\   r  zOwnerCommandMiddleware.handle  sH     +*.*D*D\m)	 +E +
 +
'Xx  
	x 
	KKMck3+;[    3S[*wK*w*w*wxx>>>! ! !    F 	$8 	$ 	$KKFck3+;[   !,C#CLgiir^   Nr   )
rc  r<  ri  r`  rm  rR   re  rR   rS   r  r  )r   r   r   r   r~  rT  r  rY  r   r  r   r  r  r   r^   r\   r  r  \  s           D %9 & & &  I        \ (' (' (' [('T     r^   r  c                      e Zd ZdZdZd	dZdS )
BuildSourceMiddlewarez(Build SessionSource from context fields.zbuild-sourcer  r]  rS   r   c           	        K   |j         }|                    |j        |j        |j        |j        pd |j        p|j        |j        dk    rdnd           |_         |             d {V  d S )Nr  r  )rl  rm  rn  r  	user_namer  )r^  r  rl  rm  rn  re  rh  rO  r  r  r  r^  s       r\   r  zBuildSourceMiddleware.handle  s      +))Kmm$,)=S-= # 8 8ffd * 
 

 giir^   Nr  r  r   r^   r\   r
  r
    s3        22D
 
 
 
 
 
r^   r
  c                      e Zd ZdZdZedd	            Zedd            Zedd            Zedddd            Z	ddZ
dS )GroupAtGuardMiddlewarezIn group chat, observe non-@bot messages; only reply on @Bot.

    Owner commands skip @Bot detection (owner doesn't need to @Bot).
    zgroup-at-guardri  r`  r%  rq  rS   rT   c                t   |sdS | D ]}|                     d          dk    r|                     di                                dd          }|sI	 t          j        |          }n# t          j        t          f$ r Y ww xY w|                     d          dk    r|                     d	          |k    r d
S dS )a  Detect whether the message @Bot.

        AT element format: TIMCustomElem, msg_content.data is a JSON string:
            {"elem_type": 1002, "text": "@xxx", "user_id": "<botId>"}
        Considered @Bot when elem_type == 1002 and user_id == bot_id.
        Frs  r  r  r$  rn   r  r  r  T)r  r  r  rN  r  )ri  r%  r  r  r  s        r\   
_is_at_botz!GroupAtGuardMiddleware._is_at_bot  s      	5 	 	Dxx
##66xxr2266vrBBH H--()4   zz+&&$..6::i3H3HF3R3Rttu   A&&A?>A?rR   c                   |sdS | D ]}|                     d          dk    r|                     di                                dd          }|sI	 t          j        |          }n# t          j        t          f$ r Y ww xY w|                     d          dk    rU|                     d          |k    r<t          |                     d	          pd                                          }|r|c S dS )
zLExtract the display text used to @-mention this bot (e.g. ``@yuanbao-bot``).rn   rs  r  r  r$  r  r  r  rQ   )r  r  r  rN  r  rR   rc   )ri  r%  r  r  r  mention_texts         r\   _extract_bot_mention_textz0GroupAtGuardMiddleware._extract_bot_mention_text  s     	2 	( 	(Dxx
##66xxr2266vrBBH H--()4   zz+&&$..6::i3H3HF3R3R"6::f#5#5#;<<BBDD (''''rr  c                p    t          |pd          }t                              | |          pd}d| d| dS )zOBuild a per-turn group-chat prompt that highlights which message to respond to.unknownzHYou are handling a Yuanbao group chat message.
- Your identity: user_id=z, @-mention name in this group=z
- Lines in history prefixed with `[nickname|user_id]` are observed group context and are not necessarily addressed to you.
- Treat only the current new message as a request explicitly directed at you, and answer it directly.)rR   r  r  )ri  r%  bidbot_mentions       r\   _build_group_channel_promptz2GroupAtGuardMiddleware._build_group_channel_prompt  s[     &%I&&,FFxQWXXe\e&(+& &LW& & &	
r^   Nrj  sender_displayrQ   rj  r   c                  t          | dd          }|sdS 	 |                    |          }|j        pd}d| d| d| }d|t          j        t
          j                                                  d	d
}	|r||	d<   |                    |j	        |	           dS # t          $ r,}
t                              d| j        |
           Y d}
~
dS d}
~
ww xY w)as  Write a group message into the session transcript without triggering the agent.

        This allows the model to see the full group conversation when it is
        eventually invoked via @bot.  Messages are stored with ``role: "user"``
        in the format ``[nickname|user_id]\n<content>`` so the model
        can distinguish participants and their user ids.
        r2  Nr  r  ra   ]
r4  r   T)r3  r5  r   observedrI  z([%s] Failed to observe group message: %s)r6  r7  r  r   r   r   rP  rQ  rO  r8  r9  r4  r>  r~  )r^  rO  r  rQ   rj  r<  session_entryr  
attributedr   rH  s              r\   _observe_group_messagez-GroupAtGuardMiddleware._observe_group_message  s(    !1488 	F	Z!77??Mn1	G@^@@g@@$@@J%%\X\:::DDFF 	 E  -&,l#&&(      	Z 	Z 	ZNNEw|UXYYYYYYYYY	Zs   BB 
C$!CCr  r]  c                f  K   |j         }|j        dk    r|j        s|                     |j        |j                  se|                     ||j        |j        p|j	        |j
        |j        pd            t                              d|j        |j        |j	                   d S  |             d {V  d S )Nr  r  z6[%s] Group message observed (no @bot): chat=%s from=%s)r^  rm  rr  r  ri  rZ  r"  rO  rh  re  ro  rj  r4  r5  r~  rl  r  s       r\   r  zGroupAtGuardMiddleware.handle4  s      +=G##C,=#dooVYVbdkdsFtFt#''S%8%LC<Lclz)T (    KKHck3+;   Fgiir^   )ri  r`  r%  rq  rS   rT   )ri  r`  r%  rq  rS   rR   )r  rR   rQ   rR   rj  rq  rS   r   r  )r   r   r   r   r~  r   r  r  r  r"  r  r   r^   r\   r  r    s         
 D   \.    \( 
 
 
 \
  $(Z Z Z Z Z \ZB     r^   r  c                      e Zd ZdZdZd	dZdS )
GroupAttributionMiddlewarea  Tag group @bot messages with [nickname|user_id] attribution and channel_prompt.

    For group messages that pass the @bot guard (i.e. the bot is mentioned),
    this middleware:
      - Builds a per-turn channel_prompt so the model knows its identity and
        the attribution scheme.
      - Rewrites ctx.raw_text to ``[nickname|user_id]\n<content>`` to match
        the observed-history format.
      - Suppresses the runner's default ``[user_name]`` shared-thread prefix
        by clearing ``source.user_name``.
    zgroup-attributionr  r]  rS   r   c                Z  K   |j         dk    r|j        s|j        }t                              |j        |j                  |_        |j        pd}|j	        p|j        pd}d| d| d|j
         |_
        |j         t          j        |j        d           |_         |             d {V  d S )Nr  r  r  ra   r  )r  )rm  rr  r^  r  r  ri  rZ  rz  re  rh  ro  rO  dataclassesr   )r  r  r  r^  user_id_labelnickname_labels         r\   r  z!GroupAttributionMiddleware.handleR  s      =G##C,=#kG!7!S!Sgo" "C  ,9	M 0QC4DQ	NP~PPPP#,PPCL z%(0tLLL
giir^   Nr  r  r   r^   r\   r%  r%  C  s9        
 
 D     r^   r%  c                  6    e Zd ZdZdZedd	            ZddZdS )ClassifyMessageTypeMiddlewarez>Determine MessageType from text content and msg_body elements.zclassify-msg-typerQ   rR   ri  r`  rS   r   c                2   |                      d          rt          j        S |D ]h}|                    dd          }|dk    rt          j        c S |dk    rt          j        c S |dk    rt          j        c S |dk    rt          j        c S it          j        S )z1Classify message type based on text and msg_body.r  rs  rn   r  r  r  r  )	rY   r   COMMANDr  PHOTOVOICEVIDEODOCUMENTr'  )rQ   ri  r  etypes       r\   	_classifyz'ClassifyMessageTypeMiddleware._classifyg  s     ??3 	'&& 		, 		,DHHZ,,E&&"((((&&"((((***"((((%%"++++ &r^   r  r]  r   c                t   K   |                      |j        |j                  |_         |             d {V  d S r   )r3  ro  ri  rs  r  s      r\   r  z$ClassifyMessageTypeMiddleware.handlex  s:      ~~clCLAAgiir^   N)rQ   rR   ri  r`  rS   r   r  )r   r   r   r   r~  r   r3  r  r   r^   r\   r+  r+  b  sQ        HHD      \       r^   r+  c                  6    e Zd ZdZdZedd            ZddZdS )QuoteContextMiddlewarez3Extract quote/reply context from cloud_custom_data.zquote-contextrk  rR   rS   )Tuple[Optional[str], Optional[str], list]c                F   | sddg fS 	 t          j        |           }n!# t           j        t          f$ r ddg fcY S w xY wt	          |t
                    r|                    d          nd}t	          |t
                    sddg fS t          |                    d          pd          }t          |                    d          pd          	                                }|dk    r|sd}|sddg fS t          |                    d	          pd          	                                pd}t          |                    d
          p|                    d          pd          	                                }|r| d| n|}g }t                              |          D ]}	|	                    d          }
|	                    d          }|
                    d          \  }}}|	                                }|                    |||	                                f           |||fS )zExtract quote context, mapping to MessageEvent.reply_to_*.

        Returns:
          (reply_to_message_id, reply_to_text, quote_media_refs)
          where quote_media_refs is a list of (rid, kind, filename) tuples
        Nquotetyper   descrn   rB   r  idrh  ri  r"  ro   :)r  r  rN  r  r;  r<  r  ri   rR   rc   _YB_RES_REF_REru   r  	partitionr   )rk  r  r9  
quote_typer;  quote_idsender
quote_textrp  r   r   r  r  r  r  s                  r\   _extract_quote_contextz-QuoteContextMiddleware._extract_quote_context  s;    ! 	"r>!	"Z 122FF$i0 	" 	" 	"r>!!!	" (2&$'?'?I

7###T%&& 	"r>! 6**/a00
599V$$*++1133??4?D 	"r>!uyy,"--3355=UYY011QUYY{5K5KQrRRXXZZ,2<(($(((
 
((.. 	= 	=A771::D''!**C $s 3 3D!X::<<DsD(..*:*:;<<<<Z//s    <<r  r]  r   c                   K   |                      |j                  \  |_        |_        |_         |             d {V  d S r   )rD  rk  rt  ru  rv  r  s      r\   r  zQuoteContextMiddleware.handle  sH      KOKfKfgjg|K}K}H!2C4Hgiir^   N)rk  rR   rS   r7  r  )r   r   r   r   r~  r   rD  r  r   r^   r\   r6  r6  }  sR        ==D'0 '0 '0 \'0R     r^   r6  c                      e Zd ZdZdZedd            Zedd            Zedd	            Ze	d
ddd d            Z
e	dd            Ze	d!d            Ze	d"d            Zd#dZd
S )$MediaResolveMiddlewarez6Resolve inbound media references to downloadable URLs.zmedia-resolverA  rR   rS   c                    t           j                            |           j        }t          j                            |          d                                         }|dv r|S dS )z$Guess image extension from URL path.ro   >   .heic.tiff.bmp.gif.jpg.png.jpeg.webprM  )r  r  r  rR  r  splitextr  )rA  rR  exts      r\   _guess_image_ext_from_urlz0MediaResolveMiddleware._guess_image_ext_from_url  sX     |$$S)).gt$$Q'--//VVVJvr^   resource_idc                  K   |                                 }|st          d          |                                  d{V }t          |                    d          pd                                           }t          |                    d          pd                                           pd}t          |                    d          p| j        p| j                                                   }|r|st          d          | j         d	}d
|||d}t          j	        dd          4 d{V }t          d          D ]M}	|                    |d|i|           d{V }
|
j        dk    r|	dk    rt                              | j        | j        | j                   d{V }t          |                    d          pd                                           }t          |                    d          p|pd                                           pd}t          |                    d          p| j        p| j                                                   }|r|s n0||d<   ||d<   ||d<   0|
                                 |
                                }|                    d          }|dvr)t          d| d|                    dd                     t#          |                    d          t$                    r|                    d          n|}t          |pi                     d          p|pi                     d          pd                                           }|r|c cddd          d{V  S t          d          ddd          d{V  n# 1 d{V swxY w Y   t          d           )!zLow-level helper: exchange a ``resourceId`` for a direct download URL.

        Handles token retrieval, the ``/api/resource/v1/download`` API call,
        and a single 401-retry with token force-refresh.  Raises on failure.
        zmissing resource_idNrM  rn   rO  webr%  z-missing token or bot_id for resource downloadz/api/resource/v1/downloadr  )r  X-IDX-TokenX-Sourcer9   T)r  follow_redirectsrB   r  )paramsr   i  r   rW  rX  rY  r#  >   Nr   z"resource/v1/download failed: code=r'  r&  r$  rA  realUrlz(resource/v1/download missing url/realUrlz)resource/v1/download did not return a URL)rc   r8  _get_cached_tokenrR   r  rZ  _app_key_api_domainr)  r*  r,  r7  r   rV  _app_secretraise_for_statusr  r;  r<  )r^  rT  
token_datarM  rO  r%  api_urlr   rB  rC  resprD  r#  r$  real_urls                  r\   _fetch_resource_urlz*MediaResolveMiddleware._fetch_resource_url  s      "'')) 	64555"4466666666
JNN7++1r2288::Z^^H--677==??H5Z^^H--TTGDTUU[[]] 	PF 	PNOOO(CCC.	
 
 $TDIII 	O 	O 	O 	O 	O 	O 	OV 88 O O#ZZ{8S]dZeeeeeeee#s**w!||'2'@'@('*=w?R( ( " " " " " "J  
w 7 7 =2>>DDFFE !9!9!LV!LuMMSSUU^Y^F !9!9!`W_!`PWP`aaggiiF   &,GFO).GI&*0GJ'%%'''))++{{6**y((&aTaaUZ\^I_I_aa   /9V9L9Ld.S.S`w{{6***Y`
//66[4:2:J:J9:U:U[Y[\\bbdd $#OO9	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O: ##MNNN;	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O 	O> FGGGs   2H=N#N##
N-0N-c                  K   	 t           j                            |          }n# t          $ r |cY S w xY wt           j                            |j                  }|                    d          p|                    d          pg }|r't          |d                                                   nd}|s|S 	 t          
                    | |           d{V S # t          $ r |cY S w xY w)a"  Resolve Yuanbao resource placeholder to a directly fetchable real URL.

        Common URL patterns:
          https://hunyuan.tencent.com/api/resource/download?resourceId=...
        Direct GET returns 401; need business API:
          GET /api/resource/v1/download?resourceId=...
        r  r  r   rn   N)r  r  r  r9  r  r  r  rR   rc   rG  rf  )r^  rA  r  r  resource_idsrT  s         r\   _resolve_download_urlz,MediaResolveMiddleware._resolve_download_url  s     	\**3//FF 	 	 	JJJ	 %%fl33yy..O%))L2I2IOR6BJc,q/**00222 	J	/CCG[YYYYYYYYY 	 	 	JJJ	s   $ 336 C C&%C&Nrn   )r  log_tag	fetch_urlr  r  rq  rj  Optional[Tuple[str, str]]c               j  K   	 t          ||j                   d{V \  }}n;# t          $ r.}t                              d|j        |||           Y d}~dS d}~ww xY w|dk    r|                     |          }		 t          ||	          }
n:# t          $ r-}t                              d|j        ||           Y d}~dS d}~ww xY wt          d|	           }|
                    d          s|
                    d          r|nd}|
|fS |sEt          j                            |          }t          j                            |j                  pd	}	 t#          ||          }
n:# t          $ r-}t                              d
|j        ||           Y d}~dS d}~ww xY wt          |          p|pd}|
|fS )zZDownload a Yuanbao resource and cache locally. Returns ``(local_path, mime)`` or ``None``.max_size_mbNz5[%s] inbound media download failed: kind=%s %s err=%srK   )rR  z,[%s] inbound image cache rejected: %s err=%simage/
image/jpegrL   z)[%s] inbound file cache failed: %s err=%sapplication/octet-stream)media_download_urlMEDIA_MAX_SIZE_MBr9  r4  r>  r~  rS  r   r:  r   rY   r  r  r  r  rR  basenamer   )r   r^  rk  r  r  rj  
file_bytescontent_typerH  rR  
local_pathmimer  s                r\   _download_and_cachez*MediaResolveMiddleware._download_and_cache	  s?     		-?w'@. . . ( ( ( ( ( ($J  	 	 	NNGdGS   44444	 7??//	::C3JCHHH

   BL'3   ttttt #=3==11D??8,, ['3'>'>x'H'HZ||lt##  	@\**955F((55?I	2:yIIJJ 	 	 	NN;gs   44444	 y))W\W=W4sD   $ 
A#AA;B 
C"B??CE$ $
F."FFc                >   K   |                      ||           d{V S )z[Exchange a Yuanbao ``resourceId`` for a short-lived direct download URL. Raises on failure.N)rf  )r   r^  rT  s      r\   _resolve_by_resource_idz.MediaResolveMiddleware._resolve_by_resource_idC	  s0       ,,WkBBBBBBBBBr^   rp  r  Tuple[List[str], List[str]]c                  K   g }g }|D ]y}t          |                    d          pd                                                                          }t          |                    d          pd                                          }|t          vs|s	 |                     ||           d{V }n:# t          $ r-}	t                              d|j	        |||	           Y d}	~	d}	~	ww xY w| 
                    |||t          |                    d          pd                                          pdd|dd          	           d{V }
|
J|
\  }}|                    |           |                    |           {||fS )
zResolve inbound media refs: download to local cache, return (local_paths, mime_types).

        Yuanbao COS hostnames resolve to private IPs, tripping the SSRF guard
        in vision_tools. We download ourselves and return local cache paths.
        r  rn   rA  Nz8[%s] inbound media resolve failed: kind=%s url=%s err=%sr~  zplaceholder_url=P   rk  r  r  rj  )rR   r  rc   r  _RESOLVABLE_MEDIA_KINDSri  r9  r4  r>  r~  rz  r   )r   r^  rp  rw  rx  r  r  rA  rk  rH  rQ  rx  ry  s                r\   _resolve_media_urlsz*MediaResolveMiddleware._resolve_media_urlsH	  s      !#
!# 	% 	%Cswwv,"--3355;;==Dcggenn*++1133C222#2"%";";GS"I"IIIIIII		   NL$S    22#cggfoo344::<<D53ss855 3        F ~%Jj)))t$$$$;&&s   B44
C+>#C&&C+c           	     x  K   t          |dd          }|sg g fS 	 |                    |          }|                    |j                  }n<# t          $ r/}t
                              d|j        |           g g fcY d}~S d}~ww xY w|sg g fS t          dt          |          t          z
            }g }t                      }	||d         D ](}
|
                    d          }t          |t                    rd|vr2t                              |          D ]}|                    d          }|                    d          }|                    d	          \  }}}|                                }|t(          vrc||	v rh|	                    |           |                    |||                                f           t          |          t.          k    r nt          |          t.          k    r n*|sg g fS g }g }|D ]\  }}}	 |                     ||           d{V }n:# t          $ r-}t
                              d
|j        |||           Y d}~Vd}~ww xY w|                     ||||pdd|            d{V }||\  }}|                    |           |                    |           ||fS )zYResolve recent observed image/file anchors from transcript into ``(local_paths, mimes)``.r2  Nz.[%s] Observed-media hydration setup failed: %sr   r5  r  ro   rB   r=  z9[%s] observed-media resolve failed: rid=%s kind=%s err=%szrid=r  )r6  r7  r9  r8  r9  r4  r>  r~  maxrq    OBSERVED_MEDIA_BACKFILL_LOOKBACKr   r  r;  rR   r>  ru   r  r?  rc   r  r   r   ,OBSERVED_MEDIA_BACKFILL_MAX_RESOLVE_PER_TURNr|  rz  )r   r^  rO  r<  r   historyrH  startorderseenr&  r5  r   r   r  r  r  r  media_pathsmimes	fresh_urlrQ  rR  ry  s                           r\   _collect_observed_mediaz.MediaResolveMiddleware._collect_observed_mediar	  se     
 !1488 	r6M	!77??M++M,DEEGG 	 	 	NN@c   r6MMMMMM	  	r6MAs7||&FFGG,.EE566? 	 	Cggi((Ggs++ y/G/G#,,W55  wwqzzggajj$(NN3$7$7!azz||666$;;c4)9)9:;;;u::!MMME N5zzIII J  	r6M!##( 	 	Cx"%"="=gs"K"KKKKKKK		   OL#tS    22#"*d$s 3        F ~JD$t$$$LLE!!s5   /A 
B$A?9B?B	H&&
I0#IIr  r]  r   c                H  K   |j         }|                     ||j                   d {V \  |_        |_        t
                              |j        t          |j                            r(t          
                    d|j        |j                   d S  |             d {V  d S )Nz.[%s] Skip placeholder after media download: %r)r^  r  rp  rw  rx  r  r  ro  rq   r4  r  r~  r  s       r\   r  zMediaResolveMiddleware.handle	  s      +040H0HRUR`0a0a*a*a*a*a*a*a'&??cRUR`NaNabb 	LLI7<Y\YefffFgiir^   r  )rT  rR   rS   rR   )
rk  rR   r  rR   r  rq  rj  rR   rS   rl  )rp  r  rS   r}  )rS   r}  r  )r   r   r   r   r~  r   rS  rf  ri  r   rz  r|  r  r  r  r   r^   r\   rG  rG    s2       @@D   \ 8H 8H 8H \8Ht    \0  $(,  ,  ,  ,  ,  [, \ C C C [C '' '' '' [''R D" D" D" [D"L     r^   rG  c                  6    e Zd ZdZdZddZedd            ZdS )DispatchMiddlewarez.Build MessageEvent and dispatch to AI handler.dispatchr  r]  rS   r   c                  	K   j         	t          j        	j        j                            dd          	j        j                            dd                    d	fd}j        d	k    r	j        v}	j                            t          j
                              }|                    |           t                              d
	j        |                                pdd d                    |rpt          j        |                     	          dpdd d                    }	j                            |           |                    	j        j                   nat          j         |            dj        pd           }	j                            |           |                    	j        j                    |             d {V  d S )Ngroup_sessions_per_userTthread_sessions_per_userF)r  r  rS   r   c                 ~  K   t           j                  } t           j                  } j        v j        s	 t          dd           }|rJ|                     j                  }|                    |j	                  }t          |pg           D ]}|                    dd          }|r| j        k    r|                    dd          }t          |t                    rd|v rt                              |          D ]}|                    d          }	|                    d          }
|	                    d          \  }}}|                                }|t&          v r/ j                            |
||                                f            nn8# t*          $ r+}t,                              d	j        |           Y d }~nd }~ww xY w j        D ]\  }
}}|t&          vr	 t2                              |
           d {V }n:# t*          $ r-}t,                              d
j        |
||           Y d }~ed }~ww xY wt2                              |||pd d|
            d {V }||\  }}|| vr*|                     |           |                    |           ng }g }	 t2                               j                   d {V \  }}n8# t*          $ r+}t,                              dj        |           Y d }~nd }~ww xY w|rit;          |           }t=          ||          D ]I\  }}||v r
|                     |           |                    |           |                    |           J j         }t=          | |          D ]\  }}|!                    d          st          "                    |          }|s9|                    d          }	|	                    d          \  }}}|                                }|dk    r|!                    d          rd| d}nD|dk    r=|                                ptF          j$        %                    |          }d| d| d}n|d |&                                         |z   ||'                                d          z   }tQ          |tS          d |D                       rtT          j+        n j,         j         j-        pd  j.        | | j         j/         j0        
  
        }r' j-        r  j-        j1        <    j         pdj2        <    j-        r] j         rVj3        } j         | j-        <   ti          |          dk    r-t          |          d ti          |          dz
           D ]}||= 5                    |           d {V  d S )Nr2  rI  rn   r5  r  ro   rB   r=  z'[%s] quote transcript lookup failed: %sz6[%s] quote media resolve failed: rid=%s kind=%s err=%sz
quote rid=r  z;[%s] observed-image hydration raised, continuing anyway: %sr  rK   rp  z[image: r  rL   r  u    → c              3  @   K   | ]}|                     d           V  dS ))zapplication/ztext/NrY   )r   mts     r\   r   zMDispatchMiddleware.handle.<locals>._dispatch_inbound_event.<locals>.<genexpr>?
  s/      ZZ2==)BCCZZZZZZr^   )
rQ   r$  rO  rI  raw_messagerw  rx  rt  ru  rz  r!  )6r`  rw  rx  rt  rv  r6  r7  rO  r9  r8  reversedr  r;  rR   r>  ru   r  r?  rc   r  r   r9  r4  r>  r~  rG  r|  rz  r  r   zipr   ro  rY   searchr  rR  ru  r  rv   r   anyr   r1  rs  rj  rc  ru  rz  r  r)  r  rq   handle_message)!rw  rx  r<  r   r  r&  r{   _contentr   r   r  r  r  r  rH  r  rQ  rR  ry  extra_img_urlsextra_img_mimesr   u_patched_event_textanchor_matchreplacementlabeleventcacher  _skr^  r  s!                                 r\   _dispatch_inbound_eventz:DispatchMiddleware.handle.<locals>._dispatch_inbound_event	  s     cn--Js//K
 &2+  '1A4 H H  *,1,G,G
,S,SM&+&;&;M<T&U&UG'/2'>'> * *&)gglB&?&?#& 
!*3#2I+I+I/2wwy"/E/EH'1(C'@'@ %kYRZEZEZ1?1H1H1R1R )k )kA34771::D23''!**C@Ds@S@S,=D!X37::<<D/37N/N/N030D0K0KSRVX`XfXfXhXhLi0j0j0j$)E$   E#L#        ,/+? 1 1'Cx#::: !*@*X*XY`be*f*f$f$f$f$f$f$f		$ ! ! !T#L#tS   !! $:#M#M"+!"*"2d 2S 2 2 $N $ $      F ~ !'JD$:--"))$///#**4000116 -/-/<R<j<j= = 7 7 7 7 7 73NOO !   NNUc       
 " '!*ooG #NO D D ' '1<<$"))!,,,#**1---A #&,J44  1||C(( -445HII# #))!,,$(NN3$7$7!azz||7??q||H'='=?"1Q///KKV^^$NN,,C0@0@0C0CE"<E"<"<"<"<"<KK'(=););)=)=(=>!"),*:*:*<*<*=*=>? $# !( ZZkZZZZZ&K((z:-H%'$'$;!/"1  E   Hsz H36:+C058\5GR-c2z %cl %2$'Lcj!u::##!%[[):#e**s*:):; % %!!HH((///////////sH   EF 
G&!GG,!H
I#I  I/)K 
L#!L		Lr  z-[%s] Group message enqueued (qsize=%d) for %srn   rM   zyuanbao-group-consumer-r&  r  zyuanbao-inbound-r  r   )r^  r7   rO  configextrar  rm  _group_queues
setdefaultr   Queue
put_nowaitr4  r5  r~  qsizer@  _consume_group_queue_inbound_tasksr   rB  rC  rj  )
r  r  r  r  is_newqueueconsumerrD  r  r^  s
    `      @@r\   r  zDispatchMiddleware.handle	  s     +J$+N$8$<$<=VX\$]$]%,^%9%=%=>XZ_%`%`
 
 
C	0 C	0 C	0 C	0 C	0 C	0 C	0 C	0J =G## 55F)44S'-//JJE4555KK?ekkmmciR"-=    K".--gs;;ECI2ss3CEE   &**8444**7+A+IJJJ&''))A
(?iAA  D "&&t,,,""7#9#ABBBgiir^   r^  'YuanbaoAdapter'r"  rR   c                  K   d}| j                             |          }|sdS 	 	 	 t          j        |                                |           d{V }n# t          j        $ r Y nw xY wt
                              d| j        |pddd         |                                           	  |             d{V  || j	        v r#t          j
        d           d{V  || j	        v #n0# t          $ r# t
                              d	| j                   Y nw xY w	 | j                             |d           dS # | j                             |d           w xY w)
zIDrain the group queue one dispatch at a time, waiting for each to finish.rI   NTr  z3[%s] Group queue: dispatching for %s (remaining=%d)rn   rM   g?z[%s] Group queue consumer error)r  r  r   wait_forTimeoutErrorr4  r  r~  r  r   r@  r9  	exceptionr  )r^  r"  _IDLE_TIMEOUTr  dispatch_fns        r\   r  z'DispatchMiddleware._consume_group_queueo
  s      %))+66 	F	9V(/(8m(\(\(\"\"\"\"\"\"\KK+   EIL;#4"crc":EKKMM  V%+--'''''''%)AAA%mC000000000 &)AAA  V V V$$%FUUUUUVV  !%%k488888G!%%k48888sS   D5 .A D5 A'$D5 &A''AD5 )<C& %D5 &*DD5 DD5 5ENr  )r^  r  r"  rR   rS   r   )r   r   r   r   r~  r  r   r  r   r^   r\   r  r  	  sX        88De e e eN 9 9 9 \9 9 9r^   r  c                  ^    e Zd ZU dZeeeeee	e
eeeeeeeeeeegZded<   edd            ZdS )	InboundPipelineBuilderzFactory for building InboundPipeline instances.

    Separates pipeline assembly (business knowledge) from the pipeline engine
    (InboundPipeline) so the engine stays generic and reusable.
    z
list[type]_DEFAULT_MIDDLEWARESrS   r  c                p    t                      }| j        D ]}|                     |                        |S )z6Build the default inbound message processing pipeline.)r  r  r  )r   pipelinemw_clss      r\   buildzInboundPipelineBuilder.build
  sA     #$$. 	# 	#FLL""""r^   N)rS   r  )r   r   r   r   r  r  r  r  rV  r\  rt  ry  r  r  r  r
  r  r%  r+  r6  rG  r  r  rY  r   r  r   r^   r\   r  r  
  s           	 #"%%(    *    [  r^   r  c                     e Zd ZU dZd,dZed             Zed-d	            Zed.d            Zed/d            Z	d/dZ
d0dZd1dZd2dZd0dZd0dZd3dZdZded<   d4dZd5d Zd6d"Zefd7d&Zd0d'Zd/d(Zd/d)Zd0d*Zd+S )8ConnectionManagera  Manages the WebSocket connection lifecycle for YuanbaoAdapter.

    Responsibilities:
      - Opening and closing the WebSocket
      - AUTH_BIND handshake
      - Heartbeat (ping/pong) loop
      - Receive loop (frame dispatch)
      - Reconnect with exponential backoff
    r^  r  rS   r   c                    || _         d | _        d | _        d | _        d | _        i | _        d | _        d| _        d| _        d| _	        i | _
        i | _        d S )Nr   F)_adapter_ws_connect_id_heartbeat_task
_recv_task_pending_acks_pending_pong_consecutive_hb_timeouts_reconnect_attempts_reconnecting_inbound_buffer_inbound_timersr  r^  s     r\   r  zConnectionManager.__init__
  se    *.7;268:7;-.%() #(02?Ar^   c                    | j         S r   )r  r  s    r\   wszConnectionManager.ws
  s	    xr^   rq  c                    | j         S r   )r  r  s    r\   
connect_idzConnectionManager.connect_id
  s    r^   ri   c                    | j         S r   )r  r  s    r\   reconnect_attemptsz$ConnectionManager.reconnect_attempts
  s    ''r^   rT   c                    | j         dS t          | j         dd           }|du rdS t          |          r)	 t           |                      S # t          $ r Y dS w xY wdS )NFrM  T)r  r6  callablerT   r9  )r  	open_attrs     r\   is_connectedzConnectionManager.is_connected
  s    85DHfd33	4I 	IIKK(((   uuus   A 
AAc                   K   | j         }t          s=d}|                    d|d           t                              d|j        |           dS |j        r|j        s=d}|                    d|d           t                              d	|j        |           dS | j	        g	 t          | j	        dd
          }|du st          |          r, |            r"t                              d|j                   dS n# t          $ r Y nw xY w|                    d|j        d          sdS 	 t                              d|j        |j                   t"                              |j        |j        |j        |j                   d
{V }|                    d          rt+          |d                   |_        t                              d|j        |j                   t1          j        t5          j        |j        d
d
d          t8                     d
{V | _	        |                     |           d
{V }|s|                                  d
{V  dS d| _        |                                  t1          j!                    |_"        t1          j#        | $                                d| j%                   | _&        t1          j#        | '                                d| j%                   | _(        t                              d|j        | j%        |j                   tR          *                    |           dS # t0          j+        $ rR t                              d|j                   |                                  d
{V  |,                                 Y dS t          $ r\}t                              d|j        |d           |                                  d
{V  |,                                 Y d
}~dS d
}~ww xY w)u   Open WebSocket connection: sign-token → WS connect → AUTH_BIND → start loops.

        Returns True on success, False on failure.
        z:Yuanbao startup failed: 'websockets' package not installedyuanbao_missing_dependencyT)	retryablez$[%s] %s. Run: pip install websocketsFzJYuanbao startup failed: YUANBAO_APP_ID and YUANBAO_APP_SECRET are requiredyuanbao_missing_credentialsz[%s] %sNrM  z*[%s] Already connected, skipping connect()zyuanbao-app-keyzYuanbao app keyz [%s] Fetching sign token from %sr  r%  z[%s] Connecting to %s   ping_intervalping_timeoutclose_timeoutr  r   yuanbao-heartbeat-r  yuanbao-recv-z%[%s] Connected. connectId=%s botId=%sz[%s] Connection timed outz[%s] connect() failed: %sr  )-r  WEBSOCKETS_AVAILABLE_set_fatal_errorr4  r>  r~  r^  r`  r  r  r6  r  r  r9  _acquire_platform_lockr5  r_  r   rS  
_route_envr  rR   rZ  _ws_urlr   r  
websocketsconnectCONNECT_TIMEOUT_SECONDS_authenticate_cleanup_wsr  _mark_connectedget_running_loop_loopr@  _heartbeat_loopr  r  _receive_loopr  YuanbaoAdapter
set_activer  _release_platform_lock)r  r^  r&  r  rb  authedrH  s          r\   rM  zConnectionManager.open
  s     
 -# 	NC$$%A3RV$WWWNNA7<QTUUU5 	w': 	E  $$%BCSX$YYYLLGL#6665 8#DHfd;;	$$))<)<$$LL!Mw|\\\4    --w/1B
 
 	 5:	KK:GL'J]^^^*44 '"5w7J!,  5          J ~~h'' <"%j&:";"; KK/wOOO$-"O"&!%"#	   0        DH  --j99999999F &&(((((((((u ()D$##%%%#466GM#*#6$$&&-T$BR-T-T$ $ $D  &1""$$+M4;K+M+M  DO KK7d.  
 %%g...4# 	 	 	LL4glCCC""$$$$$$$$$**,,,55 	 	 	LL4glCRVLWWW""$$$$$$$$$**,,,55555		s;   !AC7 7
DD&D+L8 C#L8 8AO=	O=!AO88O=c                v  K   | j         rD| j                                          	 | j          d{V  n# t          j        $ r Y nw xY wd| _         | j        rD| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        t          d          }| j                                        D ]+}|                                s|	                    |           ,| j        
                                 t                                           |                                  d{V  dS )zGCancel background tasks, fail pending futures, and close the WebSocket.NzYuanbaoAdapter disconnected)r  cancelr   CancelledErrorr  r8  r  valuesdoneset_exceptionr   r   r	  r  )r  disc_excfuts      r\   closezConnectionManager.closeI  s       	( '')))*********)   #'D ? 	#O""$$$o%%%%%%%%)   "DO   =>>%,,.. 	, 	,C88:: ,!!(+++  """ 	!!!           s!   2 AA/A= =BBrb  r<  c                :  K   | j         }| j        dS |                    dd          }|j        p|                    dd          }|                    d          pd}|j        p|                    dd          pd}t          t          j                              }t          d	||||t          t          t          |
	  	        }| j                            |           d{V  t                              d|j        ||           	 t!          j                    }	|	                                t&          z   }
	 |
|	                                z
  }|dk    r"t                              d|j                   dS t!          j        | j                                        |           d{V }t/          |t0          t2          f          s	 t5          t1          |                    }n# t6          $ r Y w xY w|                    di           }|                    dd          }|                    dd          }|t8          d         k    ri|dk    rc|                     |          }|r*|| _        t                              d|j        |           dS t                              d|j                   dS {# t           j         $ r$ t                              d|j                   Y dS t6          $ r.}t                              d|j        |d           Y d}~dS d}~ww xY w)zSend AUTH_BIND and read frames until BIND_ACK is received.

        Returns True on success, False on failure/timeout.
        NFrM  rn   r%  rO  botr  ybBot)	biz_iduidrO  rM  rj  app_versionoperation_systembot_versionr  z&[%s] AUTH_BIND sent (msg_id=%s uid=%s)Tr   z+[%s] AUTH_BIND timeout waiting for BIND_ACKr  r   cmd_typer`   r  Responsez	auth-bindz$[%s] BIND_ACK received: connectId=%sz[%s] BIND_ACK missing connectIdz[%s] AUTH_BIND timeoutz[%s] AUTH_BIND error: %sr  )!r  r  r  rZ  r  rR   uuiduuid4r-   r0  r1  r3  r  r4  r  r~  r   r  r  AUTH_TIMEOUT_SECONDSr  r  recvr;  r  	bytearrayr)   r9  r!   _extract_connect_idr  r5  r  )r  rb  r^  rM  r  rO  r  rj  
auth_bytesr  deadliner   rawr&  r   r  r  r  rH  s                      r\   r  zConnectionManager._authenticatej  s%     
 -85w++o="!=!=))2U&O*..b*I*IOR	TZ\\""%$.$

 

 


 hmmJ'''''''''=w|VUXYYY%	,..Ezz||&::H%$uzz||3	>>LL!NPWP\]]] 5#,TX]]__iPPPPPPPPP!#y'9:: )%**55CC    H wwvr**88J33hhub))x
333{8J8J!%!9!9#!>!>J! %+5($JGLZdeee#t%FUUU$u7%: # 	 	 	LL17<@@@55 	 	 	LL3W\3QULVVV55555	sW   A-J1 5AJ1 G# "J1 #
G0-J1 /G00BJ1  J1 /J1 1/L#	L,#LLdecoded_msgc                   |                     dd          }|sdS 	 t          t          |                    }t          |d          }|dk    r9t	          |d          }t
                              d| j        j        ||           dS t	          |d          }|r|ndS # t          $ r1}t
          
                    d	| j        j        |           Y d}~dS d}~ww xY w)
z0Extract connectId from decoded BIND_ACK message.r$  r^   Nro   r   rB   z*[%s] AuthBindRsp error: code=%d message=%rr   z$[%s] Failed to extract connectId: %s)r  r"   r%   r$   r#   r4  r  r  r~  r9  r>  )r  r  r$  fdictr#  messager  rH  s           r\   r  z%ConnectionManager._extract_connect_id  s    !oofc22 	4	#M$$7$788Eua((Dqyy%eQ//@M&g   t$UA..J!+5::5 	 	 	NNA4=CUWZ[[[44444	s   A)B B 
C'&CCc                  K   | j         }	 |j        rWt          j        t                     d{V  | j        /	 t          t          j                              }t          |          }t          j
                    }|                                }|| _        || j        |<   | j                            |           d{V  t                              d|j        |           	 t          j        |d           d{V  d| _        n# t          j        $ r | j                            |d           | xj        dz  c_        t                              d|j        | j        t.                     | j        t.          k    rYt                              d|j                   |                                  Y | j                            |d           d| _        dS Y nw xY w| j                            |d           d| _        n'# | j                            |d           d| _        w xY wn8# t2          $ r+}t                              d	|j        |           Y d}~nd}~ww xY w|j        UdS dS # t          j        $ r Y dS w xY w)
zJSend HEARTBEAT (ping) every 30s; trigger reconnect after threshold misses.Nz[%s] PING sent (msg_id=%s)r:   r  r   ro   z[%s] PONG timeout (%d/%d)z7[%s] Heartbeat threshold exceeded, triggering reconnectz[%s] Heartbeat send failed: %s)r  _runningr   r@  HEARTBEAT_INTERVAL_SECONDSr  rR   r  r  r.   r  create_futurer  r  r  r4  r  r~  r  r  r  r  r>  HEARTBEAT_TIMEOUT_THRESHOLDschedule_reconnectr9  r  )r  r^  rj  
ping_byteslooppong_futurerH  s          r\   r  z!ConnectionManager._heartbeat_loop  s     -"	" Vm$>?????????8#V ..F!,V!4!4J"355D262D2D2F2FK)4D&1<D&v.(--
333333333LL!=w|VTTT2%.{DIIIIIIIIII8955"/ 
# 
# 
#*..vt<<<55:557#L$*GId    8<WWW"NN+dfmfrsss 33555"*..vt<<<-1*** XW
# *..vt<<<-1** *..vt<<<-1*1111*  V V VLL!A7<QTUUUUUUUUV= " V V V V V@ % 	 	 	DD	s~   /I, B(H( $#D H  B*G2H  3"H( H  GH  #H(  $H$$H( 'I, (
I2!II, II, ,I?>I?c                  K   | j         }	 | j        2 3 d{V }t          |t          t          f          s$|                     t          |                     d{V  M6 dS # t          j        $ r Y dS t          j	        j
        $ r}t          |dd          }t                              d|j        |t          |dd                     |r?|t          v r6t                              d|j        |           |                                 n|                                  Y d}~dS Y d}~dS d}~wt&          $ r@}t                              d|j        |           |                                  Y d}~dS d}~ww xY w)z(Read WS frames and dispatch by cmd_type.Nr#  z3[%s] WebSocket connection closed: code=%s reason=%sreasonrn   z7[%s] Close code %d is non-recoverable, NOT reconnectingz[%s] receive_loop exited: %s)r  r  r;  r  r  _handle_framer   r  r  
exceptionsConnectionClosedr6  r4  r>  r~  NO_RECONNECT_CLOSE_CODESr  _mark_disconnectedr$  r9  )r  r^  r  	close_exc
close_coderH  s         r\   r  zConnectionManager._receive_loop  s     -	&!X 5 5 5 5 5 5 5c!#y'9:: ((s4444444444 &XX % 	 	 	DD$5 	* 	* 	* FD99JNNEj')Xr*J*J    *j,DDDML*   **,,,,''))))))))) -,,,,,  	& 	& 	&NN97<MMM##%%%%%%%%%	&s5   A" AAA" "E74E7BD**E775E22E7r  r  c           	     .  K   | j         }	 t          |          }n9# t          $ r,}t                              d|j        |           Y d}~dS d}~ww xY w|                    di           }|                    dd          }|                    dd          }|                    dd          }|                    d	d
          }	|                    dd          }
|t          d         k    r|dk    rt                              d|j        |           | j        4| j        	                                s| j        
                    d           nN|rL|| j        v rC| j                            |          }|	                                s|
                    d           dS |t          d         k    r(|dv r$t                              d|j        ||           dS |t          d         k    r~|rX|| j        v rO| j                            |          }|	                                s d|i}|
r|
|d<   |
                    |           n"t                              d|j        ||           dS |t          d         k    r}t                              d|j        ||t          |
                     |	rp| j        i	 t!          |          }| j                            |           d{V  n8# t          $ r+}t                              d|j        |           Y d}~nd}~ww xY w|r|| j        v r| j                            |          }|	                                sX	 |
rt%          |
          nd|i}|
                    |           n,# t          $ r}|                    |           Y d}~nd}~ww xY wdS |
rDt                              d|j        |t          |
                     |                     |
           dS t                              d|j        |||           dS )z Handle a single WebSocket frame.z[%s] Failed to decode frame: %sNr   r  r`   r  rn   rj  need_ackFr$  r^   r  pingz'[%s] HEARTBEAT_ACK received (msg_id=%s)T>   send_group_heartbeatsend_private_heartbeatz-[%s] Heartbeat ACK received: cmd=%s msg_id=%sz)[%s] Unmatched Response: cmd=%s msg_id=%sPushz0[%s] Push received: cmd=%s msg_id=%s data_len=%dz[%s] Failed to send PushAck: %szL[%s] WS received inbound push, decoding and dispatching: cmd=%s, data_len=%dz1[%s] Ignoring frame: cmd_type=%d cmd=%s msg_id=%s)r  r)   r9  r4  r  r~  r  r!   r  r  
set_resultr  r  r5  rq   r  r/   r  r*   r  _push_to_inbound)r  r  r^  r&  rH  r   r  r  rj  r2  r$  r  r   	ack_bytesack_excdecodeds                   r\   r*  zConnectionManager._handle_frame  s     -	!#&&CC 	 	 	LL:GL#NNNFFFFF	 wwvr""88J++hhub!!(B''88J..ggfc** x
+++vLLBGLRXYYY!-d6H6M6M6O6O-"--d3333 )Fd&888(,,V44xxzz )NN4(((F x
+++ 8
 1
 1
 LLH',X[]cdddF x
+++ &D$666(,,V44xxzz +$d^F .)-vNN6***?L#v   F x'''KKJGLZ]_egjkogpgpqqq [DH0[ / 5 5I(--	2222222222  [ [ [LL!BGLRYZZZZZZZZ[  &D$666(,,V44xxzz //?C"W"5d";";";&RVw////$ / / /))#......../  ,bL#s4yy   %%d+++F?L(C	
 	
 	
 	
 	
sD    
A!AA//K 
L)!LL*M< <
N%N  N%g      ?float_DEBOUNCE_WINDOWr  rR   c                T   	 t          j        |                    d                    }t          |t                    rw|                    dd          p|                    dd          }|                    dd          p+|                    dd          p|                    dd          }|r| d| S n# t          $ r Y nw xY w	 t          |          }|r/|                    dd           d|                    dd           S n# t          $ r Y nw xY wd	t          |           S )
zLightweight decode to extract sender key for debounce grouping.

        Returns 'from_account:group_code' or a fallback unique key.
        r  re  rn   r  rf  r  r  r=  
__unknown_)	r  r  r  r;  r<  r  r9  r*   r<  )r  r  r  re  rf  rc  s         r\   _extract_sender_keyz%ConnectionManager._extract_sender_keye  sk   
	Z 8 899F&$'' :JJ~r22 6zz."55 
 JJ|R00 2zz)R002zz*b11 
   :*99Z999 	 	 	D		&x00D V((>266UU,PR9S9SUUUV 	 	 	D	 +BxLL***s$   B2B6 6
CC?D 
DDc           	        |                      |          }| j                            |d          }|r|                                 || j        vr
g | j        |<   | j        |                             |           t                              d| j        j	        |t          | j        |                              t          j                    }|                    | j        | j        |          }|| j        |<   dS )a=  Debounced inbound dispatch.

        Buffers raw frames from the same sender within a short time window,
        then dispatches all buffered data as a single aggregated pipeline
        execution.  This merges multi-part messages (e.g. image + text sent
        as separate WS pushes) into one pipeline run.
        Nz2[%s] Debounce: buffered frame for key=%s, count=%d)r@  r  r  r   r  r   r4  r  r  r~  rq   r   r  
call_laterr=  _flush_inbound_buffer)r  r  keyexisting_timerr&  timers         r\   r8  z"ConnectionManager._push_to_inbound  s     &&x00 -11#t<< 	$!!### d***(*D %S!((222@MS)=c)B%C%C	
 	
 	
 '))!&
 

 %*S!!!r^   rD  c                   | j                             |d           | j                            |g           }|sdS | j        }t                              d|j        |t          |                     t          ||          }|	                    t          j        |j                            |          d|                      dS )uC   Flush the debounce buffer for a given key — execute the pipeline.Nz1[%s] Debounce flush: key=%s, aggregated %d frames)r^  ra  zyuanbao-pipeline-r  )r  r  r  r  r4  r5  r~  rq   r]  r  r   r@  _inbound_pipeliner  )r  rD  r  r^  r  s        r\   rC  z'ConnectionManager._flush_inbound_buffer  s      d+++(,,S"55	 	F-?L#s9~~	
 	
 	

 WCCCG/%--c22*S**
 
 
 	 	 	 	 	r^   encoded_conn_msgreq_idr  c                  K   | j         t          d          t          j                    }|                                }|| j        |<   	 | j                             |           d{V  t          j        t          j        |          |           d{V }|| j        	                    |d           S # t          j
        $ r  t          $ r  w xY w# | j        	                    |d           w xY w)a	  Send a business-layer request and wait for the response.

        1. Register a Future in pending_acks[req_id]
        2. Send encoded_conn_msg (bytes) to WS
        3. asyncio.wait_for(future, timeout)
        4. Clean up pending_acks on timeout/exception
        NNot connectedr  )r  r8  r   r  r"  r  r  r  shieldr  r  r9  )r  rI  rJ  r  r&  futurer   s          r\   send_biz_requestz"ConnectionManager.send_biz_request  s"      8///'))!%!3!3!5!5%+6"		1(-- 0111111111"+GN6,B,BGTTTTTTTTTF ""640000 # 	 	 	 	 	 		 ""640000s   AB6 6CC C1c                    | j         j        r/| j        s*t          j        |                                            dS dS dS )zBSchedule a reconnect only if running and not already reconnecting.N)r  r   r  r   r@  _reconnect_with_backoffr  s    r\   r$  z$ConnectionManager.schedule_reconnect  sV    =! 	@$*< 	@ < < > >?????	@ 	@ 	@ 	@r^   c                   K   | j         r't                              d| j        j                   dS d| _         	 |                                  d{V 	 d| _         S # d| _         w xY w)u?   Reconnect with exponential backoff (1s, 2s, 4s, … up to 60s).z,[%s] Reconnect already in progress, skippingFTN)r  r4  r  r  r~  _do_reconnectr  s    r\   rQ  z)ConnectionManager._reconnect_with_backoff  s       	LLGI[\\\5!	'++---------!&DD&&&&s   A 	A$c           	       K   | j         }t          t                    D ] }|dz   | _        t	          d|z  d          }t
                              d|j        |dz   t          |           t          j	        |           d{V  | 
                                 d{V  	 t                              |j        |j        |j        |j                   d{V }|                    d          rt%          |d                   |_        t          j        t+          j        |j        ddd	          t0          
           d{V | _        |                     |           d{V }|s@t
                              d|j        |dz              | 
                                 d{V  d| _        d| _        |                                 | j        r2| j                                        s| j                                          t          j!        | "                                d| j#                   | _        | j$        r2| j$                                        s| j$                                          t          j!        | %                                d| j#                   | _$        t
                              d|j        |dz   | j#                    dS # t          j&        $ r( t
                              d|j        |dz              Y tN          $ r0}t
                              d|j        |dz   |           Y d}~d}~ww xY wt
          (                    d|j        t                     |)                                 dS )z>Internal reconnect loop, called under the _reconnecting guard.ro   rB   r   z#[%s] Reconnect attempt %d/%d in %dsNr  r%  r  r  r  z![%s] Re-auth failed on attempt %dr   r  r  r  z,[%s] Reconnected on attempt %d. connectId=%sTz#[%s] Reconnect attempt %d timed outz$[%s] Reconnect attempt %d failed: %sz*[%s] Giving up after %d reconnect attemptsF)*r  r,  MAX_RECONNECT_ATTEMPTSr  minr4  r5  r~  r   r@  r  r   rV  r^  r`  r_  r  r  rR   rZ  r  r  r  r  r  r  r  r>  r  r  r  r  r   r@  r  r  r  r  r  r9  r  r.  )r  r^  rC  waitrb  r  rH  s          r\   rS  zConnectionManager._do_reconnect  s     -344 @	 @	G'.{D$qG|R((DKK5gk+A4   -%%%%%%%%%""$$$$$$$$$5#.#<#<$g&97;N%0 $= $ $      
 >>(++ @&)*X*>&?&?GO!(!1&&*%)&'	   4" " "        $11*======== NN#FV]`aVabbb**,,,,,,,,,+,(01-'')))' 20D0I0I0K0K 2(//111'.':((**@d.>@@( ( ($
 ? -4?+?+?+A+A -O**,,,")"5&&((;)9;;# # #
 BL'A+t/?   tt' a a aDglT[^_T_`````   :GL'TU+WZ       
 	8',H^	
 	
 	
 	""$$$us&   DK#D(K3L=	L=%L88L=c                   K   | j         }d| _         |.	 |                                 d{V  dS # t          $ r Y dS w xY wdS )z)Close and clear the WebSocket connection.N)r  r  r9  )r  r  s     r\   r  zConnectionManager._cleanup_ws7  sm      X>hhjj             >s   0 
>>Nr^  r  rS   r   )rS   rq  rW  rS   rT   r   )rb  r<  rS   rT   )r  r<  rS   rq  )r  r  rS   r   )r  r  rS   rR   )r  r  rS   r   )rD  rR   rS   r   )rI  r  rJ  rR   r  r<  rS   r<  )r   r   r   r   r  r  r  r  r  r  rM  r  r  r  r  r  r*  r=  rY  r@  r8  rC  DEFAULT_SEND_TIMEOUTrO  r$  rQ  rS  r  r   r^   r\   r  r  
  s         B B B B"   X       X  ( ( ( X(    X` ` ` `D! ! ! !BC C C CJ   .% % % %R& & & &8T
 T
 T
 T
p "!!!!+ + + +> *  *  *  *D   4 .	1 1 1 1 1@@ @ @ @
	' 	' 	' 	'I I I IV     r^   r  c                  X    e Zd ZdZedd            Zedd            ZddZ	 	 dddZdS )MediaSendHandleru{  Abstract base class for media send strategies.

    Subclasses implement:
      - acquire_file(): how to obtain file bytes (download URL / read local)
      - build_msg_body(): how to build TIMxxxElem from upload result

    The shared flow (check ws → cancel notifier → validate → COS upload
    → lock → dispatch) is handled by the base handle() template method.
    r^  r  kwargsr
   rS   Tuple[bytes, str, str]c                
   K   dS )zReturn (file_bytes, filename, content_type).

        Raises:
            ValueError: when file cannot be acquired (not found, empty, etc.)
        Nr   r  r^  r^  s      r\   acquire_filezMediaSendHandler.acquire_fileL  r  r^   upload_resultr<  r`  c                    dS )z<Build platform-specific MsgBody list from COS upload result.Nr   r  rc  r^  s      r\   build_msg_bodyzMediaSendHandler.build_msg_bodyV  s      r^   rT   c                    dS )z:Override to return False for non-COS media (e.g. sticker).Tr   r  s    r\   needs_cos_uploadz!MediaSendHandler.needs_cos_uploadZ  s    tr^   Nrl  rR   reply_torq  caption'SendResult'c           	     4  K   |j         }|j        j        }|j        t	          ddd          S |j                            |           	  | j        |fi | d{V \  }}	}
|                                 r4t          	                    ||	|j
                  }|rt	          d|          S |                                 rt          |          }|                                 d{V }|                    dd          }|                    d	d          p|j        pd}t          |j        |j        ||	||j        
           d{V }t'          ||	|
||d         |d                    d{V }d |                                D             } | j        |f||	|
d|}n | j        i fi |}|r|                    dd|id           |                    dd          }|                    ||||           d{V S # t0          $ r(}t	          dt3          |                    cY d}~S d}~wt4          $ r`}t7          |           j        }t:                              d|j        ||d           t	          dt3          |                    cY d}~S d}~ww xY w)z(Template method: shared media send flow.NFrL  Tsuccessr  r  rn  r  rM  rn   r%  )r   r  rM  r  r%  r  
bucketNameregion)rv  r  rw  credentialsbucketrq  c                "    i | ]\  }}|d v	||S )>   r  	file_uuidrw  r   )r   r  r  s      r\   
<dictcomp>z+MediaSendHandler.handle.<locals>.<dictcomp>  s4       !Q III qIIIr^   )ru  r  rw  r  rQ   r  rf  rf  z[%s] %s.handle() failed: %sr  ) _connection	_outboundrB  r  r   cancel_slow_notifierrb  rh  MessageSendervalidate_mediart  r    r]  r  rZ  r   r^  r_  r  r   r  rf  r   dispatch_msg_bodyr:  rR   r9  r:  r   r4  r  r~  )r  r^  rl  ri  rj  r^  connrB  rv  r  rw  validation_errru  rb  rM  r%  rr  rc  
fwd_kwargsri  gcverH  handler_names                           r\   r  zMediaSendHandler.handle^  s      "")7?e?dSSSS..w777P	=7Ht7H8 8!8 8 2 2 2 2 2 2.J, $$&& K!.!=!='*C" " " K%e>JJJJ$$&& ,=#J//	 $+#<#<#>#>>>>>>>
'^^GR88NN8R00IGOIr  %8#,&2%!%0% % %       '4)%!- +&|4&x0' ' ' ! ! ! ! ! ! %+\\^^  
 /4.!'%!-	 
 !  /4.r<<V<<  !.?PQQ  
 L"--B11'8XZ\1]]]]]]]]] 	< 	< 	<e3r77;;;;;;;;; 	= 	= 	=::.LLL-lC$     e3s88<<<<<<<<<	=s9   
AG= *EG= =
JH*$J*J7AJJJ)r^  r  r^  r
   rS   r_  )rc  r<  r^  r
   rS   r`  rZ  r  )r^  r  rl  rR   ri  rq  rj  rq  r^  r
   rS   rk  )	r   r   r   r   r	   rb  rf  rh  r  r   r^   r\   r]  r]  A  s             ^ K K K ^K    #'!%a= a= a= a= a= a= a=r^   r]  c                      e Zd ZdZd Zd ZdS )ImageUrlHandleruD   Strategy: send image from a URL (download → COS → TIMImageElem).c                |  K   |d         }t                               d|j        |           t          ||j                   d {V \  }}|r|dk    r,|                    d          d         }t          |          pd}t          j        	                    |                    d          d                   pd}|||fS )	Nr  z$[%s] ImageUrlHandler: downloading %srn  rr  ?r   rq  	image.jpg)
r4  r5  r~  rs  rt  rX   r   r  rR  ru  )r  r^  r^  r  rv  rw  	path_partr  s           r\   rb  zImageUrlHandler.acquire_file  s      ,	:GL)TTT);7#<*
 *
 *
 $
 $
 $
 $
 $
 $
 
L  	F|/III!,,Q/I*955EL7##IOOC$8$8$;<<K8\11r^   c                    t          |d         |d         |d         |d         |                    dd          |                    dd          |d         	          S 
NrA  ru  r  sizewidthr   heightrw  )rA  r  r  r  r  r  	mime_typer   r  re  s      r\   rf  zImageUrlHandler.build_msg_body  g    #e$$J'v&##GQ// $$Xq11^,
 
 
 	
r^   Nr   r   r   r   rb  rf  r   r^   r\   r  r    s8        NN
2 
2 
2	
 	
 	
 	
 	
r^   r  c                      e Zd ZdZd Zd ZdS )ImageFileHandleruL   Strategy: send image from a local file path (read → COS → TIMImageElem).c                  K   |d         }t           j                            |          st          d|           t                              d|j        |           t          |d          5 }|                                }d d d            n# 1 swxY w Y   t           j        	                    |          pd}t          |          pd}|||fS )N
image_pathFile not found: z![%s] ImageFileHandler: reading %srbr  rq  )r  rR  isfiler:  r4  r5  r~  rM  readru  r   )r  r^  r^  r  rS  rv  r  rw  s           r\   rb  zImageFileHandler.acquire_file  s       .
w~~j)) 	><
<<===7zRRR*d## 	"qJ	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"7##J//>;&x00@L8\11   -BBBc                    t          |d         |d         |d         |d         |                    dd          |                    dd          |d         	          S r  r  re  s      r\   rf  zImageFileHandler.build_msg_body  r  r^   Nr  r   r^   r\   r  r    s8        VV	2 	2 	2	
 	
 	
 	
 	
r^   r  c                      e Zd ZdZd Zd ZdS )FileUrlHandleruB   Strategy: send file from a URL (download → COS → TIMFileElem).c                x  K   |d         }t                               d|j        |           t          ||j                   d {V \  }}|                    d          }|s<|                    d          d         }t          j        	                    |          pd}|r|dk    rt          |          pd}|||fS )	Nr  z#[%s] FileUrlHandler: downloading %srn  r  r  r   rL   rr  )r4  r5  r~  rs  rt  r  rX   r  rR  ru  r   )r  r^  r^  r  rv  rw  r  r  s           r\   rb  zFileUrlHandler.acquire_file  s      z*97<RRR);'";*
 *
 *
 $
 $
 $
 $
 $
 $
 
L ::j)) 	= s++A.Iw''	22<fH 	S|/III*844R8RL8\11r^   c                X    t          |d         |d         |d         |d                   S NrA  r  ru  r  )rA  r  r  r  r   re  s      r\   rf  zFileUrlHandler.build_msg_body  9    "e$J'$v&	
 
 
 	
r^   Nr  r   r^   r\   r  r    s8        LL2 2 2
 
 
 
 
r^   r  c                      e Zd ZdZd Zd ZdS )DocumentHandleruB   Strategy: send local file/document (read → COS → TIMFileElem).c                  K   |d         }t           j                            |          st          d|           t                              d|j        |           t          |d          5 }|                                }d d d            n# 1 swxY w Y   |	                    d          p t           j        
                    |          pd}t          |          pd}|||fS )N	file_pathr  z [%s] DocumentHandler: reading %sr  r  documentrr  )r  rR  r  r:  r4  r5  r~  rM  r  r  ru  r   )r  r^  r^  r  rS  rv  r  rw  s           r\   rb  zDocumentHandler.acquire_file  s     ,	w~~i(( 	=;	;;<<<6iPPP)T"" 	"aJ	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"::j))VRW-=-=i-H-HVJ&x00N4N8\11r  c                X    t          |d         |d         |d         |d                   S r  r  re  s      r\   rf  zDocumentHandler.build_msg_body  r  r^   Nr  r   r^   r\   r  r    s8        LL	2 	2 	2
 
 
 
 
r^   r  c                  &    e Zd ZdZddZd Zd ZdS )	StickerHandlerzAStrategy: send sticker/emoji (TIMFaceElem, no COS upload needed).rS   rT   c                    dS )NFr   r  s    r\   rh  zStickerHandler.needs_cos_upload+  s    ur^   c                
   K   dS )N)r^   stickerrr  r   ra  s      r\   rb  zStickerHandler.acquire_file.  s      99r^   c                   ddl m}m}m}m} |                    d          }|                    d          }|* ||          }	|	t          d|           ||	          S | ||          S  |            }	 ||	          S )Nr   )get_sticker_by_nameget_random_stickerbuild_face_msg_bodybuild_sticker_msg_bodysticker_name
face_indexzSticker not found: )r  )!gateway.platforms.yuanbao_stickerr  r  r  r  r  r:  )
r  rc  r^  r  r  r  r  r  r  r  s
             r\   rf  zStickerHandler.build_msg_body2  s    	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 zz.11ZZ--
#)),77G !G|!G!GHHH))'222#&&*====((**G))'222r^   NrZ  )r   r   r   r   rh  rb  rf  r   r^   r\   r  r  (  sL        KK   : : :3 3 3 3 3r^   r  c                  D    e Zd ZdZddZdd
Z	 dddZddZ	 	 dd dZdS )!GroupQueryServiceaN  Encapsulates all group query operations (both low-level WS calls and
    higher-level AI-tool-facing wrappers).

    Responsibilities:
      - Low-level WS encode/decode for group info and member list queries
      - Chat-id parsing, error wrapping and result filtering for AI tools
      - Member cache population on the adapter
    r^  r  rS   r   c                    || _         d S r   )r  r  s     r\   r  zGroupQueryService.__init__Q  s    r^   rf  rR   rb  c                  K   | j         }|j        j        dS t          |          }ddlm}  ||          }|d         d         }	 |j                            ||           d{V }|                    di           }|                    dd          }	|	dk    r#t          	                    d|j
        |	           dS |                    d	d
          p|                    dd
          }
|
r$t          |
t                    rt          |
          S d|iS # t          j        $ r% t          	                    d|j
        |           Y dS t           $ r,}t          	                    d|j
        |           Y d}~dS d}~ww xY w)zQuery group info via WS (group name, owner, member count, etc.).

        Returns:
            Decoded dict or None on failure.
        Nr   r)   r   rj  rJ  statusz'[%s] query_group_info failed: status=%dr$  r^   rF  rf  z'[%s] query_group_info timeout: group=%sz [%s] query_group_info failed: %s)r  rx  r  r4   gateway.platforms.yuanbao_protor)   rO  r  r4  r>  r~  r;  r  r+   r   r  r9  )r  rf  r^  encoded_decoder;  rJ  rE  r   r  biz_datarH  s               r\   query_group_info_rawz&GroupQueryService.query_group_info_rawX  s      -!)4)*55NNNNNN''""*	$0AA'RXAYYYYYYYYH<<++DXXh**F{{H',X^___t||FC00MHLL4M4MH =Jx77 =28<<< *--# 	 	 	NNDglT^___44 	 	 	NN=w|SQQQ44444	s,   A5D >AD D 0E<	E<!E77E<r   r!  offsetri   limitc                  K   | j         }|j        j        dS t          |||          }ddlm}  ||          }|d         d         }	 |j                            ||           d{V }	|	                    di           }
|
                    dd          }|dk    r#t          	                    d	|j
        |           dS |	                    d
d          p|	                    dd          }|r%t          |t                    rt          |          }ng ddd}|r8|                    d          r#t          j                    |d         f|j        |<   |S # t           j        $ r% t          	                    d|j
        |           Y dS t$          $ r,}t          	                    d|j
        |           Y d}~dS d}~ww xY w)zQuery group member list via WS.

        Returns:
            Decoded dict or None on failure.  Also populates adapter._member_cache.
        Nr  r  r   r  r   rj  r  r  z,[%s] get_group_member_list failed: status=%dr$  r^   rF  T)membersnext_offsetis_completer  z,[%s] get_group_member_list timeout: group=%sz%[%s] get_group_member_list failed: %s)r  rx  r  r5   r  r)   rO  r  r4  r>  r~  r;  r  r,   r  _member_cacher   r  r9  )r  rf  r  r  r^  r  r  r;  rJ  rE  r   r  r  r   rH  s                  r\   get_group_member_list_rawz+GroupQueryService.get_group_member_list_raww  s      -!)4.z&PUVVVNNNNNN''""*	$0AA'RXAYYYYYYYYH<<++DXXh**F{{Mw|]cdddt||FC00MHLL4M4MH PJx77 P9(CC%'$OO U&**Y// U59Y[[&BS4T%j1M# 	 	 	NNI7<Ycddd44 	 	 	NNBGLRUVVV44444	s&   
A5E BE 0F>		F>!F99F>rl  r<  c                   K   |                     d          sddiS |t          d          d         }|                     |           d{V }|ddiS |S )zAI tool: Query current group info.

        No parameters needed (group_code extracted from session context).
        Returns group name, owner, member count, etc.
        r
  r  -This command is only available in group chatsNzFailed to query group info)rY   rq   r  )r  rl  rf  r   s       r\   query_group_infoz"GroupQueryService.query_group_info  sz       !!(++ 	NLMMS]]^^,
00<<<<<<<<>9::r^   list_allNactionr~  rq  c                  	K   |                     d          sddiS |t          d          d         }|                     |           d{V }|ddiS |                    dg           }|dk    r%|r#|                                		fd|D             }n|d	k    rd
 |D             }d}|r7t          |          dk    r$d |D             }dd                    |          z   }|dd         t          |          |dS )a\  AI tool: Query group member list.

        Args:
            chat_id: Chat ID (extracted from session context)
            action: 'find' (search by name) | 'list_bots' (list bots) | 'list_all' (list all)
            name: Search keyword when action='find'

        Returns:
            {"members": [...], "total": int, "mentionHint": str}
        r
  r  r  NzFailed to query group membersr  findc                   g | ]}|                     d d          pd                                v sX|                     dd          pd                                v s,|                     dd          pd                                v |S )nicknamern   	name_cardr  r  r  )r   r   r  s     r\   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>  s       QUU:r228b??AAAAQUU;339r@@BBBBQUU9b117R>>@@@@  A@@r^   	list_botsc                j    g | ]0}d |                     dd          pd                                v .|1S )r	  r  rn   r  r   r   s     r\   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>  sB    \\\QUquuZ7L7L7RPR6Y6Y6[6[-[-[q-[-[-[r^   rn   
   c                    g | ]B}|                     d           p*|                     d          p|                     dd          CS )r  r  r  rn   r  r  s     r\   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>  sJ    fffYZQUU;''T155+<+<TiQS@T@Tfffr^   zMention with @name: z, rM   )r  totalmentionHint)rY   rq   r  r  r  r   )
r  rl  r  r~  rf  r   r  mention_hintnamesr  s
            @r\   query_session_membersz'GroupQueryService.query_session_members  sb       !!(++ 	NLMMS]]^^,
55jAAAAAAAA><==**Y++VJJLLE   "  GG {""\\'\\\G  	Es7||r))ff^efffE1DIIe4D4DDL ss|\\'
 
 	
r^   rY  rf  rR   rS   rb  r   r!  rf  rR   r  ri   r  ri   rS   rb  )rl  rR   rS   r<  )r  N)rl  rR   r  rR   r~  rq  rS   r<  )	r   r   r   r   r  r  r  r  r  r   r^   r\   r  r  G  s                   @ >A# # # # #R   " !"	.
 .
 .
 .
 .
 .
 .
r^   r  c                  D    e Zd ZdZddZddZddZddZdddZddZ	dS )HeartbeatManagerzManages reply heartbeat (RUNNING / FINISH) lifecycle.

    Responsibilities:
      - Periodic RUNNING heartbeat sender (every 2s)
      - Auto-FINISH after 30s inactivity
      - Explicit stop with optional FINISH signal
    r^  r  rS   r   c                0    || _         i | _        i | _        d S r   )r  _reply_heartbeat_tasks_reply_hb_last_activer  s     r\   r  zHeartbeatManager.__init__  s    ?A#79"""r^   rl  rR   heartbeat_valri   c                <  K   | j         }|j        }|j        |j        sdS 	 |                    d          r/|t          d          d         }t          |j        ||          }n,|                    d          }t          |j        ||          }|j        	                    |           d{V  |t          k    rdnd}t                              d|j        ||           dS # t          $ r,}	t                              d	|j        |	           Y d}	~	dS d}	~	ww xY w)
z9Send a single heartbeat (RUNNING or FINISH), best effort.Nr
  )re  rf  	heartbeatr  )re  r  r  RUNNINGFINISHz%[%s] Reply heartbeat %s sent: chat=%sz#[%s] send_heartbeat_once failed: %s)r  rx  r  rZ  rY   rq   r3   removeprefixr2   r  r&   r4  r  r~  r9  )
r  rl  r  r^  r~  rf  r  r  status_namerH  s
             r\   send_heartbeat_oncez$HeartbeatManager.send_heartbeat_once  se     -"7?'/?F	S!!(++ $S]]^^4
5!()+   %11)<<
7!()+  
 ',,w''''''''''48L'L'L))RZKLL7k7      	S 	S 	SLL>cRRRRRRRRR	Ss   CC% %
D/!DDc                  K   | j         }|j        }|j        |j        sdS | j                            |          }|r1|                                st          j                    | j        |<   dS t          j                    | j        |<   t          j
        |                     |          d|           }|| j        |<   dS )zGStart or renew the Reply Heartbeat periodic sender (RUNNING, every 2s).Nzyuanbao-reply-hb-r  )r  rx  r  rZ  r  r  r  r  r  r   r@  _worker)r  rl  r^  r~  existingrD  s         r\   r  zHeartbeatManager.start  s      -"7?'/?F.227;; 	HMMOO 	26)++D&w/F.2ikk"7+"LL!!.W..
 
 
 04#G,,,r^   c                t  K   	 |                      |t                     d{V  	 t          j        t                     d{V  | j                            |d          }t          j                    |z
  t          k    rn6| j	        j
        }|j        n"|                      |t                     d{V  d}n$# t          j        $ r d}Y nt          $ r d}Y nw xY w|s3	 |                      |t                     d{V  n# t          $ r Y nw xY w| j                            |d           | j                            |d           dS # |s3	 |                      |t                     d{V  n# t          $ r Y nw xY w| j                            |d           | j                            |d           w xY w)ztBackground coroutine: send RUNNING heartbeat every 2s.
        30s without renewal -> send FINISH and exit.
        NTr   F)r  r&   r   r@  REPLY_HEARTBEAT_INTERVAL_Sr  r  r  REPLY_HEARTBEAT_TIMEOUT_Sr  rx  r  r  r9  r'   r  r  )r  rl  last_activer~  	cancelleds        r\   r  zHeartbeatManager._worker  sF     	:**74HIIIIIIIIINm$>?????????"8<<WaHH9;;,/HHH}07?..w8LMMMMMMMMMN$ II % 	 	 	III 	 	 	III	
  227<OPPPPPPPPPP    D'++GT:::&**7D99999  227<OPPPPPPPPPP    D'++GT:::&**7D9999sl   B2B9 6E
 9C
E
 CE
 CE
  !D 
DD
F7!E0/F70
E=:F7<E==:F7Tsend_finishrT   c                @  K   | j                             |d          }|rG|                                s3|                                 	 | d{V  n# t          j        $ r Y nw xY w|r5	 |                     |t                     d{V  dS # t          $ r Y dS w xY wdS )z0Stop Reply Heartbeat and optionally send FINISH.N)	r  r  r  r   r   r  r  r'   r9  )r  rl  r  rD  s       r\   stopzHeartbeatManager.stopB  s      *..w== 			 	KKMMM







)    	..w8KLLLLLLLLLLL   	 	s$   	A A$#A$*!B 
BBc                  K   t          | j                                                  D ]*}|                                s|                                 +| j                                         | j                                         dS )z!Cancel all reply heartbeat tasks.N)r`  r  r  r  r   r   r  r  rD  s     r\   r  zHeartbeatManager.closeQ  sz      4;;==>> 	 	D99;; #))+++"((*****r^   NrY  )rl  rR   r  ri   rS   r   rl  rR   rS   r   )Trl  rR   r  rT   rS   r   r   )
r   r   r   r   r  r  r  r  r  r  r   r^   r\   r  r    s         : : : :
S S S S<4 4 4 4(!: !: !: !:F    + + + + + +r^   r  c                  :    e Zd ZdZddZddZddZddZddZdS )SlowResponseNotifierzManages delayed 'please wait' notifications for slow agent responses.

    Starts a timer per chat_id; if the agent hasn't replied within
    SLOW_RESPONSE_TIMEOUT_S seconds, sends a courtesy message.
    r^  r  rB  'MessageSender'rS   r   c                0    || _         || _        i | _        d S r   )r  _sender_tasks)r  r^  rB  s      r\   r  zSlowResponseNotifier.__init__a  s    /1r^   rl  rR   c                   K   |                      |           t          j        |                     |          d|           }|| j        |<   dS )zCStart a delayed task that notifies the user when the agent is slow.zyuanbao-slow-resp-r  N)r   r   r@  	_notifierr  r  rl  rD  s      r\   r  zSlowResponseNotifier.startf  s]      G"NN7##/g//
 
 
  $Gr^   c                  K   	 t          j        t                     d{V  t                              d| j        j        t          t                    |           | j        	                    |t                     d{V  dS # t           j        $ r Y dS t          $ r1}t                              d| j        j        |           Y d}~dS d}~ww xY w)z@Wait SLOW_RESPONSE_TIMEOUT_S, then push a 'please wait' message.Nz<[%s] Agent response exceeded %ds for %s, sending wait noticez&[%s] Slow-response notifier failed: %s)r   r@  SLOW_RESPONSE_TIMEOUT_Sr4  r5  r  r~  ri   r  send_text_chunkSLOW_RESPONSE_MESSAGEr  r9  r  )r  rl  rH  s      r\   r  zSlowResponseNotifier._notifiero  s      
	\- 7888888888KKN"C(?$@$@'   ,..w8MNNNNNNNNNNN% 	 	 	DD 	\ 	\ 	\LLA4=CUWZ[[[[[[[[[	\s   A>B C	C&CCc                    | j                             |d          }|r*|                                s|                                 dS dS dS )z@Cancel the pending slow-response notifier for *chat_id*, if any.N)r  r  r  r   r  s      r\   r   zSlowResponseNotifier.cancel}  sS    {w-- 			 	KKMMMMM	 	 	 	r^   c                   K   t          | j                                                  D ]*}|                                s|                                 +| j                                         dS )zCancel all slow-response tasks.N)r`  r  r  r  r   r   r  s     r\   r  zSlowResponseNotifier.close  sc      ++--.. 	 	D99;; r^   N)r^  r  rB  r  rS   r   r  r   )	r   r   r   r   r  r  r  r   r  r   r^   r\   r  r  Z  s         2 2 2 2
$ $ $ $\ \ \ \        r^   r  c                  v   e Zd ZU dZ eh d          Zded<   dZded<   dIdZdJdZ	dKdZ
	 	 dLdMdZ	 	 dNdOd!Z	 dPdQd&Z	 	 dLdRd)Z	 	 	 dSdTd.ZdUdVd1Z	 dPdWd2Z ej        d3ej                  ZdXd4ZdUdYd5Z	 dPdZd6Zed[d:            Ze	 d\d]d@            Ze	 	 d^d_dF            Zed`dG            ZdadHZdS )br{  ac  Core message sending dispatcher for YuanbaoAdapter.

    Responsibilities:
      - Per-chat-id lock management (serial send ordering)
      - Text chunk sending with retry
      - C2C / Group message encoding and dispatch
      - Media send helpers (image, file, sticker, document)
      - Direct send helper (text + media, used by send_message tool)
    >   rK  rL  rM  rN  rO  rP  zClassVar[frozenset]
IMAGE_EXTSr  ClassVar[int]CHAT_DICT_MAX_SIZEr^  r  rS   r   c                    || _         t          j                    | _        d | _        d | _        t                      t                      t                      t                      t                      d| _        d S )N)r  
image_filer  r  r  )r  collectionsOrderedDict_chat_locks_on_send_start_on_send_finishr  r  r  r  r  _media_handlersr  s     r\   r  zMessageSender.__init__  sq    GRG^G`G` ?C?C )***,,&(('))%''=
 =
r^   r~  rR   r  r]  c                    || j         |<   dS )z1Register (or replace) a named media send handler.N)r  )r  r~  r  s      r\   register_handlerzMessageSender.register_handler  s    %,T"""r^   rl  r   c                   || j         v r'| j                             |           | j         |         S t          | j                   | j        k    rd}t	          | j                   D ]?}| j         |                                         s| j                             |           d} n@|s9| j                             t          t          | j                                        t          j
                    | j         |<   | j         |         S )z=Return (or create) a per-chat-id lock with safe LRU eviction.FT)r  move_to_endrq   r  r`  lockedr  r  iterr   r   )r  rl  evictedrD  s       r\   get_chat_lockzMessageSender.get_chat_lock  s   d&&&((111#G,,t  D$;;;GD,--  ',3355 $((---"GE  C $$T$t/?*@*@%A%ABBB$+LNN!((r^   Nrn   r5  ri  rq  rf  rk  c           
     H  K   | j         }|j        }|j        t          ddd          S | j        r|                     |           |                     |          }|4 d{V  |                     |          }|                     ||j                  }	t          
                    d|j        t          |          |j        t          |	          d |	D                        t          |	          D ]K\  }
}|
dk    r|nd}|                     ||||	           d{V }|j        s|c cddd          d{V  S L	 ddd          d{V  n# 1 d{V swxY w Y   | j        r-	 |                     |           d{V  n# t"          $ r Y nw xY wt          d
          S )zHSend text message with auto-chunking and per-chat-id ordering guarantee.NFrL  Trm  zJ[%s] truncate_message: input=%d chars, max=%d, output=%d chunk(s) sizes=%sc                ,    g | ]}t          |          S r   rq   r   s     r\   r   z+MessageSender.send_text.<locals>.<listcomp>  s    555c!ff555r^   r   rw  )rn  )r  rx  r  r   r  r"  strip_cron_wrappertruncate_messageMAX_TEXT_CHUNKr4  r5  r~  rq   r   r  rn  r  r9  )r  rl  r5  ri  rf  r^  r~  lockcontent_to_sendr   r   r   r_tor   s                 r\   	send_textzMessageSender.send_text  s      -"7?e?dSSSS 	)(((!!'** 	" 	" 	" 	" 	" 	" 	" 	""55g>>O**?G<RSSFKK\c/22G4JF55f555  
 &f-- " "5#$66xxt#33GUDU_3````````~ "!MM	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"""	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"  	**73333333333   $''''s+   #CE;E
EE(F 
FFr  rj  r^  r
   c                   K   | j                             |          }|t          dd|          S  |j        | j        |f||d| d{V S )2Dispatch media send to the named handler strategy.NFzUnknown media handler: ro  )ri  rj  )r  r  r   r  r  )r  rl  r  ri  rj  r^  r  s          r\   
send_mediazMessageSender.send_media  s       &**<88?@@@    $W^M7
w
 
28
 
 
 
 
 
 
 
 	
r^   r  media_files Optional[List[Tuple[str, bool]]]Dict[str, Any]c                  K   | j         }d}|                                r/|                    ||           d{V }|j        sdd|j         iS |pg D ]\  }}t          |          j                                        }|| j        v r|	                    ||           d{V }n|
                    ||           d{V }|j        sdd|j         ic S |ddiS dd||r|j        nddS )	a@  Send text + media via Yuanbao (used by the ``send_message`` tool).

        Unlike Weixin which creates a fresh adapter per call, Yuanbao reuses
        the running gateway adapter (persistent WebSocket).  Logic mirrors
        send_weixin_direct: send text first, then iterate media_files by
        extension.
        Nr  zYuanbao send failed: zYuanbao media send failed: z6No deliverable text or media remained after processingTyuanbao)rn  platformrl  rI  )r  rc   r  rn  r  r   suffixr  r  send_image_filesend_documentrI  )	r  rl  r  r0  r^  last_result
media_path	_is_voicerR  s	            r\   send_directzMessageSender.send_direct  sr      -.2 ==?? 	N 'Wg > >>>>>>>K& N!L9J!L!LMM &1%6B 	T 	T!J	z"")//11Cdo%%$+$;$;GZ$P$PPPPPPP$+$9$9':$N$NNNNNNN& T!R{?P!R!RSSSST UVV !4?I+00T	
 
 	
r^   ri  r`  c                @  K   |                      |          }|4 d{V  |                    d          r5|t          d          d         }|                     |||           d{V }n3|                    d          }|                     |||           d{V }	 ddd          d{V  n# 1 d{V swxY w Y   |                    d          r$t          d|                    d                    S t          d	|                    d
d                    S )z5Lock + dispatch an arbitrary MsgBody to C2C or group.Nr
  r  rw  rn  Tr  rn  rI  Fr  Unknown errorro  )r"  rY   rq   send_group_msg_bodyr  send_c2c_msg_bodyr  r   )	r  rl  ri  ri  rf  r)  grpr   r  s	            r\   r}  zMessageSender.dispatch_msg_body-  s      !!'** 	c 	c 	c 	c 	c 	c 	c 	c!!(++ cc(mmnn-#77XxPPPPPPPP$11)<<
#55j(Wa5bbbbbbbbb	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c 	c ::i   	Ndvzz)7L7LMMMM%vzz'?/S/STTTTs   A>B11
B;>B;r   rQ   retryri   c           	     |  K   | j         }d}t          |          D ]k}	 |                    d          r5|t          d          d         }	|                     |	||           d{V }
n3|                    d          }|                     |||           d{V }
|
                    d          r&t          d|
                    d          	          c S |
                    d
d          }t          
                    d|j        |dz   ||           nL# t          $ r?}t          |          }t          
                    d|j        |dz   ||           Y d}~nd}~ww xY w||dz
  k     rt          j        d|z             d{V  mt                              d|j        ||           t          dd|           S )zFSend a single text chunk with retry (exponential backoff: 1s, 2s, 4s).r?  r
  Nr  rw  rn  Tr  r>  r  z-[%s] send_text_chunk attempt %d/%d failed: %sro   z0[%s] send_text_chunk attempt %d/%d exception: %srB   z>[%s] send_text_chunk max retries (%d) exceeded. Last error: %sFzMax retries exceeded: ro  )r  r,  rY   rq   send_group_messager  send_c2c_messager  r   r4  r>  r~  r9  rR   r   r@  r  )r  rl  rQ   ri  rC  rf  r^  
last_errorrC  rB  r  r  rH  s                r\   r  zMessageSender.send_text_chunkB  s#      -)
U|| 	2 	2G%%h// _!#h--..1C $ 7 7T8 L LLLLLLLCC!(!5!5i!@!@J $ 5 5j$S] 5 ^ ^^^^^^^C779%% S%dswwy?Q?QRRRRRR WWWo>>
CL'A+uj        XX
FL'A+uj        ""mAL111111111LL%	
 	
 	
 %/T
/T/TUUUUs   B5D<D
E5EEr  r<  c                R   K   dd|idg}|                      |||           d{V S )z<Send C2C text message, return {success: bool, msg_key: str}.r  rQ   r  rw  N)rA  )r  r  rQ   rf  ri  s        r\   rF  zMessageSender.send_c2c_messagep  sG      !.~NNO++JZ+XXXXXXXXXr^   c                l   K   |                      ||          }|                     |||           d{V S )zDSend group text message, auto-converting @nickname to TIMCustomElem.N)_build_msg_body_with_mentionsr@  )r  rf  rQ   ri  ri  s        r\   rE  z MessageSender.send_group_messageu  sG       55dJGG--j(HMMMMMMMMMr^   z!(?:(?<=\s)|(?<=^))@(\S+?)(?=\s|$)c                   | j         j                            |          }|r.|\  }}t          j                    |z
  | j         j        k     r|ng }ng }|sdd|idgS i }|D ]b}|                    d          p|                    d          pd}	|                    d          pd}
|	r|
r|	|
f||	                                <   cg }d}| j                            |          D ]}|                                }||k    r8|||         	                                }|r|
                    dd|id           |                    d	          }|                    |                                          }|r9|\  }}
|
                    d
dt          j        dd| |
d          id           n|
                    ddd| id           |                                }|t          |          k     r8||d         	                                }|r|
                    dd|id           |s|
                    dd|id           |S )zNParse @nickname patterns and build mixed TIMTextElem + TIMCustomElem msg_body.r  rQ   r  r  r  rn   r  r   ro   r  r$  r  @)r  rQ   r  N)r  r  r  r  MEMBER_CACHE_TTL_Sr  _AT_USER_REru   r  rc   r   r  r  dumpsrv   rq   )r  rQ   rf  rQ  tsmember_listr  nickname_to_uidr   nickr  ri  last_idxr   r  segr  r   	real_nicktails                       r\   rJ  z+MessageSender._build_msg_body_with_mentions  s   ,00<< 	$OB&*ikkB&69Y&Y&Ykk`bGGG 	P!.~NNOO 	< 	<A55$$@k(:(:@bD%%	""(bC < <15s

-%..t44 	# 	#EKKMMEx8E>*0022 _OOPVX[}$]$]^^^{{1~~H#''(8(899E 	f!&	3 /
9cf+g+g h h$! !     ]FTbX`TbTbKc d deeeyy{{HHc$ii		?((**D \]FTX> Z Z[[[ 	XOOPT~VVWWWr^   c                   K   | j         }dt                       }t          |||j        pd||          }|                     |||           d{V S )z(Send C2C message with arbitrary MsgBody.c2c_rn   )r  ri  re  rj  rf  N)r  r6   r0   rZ  _dispatch_encoded)r  r  ri  rf  r^  rJ  r  s          r\   rA  zMessageSender.send_c2c_msg_body  st      -''')! .B!
 
 
 ++GWfEEEEEEEEEr^   c                   K   | j         }dt                       }t          |||j        pd||pd          }|                     |||           d{V S )z*Send group message with arbitrary MsgBody.grp_rn   )rf  ri  re  rj  
ref_msg_idN)r  r6   r1   rZ  rZ  )r  rf  ri  ri  r^  rJ  r  s          r\   r@  z!MessageSender.send_group_msg_body  sz       -'''+! .B~2
 
 
 ++GWfEEEEEEEEEr^   r  r  rJ  c                  K   	 | j                             ||           d{V }d|                    dd          dS # t          j        $ r ddt
           d	d
cY S t          $ r}dt          |          d
cY d}~S d}~ww xY w)zBSend pre-encoded bytes via WS and return a normalised result dict.r  NTrj  rn   )rn  r  FzRequest timeout after sro  )rx  rO  r  r   r  r[  r9  rR   )r^  r  rJ  rE  rH  s        r\   rZ  zMessageSender._dispatch_encoded  s      
	9$0AA'RXAYYYYYYYYH#Xr0J0JKKK# 	a 	a 	a$/_H\/_/_/_````` 	9 	9 	9$s3xx88888888	9s!   :? B	B'A>8B>B   rv  Optional[bytes]r  ro  c                    | t          |           dk    rd| S |dz  dz  }t          |           |k    r"t          |           dz  dz  }d| d|dd| d	S dS )
zMedia pre-validation: check file validity before sending/uploading.

        Returns:
            Error description (str) if validation fails, otherwise None.
        Nr   zEmpty file: i   zFile too large: z (z.1fzMB > zMB)r%  )rv  r  ro  	max_bytessize_mbs        r\   r|  zMessageSender.validate_media  s     ZA!5!5,(,,,$&-	z??Y&&*oo,t3GThTT'TTTKTTTTtr^   r   
max_lengthrj   rk   	List[str]c                    |pt           } ||           |k    r| gS t                              | ||          }d |D             }|r|n| gS )aj  
        Split a long message into chunks with table-awareness.

        Delegates core splitting to ``MarkdownProcessor.chunk_markdown_text``
        and strips page indicators like ``(1/3)`` from the output.

        Falls back to ``BasePlatformAdapter.truncate_message`` for non-table
        content and for overall text that fits in a single chunk.
        r   c                D    g | ]}t                               d |          S rX  )_INDICATOR_REsubr   s     r\   r   z2MessageSender.truncate_message.<locals>.<listcomp>  s(    ;;;q-##B**;;;r^   )rq   rP   r   )r5  re  rj   rw   r   s        r\   r'  zMessageSender.truncate_message  sq     }4==J&&9 #66Z 7 
 

 <;F;;;.vvgY.r^   c                8   |                      d          s| S d}d}|                     |          }|                     |          }|dk     s|dk     s||k    r| S | d|         }d|vr| S |t          |          z   }| ||                                         }|p| S )zFStrip scheduler cron header/footer wrapper for cleaner Yuanbao output.zCronjob Response: z
-------------

zI

To stop or manage this job, send me a new message (e.g. "stop reminder r   Nz

(job_id: )rY   r  rr   rq   rc   )r5  dividerfooter_prefixdivider_pos
footer_posr  
body_startrF  s           r\   r&  z MessageSender.strip_cron_wrapper  s     !!"677 	N'ell7++]]=11
??j1nn
k0I0IN+&&&N 3w<</
z*,-3355wr^   c                <   K   | j                                          dS )zCRelease chat locks (no-op for now; placeholder for future cleanup).N)r  r   r  s    r\   r  zMessageSender.close/  s!           r^   rY  )r~  rR   r  r]  rS   r   rl  rR   rS   r   r  
rl  rR   r5  rR   ri  rq  rf  rR   rS   rk  r  )rl  rR   r  rR   ri  rq  rj  rq  r^  r
   rS   rk  r   rl  rR   r  rR   r0  r1  rS   r2  )
rl  rR   ri  r`  ri  rq  rf  rR   rS   rk  )Nr   rn   )rl  rR   rQ   rR   ri  rq  rC  ri   rf  rR   rS   rk  rX  )r  rR   rQ   rR   rf  rR   rS   r<  )rf  rR   rQ   rR   ri  rq  rS   r<  )rQ   rR   rf  rR   rS   r`  )r  rR   ri  r`  rf  rR   rS   r<  )rf  rR   ri  r`  ri  rq  rS   r<  )r^  r  r  r  rJ  rR   rS   r<  r`  rv  ra  r  rR   ro  ri   rS   rq  r   )r5  rR   re  ri   rj   rk   rS   rf  )r5  rR   rS   rR   r   )r   r   r   r   rT  r  rY  r  r  r  r"  r,  r/  r<  r}  r  rF  rE  rs   rt   	MULTILINErN  rJ  rA  r@  r   rZ  r|  r'  r&  r  r   r^   r\   r{  r{    s          '0i0b0b0b&c&cJcccc(,,,,,
 
 
 
&- - - -) ) ) ). #'%( %( %( %( %(V #'!%
 
 
 
 
4 9=	)
 )
 )
 )
 )
^ #'U U U U U2 #'*V *V *V *V *V\Y Y Y Y Y #'	N N N N N "*A2<PPK2 2 2 2hF F F F F" #'	F F F F F( 
9 
9 
9 \
9 GI    \$  15/ / / / \/<    \,! ! ! ! ! !r^   r{  c                      e Zd ZU dZej        Zded<   d1dZd2dZd2dZ		 	 d3d4dZ
d5dZ	 d6d7dZd2dZd8d9d"Zd2d#Zd2d$Zd:d&Zed;d(            Ze	 d<d=d/            Zd>d0ZdS )?OutboundManageru  Outbound coordinator that orchestrates sending, heartbeat and slow-response.

    Composes:
      - MessageSender   — core text/media sending
      - HeartbeatManager — reply heartbeat (RUNNING / FINISH) lifecycle
      - SlowResponseNotifier — delayed 'please wait' notifications

    YuanbaoAdapter holds a single ``_outbound: OutboundManager`` and delegates
    all outbound operations through it.
    r  r  r^  r  rS   r   c                    || _         t          |          | _        t          |          | _        t          || j                  | _        | j        | j        _        | j	        | j        _
        d S r   )r  r{  rB  r  r  r  slow_notifier_handle_send_startr  _handle_send_finishr  r  s     r\   r  zOutboundManager.__init__C  s_    %27%;%;+;G+D+D3GQUQ\3]3] &*%<"&*&>###r^   rl  rR   c                :    | j                             |           dS )zFCalled by MessageSender before sending: cancel slow-response notifier.Nr{  r   r  rl  s     r\   r|  z"OutboundManager._handle_send_startO      !!'*****r^   c                V   K   | j                             |t                     d{V  dS )z=Called by MessageSender after sending: send FINISH heartbeat.N)r  r  r'   r  s     r\   r}  z#OutboundManager._handle_send_finishS  s7      n00:MNNNNNNNNNNNr^   Nrn   r5  ri  rq  rf  rk  c                N   K   | j                             ||||           d{V S )z%Send text message with auto-chunking.rw  N)rB  r,  )r  rl  r5  ri  rf  s        r\   r,  zOutboundManager.send_textY  s:      
 [**7GXR\*]]]]]]]]]r^   r  r^  r
   c                :   K    | j         j        ||fi | d{V S )r.  N)rB  r/  )r  rl  r  r^  s       r\   r/  zOutboundManager.send_media`  s9       ,T[+G\LLVLLLLLLLLLr^   r  r0  r1  r2  c                J   K   | j                             |||           d{V S )z.Send text + media (used by send_message tool).N)rB  r<  )r  rl  r  r0  s       r\   r<  zOutboundManager.send_directf  s4      
 [,,Wg{KKKKKKKKKr^   c                J   K   | j                             |           d{V  dS )z Start reply heartbeat (RUNNING).N)r  r  r  s     r\   start_typingzOutboundManager.start_typingm  s4      n""7+++++++++++r^   Fr  rT   c                N   K   | j                             ||           d{V  dS )zStop reply heartbeat.r  N)r  r  )r  rl  r  s      r\   stop_typingzOutboundManager.stop_typingq  s9      n!!'{!CCCCCCCCCCCr^   c                J   K   | j                             |           d{V  dS )zStart slow-response notifier.N)r{  r  r  s     r\   start_slow_notifierz#OutboundManager.start_slow_notifieru  s5       &&w///////////r^   c                :    | j                             |           dS )zCancel slow-response notifier.Nr  r  s     r\   rz  z$OutboundManager.cancel_slow_notifiery  r  r^   r   c                6    | j                             |          S )z@Proxy to MessageSender.get_chat_lock for backward compatibility.)rB  r"  r  s     r\   r"  zOutboundManager.get_chat_lock}  s    {((111r^   collections.OrderedDictc                    | j         j        S )z>Proxy to MessageSender._chat_locks for backward compatibility.)rB  r  r  s    r\   r  zOutboundManager._chat_locks  s     {&&r^   r`  rv  ra  r  ro  ri   c                :    t                               | ||          S )z&Proxy to MessageSender.validate_media.)r{  r|  )rv  r  ro  s      r\   r|  zOutboundManager.validate_media  s    
 ++J+NNNr^   c                   K   | j                                          d{V  | j                                         d{V  | j                                         d{V  dS )zShut down all sub-managers.N)rB  r  r  r{  r  s    r\   r  zOutboundManager.close  s      k!!!!!!!!!n""$$$$$$$$$ &&(((((((((((r^   rY  r  r  rs  )rl  rR   r  rR   r^  r
   rS   rk  r   rt  )Fr  rr  )rS   r  ru  rv  r   )r   r   r   r   r{  r  rY  r  r|  r}  r,  r/  r<  r  r  r  rz  r"  r  r  r   r|  r  r   r^   r\   ry  ry  4  s        	 	 )6(HHHHH? ? ? ?+ + + +O O O O EI^ ^ ^ ^ ^M M M M 9=L L L L L, , , ,D D D D D0 0 0 0+ + + +2 2 2 2 ' ' ' X' GIO O O O \O) ) ) ) ) )r^   ry  c                  n    e Zd ZU dZej        ZdZded<   dZ	ded<   dZ
ded	<   d
Zded<   edKd            ZedLd            ZdM fdZdNdZdOdZdPdZ	 	 	 dQdRd(ZdSd*ZdTdUd,ZdVd-ZdW fd/ZdXd0Z	 dYdZd5Zd6Zd[d\d9Z	 	 	 d]d^d<Z	 	 	 d]d_d>Z	 	 	 d]d`dAZ	 	 	 d]dadEZ	 	 	 	 dbdcdGZ dddIZ!dddJZ" xZ#S )er  zCYuanbao AI Bot adapter backed by a persistent WebSocket connection.r   ri   r(  rM   rt  i  r  REPLY_REF_MAX_ENTRIESNz$ClassVar[Optional['YuanbaoAdapter']]_active_instancerS   Optional['YuanbaoAdapter']c                    | j         S )z7Return the currently connected YuanbaoAdapter, or None.r  r  s    r\   
get_activezYuanbaoAdapter.get_active  s     ##r^   r^  r   c                    || _         dS )z0Register (or clear) the active adapter instance.Nr  )r   r^  s     r\   r  zYuanbaoAdapter.set_active  s      'r^   r  r   r^  r
   c                <   t                                          |t          j                   |j        pi }|                    d          pd                                | _        |                    d          pd                                | _        |                    d          pd | _	        |                    d          pt                                          | _        |                    d          pt                              d          | _        |                    d          pd                                | _        t!          |           | _        t%          |           | _        t)                      | _        t)                      | _        i | _        d	| _        t3          d
          | _        i | _        i | _        i | _        i | _        |                    d          pt?          j         dd                                          !                                }|                    d          pt?          j         dd          }d |"                    d          D             }|                    d          pt?          j         dd                                          !                                }|                    d          pt?          j         dd          }d |"                    d          D             }	tG          ||||	          | _$        tK          |           | _&        tN          (                                | _)        t?          j         d          p|j*        r|j*        j+        nd}
tY          |
          o|
-                    d           | _.        d S )Napp_idrn   r   r%  ws_urlr  r  r  rJ   i,  )ttl_secondsr`  YUANBAO_DM_POLICYrM  ra  YUANBAO_DM_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r   xs     r\   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>  s2    #b#b#b!XYX_X_XaXa#bAGGII#b#b#br^   ,rb  YUANBAO_GROUP_POLICYrc  YUANBAO_GROUP_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r  s     r\   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>  s2    &h&h&hQ^_^e^e^g^g&hqwwyy&h&h&hr^   )r`  ra  rb  rc  r{  r
  )/superr  r   YUANBAOr  r  rc   r^  r`  rZ  DEFAULT_WS_GATEWAY_URLr  DEFAULT_API_DOMAINrb   r_  r  r  rx  ry  ry  r   r  rA  r  rM  r   r  r  r  r)  r  r  r  r  rX   r_  rv  r  _group_queryr  r  rH  home_channelrl  rT   rY   r~  )r  r  r^  _extrar`  _dm_allow_from_rawra  rb  _group_allow_from_rawrc  _existing_homer  s              r\   r  zYuanbaoAdapter.__init__  sR   !1222 #$jj228b??AA!'L!9!9!?R F F H H&,jj&:&:&Bd#ZZ11K5KRRTT!'L!9!9!O=O W WX[ \ \ &

; 7 7 =2DDFF /@.E.E*9$*?*? 25 58EE
 =?). *c::: 8: 46 57" 35 JJ{## 6y,f55
%''%%'' 	 JJ'' 6y0"55 	 $c#b7I7O7OPS7T7T#b#b#b JJ~&& 9y/88
%''%%'' 	 JJ)** 9y3R88 	 'i&h:O:U:UVY:Z:Z&h&h&h*'%-	
 
 
 .d33 3I2N2N2P2P #9:: 
+1+>FF''B 	 )-^(<(<(h^E^E^_gEhEhAhr^   rD  asyncio.Taskc                x    | j                             |           |                    | j         j                   |S )z@Register a fire-and-forget task so it won't be GC'd prematurely.)rA  r   rB  rC  r  s     r\   r  zYuanbaoAdapter._track_task  s8    ""4(((t5=>>>r^   rT   c                D   K   | j                                          d{V S )zhConnect to Yuanbao WS gateway and authenticate.

        Delegates to ConnectionManager.open().
        N)rx  rM  r  s    r\   r  zYuanbaoAdapter.connect  s/      
 %**,,,,,,,,,r^   c                V  K   t           j        | u rt                               d           d| _        |                                  |                                  | j                                         d{V  | j                                         d{V  t          | j
                  D ]*}|                                s|                                 +| j
                                         | j                                         t                              d| j                   dS )z;Cancel background tasks and close the WebSocket connection.NFz[%s] Disconnected)r  r  r  r   r.  r  rx  r  ry  r`  r  r  r   r   r  r4  r5  r~  r  s     r\   
disconnectzYuanbaoAdapter.disconnect  s#     *d22%%d+++!!!##%%% $$&&&&&&&&&n""$$$$$$$$$ ,-- 	 	D99;; !!###  """'33333r^   rn   rl  rR   r5  ri  rq  metadataOptional[Dict[str, Any]]rf  r   c                N   K   | j                             ||||           d{V S )zCSend text message with auto-chunking. Delegates to OutboundManager.rw  N)ry  r,  )r  rl  r5  ri  r  rf  s         r\   r  zYuanbaoAdapter.send4  s:       ^--gwU_-`````````r^   r2  c                D   K   |                     d          r|ddS |ddS )u  Return basic chat metadata derived from the chat_id prefix.

        chat_id conventions:
          "group:<group_code>"  → group chat
          "direct:<account>"   → C2C / direct message (default)

        TODO (T06): fetch real chat name/member-count from Yuanbao API.
        r
  r  )r~  r:  r  r  r  s     r\   get_chat_infozYuanbaoAdapter.get_chat_info?  s:       h'' 	6#W555...r^   rb  c                n   K   	 | j                             |           d{V  dS # t          $ r Y dS w xY w)zGSend "typing" status heartbeat (RUNNING). Delegates to OutboundManager.N)ry  r  r9  )r  rl  r  s      r\   send_typingzYuanbaoAdapter.send_typingL  s[      	.--g66666666666 	 	 	DD	s    & 
44c                r   K   	 | j                             |d           d{V  dS # t          $ r Y dS w xY w)zStop the RUNNING heartbeat loop without sending FINISH immediately.

        FINISH is sent by send() after actual message delivery to ensure correct ordering:
        RUNNING... -> message arrives -> FINISH.
        Fr  N)ry  r  r9  r  s     r\   r  zYuanbaoAdapter.stop_typingS  s`      	.,,W%,HHHHHHHHHHH 	 	 	DD	s   "( 
66r"  c                (  K   |j         j        }| j                            |           d{V  	 t	                                          ||           d{V  | j                            |           dS # | j                            |           w xY w)z9Wrap base class processing with a slow-response notifier.N)rO  rl  ry  r  r  _process_message_backgroundrz  )r  r  r"  rl  r  s       r\   r  z*YuanbaoAdapter._process_message_background^  s      ,&n00999999999	9''55e[IIIIIIIIIN//88888DN//8888s   (A5 5Bc                F   K   | j                             |           d{V S )z2Query group info (delegates to GroupQueryService).N)r  r  ro  s     r\   r  zYuanbaoAdapter.query_group_infok  s/      &;;JGGGGGGGGGr^   r   r!  r  r  c                L   K   | j                             |||           d{V S )z9Query group member list (delegates to GroupQueryService).r  N)r  r  )r  rf  r  r  s       r\   get_group_member_listz$YuanbaoAdapter.get_group_member_listo  s:       &@@TZbg@hhhhhhhhhr^   i'  r  rQ   c                   K   | j                             |          st          dd          S t          |          | j        k    r|d| j                 dz   }d| }|                     |||           d{V S )a  
        Actively send C2C private chat message.

        Args:
            user_id: Target user ID
            text: Message text (limit 10000 characters)
            group_code: Source group code (for group-originated DM context)

        Returns:
            SendResult
        FzDM access denied for this userro  Nz
...(truncated)r  rw  )rv  rm  r   rq   DM_MAX_CHARSr  )r  r  rQ   rf  rl  s        r\   send_dmzYuanbaoAdapter.send_dm{  s       "0099 	Ue3STTTTt99t(((***+.@@D%G%%YYwYDDDDDDDDDr^   r  rj  c                B   K    | j         j        |df|||d| d{V S )zKSend image message (URL). Delegates to OutboundManager via ImageUrlHandler.r  )ri  rj  r  Nry  r/  )r  rl  r  rj  ri  r  r^  s          r\   
send_imagezYuanbaoAdapter.send_image  s_       /T^.[
w)
 
 
 
 
 
 
 
 
 
 	
r^   r  c                B   K    | j         j        |df|||d| d{V S )zISend local image file. Delegates to OutboundManager via ImageFileHandler.r  )ri  rj  r  Nr  )r  rl  r  rj  ri  r  r^  s          r\   r7  zYuanbaoAdapter.send_image_file  s_       /T^.\
w:
 
 
 
 
 
 
 
 
 
 	
r^   r  r  c                B   K    | j         j        |df|||d| d{V S )zISend file message (URL). Delegates to OutboundManager via FileUrlHandler.r  )ri  r  r  Nr  )r  rl  r  r  ri  r  r^  s          r\   	send_filezYuanbaoAdapter.send_file  s_       /T^.Z
8
 
 
 
 
 
 
 
 
 
 	
r^   r  r  Optional[int]c                B   K    | j         j        |df|||d| d{V S )zDSend sticker/emoji. Delegates to OutboundManager via StickerHandler.r  )ri  r  r  Nr  )r  rl  r  r  ri  r^  s         r\   send_stickerzYuanbaoAdapter.send_sticker  s`       /T^.Y
%*
 
 	
 
 
 
 
 
 
 
 	
r^   r  c                D   K    | j         j        |df||||d| d{V S )zMSend local file (document). Delegates to OutboundManager via DocumentHandler.r  )ri  rj  r  r  Nr  )r  rl  r  r  rj  ri  r  r^  s           r\   r8  zYuanbaoAdapter.send_document  sb       /T^.Z
w(
 
 	
 
 
 
 
 
 
 
 	
r^   r<  c                v   K   t                               | j        | j        | j        | j                   d{V S )z<Get the current valid sign token (using module-level cache).r  N)r   rS  r^  r`  r_  r  r  s    r\   r]  z YuanbaoAdapter._get_cached_token  sU       **M4+T-=o + 
 
 
 
 
 
 
 
 	
r^   c                R    | j         }|j        | j        |j        |j        | j        dS )z3Return a snapshot of the current connection status.)	connectedr%  r  r  r  )rx  r  rZ  r  r  r  )r  r~  s     r\   
get_statuszYuanbaoAdapter.get_status  s4    *l/"&"9l
 
 	
r^   rS   r  )r^  r  rS   r   )r  r   r^  r
   rS   r   )rD  r  rS   r  rZ  r   )NNrn   )rl  rR   r5  rR   ri  rq  r  r  rf  rR   rS   r   )rl  rR   rS   r2  r   )rl  rR   r  rb  rS   r   r  )r"  rR   rS   r   r  r  r  rX  )r  rR   rQ   rR   rf  rR   rS   r   )NNN)rl  rR   r  rR   rj  rq  ri  rq  r  rb  r^  r
   rS   r   )rl  rR   r  rR   rj  rq  ri  rq  r  rb  r^  r
   rS   r   )rl  rR   r  rR   r  rq  ri  rq  r  rb  r^  r
   rS   r   )rl  rR   r  rq  r  r  ri  rq  r^  r
   rS   r   )NNNN)rl  rR   r  rR   r  rq  rj  rq  ri  rq  r  rb  r^  r
   rS   r   )rS   r<  )$r   r   r   r   r   r  PLATFORMr(  rY  rt  r  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r7  r  r  r8  r]  r  __classcell__)r  s   @r\   r  r    s        MMHN+..... >BAAAA$ $ $ [$ ' ' ' ['\i \i \i \i \i \iD   - - - -4 4 4 46 #'-1	a 	a 	a 	a 	a/ / / /    	 	 	 	9 9 9 9 9 9H H H H
 >Ai i i i i LE E E E E6 "&"&#'
 
 
 
 
( "&"&#'
 
 
 
 
( #'"&#'
 
 
 
 
& '+$("&
 
 
 
 
( #'!%"&#'
 
 
 
 
$
 
 
 
	
 	
 	
 	
 	
 	
 	
 	
r^   r  rS   r  c                 4    t                                           S )z,Delegate to ``YuanbaoAdapter.get_active()``.)r  r  r   r^   r\   get_active_adapterr    s    $$&&&r^   r^  r  rl  rR   r  r0  r1  r2  c                J   K   | j                             |||           d{V S )z,Delegate to ``OutboundManager.send_direct``.N)ry  r<  )r^  rl  r  r0  s       r\   send_yuanbao_directr    s5       "..wMMMMMMMMMr^   r  r   )
r^  r  rl  rR   r  rR   r0  r1  rS   r2  )r   
__future__r   r   r  r'  r   r   r  loggingr  rs   r.  r  urllib.parser  r  r   r   r   pathlibr   abcr   r	   typingr
   r   r   r   r   r   r   sysr)  r  websockets.exceptionsr  ImportErrorgateway.configr   r   gateway.platforms.baser   r   r   r   r   r   gateway.platforms.helpersr   gateway.platforms.yuanbao_mediar   rs  r   r   r   r   r   r    r  r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   gateway.sessionr7   	getLoggerr   r4  
hermes_clir8   _HERMES_VERSIONr0  r3  rR   r2  r5  r1  r  r  r!  r  r  rU  r[  r-  r#  AUTH_FAILED_CODESAUTH_RETRYABLE_CODESr  r  REPLY_REF_TTL_Sr
  r  rt   r>  rT  r  ri  r  r  rP   r   rZ  r[  r{  r]  r}  r  r  r  r  r  rV  r\  r_  rt  ry  r  r  r  r
  r  r%  r+  r6  rG  r  r  r  r]  r  r  r  r  r  r  r  r  r{  ry  r  r  r  r   r^   r\   <module>r     s   " # " " " " "              				 				        2 2 2 2 2 2 2 2 2 2       # # # # # # # # G G G G G G G G G G G G G G G G G G 



         JJJ 4 3 3 3 3 3 3 3                : 9 9 9 9 9                                                                0 . - - - - -		8	$	$
9999999   OOO s-.. L  L 6 !      @??     '&& )))  !       Y  I 
 $)Wf$566  
-.. $&  /1 ,^
 ^
 ^
 ^
 ^
 ^
 ^
 ^
@p) p) p) p) p) p) p) p)f 5 4 4 4 4 4 4 4
6) 6) 6) 6) 6) 6) 6) 6)rA A A A A A A A6] ] ] ] ] ] ] ]|] ] ] ] ]( ] ] ]@    /   "	 	 	 	 	' 	 	 	Ld Ld Ld Ld Ld- Ld Ld Ld^    *   &    -   "*" *" *" *" *" *" *" *"Z    -   2* * * * *- * * *Zu u u u u0 u u un    "3   2^ ^ ^ ^ ^. ^ ^ ^B    -   $q q q q q. q q qh    !2   >    $5   62 2 2 2 2. 2 2 2jN N N N N. N N NbD9 D9 D9 D9 D9* D9 D9 D9N# # # # # # # #JP
 P
 P
 P
 P
 P
 P
 P
d~= ~= ~= ~= ~=s ~= ~= ~=B
 
 
 
 
& 
 
 
6
 
 
 
 
' 
 
 
4
 
 
 
 
% 
 
 
4
 
 
 
 
& 
 
 
.3 3 3 3 3% 3 3 3>U
 U
 U
 U
 U
 U
 U
 U
px+ x+ x+ x+ x+ x+ x+ x+v. . . . . . . .bf! f! f! f! f! f! f! f!R]) ]) ]) ]) ]) ]) ]) ])@`
 `
 `
 `
 `
( `
 `
 `
P' ' ' ' 59	N N N N N N Ns$   0
A; ;	BBD DD