
    ie                   2   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 ejq        d!          Zsd"Ztd#Zu G d$ d%          Zv G d& d'          Zwdd(lmxZxmyZz ex G d) d*                      Z{ G d+ d,e          Z| G d- d.          Z} G d/ d0e|          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<          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 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 dV          Z G dW dX          Z G dY dZe          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 df          Z G dg dh          Z G di dj          Z G dk dl          Z G dm dn          Z G do dpe+          Zd}dsZ	 d~dd|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_\-]+)\]z\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)rO   in_fencelines      >/home/ubuntu/.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
        FrT   |)rstriprV   striprW   endswith)rO   trimmed	last_lines      rZ   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^   rT   )lenrfindrecompilefinditerend)rO   rf   rh   _lenwindowlohimidpossentence_end_rebest_posmcuts                rZ   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 ```).rU   )lstriprW   rO   s    rZ   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 |).rT   r   r_   )rV   ra   rW   rb   )rO   
first_lines     rZ   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)
        rT   FrY   rP   rQ   rR   c                ~    |                                  }|                    d          o|                    d          S )Nr_   )ra   rW   rb   )rY   strippeds     rZ   _is_table_linez:MarkdownProcessor.split_into_atoms.<locals>._is_table_line9  s6    zz||H&&s++F0A0A#0F0FFr\   Nonec                     rTd                               } |                                 r                    |                                             d S d S )NrT   )joinra   appendclear)atomatomscurrent_liness    rZ   _flush_currentz:MarkdownProcessor.split_into_atoms.<locals>._flush_current=  s`     &yy//::<< 'LL&&&##%%%%%	& &r\   rU   rm   Tr^   rl   )rY   rP   rQ   rR   rQ   r   )rV   r   rW   ro   ra   )rO   linesrX   r   r   rY   r   r   s         @@rZ   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   rQ   r   c                 `    r*                      d                                         d S d S )Nrn   )r   r   )chunkscurrent_partss   rZ   _flush_partsz;MarkdownProcessor.chunk_markdown_text.<locals>._flush_parts  s9     :fkk-8899999: :r\   rB   rh   Nrm   r^   rn   c                    g | ]}||S  r   .0cs     rZ   
<listcomp>z9MarkdownProcessor.chunk_markdown_text.<locals>.<listcomp>  s    '''aQ''''r\   r   )
ro   r   setr   r   addr   	enumerater[   r   )clsrO   rf   rh   ru   r   indivisible_setcurrent_lenr   r   atom_lensep_lenprojected_lenresultidxchunk	remainingheadmergedprevcombinedr   r   s                        @@rZ   chunk_markdown_textz%MarkdownProcessor.chunk_markdown_text_  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'
        rU   rT   r   rl   r_   rn   )r`   r   rb   rW   re   rV   ra   )r   r   r   prev_trimmednext_trimmedr   s         rZ   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   rm   )ro   r[   r   r   )r   r   r   icurrentseps         rZ   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)
        rT      r   r^   z^```(?:markdown|md)?\s*$rU   rm   )rV   ro   ra   rq   match
IGNORECASEr   )rO   r   r   rd   inners        rZ   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
        r_   rT   z^\|[\s\-:]+(\|[\s\-:]+)+\|$c              3  j   K   | ].}|                                 r|                                 n|V  /d S Nra   )r   cells     rZ   	<genexpr>z<MarkdownProcessor.sanitize_markdown_table.<locals>.<genexpr>[  sO       * *  )-

>

$* * * * * *r\   z||rl   )	rV   ra   rW   rb   rq   r   r   r   replace)rO   r   result_linesrY   r   cells
normalizeds          rZ   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\   rZ   markdown_hint_system_promptz-MarkdownProcessor.markdown_hint_system_promptl  s    V	
 	
r\   )rO   rP   rQ   rR   r   )rO   rP   rf   rg   rh   ri   rQ   rj   )rO   rP   rQ   r   r   N)rO   rP   rf   rg   rh   ri   rQ   r   )r   rP   r   rP   rQ   rP   )r   r   rQ   r   rO   rP   rQ   rP   rQ   rP   )__name__
__module____qualname____doc__staticmethodr[   re   r   r   r   r   classmethodr   r   r   r   r   r   r   r\   rZ   rN   rN      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\   rN   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_keyrP   rQ   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     rZ   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        rZ   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    rZ   build_timestampzSignManager.build_timestamp  s<     )!*<*<*<!=!=>>>8999r\   entrydict[str, Any]rR   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     rZ   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    rZ   clear_lockszSignManager.clear_locks  s     	
r\   rg   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      rZ   r   z-SignManager.purge_expired.<locals>.<listcomp>  sE     
 
 
!QQUU;***Q.. ...r\   N)r  r   itemspopro   )r   expired_keysr  r   s      @rZ   purge_expiredzSignManager.purge_expired  s~     ikk
 
 
 
***,,
 
 
  	$ 	$AJNN1d####<   r\   rl   
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)./timeoutNrm      )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 )rl   )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) r`   
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_coderO   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                       rZ   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     tokenrl   r#  productsourcerK  r#  rI  rL  rM  r  )r  r   r  r  rg   r  r2  r3  r:  r   rG  )
r   r   r   r  r  cachedremainr"  rI  r  s
             rZ   	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=****%sNrI  r   rJ  rK  rl   r#  rL  rM  rN  )	r2  r<  r   r   r  rG  r  r  r:  )r   r   r   r  r  r"  rI  r  s           rZ   force_refreshzSignManager.force_refreshT  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   rP   rQ   r   )
r   rP   r   rP   r   rP   r   rP   rQ   rP   r   )r   r   rQ   rR   r   rQ   rg   rl   )
r   rP   r   rP   r  rP   r  rP   rQ   r   )r   r   r   r   r&  r;  r+  r=  r  r)  r   __annotations__r   r   r   r   r   r   r  r  r  rG  rQ  rT  r   r\   rZ   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"<   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]pushrl   rP   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]rM  msg_typereply_to_message_idreply_to_text
media_urlsmedia_types	link_urlschannel_prompt)r   r   r   r   rW  dc_fieldr^  r_  ra  rb  rc  rd  re  rf  rg  rh  ri  rj  rk  rl  rm  rn  rp  rM  rq  rr  rs  rt  ru  rv  rw  r   r\   rZ   r[  r[  s  s%          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''''  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.
    rl   rP   namectxr[  next_fnr   rQ   r   c                
   K   dS )zEProcess *ctx* and optionally call *next_fn* to continue the pipeline.Nr   selfr|  r}  s      rZ   handlezInboundMiddleware.handle  
        r\   c                >   K   |                      ||           d{V S )zFAllow middleware instances to be called directly (duck-typing compat).N)r  r  s      rZ   __call__zInboundMiddleware.__call__  s,      [[g.........r\   c                2    d| j         j         d| j        dS )N<z name=>)	__class__r   r{  r  s    rZ   __repr__zInboundMiddleware.__repr__  s"    @4>*@@$)@@@@r\   N)r|  r[  r}  r   rQ   r   r   )
r   r   r   r   r{  rW  r	   r  r  r  r   r\   rZ   rz  rz    s         
 
 DNNNNT T T ^T/ / / /A A A A A Ar\   rz  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.
    rQ   r   c                    g | _         d S r   _middlewaresr  s    rZ   __init__zInboundPipeline.__init__  s    "$r\   Nc                F    t          | t                    r	| j        | fS | |fS )zHNormalize (name, handler) or (InboundMiddleware,) into (name, callable).)r9  rz  r{  )
name_or_mwhandlers     rZ   
_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         rZ   usezInboundPipeline.use  s=     //*g66a  $4111r\   targetrP   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       rZ   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	    `       rZ   
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       rZ   r   z,InboundPipeline.use_after.<locals>.<genexpr>  r  r\   Nrm   r  r  s	    `       rZ   	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       rZ   r   z*InboundPipeline.remove.<locals>.<listcomp>  s+    UUU71a1PT99aAY999r\   r  )r  r{  s    `rZ   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      rZ   r   z4InboundPipeline.middleware_names.<locals>.<listcomp>  s    333gaA333r\   r  r  s    rZ   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   rQ   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 )Nrm   z'[InboundPipeline] middleware [%s] errorTexc_info)ro   r7  r2  error)r{  r  when_fnchainr|  indexr}  s      rZ   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    `@@@rZ   executezInboundPipeline.execute  sa      !	 	 	 	 	 	 	 	 	  giir\   r   r   NN)rQ   r  )r  rP   rQ   r  )r{  rP   rQ   r  )rQ   r^  r|  r[  rQ   r   )r   r   r   r   r  r   r  r  r  r  r  propertyr  r  r   r\   rZ   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^  rQ   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.
        rq  MsgTyperl   msg_content
MsgContentrO   rq  r  )r9  r:  r  rP   r  loadsr7  r   )r  r   itemrq  r  s        rZ   convert_json_msg_bodyz&DecodeMiddleware.convert_json_msg_body0  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.).
        Nrc  rl   From_Accountrd  GroupIdgroup_idrg  MsgBodycallback_command
to_account
To_Accountrf  	nick_namere  msg_seqr   MsgSeqrh  msg_keyMsgKeyri  CloudCustomDatabot_owner_id
botOwnerIdrecall_msg_seq_listlog_exttrace_id)r  rc  r  rf  rd  re  r  rh  rg  ri  r  r  r  )r  r  r  r9  r:  )r  rc  rd  msg_body_rawrg  s        rZ   parse_json_pushz DecodeMiddleware.parse_json_pushE  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Nrl   )r  r  r  r7  r9  r:  r  r*   )r  r\  r"  	conn_jsonra  s        rZ   _decode_singlezDecodeMiddleware._decode_singley  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 )Nrl   z;[%s] Push decoded but no valid message. raw hex(first64)=%s   z(empty)z#[%s] Frame decoded (via=%s): len=%drg  TIMTextElemrO   rT   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=%src  rd  rh  c                :    g | ]}|                     d d          S )rq  rl   r
  r   es     rZ   r   z+DecodeMiddleware.handle.<locals>.<listcomp>  s&    IIIqQUU:r""IIIr\   z[%s] Push payload: %s)r_  r  r\  r2  r3  r{  hexro   r  ra  rb  debug)r  r|  r}  	data_listmerged_pushrb  r"  ra  via
extra_body_seps              rZ   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^  rQ   r^  )r  r:  rQ   r  )r"  r  rQ   r  r  )
r   r   r   r   r{  r   r  r  r  r  r   r\   rZ   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[  rQ   r   c                  K   |j         }|                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dd          |_        |                    dg           |_        |                    dd          |_        |                    dd          |_         |             d {V  d S )	Nrc  rl   rd  re  rf  rg  rh  ri  )	ra  r  rc  rd  re  rf  rg  rh  ri  )r  r|  r}  ra  s       rZ   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\   rZ   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[  rQ   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)rh  r\  _dedupis_duplicater2  r  r{  r  s      rZ   r  zDedupMiddleware.handle  sl      : 	#+,99#*EE 	LLDckFVX[XbcccFgiir\   Nr  r  r   r\   rZ   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[  rQ   r   c                   K   |j         pi                     dd          }|| j        vr |             d {V  d S |                     ||           d S )Nr  rl   )ra  r  _RECALL_COMMANDS_handle_recall)r  r|  r}  cmds       rZ   r  zRecallGuardMiddleware.handle  sh      x~2""#5r::d+++'))OOOOOOOFC%%%%%r\   rd  rP   rc  c                \    |                      |rd| nd| |rdnd|pd |rdnd           S )Ngroup:direct:groupdmmain)rj  rk  user_id	thread_id)build_source)r\  rd  rc  s      rZ   _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  rh  rl   r  )rh  r  z2[%s] Recall callback with empty seq_list, skippingrd  rc  )r\  ra  r  r2  r  r{  ra   rP   _find_processing_session_interrupt_for_recall_msg_content_cache_patch_transcript)r  r|  r  r\  ra  seq_listry   seqrd  rc  	seq_entryrecalled_id
matched_skrecalled_contents                 rZ   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  ro  c                j    | j                                         D ]\  }}||k    r|| j        v r|c S d S r   )_processing_msg_idsr  _active_sessions)r\  r  skry   s       rZ   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)rO   message_typerM  internalz+[%s] Recall interrupt: msg_id=%s session=%s   rl   )r   r   TEXTr  _pending_messagesr  r  r   r2  r3  r{  _processing_msg_texts_schedule_content_redact)r   r\  r  r  rd  rc  whererecall_textsynth_eventactive_eventrecalled_texts              rZ   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 )NrQ   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_idr7  r*  r   r>  load_transcriptr  	_REDACTEDrewrite_transcriptr2  r3  r{  r<  r  )storesidr  
transcriptr   rF  r\  r   rc  rd  r,  r  s         rZ   _redactz?RecallGuardMiddleware._schedule_content_redact.<locals>._redactR  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,  rd  rc  r<  tasks   ``````  rZ   r'  z.RecallGuardMiddleware._schedule_content_redactO  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 )Nr/  z*[%s] Recall: failed to resolve session: %srr  encodingz*[%s] Recall: failed to load transcript: %s
message_idr0  r1  r2  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   )r0  r2  r   z1[%s] Recall: system note for msg_id=%s (branch B))r3  r4  r  r5  r7  r2  r<  r{  get_transcript_pathexistsopenra   r   r  r  JSONDecodeErrorr  r7  r8  r3  append_to_transcriptr   r   r   utc	isoformat)r   r\  r  rd  rc  r  r9  r:  rF  r;  pathfrY   r  r   s                  rZ   r  z'RecallGuardMiddleware._patch_transcriptu  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  )rd  rP   rc  rP   )r|  r[  r  rP   rQ   r   )r  rP   rQ   ro  )
r  rP   r  rP   rd  rP   rc  rP   rQ   r   )
r  rP   r,  rP   rd  rP   rc  rP   rQ   r   r   )
r  rP   rd  rP   rc  rP   r  ro  rQ   r   )r   r   r   r   r{  	frozensetr  r7  r  r   r  r  r  r   r  r'  r  r   r\   rZ   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-selfrc  rP   r#  ro  rQ   rR   c                    | r|sdS | |k    S )z2Detect whether the message is from the bot itself.Fr   )rc  r#  s     rZ   _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)rU  rc  r\  _bot_idr2  r  r{  r  s      rZ   r  zSkipSelfMiddleware.handle  sf      ""3#3S[5HII 	LLBCKDTVYVfgggFgiir\   N)rc  rP   r#  ro  rQ   rR   r  )r   r   r   r   r{  r   rU  r  r   r\   rZ   rS  rS    sQ        ((D& & & \&     r\   rS  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[  rQ   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
  )rd  rj  rk  re  rl  rc  rf  r  s      rZ   r  zChatRoutingMiddleware.handle  s      > 	D33>33CK#CMN<cnCMM6C$466CK CM/C33CCMgiir\   Nr  r  r   r\   rZ   rY  rY    s3        CCD	 	 	 	 	 	r\   rY  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_policyrP   dm_allow_fromr   group_policygroup_allow_fromrQ   r   c                >    || _         || _        || _        || _        d S r   )
_dm_policy_dm_allow_from_group_policy_group_allow_from)r  r]  r^  r_  r`  s        rZ   r  zAccessPolicy.__init__  s)     $+)!1r\   	sender_idrR   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)rb  ra   rc  )r  rf  s     rZ   is_dm_allowedzAccessPolicy.is_dm_allowed  s>    ?j((5?k))??$$(;;;tr\   rd  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).rh  Fri  T)rd  ra   re  r  rd  s     rZ   is_group_allowedzAccessPolicy.is_group_allowed  sB    ++5,,##%%)???tr\   c                    | j         S r   )rb  r  s    rZ   r]  zAccessPolicy.dm_policy  s
    r\   c                    | j         S r   )rd  r  s    rZ   r_  zAccessPolicy.group_policy  s    !!r\   N)
r]  rP   r^  r   r_  rP   r`  r   rQ   r   )rf  rP   rQ   rR   )rd  rP   rQ   rR   r   )
r   r   r   r   r  rj  rm  r  r]  r_  r   r\   rZ   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[  rQ   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_policyrk  rj  rc  r2  r  r{  r]  rm  rd  r_  )r  r|  r}  r\  policys        rZ   r  zAccessGuardMiddleware.handle  s      +&5=D  ''(899 =L#"2F4D    ]g%%**3>:: >L#.&2E   giir\   Nr  r  r   r\   rZ   rq  rq    s3        88D     r\   rq  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[  rQ   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_CHANNELrl   r  r
  Tr   )get_hermes_home)atomic_yaml_writezconfig.yamlr  rD  z=[%s] Auto-sethome: designated %s (%s) as Yuanbao home channelz[%s] Auto-sethome failed: %s)r\  _auto_sethome_doneosgetenvrW   rk  hermes_constantsry  utilsrz  yamlrI  rJ  	safe_loadrj  rP   environr2  r3  r{  rl  r7  r<  )r  r|  r}  r\  	_cur_home_should_setry  rz  r  _homeconfig_pathuser_configrP  r  s                 rZ   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\   rZ   rv  rv    s9          D           r\   rv  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ddZdS )ExtractContentMiddlewarez.Extract raw text and media refs from msg_body.zextract-content  customr:  rQ   rP   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.titlerl   linkz[share_card: z | ])card_content
wechat_desNz...(truncated)z	Preview: z[visit link for full content]rT   )r  r  _CARD_CONTENT_MAX_LENGTHr9  rP   ro   r   r   )	r  r  r  headerr   max_lenrY  valpreviews	            rZ   _format_shared_linkz,ExtractContentMiddleware._format_shared_linkO  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\   ro  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.r2  Nr  z[link: z | visit link for full content])r  r  r  r9  r:  rK  	TypeErrorrP   )r  r2  parsedr  s       rZ   _format_link_understandingz3ExtractContentMiddleware._format_link_understandinga  s     **Y'' 	4	Z((F)3FD)A)AK6::f%%%tDD$i0 	 	 	DDD	 	:dC00 	4>>>>>s   A A A76A7rg  r^  c                   g }|D ]S}|                     dd          }|                     di           }|dk    r.|                     dd          }|r|                    |           c|dk    r|                    d           |dk    r\|                     d	|                     d
|                     dd                              }|                    |rd| dnd           |dk    r|                    d           |dk    r|                    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           O# 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           8|r|                    d| d           U|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
        rq  rl   r  r  rO   TIMImageElem[image]TIMFileElem	file_namefileNamefilename[file: r  [file]TIMSoundElem[voice]TIMVideoFileElem[video]TIMCustomElemr"  z[unsupported message type]	elem_type  z	[mention]    TIMFaceElemr{  z[emoji: z[emoji][ )r  r   r  r  r9  r:  r  r  rK  r  ra   AttributeErrorr   )r   rg  partselemr  r2  rO   r  data_valr  ctyperaw_data	face_name	face_datas                 rZ   _extract_textz&ExtractContentMiddleware._extract_textp  s     7	/ 7	/D!XXj"55I HH]B77GM)){{62.. 'LL&&&n,,Y''''m++";;{GKK
GKKXbdfLgLg4h4hiihL2x2222HMMMMn,,Y''''000Y''''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+    >I CI+JJ>=K<<LLrO   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  rm   Nra   rW   r   s    rZ   _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"}]
        rq  rl   r  r  image_info_arrayNrm   r   r?  image)kindr?  r  r  r  r  filer{  )r9  r:  r  r^  ro   rP   ra   r   )rg  refsr  rq  r2  r  
image_info	image_urlfile_urlr  refs              rZ   _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.rq  r  r  r"  rl   r  r  r  r  r2  N)	r9  r:  r  r  r  rK  r  rP   r   )	rg  urlsr  data_strr  r  r  r2  r  s	            rZ   _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  rg  rm  r  rn  r  rv  r  s      rZ   r  zExtractContentMiddleware.handle  st      2243E3Ecl3S3STT99#,GG//==giir\   N)r  r:  rQ   rP   )r  r:  rQ   ro  )rg  r^  rQ   rP   r   )rg  r^  rQ   r  )rg  r^  rQ   r^  r  )r   r   r   r   r{  r  r   r  r  r   r  r  r  r  r  r   r\   rZ   r  r  H  s        88D#      \ " ? ? ? \? F0 F0 F0 [F0P    \ * * * \*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  rQ  SKIPPABLE_PLACEHOLDERSr   rO   rP   media_countrg   rQ   rR   c                L    |dk    rdS |                                 }|| j        v S )zEDetect whether the message is a pure placeholder (should be skipped).r   F)ra   r  )r   rO   r  r   s       rZ   is_skippable_placeholderz4PlaceholderFilterMiddleware.is_skippable_placeholder!  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  rm  ro   rn  r2  r  r\  r{  r  s      rZ   r  z"PlaceholderFilterMiddleware.handle)  sj      ((s3>7J7JKK 	LL@#+BRTWT`aaaFgiir\   N)r   )rO   rP   r  rg   rQ   rR   r  )r   r   r   r   r{  rQ  r  rW  r   r  r  r   r\   rZ   r  r    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/backgroundrQ  	ALLOWLISTrO   rP   rQ   c                r    |                                  } |                     d          rd| dd         z   } | S )z?Normalize full-width slash to ASCII slash and strip whitespace.r  r  rm   Nr  r   s    rZ   r  z-OwnerCommandMiddleware._rewrite_slash_command@  s;     zz||??8$$ 	"abb>Dr\   ra  r:  rg  r^  rk  rc  )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 )rq  r  r
  r  s     rZ   r   z@OwnerCommandMiddleware._detect_owner_command.<locals>.<listcomp>\  s9     
 
 
uuZ  M11 111r\   rm   r   r  rO   rl   r  )maxsplitr  )
r  ro   r  r  rW   rV   lowerrP   ra   rR   )r   ra  rg  rk  rc  
text_elemsrO   cmd_liner  owner_idis_owners              rZ   _detect_owner_commandz,OwnerCommandMiddleware._detect_owner_commandH  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)ra  rg  rk  rc  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  ra  rg  rk  rc  r2  r3  r{  rj  _track_taskr   r=  sendrp  rm  )r  r|  r}  r\  matched_cmdr  r  s          rZ   r  zOwnerCommandMiddleware.handles  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   )
ra  r:  rg  r^  rk  rP   rc  rP   rQ   r  r  )r   r   r   r   r{  rQ  r  rW  r   r  r   r  r  r   r\   rZ   r  r  0  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[  rQ   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  )rj  rk  rl  r  	user_namer  )r\  r  rj  rk  rl  rc  rf  rM  r  r|  r}  r\  s       rZ   r  zBuildSourceMiddleware.handle  s      +))Kmm$,)=S-= # 8 8ffd * 
 

 giir\   Nr  r  r   r\   rZ   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-guardrg  r^  r#  ro  rQ   rR   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.
        Frq  r  r  r"  rl   r  r  r  T)r  r  r  rK  r  )rg  r#  r  r  r  s        rZ   
_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?rP   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``).rl   rq  r  r  r"  r  r  r  rO   )r  r  r  rK  r  rP   ra   )rg  r#  r  r  r  mention_texts         rZ   _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.)rP   r  r  )rg  r#  bidbot_mentions       rZ   _build_group_channel_promptz2GroupAtGuardMiddleware._build_group_channel_prompt  s[     &%I&&,FFxQWXXe\e&(+& &LW& & &	
r\   Nrh  sender_displayrO   rh  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.
        r/  Nr  r  r_   ]
r1  r   T)r0  r2  r   observedrF  z([%s] Failed to observe group message: %s)r3  r4  r  r   r   r   rM  rN  rL  r5  r7  r2  r<  r{  )r\  rM  r  rO   rh  r9  session_entryr  
attributedr   rF  s              rZ   _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\  rk  rp  r  rg  rW  r  rM  rf  rc  rm  rh  r2  r3  r{  rj  r  s       rZ   r  zGroupAtGuardMiddleware.handle  s      +=G##C,=#dooVYVbdkdsFtFt#''S%8%LC<Lclz)T (    KKHck3+;   Fgiir\   )rg  r^  r#  ro  rQ   rR   )rg  r^  r#  ro  rQ   rP   )r  rP   rO   rP   rh  ro  rQ   r   r  )r   r   r   r   r{  r   r  r  r
  r  r  r   r\   rZ   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[  rQ   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  r_   r  )r  )rk  rp  r\  r  r
  rg  rW  rw  rc  rf  rm  rM  dataclassesr   )r  r|  r}  r\  user_id_labelnickname_labels         rZ   r  z!GroupAttributionMiddleware.handle&  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\   rZ   r  r    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-typerO   rP   rg  r^  rQ   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  rq  rl   r  r  r  r  )	rW   r   COMMANDr  PHOTOVOICEVIDEODOCUMENTr$  )rO   rg  r  etypes       rZ   	_classifyz'ClassifyMessageTypeMiddleware._classify;  s     ??3 	'&& 		, 		,DHHZ,,E&&"((((&&"((((***"((((%%"++++ &r\   r|  r[  r   c                t   K   |                      |j        |j                  |_         |             d {V  d S r   )r#  rm  rg  rq  r  s      rZ   r  z$ClassifyMessageTypeMiddleware.handleL  s:      ~~clCLAAgiir\   N)rO   rP   rg  r^  rQ   r   r  )r   r   r   r   r{  r   r#  r  r   r\   rZ   r  r  6  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-contextri  rP   rQ   #Tuple[Optional[str], Optional[str]]c                   | sdS 	 t          j        |           }n# t           j        t          f$ r Y dS w xY wt	          |t
                    r|                    d          nd}t	          |t
                    sdS t          |                    d          pd          }t          |                    d          pd          	                                }|dk    r|sd	}|sdS t          |                    d
          pd          	                                pd}t          |                    d          p|                    d          pd          	                                }|r| d| n|}||fS )zExtract quote context, mapping to MessageEvent.reply_to_*.

        Returns:
          (reply_to_message_id, reply_to_text)
        r  quoteNtyper   descrl   rB   r  idrf  rf  r   )
r  r  rK  r  r9  r:  r  rg   rP   ra   )ri  r  r)  
quote_typer+  quote_idsender
quote_texts           rZ   _extract_quote_contextz-QuoteContextMiddleware._extract_quote_contextV  s    ! 	:	Z 122FF$i0 	 	 	::	 (2&$'?'?I

7###T%&& 	: 6**/a00
599V$$*++1133??4?D 	:uyy,"--3355=UYY011QUYY{5K5KQrRRXXZZ,2<(($(((
##s    55r|  r[  r   c                x   K   |                      |j                  \  |_        |_         |             d {V  d S r   )r1  ri  rr  rs  r  s      rZ   r  zQuoteContextMiddleware.handleu  sC      595P5PQTQf5g5g2!2giir\   N)ri  rP   rQ   r'  r  )r   r   r   r   r{  r   r1  r  r   r\   rZ   r&  r&  Q  sQ        ==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 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-resolver?  rP   rQ   c                    t           j                            |           j        }t          j                            |          d                                         }|dv r|S dS )z$Guess image extension from URL path.rm   >   .heic.tiff.bmp.gif.jpg.png.jpeg.webpr:  )urllibparseurlparserO  r|  splitextr  )r?  rO  exts      rZ   _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_idNrK  rl   rM  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   
resourceId)paramsr  i  r   rG  rH  rI  r!  )Nr   z"resource/v1/download failed: code=r%  r$  r"  r?  realUrlz(resource/v1/download missing url/realUrlz)resource/v1/download did not return a URL)ra   r6  _get_cached_tokenrP   r  rW  _app_key_api_domainr'  r(  r*  r5  r   rT  _app_secretraise_for_statusr  r9  r:  )r\  rD  
token_datarK  rM  r#  api_urlr  r@  rA  resprB  r!  r"  real_urls                  rZ   _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=...
        rK  
resourceidr   rl   N)r>  r?  r@  r7  parse_qsqueryr  rP   ra   r4  rW  )r\  r?  r  r[  resource_idsrD  s         rZ   _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&Nrl   )r  log_tag	fetch_urlr  r  ro  r^  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=%sr  )rB  z,[%s] inbound image cache rejected: %s err=%simage/
image/jpegr  z)[%s] inbound file cache failed: %s err=%sapplication/octet-stream)media_download_urlMEDIA_MAX_SIZE_MBr7  r2  r<  r{  rC  r   r8  r   rW   r>  r?  r@  r|  rO  basenamer   )r   r\  r_  r  r  r^  
file_bytescontent_typerF  rB  
local_pathmimer  s                rZ   _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)rW  )r   r\  rD  s      rZ   _resolve_by_resource_idz.MediaResolveMiddleware._resolve_by_resource_id	  s0       ,,WkBBBBBBBBBr\   rn  r  Tuple[List[str], List[str]]c                  K   g }g }|D ]t}t          |                    d          pd                                                                          }t          |                    d          pd                                          }|d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 }
|
E|
\  }}|
                    |           |
                    |           v||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  rl   r?  >   r  r  Nz8[%s] inbound media resolve failed: kind=%s url=%s err=%sr{  zplaceholder_url=P   r_  r  r  r^  )rP   r  ra   r  r]  r7  r2  r<  r{  rn  r   )r   r\  rn  rt  ru  r  r  r?  r_  rF  rO  rl  rm  s                rZ   _resolve_media_urlsz*MediaResolveMiddleware._resolve_media_urls	  s      !#
!# 	% 	%Cswwv,"--3355;;==Dcggenn*++1133C,,,C,"%";";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   B//
C&9#C!!C&c           	     n  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	          \  }}}|                                }|d
vr^||	v rc|	                    |           |                    |||                                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)``.r/  Nz.[%s] Observed-media hydration setup failed: %sr   r2  z|ybres:rm   rB   :)r  r  z9[%s] observed-media resolve failed: rid=%s kind=%s err=%szrid=rt  )r3  r4  r6  r5  r7  r2  r<  r{  maxro    OBSERVED_MEDIA_BACKFILL_LOOKBACKr   r  r9  rP   _YB_RES_REF_RErs   r	  	partitionra   r   r   ,OBSERVED_MEDIA_BACKFILL_MAX_RESOLVE_PER_TURNrp  rn  )r   r\  rM  r9  r  historyrF  startorderseenr$  r2  r}   r   ridr  r  r  media_pathsmimes	fresh_urlrO  rO  rm  s                           rZ   _collect_observed_mediaz.MediaResolveMiddleware._collect_observed_media:	  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||000$;;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+#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\  ru  rn  rt  ru  r  r  rm  ro   r2  r  r{  r  s       rZ   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?  rP   rQ   rP   )rD  rP   rQ   rP   )
r_  rP   r  rP   r  ro  r^  rP   rQ   r`  )rn  r  rQ   rq  )rQ   rq  r  )r   r   r   r   r{  r   rC  rW  r]  r   rn  rp  ru  r  r  r   r\   rZ   r4  r4  z  s2       @@D   \ 8H 8H 8H \8Ht    \0  $(,  ,  ,  ,  ,  [, \ C C C [C '' '' '' [''R D" D" D" [D"L     r\   r4  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[  rQ   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  rQ   r   c                 8  K   t          j                  } t          j                  }g }g }	 t                              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t*          j                            |          }d
| d| d}n|d |	                                         |z   ||	                                d          z   }t5          |j        j        j        pd j        | |j        j        j         
  
        }r'j        r j        j!        <   j        pdj"        <   j        r]j        rVj#        }j        |j        <   tI          |          dk    r-t          |          d tI          |          dz
           D ]}||= %                    |           d {V  d S )Nz;[%s] observed-image hydration raised, continuing anyway: %sr  rm   rw  r  rd  z[image: r  r  r  u    → )
rO   r!  rM  rF  raw_messagert  ru  rr  rs  rw  rl   r  )&r^  rt  ru  r4  r  rM  r7  r2  r<  r{  r   zipr   r   rm  rW   rz  searchr	  r{  ra   r|  rO  ri  r~  rt   r   rq  rh  ra  rr  rs  rw  r  r&  r  ro   handle_message)rt  ru  extra_img_urlsextra_img_mimesrF  r   ur}   _patched_event_textanchor_matchr   r  r  r  replacementlabeleventcacher  _skr\  r|  s                      rZ   _dispatch_inbound_eventz:DispatchMiddleware.handle.<locals>._dispatch_inbound_event	  s     cn--Js//K )+N)+O8N8f8fSZ9 9 3 3 3 3 3 3/    QL#       
  #j//@@ # #DAqG|| %%a(((&&q)))KKNNNN #&,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:-H%'$'$;!/"1  E  Hsz H36:+C058\5GR-c2z %cl %2$'Lcj!u::##!%[[):#e**s*:):; % %!!HH((///////////s   )A 
B%!BBr	  z-[%s] Group message enqueued (qsize=%d) for %srl   rK   zyuanbao-group-consumer-r#  r  zyuanbao-inbound-r  r   )r\  r7   rM  configextrar  rk  _group_queues
setdefaultr   Queue
put_nowaitr2  r3  r{  qsizer=  _consume_group_queue_inbound_tasksr   r?  r@  rh  )
r  r|  r}  r  is_newqueueconsumerrA  r  r\  s
    `      @@rZ   r  zDispatchMiddleware.handle	  s     +J$+N$8$<$<=VX\$]$]%,^%9%=%=>XZ_%`%`
 
 
G	0 G	0 G	0 G	0 G	0 G	0 G	0 G	0R =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  rP   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)rl   rK   g?z[%s] Group queue consumer error)r  r  r   wait_forTimeoutErrorr2  r  r{  r  r  r>  r7  	exceptionr  )r\  r  _IDLE_TIMEOUTr  dispatch_fns        rZ   r  z'DispatchMiddleware._consume_group_queue	  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  rP   rQ   r   )r   r   r   r   r{  r  r   r  r   r\   rZ   r  r  	  sX        88Di i i iV 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_MIDDLEWARESrQ   r  c                p    t                      }| j        D ]}|                     |                        |S )z6Build the default inbound message processing pipeline.)r  r  r  )r   pipelinemw_clss      rZ   buildzInboundPipelineBuilder.build3
  sA     #$$. 	# 	#FLL""""r\   N)rQ   r  )r   r   r   r   r  r  r  r  rS  rY  rq  rv  r  r  r  r  r  r  r  r&  r4  r  r  rW  r   r  r   r\   rZ   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  rQ   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     rZ   r  zConnectionManager.__init__F
  se    *.7;268:7;-.%() #(02?Ar\   c                    | j         S r   )r  r  s    rZ   wszConnectionManager.wsW
  s	    xr\   ro  c                    | j         S r   )r  r  s    rZ   
connect_idzConnectionManager.connect_id[
  s    r\   rg   c                    | j         S r   )r  r  s    rZ   reconnect_attemptsz$ConnectionManager.reconnect_attempts_
  s    ''r\   rR   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 )NFrJ  T)r  r3  callablerR   r7  )r  	open_attrs     rZ   is_connectedzConnectionManager.is_connectedc
  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] %sNrJ  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_errorr2  r<  r{  rO  rQ  r  r  r3  r  r  r7  _acquire_platform_lockr3  rP  r   rQ  
_route_envr  rP   rW  _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  rS  authedrF  s          rZ   rJ  zConnectionManager.opens
  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  r6  r  valuesdoneset_exceptionr   r   r  r  )r  disc_excfuts      rZ   closezConnectionManager.close
  s       	( '')))*********)   #'D ? 	#O""$$$o%%%%%%%%)   "DO   =>>%,,.. 	, 	,C88:: ,!!(+++  """ 	!!!           s!   2 AA/A= =BBrS  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.
        NFrK  rl   r#  rM  botr  ybBot)	biz_iduidrM  rK  rh  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  rW  r  rP   uuiduuid4r-   r.  r/  r1  r  r2  r  r{  r   r  r  AUTH_TIMEOUT_SECONDSr  r  recvr9  r  	bytearrayr)   r7  r!   _extract_connect_idr  r3  r  )r  rS  r\  rK  r  rM  r  rh  
auth_bytesr  deadliner   rawr$  r   r  r  r  rF  s                      rZ   r  zConnectionManager._authenticate
  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\   Nrm   r   rB   z*[%s] AuthBindRsp error: code=%d message=%rr   z$[%s] Failed to extract connectId: %s)r  r"   r%   r$   r#   r2  r  r  r{  r7  r<  )r  r  r"  fdictr!  messager  rF  s           rZ   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   rm   z[%s] PONG timeout (%d/%d)z7[%s] Heartbeat threshold exceeded, triggering reconnectz[%s] Heartbeat send failed: %s)r  _runningr   r>  HEARTBEAT_INTERVAL_SECONDSr  rP   r  r  r.   r  create_futurer  r  r  r2  r  r{  r  r  r  r  r<  HEARTBEAT_TIMEOUT_THRESHOLDschedule_reconnectr7  r  )r  r\  rh  
ping_byteslooppong_futurerF  s          rZ   r  z!ConnectionManager._heartbeat_loopR  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reasonrl   z7[%s] Close code %d is non-recoverable, NOT reconnectingz[%s] receive_loop exited: %s)r  r  r9  r  r  _handle_framer   r  r  
exceptionsConnectionClosedr3  r2  r<  r{  NO_RECONNECT_CLOSE_CODESr  _mark_disconnectedr  r7  )r  r\  r  	close_exc
close_coderF  s         rZ   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  rl   rh  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)   r7  r2  r  r{  r  r!   r  r  
set_resultr  r  r3  ro   r  r/   r  r*   r  _push_to_inbound)r  r  r\  r$  rF  r   r  r  rh  r#  r"  r  r   	ack_bytesack_excdecodeds                   rZ   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  rP   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  rc  rl   r  rd  r  r  rw  
__unknown_)	r  r  r  r9  r:  r  r7  r*   r,  )r  r  r  rc  rd  ra  s         rZ   _extract_sender_keyz%ConnectionManager._extract_sender_key  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)r1  r  r  r  r  r   r2  r  r  r{  ro   r   r  
call_laterr.  _flush_inbound_buffer)r  r  keyexisting_timerr  timers         rZ   r)  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\   r5  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\  r_  zyuanbao-pipeline-r  )r  r  r  r  r2  r3  r{  ro   r[  r  r   r=  _inbound_pipeliner  )r  r5  r  r\  r|  s        rZ   r4  z'ConnectionManager._flush_inbound_buffer2  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  r6  r   r  r  r  r  r  shieldr  r  r7  )r  r:  r;  r  r  futurer   s          rZ   send_biz_requestz"ConnectionManager.send_biz_requestH  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    rZ   r  z$ConnectionManager.schedule_reconnecth  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  r2  r  r  r{  _do_reconnectr  s    rZ   rB  z)ConnectionManager._reconnect_with_backoffm  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.rm   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  minr2  r3  r{  r   r>  r  r   rT  rO  rQ  rP  r  r  rP   rW  r  r  r  r  r  r  r  r<  r  r  r  r  r  r=  r  r  r  r  r  r7  r  r  )r  r\  rA  waitrS  r  rF  s          rZ   rD  zConnectionManager._do_reconnectx  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  r7  )r  r  s     rZ   r  zConnectionManager._cleanup_ws  sm      X>hhjj             >s   0 
>>Nr\  r  rQ   r   )rQ   ro  rU  rQ   rR   r   )rS  r:  rQ   rR   )r  r:  rQ   ro  )r  r  rQ   r   )r  r  rQ   rP   )r  r  rQ   r   )r5  rP   rQ   r   )r:  r  r;  rP   r  r-  rQ   r:  )r   r   r   r   r  r  r  r  r  r  rJ  r  r  r  r  r  r  r.  rW  r1  r)  r4  DEFAULT_SEND_TIMEOUTr@  r  rB  rD  r  r   r\   rZ   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
   rQ   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\  rO  s      rZ   acquire_filezMediaSendHandler.acquire_file  r  r\   upload_resultr:  r^  c                    dS )z<Build platform-specific MsgBody list from COS upload result.Nr   r  rT  rO  s      rZ   build_msg_bodyzMediaSendHandler.build_msg_body  s      r\   rR   c                    dS )z:Override to return False for non-COS media (e.g. sticker).Tr   r  s    rZ   needs_cos_uploadz!MediaSendHandler.needs_cos_upload  s    tr\   Nrj  rP   reply_toro  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.NFr=  Tsuccessr  r  r_  r  rK  rl   r#  )r   r  rK  r  r#  r  
bucketNameregion)rj  r  rk  credentialsbucketrb  c                "    i | ]\  }}|d v	||S )	file_uuidr  rk  r   )r   r  r  s      rZ   
<dictcomp>z+MediaSendHandler.handle.<locals>.<dictcomp>*  s4       !Q III qIIIr\   rf  r  rO   r  rd  rd  z[%s] %s.handle() failed: %sr  ) _connection	_outboundr/  r  r   cancel_slow_notifierrS  rY  MessageSendervalidate_mediarh  r    rN  r  rW  r   rO  rP  r  r   r  rW  r   dispatch_msg_bodyr8  rP   r7  r*  r   r2  r  r{  )r  r\  rj  rZ  r[  rO  connr/  rj  r  rk  validation_errrg  rS  rK  r#  rc  rT  
fwd_kwargsrg  gcverF  handler_names                           rZ   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  rO  r
   rQ   rP  )rT  r:  rO  r
   rQ   r^  rK  r  )r\  r  rj  rP   rZ  ro  r[  ro  rO  r
   rQ   r\  )	r   r   r   r   r	   rS  rW  rY  r  r   r\   rZ   rN  rN    s             ^ K K K ^K    #'!%a= a= a= a= a= a= a=r\   rN  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 %srb  rf  ?r   re  	image.jpg)
r2  r3  r{  rg  rh  rV   r   r|  rO  ri  )r  r\  rO  r  rj  rk  	path_partr  s           rZ   rS  zImageUrlHandler.acquire_fileQ  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 
Nr?  rg  r  sizewidthr   heightrk  )r?  r  r  r~  r  r  	mime_typer   r  rV  s      rZ   rW  zImageUrlHandler.build_msg_body]  g    #e$$J'v&##GQ// $$Xq11^,
 
 
 	
r\   Nr   r   r   r   rS  rW  r   r\   rZ   rw  rw  N  s8        NN
2 
2 
2	
 	
 	
 	
 	
r\   rw  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rbrz  re  )r|  rO  isfiler8  r2  r3  r{  rJ  readri  r   )r  r\  rO  r  rP  rj  r  rk  s           rZ   rS  zImageFileHandler.acquire_filel  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  rV  s      rZ   rW  zImageFileHandler.build_msg_bodyw  r  r\   Nr  r   r\   rZ   r  r  i  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 %srb  r  ry  r   r  rf  )r2  r3  r{  rg  rh  r  rV   r|  rO  ri  r   )r  r\  rO  r  rj  rk  r  r{  s           rZ   rS  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 Nr?  r  rg  r~  )r?  r  r  r~  r   rV  s      rZ   rW  zFileUrlHandler.build_msg_body  9    "e$J'$v&	
 
 
 	
r\   Nr  r   r\   rZ   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  documentrf  )r|  rO  r  r8  r2  r3  r{  rJ  r  r  ri  r   )r  r\  rO  r  rP  rj  r  rk  s           rZ   rS  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  rV  s      rZ   rW  zDocumentHandler.build_msg_body  r  r\   Nr  r   r\   rZ   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).rQ   rR   c                    dS )NFr   r  s    rZ   rY  zStickerHandler.needs_cos_upload  s    ur\   c                
   K   dS )N)r\   stickerrf  r   rR  s      rZ   rS  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  r8  )
r  rT  rO  r  r  r  r  r  r  r  s
             rZ   rW  zStickerHandler.build_msg_body  s    	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 zz.11ZZ--
#)),77G !G|!G!GHHH))'222#&&*====((**G))'222r\   NrK  )r   r   r   r   rY  rS  rW  r   r\   rZ   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  rQ   r   c                    || _         d S r   )r  r  s     rZ   r  zGroupQueryService.__init__  s    r\   rd  rP   r`  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   rh  r;  statusz'[%s] query_group_info failed: status=%dr"  r\   rD  rd  z'[%s] query_group_info timeout: group=%sz [%s] query_group_info failed: %s)r  rj  r  r4   gateway.platforms.yuanbao_protor)   r@  r  r2  r<  r{  r9  r  r+   r   r  r7  )r  rd  r\  encoded_decoder,  r;  rC  r   r  biz_datarF  s               rZ   query_group_info_rawz&GroupQueryService.query_group_info_raw  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  offsetrg   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   rh  r  r  z,[%s] get_group_member_list failed: status=%dr"  r\   rD  T)membersnext_offsetis_completer  z,[%s] get_group_member_list timeout: group=%sz%[%s] get_group_member_list failed: %s)r  rj  r  r5   r  r)   r@  r  r2  r<  r{  r9  r  r,   r  _member_cacher   r  r7  )r  rd  r  r  r\  r  r  r,  r;  rC  r   r  r  r   rF  s                  rZ   get_group_member_list_rawz+GroupQueryService.get_group_member_list_raw  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>rj  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)rW   ro   r  )r  rj  rd  r   s       rZ   query_group_infoz"GroupQueryService.query_group_info,  sz       !!(++ 	NLMMS]]^^,
00<<<<<<<<>9::r\   list_allNactionr{  ro  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 )nicknamerl   	name_cardr  r  r  )r   r}   r[  s     rZ   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>U  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  rl   r  r   r}   s     rZ   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>\  sB    \\\QUquuZ7L7L7RPR6Y6Y6[6[-[-[q-[-[-[r\   rl   
   c                    g | ]B}|                     d           p*|                     d          p|                     dd          CS )r  r  r  rl   r
  r  s     rZ   r   z;GroupQueryService.query_session_members.<locals>.<listcomp>a  sJ    fffYZQUU;''T155+<+<TiQS@T@Tfffr\   zMention with @name: z, rK   )r  totalmentionHint)rW   ro   r  r  r  r   )
r  rj  r  r{  rd  r   r  mention_hintnamesr[  s
            @rZ   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\   rJ  rd  rP   rQ   r`  r   r  rd  rP   r  rg   r  rg   rQ   r`  )rj  rP   rQ   r:  )r  N)rj  rP   r  rP   r{  ro  rQ   r:  )	r   r   r   r   r  r  r  r  r  r   r\   rZ   r  r    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  rQ   r   c                0    || _         i | _        i | _        d S r   )r  _reply_heartbeat_tasks_reply_hb_last_activer  s     rZ   r  zHeartbeatManager.__init__t  s    ?A#79"""r\   rj  rP   heartbeat_valrg   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  )rc  rd  	heartbeatr  )rc  r  r  RUNNINGFINISHz%[%s] Reply heartbeat %s sent: chat=%sz#[%s] send_heartbeat_once failed: %s)r  rj  r  rW  rW   ro   r3   removeprefixr2   r  r&   r2  r  r{  r7  )
r  rj  r  r\  rp  rd  r  r  status_namerF  s
             rZ   send_heartbeat_oncez$HeartbeatManager.send_heartbeat_oncey  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  rj  r  rW  r  r  r  r  r  r   r=  _worker)r  rj  r\  rp  existingrA  s         rZ   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  rj  r  r  r7  r'   r  r  )r  rj  last_activerp  	cancelleds        rZ   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_finishrR   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'   r7  )r  rj  r  rA  s       rZ   stopzHeartbeatManager.stop  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  rA  s     rZ   r  zHeartbeatManager.close  sz      4;;==>> 	 	D99;; #))+++"((*****r\   NrJ  )rj  rP   r  rg   rQ   r   rj  rP   rQ   r   )Trj  rP   r  rR   rQ   r   r   )
r   r   r   r   r  r  r~  r  r  r  r   r\   rZ   r  r  k  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  r/  'MessageSender'rQ   r   c                0    || _         || _        i | _        d S r   )r  _sender_tasks)r  r\  r/  s      rZ   r  zSlowResponseNotifier.__init__  s    /1r\   rj  rP   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  rj  rA  s      rZ   r~  zSlowResponseNotifier.start  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_Sr2  r3  r  r{  rg   r  send_text_chunkSLOW_RESPONSE_MESSAGEr  r7  r  )r  rj  rF  s      rZ   r  zSlowResponseNotifier._notifier  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      rZ   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     rZ   r  zSlowResponseNotifier.close  sc      ++--.. 	 	D99;; r\   N)r\  r  r/  r  rQ   r   r  r   )	r   r   r   r   r  r~  r  r  r  r   r\   rZ   r  r    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 )brm  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)
    >   r8  r9  r:  r;  r<  r=  zClassVar[frozenset]
IMAGE_EXTSr  ClassVar[int]CHAT_DICT_MAX_SIZEr\  r  rQ   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_finishrw  r  r  r  r  _media_handlersr  s     rZ   r  zMessageSender.__init__%  sq    GRG^G`G` ?C?C )***,,&(('))%''=
 =
r\   r{  rP   r  rN  c                    || j         |<   dS )z1Register (or replace) a named media send handler.N)r  )r  r{  r  s      rZ   register_handlerzMessageSender.register_handler8  s    %,T"""r\   rj  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_endro   r  r^  lockedr  r  iterr   r   )r  rj  evictedr5  s       rZ   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\   Nrl   r2  rZ  ro  rd  r\  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.NFr=  Tr^  zJ[%s] truncate_message: input=%d chars, max=%d, output=%d chunk(s) sizes=%sc                ,    g | ]}t          |          S r   ro   r   s     rZ   r   z+MessageSender.send_text.<locals>.<listcomp>h  s    555c!ff555r\   r   ri  )r_  )r  rj  r  r   r
  r  strip_cron_wrappertruncate_messageMAX_TEXT_CHUNKr2  r3  r{  ro   r   r  r_  r  r7  )r  rj  r2  rZ  rd  r\  rp  lockcontent_to_sendr   r   r   r_tor   s                 rZ   	send_textzMessageSender.send_textQ  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ru  r[  rO  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: r`  )rZ  r[  )r  r  r   r  r  )r  rj  ru  rZ  r[  rO  r  s          rZ   
send_mediazMessageSender.send_mediax  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)r_  platformrj  rF  )r  ra   r  r_  r  r   suffixr  r  send_image_filesend_documentrF  )	r  rj  r  r"  r\  last_result
media_path	_is_voicerB  s	            rZ   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\   rg  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  ri  r_  Tr  r_  rF  Fr  Unknown errorr`  )r  rW   ro   send_group_msg_bodyr  send_c2c_msg_bodyr  r   )	r  rj  rg  rZ  rd  r  grpr   r  s	            rZ   ro  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   rO   retryrg   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).r1  r  Nr  ri  r_  Tr  r0  r  z-[%s] send_text_chunk attempt %d/%d failed: %srm   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: r`  )r  r*  rW   ro   send_group_messager  send_c2c_messager  r   r2  r<  r{  r7  rP   r   r>  r  )r  rj  rO   rZ  r5  rd  r\  
last_errorrA  r4  r  r  rF  s                rZ   r  zMessageSender.send_text_chunk  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  rO   r  ri  N)r3  )r  r  rO   rd  rg  s        rZ   r8  zMessageSender.send_c2c_message  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_mentionsr2  )r  rd  rO   rZ  rg  s        rZ   r7  z MessageSender.send_group_message  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  rO   r  r  r  rl   r  r   rm   r  r"  r  @)r  rO   r  N)r  r  r  r  MEMBER_CACHE_TTL_Sr  _AT_USER_RErs   r~  ra   r   r	  r  dumpsrt   ro   )r  rO   rd  rO  tsmember_listr  nickname_to_uidr}   nickr  rg  last_idxr   r~  segr  r   	real_nicktails                       rZ   r<  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_rl   )r  rg  rc  rh  rd  N)r  r6   r0   rW  _dispatch_encoded)r  r  rg  rd  r\  r;  r  s          rZ   r3  zMessageSender.send_c2c_msg_bodyB  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_rl   )rd  rg  rc  rh  
ref_msg_idN)r  r6   r1   rW  rL  )r  rd  rg  rZ  r\  r;  r  s          rZ   r2  z!MessageSender.send_group_msg_bodyO  sz       -'''+! .B~2
 
 
 ++GWfEEEEEEEEEr\   r  r  r;  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  NTrh  rl   )r_  r  FzRequest timeout after sr`  )rj  r@  r  r   r  rL  r7  rP   )r\  r  r;  rC  rF  s        rZ   rL  zMessageSender._dispatch_encodedc  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   rj  Optional[bytes]r  rc  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  )rj  r  rc  	max_bytessize_mbs        rZ   rn  zMessageSender.validate_mediar  s     ZA!5!5,(,,,$&-	z??Y&&*oo,t3GThTT'TTTKTTTTtr\   r   
max_lengthrh   ri   	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 rV  )_INDICATOR_REsubr   s     rZ   r   z2MessageSender.truncate_message.<locals>.<listcomp>  s(    ;;;q-##B**;;;r\   )ro   rN   r   )r2  rW  rh   ru   r   s        rZ   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: )rW   r  rp   ro   ra   )r2  dividerfooter_prefixdivider_pos
footer_posr  
body_startrD  s           rZ   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    rZ   r  zMessageSender.close  s!           r\   rJ  )r{  rP   r  rN  rQ   r   rj  rP   rQ   r   r  
rj  rP   r2  rP   rZ  ro  rd  rP   rQ   r\  r  )rj  rP   ru  rP   rZ  ro  r[  ro  rO  r
   rQ   r\  r   rj  rP   r  rP   r"  r#  rQ   r$  )
rj  rP   rg  r^  rZ  ro  rd  rP   rQ   r\  )Nr   rl   )rj  rP   rO   rP   rZ  ro  r5  rg   rd  rP   rQ   r\  rV  )r  rP   rO   rP   rd  rP   rQ   r:  )rd  rP   rO   rP   rZ  ro  rQ   r:  )rO   rP   rd  rP   rQ   r^  )r  rP   rg  r^  rd  rP   rQ   r:  )rd  rP   rg  r^  rZ  ro  rQ   r:  )r\  r  r  r  r;  rP   rQ   r:  rR  rj  rS  r  rP   rc  rg   rQ   ro  r   )r2  rP   rW  rg   rh   ri   rQ   rX  )r2  rP   rQ   rP   r   )r   r   r   r   rQ  r  rW  r  r  r  r  r  r!  r.  ro  r  r8  r7  rq   rr   	MULTILINEr@  r<  r3  r2  r   rL  rn  r  r  r  r   r\   rZ   rm  rm    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\   rm  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  rQ   r   c                    || _         t          |          | _        t          |          | _        t          || j                  | _        | j        | j        _        | j	        | j        _
        d S r   )r  rm  r/  r  r  r  slow_notifier_handle_send_startr
  _handle_send_finishr  r  s     rZ   r  zOutboundManager.__init__  s_    %27%;%;+;G+D+D3GQUQ\3]3] &*%<"&*&>###r\   rj  rP   c                :    | j                             |           dS )zFCalled by MessageSender before sending: cancel slow-response notifier.Nrm  r  r  rj  s     rZ   rn  z"OutboundManager._handle_send_start      !!'*****r\   c                V   K   | j                             |t                     d{V  dS )z=Called by MessageSender after sending: send FINISH heartbeat.N)r  r  r'   rr  s     rZ   ro  z#OutboundManager._handle_send_finish  s7      n00:MNNNNNNNNNNNr\   Nrl   r2  rZ  ro  rd  r\  c                N   K   | j                             ||||           d{V S )z%Send text message with auto-chunking.ri  N)r/  r  )r  rj  r2  rZ  rd  s        rZ   r  zOutboundManager.send_text  s:      
 [**7GXR\*]]]]]]]]]r\   ru  rO  r
   c                :   K    | j         j        ||fi | d{V S )r   N)r/  r!  )r  rj  ru  rO  s       rZ   r!  zOutboundManager.send_media  s9       ,T[+G\LLVLLLLLLLLLr\   r  r"  r#  r$  c                J   K   | j                             |||           d{V S )z.Send text + media (used by send_message tool).N)r/  r.  )r  rj  r  r"  s       rZ   r.  zOutboundManager.send_direct  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~  rr  s     rZ   start_typingzOutboundManager.start_typing  s4      n""7+++++++++++r\   Fr  rR   c                N   K   | j                             ||           d{V  dS )zStop reply heartbeat.r  N)r  r  )r  rj  r  s      rZ   stop_typingzOutboundManager.stop_typing  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)rm  r~  rr  s     rZ   start_slow_notifierz#OutboundManager.start_slow_notifier  s5       &&w///////////r\   c                :    | j                             |           dS )zCancel slow-response notifier.Nrq  rr  s     rZ   rl  z$OutboundManager.cancel_slow_notifier  rs  r\   r   c                6    | j                             |          S )z@Proxy to MessageSender.get_chat_lock for backward compatibility.)r/  r  rr  s     rZ   r  zOutboundManager.get_chat_lock	  s    {((111r\   collections.OrderedDictc                    | j         j        S )z>Proxy to MessageSender._chat_locks for backward compatibility.)r/  r	  r  s    rZ   r	  zOutboundManager._chat_locks  s     {&&r\   rR  rj  rS  r  rc  rg   c                :    t                               | ||          S )z&Proxy to MessageSender.validate_media.)rm  rn  )rj  r  rc  s      rZ   rn  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)r/  r  r  rm  r  s    rZ   r  zOutboundManager.close  s      k!!!!!!!!!n""$$$$$$$$$ &&(((((((((((r\   rJ  r  r  re  )rj  rP   ru  rP   rO  r
   rQ   r\  r   rf  )Fr  rd  )rQ   r  rg  rh  r   )r   r   r   r   rm  r  rW  r  rn  ro  r  r!  r.  ry  r|  r~  rl  r  r  r	  r   rn  r  r   r\   rZ   rk  rk    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\   rk  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   rg   r  rK   rh  i  r  REPLY_REF_MAX_ENTRIESNz$ClassVar[Optional['YuanbaoAdapter']]_active_instancerQ   Optional['YuanbaoAdapter']c                    | j         S )z7Return the currently connected YuanbaoAdapter, or None.r  r  s    rZ   
get_activezYuanbaoAdapter.get_active,  s     ##r\   r\  r   c                    || _         dS )z0Register (or clear) the active adapter instance.Nr  )r   r\  s     rZ   r  zYuanbaoAdapter.set_active1  s      'r\   r  r   rO  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_idrl   r   r#  ws_urlr  r  r  rJ   i,  )ttl_secondsr]  YUANBAO_DM_POLICYrJ  r^  YUANBAO_DM_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r   xs     rZ   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>n  s2    #b#b#b!XYX_X_XaXa#bAGGII#b#b#br\   ,r_  YUANBAO_GROUP_POLICYr`  YUANBAO_GROUP_ALLOW_FROMc                ^    g | ]*}|                                 |                                 +S r   r   r  s     rZ   r   z+YuanbaoAdapter.__init__.<locals>.<listcomp>y  s2    &h&h&hQ^_^e^e^g^g&hqwwyy&h&h&hr\   )r]  r^  r_  r`  rx  r  )/superr  r   YUANBAOr  r  ra   rO  rQ  rW  DEFAULT_WS_GATEWAY_URLr  DEFAULT_API_DOMAINr`   rP  r  r  rj  rk  rk  r   r  r>  r  r?  r   r  r  r  r&  r  r|  r}  r  rV   r\  rs  r  _group_queryr  r  r9  home_channelrj  rR   rW   r{  )r  r  rO  _extrar]  _dm_allow_from_rawr^  r_  _group_allow_from_rawr`  _existing_homer  s              rZ   r  zYuanbaoAdapter.__init__6  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\   rA  asyncio.Taskc                x    | j                             |           |                    | j         j                   |S )z@Register a fire-and-forget task so it won't be GC'd prematurely.)r>  r   r?  r@  r  s     rZ   r  zYuanbaoAdapter._track_task  s8    ""4(((t5=>>>r\   rR   c                D   K   | j                                          d{V S )zhConnect to Yuanbao WS gateway and authenticate.

        Delegates to ConnectionManager.open().
        N)rj  rJ  r  s    rZ   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  rj  r  rk  r^  r  r  r  r   r  r2  r3  r{  r  s     rZ   
disconnectzYuanbaoAdapter.disconnect  s#     *d22%%d+++!!!##%%% $$&&&&&&&&&n""$$$$$$$$$ ,-- 	 	D99;; !!###  """'33333r\   rl   rj  rP   r2  rZ  ro  metadataOptional[Dict[str, Any]]rd  r   c                N   K   | j                             ||||           d{V S )zCSend text message with auto-chunking. Delegates to OutboundManager.ri  N)rk  r  )r  rj  r2  rZ  r  rd  s         rZ   r  zYuanbaoAdapter.send  s:       ^--gwU_-`````````r\   r$  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
  )rW   rr  s     rZ   get_chat_infozYuanbaoAdapter.get_chat_info  s:       h'' 	6#W555...r\   r`  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)rk  ry  r7  )r  rj  r  s      rZ   send_typingzYuanbaoAdapter.send_typing  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)rk  r|  r7  rr  s     rZ   r|  zYuanbaoAdapter.stop_typing  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)rM  rj  rk  r~  r  _process_message_backgroundrl  )r  r  r  rj  r  s       rZ   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  rl  s     rZ   r  zYuanbaoAdapter.query_group_info  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  rd  r  r  s       rZ   get_group_member_listz$YuanbaoAdapter.get_group_member_list  s:       &@@TZbg@hhhhhhhhhr\   i'  r  rO   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 userr`  Nz
...(truncated)r  ri  )rs  rj  r   ro   DM_MAX_CHARSr  )r  r  rO   rd  rj  s        rZ   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  r[  c                B   K    | j         j        |df|||d| d{V S )zKSend image message (URL). Delegates to OutboundManager via ImageUrlHandler.r  )rZ  r[  r  Nrk  r!  )r  rj  r  r[  rZ  r  rO  s          rZ   
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  )rZ  r[  r  Nr  )r  rj  r  r[  rZ  r  rO  s          rZ   r)  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  )rZ  r  r  Nr  )r  rj  r  r  rZ  r  rO  s          rZ   	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  )rZ  r  r  Nr  )r  rj  r  r  rZ  rO  s         rZ   send_stickerzYuanbaoAdapter.send_stickerN  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  )rZ  r[  r  r  Nr  )r  rj  r  r  r[  rZ  r  rO  s           rZ   r*  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   rQ  rO  rQ  rP  r  r  s    rZ   rN  z YuanbaoAdapter._get_cached_tokenp  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  )rj  r  rW  r  r  r  )r  rp  s     rZ   
get_statuszYuanbaoAdapter.get_statusw  s4    *l/"&"9l
 
 	
r\   rQ   r  )r\  r  rQ   r   )r  r   rO  r
   rQ   r   )rA  r  rQ   r  rK  r   )NNrl   )rj  rP   r2  rP   rZ  ro  r  r  rd  rP   rQ   r   )rj  rP   rQ   r$  r   )rj  rP   r  r`  rQ   r   r  )r  rP   rQ   r   r  r  r  rV  )r  rP   rO   rP   rd  rP   rQ   r   )NNN)rj  rP   r  rP   r[  ro  rZ  ro  r  r`  rO  r
   rQ   r   )rj  rP   r  rP   r[  ro  rZ  ro  r  r`  rO  r
   rQ   r   )rj  rP   r  rP   r  ro  rZ  ro  r  r`  rO  r
   rQ   r   )rj  rP   r  ro  r  r  rZ  ro  rO  r
   rQ   r   )NNNN)rj  rP   r  rP   r  ro  r[  ro  rZ  ro  r  r`  rO  r
   rQ   r   )rQ   r:  )$r   r   r   r   r   r  PLATFORMr  rW  rh  r  r  r   r  r  r  r  r  r  r  r  r  r|  r  r  r  r  r  r  r)  r  r  r*  rN  r  __classcell__)r  s   @rZ   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  rQ   r  c                 4    t                                           S )z,Delegate to ``YuanbaoAdapter.get_active()``.)r  r  r   r\   rZ   get_active_adapterr    s    $$&&&r\   r\  r  rj  rP   r  r"  r#  r$  c                J   K   | j                             |||           d{V S )z,Delegate to ``OutboundManager.send_direct``.N)rk  r.  )r\  rj  r  r"  s       rZ   send_yuanbao_directr    s5       "..wMMMMMMMMMr\   r  r   )
r\  r  rj  rP   r  rP   r"  r#  rQ   r$  )r   
__future__r   r   r  r  r   r   r  loggingr|  rq   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   rg  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   r2  
hermes_clir8   _HERMES_VERSIONr.  r1  rP   r0  r'  r/  r  r  r  r  r  rF  rL  r  r  AUTH_FAILED_CODESAUTH_RETRYABLE_CODESr  r  REPLY_REF_TTL_Sr  r  rr   rz  r[  ry  r|  rN   r   rX  rY  rx  r[  rz  r  r  r  r  r  rS  rY  r\  rq  rv  r  r  r  r  r  r  r  r&  r4  r  r  r  rN  rw  r  r  r  r  r  r  r  rm  rk  r  r  r  r   r\   rZ   <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 
 
-.. $&  /1 ,^
 ^
 ^
 ^
 ^
 ^
 ^
 ^
@p) p) p) p) p) p) p) p)f 5 4 4 4 4 4 4 4
5) 5) 5) 5) 5) 5) 5) 5)p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M M M M M0 M M M^    "3   2^ ^ ^ ^ ^. ^ ^ ^B    -   $q q q q q. q q qh    !2   >    $5   6& & & & &. & & &RN N N N N. N N NbH9 H9 H9 H9 H9* H9 H9 H9V# # # # # # # #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