
    `gj"                       U d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	m
Z
mZ  ej                  e      ZdCdDdZdCdZdEdZdZd	d
	 	 	 	 	 	 	 	 	 	 	 dFdZd	d
	 	 	 	 	 	 	 	 	 	 	 dFdZdZdZdZdZdZdZ	 	 	 	 	 	 	 	 dGdZdZdZ	 	 	 	 	 	 	 	 	 	 dHdZ	 	 	 	 	 	 	 	 dIdZdd	 	 	 	 	 	 	 	 	 	 	 dJdZ dKdZ!	 	 	 	 	 	 	 	 	 	 	 	 dLdZ"	 	 	 	 	 	 	 	 	 	 dMdZ#dZ$e$fdNdZ%	 	 	 	 	 	 	 	 	 	 	 	 	 	 dOdZ&dPd Z'dPd!Z(dQdRd"Z)ddl*Z+ e+jX                  d#      Z-d$Z.dSd%Z/	 	 	 	 	 	 	 	 dTd&Z0	 	 	 	 	 	 dUd'Z1	 dQ	 	 	 	 	 dVd(Z2d)Z3d*Z4d+Z5e3fdWd,Z6	 	 	 	 	 	 dXd-Z7	 	 	 	 	 	 	 	 dYd.Z8d/js                         Z:d0dd1	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dZd2Z;d0d0dd3	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d[d4Z<d\d5Z=ddl>Z>ddl?Z@d6ZAd7ZB e>j                         ZDi ZEd8eFd9<   d]d:ZGd^d;ZHd_d<ZIdQd`d=ZJd>ZKd?d@hZLddA	 	 	 	 	 	 	 dadBZMy)ba&  
Feishu/Lark drive document comment handling.

Processes ``drive.notice.comment_add_v1`` events and interacts with the
Drive v2 comment reaction API.  Kept in a separate module so that the
main ``feishu.py`` adapter does not grow further and comment-related
logic can evolve independently.

Flow:
  1. Parse event -> extract file_token, comment_id, reply_id, etc.
  2. Add OK reaction
  3. Parallel fetch: doc meta + comment details (batch_query)
  4. Branch on is_whole:
       Whole -> list whole comments timeline
       Local -> list comment thread replies
  5. Build prompt (local or whole)
  6. Create AIAgent with feishu_doc + feishu_drive tools -> agent generates reply
  7. Route reply:
       Whole -> add_whole_comment
       Local -> reply_to_comment (fallback to add_whole_comment on 1069302)
    )annotationsN)AnyDictListOptionalTuplec                   ddl m} ddlm} ddlm} | dk(  r|j                  n|j                  }|j                         j                  |      j                  |      j                  |j                  h      }	|r|	j                  |      }	|r|	j                  |      }	||	j                  |      }	|	j!                         S )zBuild a lark_oapi BaseRequest.r   AccessTokenType)
HttpMethod)BaseRequestGET)	lark_oapir   lark_oapi.core.enumr   !lark_oapi.core.model.base_requestr   r   POSTbuilderhttp_methoduritoken_typesTENANTpathsqueriesbodybuild)
methodr   r   r   r   r   r   r   r   r   s
             Z/root/.hermes/venv/lib/python3.12/site-packages/plugins/platforms/feishu/feishu_comment.py_build_requestr   %   s    ).=$*eO*..K 		[	!	S	o,,-	.	  --&//'*,,t$==?    c                  K   t         j                  d|||||rt        j                  |d      dd nd       t	        |||||      }t        j                  | j                  |       d{   }t        |dd      }t        |dd      }	i }
t        |d	d      }|r>t        |d
      r2	 t        j                  |j                        }|j                  di       }
|
s9t        |dd      }t        |t               r|}
n|rt        |d      rt#        |      }
t         j                  d||||	|
rt%        |
j'                               nd       |dk7  rzt        |d	d      }d}|rSt        |d
      rGt        |j                  t(        t*        f      r|j                  dd nt)        |j                        dd }t         j-                  d|       ||	|
fS 7 b# t        j                  t        f$ r Y w xY ww)z=Execute a lark API request and return (code, msg, data_dict).z:[Feishu-Comment] API >>> %s %s paths=%s queries=%s body=%sF)ensure_asciiN  codemsg rawcontentdata__dict__z:[Feishu-Comment] API <<< %s %s code=%s msg=%s data_keys=%semptyr   z*[Feishu-Comment] API FAIL raw response: %s)loggerinfojsondumpsr   asyncio	to_threadrequestgetattrhasattrloadsr'   getJSONDecodeErrorAttributeError
isinstancedictvarslistkeysstrbyteswarning)clientr   r   r   r   r   r1   responser#   r$   r(   r&   	body_json	resp_dataraw_contents                  r   _exec_requestrE   <   s    
KKLeW?CDu5ds;O VS%$?G&&v~~w??H8VT*D
(E2
&CD
(E4
(C
wsI&	

3;;/I==,D Hfd3	i&D79j9	?D
KKLdCddiik):Qqyht,73	*/9#++U|/T#++ds+Z]^a^i^iZjkoloZpKC[Qd?; @ $$n5 		s7   A)G0+G,;G0(1G C5G0G-)G0,G--G0c                   t         j                  dt        |       j                         t	        | dd      }|t         j                  d       yt        |t              r|nt        |d      rt        |      ni }t         j                  dt        |j                                      |j                  d      xs i }t        |t              st        |d      rt        |      ni }|j                  d      xs i }t        |t              st        |d      rt        |      ni }|j                  d	      xs i }t        |t              st        |d      rt        |      ni }t        |j                  d
      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d            t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      t        |j                  d      xs d      d
S )aG  Extract structured fields from a ``drive.notice.comment_add_v1`` payload.

    *data* may be a ``CustomizedEvent`` (WebSocket) whose ``.event`` is a dict,
    or a ``SimpleNamespace`` (Webhook) built from the full JSON body.

    Returns a flat dict with the relevant fields, or ``None`` when the
    payload is malformed.
    z8[Feishu-Comment] parse_drive_comment_event: data type=%seventNzO[Feishu-Comment] parse_drive_comment_event: no .event attribute, returning Noner)   z7[Feishu-Comment] parse_drive_comment_event: evt keys=%snotice_metafrom_user_id
to_user_idevent_idr%   
comment_idreply_idis_mentioned	timestamp
file_token	file_typenotice_typeopen_id)
rK   rL   rM   rN   rO   rP   rQ   rR   from_open_id
to_open_id)r+   debugtype__name__r2   r8   r9   r3   r:   r;   r<   r5   r=   bool)r(   rG   evtrH   	from_userto_users         r   parse_drive_comment_eventr]   g   s    LLKTRVZM`M`aD'4(E}fg#E40uj1Ur  LLJDQTQYQYQ[L\]''-(.BKk4(+2;
+Kd;'QS/52Ii&'.y*'EDO2	ool+1rGgt$#*7J#?$w-R 
+1r2#'',/526
+1r2SWW^45-34+//,7=2>5;<;??=9?R@IMM)4:;'++i06B7 r   z7/open-apis/drive/v2/files/:file_token/comments/reactionOK)reaction_typec               8  K   	 ddl m} d||d}t        | dt        d|id	|fg|
       d{   \  }}}	|dk(  }
|
rt        j                  d||||       |
S t        j                  d|||||       |
S # t        $ r t        j	                  d       Y yw xY w7 jw)a  Add an emoji reaction to a document comment reply.

    Uses the Drive v2 ``update_reaction`` endpoint::

        POST /open-apis/drive/v2/files/{file_token}/comments/reaction?file_type=...

    Returns ``True`` on success, ``False`` on failure (errors are logged).
    r   r
   z([Feishu-Comment] lark_oapi not availableFaddactionrM   r_   r   rP   rQ   r   r   r   Nz9[Feishu-Comment] Reaction '%s' added: file=%s:%s reply=%sH[Feishu-Comment] Reaction API failed: code=%s msg=%s file=%s:%s reply=%s)	r   r   ImportErrorr+   errorrE   _REACTION_URIr,   r?   )r@   rP   rQ   rM   r_   r   r   r#   r$   _	succeededs              r   add_comment_reactionrk      s      - &D 'Z(y)*	 LD#q 	IG9j(	
  	"#y*h	

 ;  ?@s2   BA4 #BBAB4BBBBc                  K   d||d}t        | dt        d|id|fg|       d{   \  }}}|dk(  }	|	rt        j                  d	||||       |	S t        j	                  d
|||||       |	S 7 Fw)us   Remove an emoji reaction from a document comment reply.

    Best-effort — errors are logged but not raised.
    deleterb   r   rP   rQ   rd   Nr   z;[Feishu-Comment] Reaction '%s' deleted: file=%s:%s reply=%sre   )rE   rh   r+   r,   r?   )
r@   rP   rQ   rM   r_   r   r#   r$   ri   rj   s
             r   delete_comment_reactionrn      s      &D 'Z(y)*	 LD#q 	II9j(	
  	"#y*h	

 's   $A/A-AA/z%/open-apis/drive/v1/metas/batch_queryz:/open-apis/drive/v1/files/:file_token/comments/batch_queryz./open-apis/drive/v1/files/:file_token/commentszB/open-apis/drive/v1/files/:file_token/comments/:comment_id/repliesz2/open-apis/drive/v1/files/:file_token/new_commentsc                  K   ||dgdd}t         j                  d||       t        | dt        |       d{   \  }}}|dk7  rt         j	                  d	||       i S |j                  d
g       }t         j                  dt        |      j                  t        |      dd        |sLt        |j                  d
      t              r|d
   j                  |i       }n.t         j                  d       i S t        |t              r|d   ni }|j                  dd      |j                  dd      |j                  d|      d}	t         j                  d|	d   |	d   r|	d   dd        |	S d       |	S 7 9w)zFetch document title and URL via batch_query meta API.

    Returns ``{"title": "...", "url": "...", "doc_type": "..."}`` or empty dict.
    )	doc_tokendoc_typeT)request_docswith_urlz@[Feishu-Comment] query_document_meta: file_token=%s file_type=%sr   )r   Nr   z8[Feishu-Comment] Meta batch_query failed: code=%s msg=%smetasz@[Feishu-Comment] query_document_meta: raw metas type=%s value=%si,  z4[Feishu-Comment] query_document_meta: no metas foundtitler%   urlrq   )ru   rv   rq   z5[Feishu-Comment] query_document_meta: title=%s url=%sP   )r+   rV   rE   _BATCH_QUERY_META_URIr?   r5   rW   rX   r=   r8   r9   r;   r,   )
r@   rP   rQ   r   r#   r$   r(   rt   metaresults
             r   query_document_metar{     sx     (2yIJD LLSU_ajk)-D OD#t qyQSWY\]	HHWb!E
LLSe%%s5z$3'79dhhw'.=$$Z4DLLOPI%eT2uQx '2&xxr"HHZ3F
 KKGwve}s!3NM KMNM7s   7E5E2D9E5   g      ?c                  K   t         j                  d||       t        t              D ]  }t	        | dt
        d|id|fdgd|gi       d{   \  }}}|d	k(  r no|t        d
z
  k  rBt         j                  d|d
z   t        ||       t        j                  t               d{    t         j                  dt        ||       i c S  j                  dg       }t         j                  dt        |t              rt        |      nd	       |rt        |t              r|d	   }	t         j                  d|	j                  d      |	j                  dd      xs ddd t        |	j                  d      t              r1t        |	j                  di       j                  dg                    |	S d       |	S t         j                  dt        |j!                                      i S 7 7 Cw)zFetch comment details via batch_query comment API.

    Retries up to 6 times on failure (handles eventual consistency).

    Returns the comment dict with fields like ``is_whole``, ``quote``,
    ``reply_list``, etc.  Empty dict on failure.
    zA[Feishu-Comment] batch_query_comment: file_token=%s comment_id=%sr   rP   rQ   user_id_typerS   comment_idsrd   Nr      z@[Feishu-Comment] batch_query_comment retry %d/%d: code=%s msg=%szM[Feishu-Comment] batch_query_comment failed after %d attempts: code=%s msg=%sitemsz2[Feishu-Comment] batch_query_comment: got %d itemszI[Feishu-Comment] batch_query_comment: is_whole=%s quote=%s reply_count=%sis_wholequoter%   <   
reply_listreplies?zC[Feishu-Comment] batch_query_comment: empty items, raw data keys=%s)r+   rV   range_COMMENT_RETRY_LIMITrE   _BATCH_QUERY_COMMENT_URIr,   r/   sleep_COMMENT_RETRY_DELAY_Sr?   r5   r8   r;   lenr9   r<   )
r@   rP   rQ   rL   attemptr#   r$   r(   r   items
             r   batch_query_commentr   0  s     LLTV`blm-.  -F4,i(+  *.!
 
c4 19)A--KKR!14 -- 6777NN_$dC I/4 HHWb!E
LLEU_`egkUls5zrstE4(Qx_HHZ(XXgr*0b#26JTUYU]U]^jUkmqJrCr266y"EF	}  y|	} 
NNXZ^_c_h_h_jZklIG
  8s&   AG+G%AG+%G(&E G+(G+c                h  K   t         j                  d|       g }d}t        d      D ]  }d|fdddg}|r|j                  d|f       t	        | d	t
        d
|i|       d{   \  }}}	|dk7  rt         j                  d||        n|	j                  dg       }
t        |
t              r:|j                  |
       t         j                  dt        |
      t        |             |	j                  d      s n|	j                  dd      }|r n t         j                  dt        |             |S 7 έw)z8List all whole-document comments (paginated, up to 500).z3[Feishu-Comment] list_whole_comments: file_token=%sr%      rQ   )r   true	page_size100r~   
page_tokenr   rP   r   r   Nr   z;[Feishu-Comment] List whole comments failed: code=%s msg=%sr   zA[Feishu-Comment] list_whole_comments: page got %d items, total=%dhas_morezE[Feishu-Comment] list_whole_comments: total %d whole comments fetched)r+   rV   r   appendrE   _LIST_COMMENTS_URIr?   r5   r8   r;   extendr   r,   )r@   rP   rQ   all_commentsr   ri   r   r#   r$   r(   r   s              r   list_whole_commentsr   c  s8     LLF
S)+LJ1X )$  '	
 NNL*56 -E-,!
 
c4
 19NNXZ^`cd"%eT"&LL\US%68 xx
#XXlB/
;> KKWY\]iYjk-
s   AD2!D0"B)D2%D2r%   expect_reply_idc          
     b  K   t         j                  d||       t        t              D ][  }g }d}d}t        d      D ]  }	d|fddg}
|r|
j	                  d|f       t        | d	t        ||d
|
       d{   \  }}}|dk7  rt         j                  d||       d} n^|j                  dg       }t        |t              r|j                  |       |j                  d      s n|j                  dd      }|r n r|s nt        fd|D              }|r nl|t        dz
  k  rBt         j                  d|dz   t               t        j                  t                d{    At         j                  dt               ^ t         j                  dt#                     |S 7 27 Hw)zList all replies in a comment thread (paginated, up to 500).

    If *expect_reply_id* is set and not found in the first fetch,
    retries up to 6 times (handles eventual consistency).
    zB[Feishu-Comment] list_comment_replies: file_token=%s comment_id=%sr%   Tr   rQ   r   r~   r   r   rP   rL   r   Nr   z4[Feishu-Comment] List replies failed: code=%s msg=%sFr   r   c              3  F   K   | ]  }|j                  d       k(    yw)rM   N)r5   ).0rr   s     r   	<genexpr>z'list_comment_replies.<locals>.<genexpr>  s     NQAEE*%8Ns   !r   zI[Feishu-Comment] list_comment_replies: reply_id=%s not found, retry %d/%dzN[Feishu-Comment] list_comment_replies: reply_id=%s not found after %d attemptsz?[Feishu-Comment] list_comment_replies: total %d replies fetched)r+   rV   r   r   r   rE   _LIST_REPLIES_URIr?   r5   r8   r;   r   anyr,   r/   r   r   r   )r@   rP   rQ   rL   r   r   all_repliesr   fetch_okri   r   r#   r$   r(   r   founds       `           r   list_comment_repliesr     s     LLUWacmn-. 2,.
q 	Ai($+G
 j9:$10%/zJ% OD#t
 qyUW[]`a HHWb)E%&""5)88J',3J7	< hN+NN)A--KK[1.B -- 6777NN`!5_2h KKQSVWbScdO> 8s-   A5F/8F*9BF/<A(F/$F-%AF/-F/c                f    | j                  dd      j                  dd      j                  dd      S )zAEscape characters not allowed in Feishu comment text_run content.&z&amp;<z&lt;>z&gt;)replace)texts    r   _sanitize_comment_textr     s-    <<W%--c6:BB3OOr   c                4  K   t        |      }t        j                  d||dd        dddd|idgii}t        | d	t        ||d
d|fg|       d{   \  }}}|dk7  rt        j                  d|||       nt        j                  d|       |dk(  |fS 7 Cw)zNPost a reply to a local comment thread.

    Returns ``(success, code)``.
    z8[Feishu-Comment] reply_to_comment: comment_id=%s text=%sNd   r'   elementstext_runr   )rW   r   r   r   rQ   rd   r   zF[Feishu-Comment] reply_to_comment FAILED: code=%s msg=%s comment_id=%sz3[Feishu-Comment] reply_to_comment OK: comment_id=%s)r   r+   r,   rE   _REPLY_COMMENT_URIr?   )	r@   rP   rQ   rL   r   r   r#   r$   ri   s	            r   reply_to_commentr     s      "$'D
KKJD#J( 	#&$@
D '*'zBy)*	 LD#q qyT#z	

 	I:V19d?s   ABBABc                $  K   t        |      }t        j                  d||dd        |d|dgd}t        | dt        d|i|	       d{   \  }}}|d
k7  rt        j                  d||       |d
k(  S t        j                  d       |d
k(  S 7 Cw)zHAdd a new whole-document comment.

    Returns ``True`` on success.
    z9[Feishu-Comment] add_whole_comment: file_token=%s text=%sNr   r   )rW   r   )rQ   reply_elementsr   rP   )r   r   r   z9[Feishu-Comment] add_whole_comment FAILED: code=%s msg=%sz%[Feishu-Comment] add_whole_comment OK)r   r+   r,   rE   _ADD_COMMENT_URIr?   )r@   rP   rQ   r   r   r#   r$   ri   s           r   add_whole_commentr     s      "$'D
KKKD#J( T*
D '(Z( LD#q
 qyRTXZ]^ 19 	;<19s   AB
BABi  c                    t        |       |k  r| gS g }| rgt        |       |k  r|j                  |        	 |S | j                  dd|      }|dk  r|}|j                  | d|        | |d j                  d      } | rg|S )z<Split text into chunks for delivery, preferring line breaks.
r   N)r   r   rfindlstrip)r   limitchunkscuts       r   _chunk_textr     s    
4yEvF
t9MM$ M jjq%(!8Cd4Cj!CDz  &  Mr   c           	       K   t        |      }t        j                  d||t        |      t        |             d}t	        |      D ]  \  }}	t        |      dkD  r-t        j                  d|dz   t        |      t        |	             |rt        | |||	       d{   }
nTt        | ||||	       d{   \  }}|rd}
n5|dk(  r.t        j                  d       t        | |||	       d{   }
d}nd}
|
rd} |S  |S 7 d7 N7 w)	zRoute agent reply to the correct API, chunking long text.

    - Whole comment -> add_whole_comment
    - Local comment -> reply_to_comment, fallback to add_whole_comment on 1069302
    zW[Feishu-Comment] deliver_comment_reply: is_whole=%s comment_id=%s text_len=%d chunks=%dTr   zF[Feishu-Comment] deliver_comment_reply: sending chunk %d/%d (%d chars)NiP zO[Feishu-Comment] Reply not allowed (1069302), falling back to add_whole_commentF)r   r+   r,   r   	enumerater   r   )r@   rP   rQ   rL   r   r   r   all_okichunkoksuccessr#   s                r   deliver_comment_replyr   *  s     F
KKi*c$iV> Ff% 5v;?KK`As6{CJ8 (YNNB"26:yR\^c"ddMGTmn,VZERRFM-, M! Od
 Ss<   BDC?D2D37D*D+D7	DDDc                   | j                  di       }t        |t              r	 t        j                  |      }|j                  dg       }g }|D ]  }|j                  d      dk(  r4|j                  di       }|j                  |j                  dd             K|j                  d      dk(  r4|j                  di       }|j                  |j                  dd             |j                  d      d	k(  s|j                  d	i       }|j                  d
|j                  dd               dj                  |      S # t        j
                  t        f$ r |cY S w xY w)z<Extract plain text from a comment reply's content structure.r'   r   rW   r   r   r%   	docs_linkrv   person@user_idunknown)	r5   r8   r=   r-   r4   r6   	TypeErrorr   join)replyr'   r   partselemr   r   r   s           r   _extract_reply_textr   Z  s8   ii	2&G'3	jj)G {{:r*HE 	A88Fz)xx
B/HLLfb12XXf,b1ILLub12XXf)XXh+FLL1VZZ	9=>?@	A 775> $$i0 	N	s   E   EEc                    | j                  dd      }t        |t              r&|j                  dd      xs |j                  dd      S t        |      S )z"Extract user_id from a reply dict.r   r%   rS   )r5   r8   r9   r=   )r   r   s     r   _get_reply_user_idr   r  sG    ii	2&G'4 {{9b)GW[[B-GGw<r   c                ,   | j                  di       }t        |t              r	 t        j                  |      }|j                  dg       }g }|D ]  }|j                  d      dk(  rA|j                  di       }|j                  dd      }|r||k(  rC|j                  d|        X|j                  d      dk(  r4|j                  di       }|j                  |j                  d	d             |j                  d      d
k(  s|j                  d
i       }	|j                  |	j                  dd              dj                  dj                  |      j                               j                         S # t        j
                  t        f$ r |cY S w xY w)zRExtract semantic text from a reply, stripping self @mentions and extra whitespace.r'   r   rW   r   r   r%   r   r   r   r   rv    )r5   r8   r=   r-   r4   r6   r   r   r   splitstrip)
r   self_open_idr'   r   r   r   r   uidr   r   s
             r   _extract_semantic_textr   z  sa   ii	2&G'3	jj)G {{:r*HE 388Fx'XXh+F**Y+C| 3LL1SE#XXf+xx
B/HLLfb12XXf,b1ILLub123 88BGGEN((*+1133' $$i0 	N	s   E5 5FFz(?:feishu\.cn|larkoffice\.com|larksuite\.com|lark\.suite\.com)/(?P<doc_type>wiki|doc|docx|sheet|sheets|slides|mindnote|bitable|base|file)/(?P<token>[A-Za-z0-9_-]{10,40})z"/open-apis/wiki/v2/spaces/get_nodec                z   t               }g }| D ]  }|j                  di       }t        |t              r	 t	        j
                  |      }|j                  dg       D ]  }|j                  d      dvr|j                  d      xs |j                  d      xs i }|j                  dd      }|sTt        j                  |      }|sl|j                  d	      }	|j                  d
      }
|
|v r|j                  |
       |j                  ||	|
d         |S # t        j                  t        f$ r Y *w xY w)zExtract unique document links from a list of comment replies.

    Returns list of ``{"url": "...", "doc_type": "...", "token": "..."}`` dicts.
    r'   r   rW   >   linkr   r   r   rv   r%   rq   token)rv   rq   r   )setr5   r8   r=   r-   r4   r6   r   _FEISHU_DOC_URL_REsearchgroupra   r   )r   seen_tokenslinksr   r'   r   	link_datarv   mrq   r   s              r   _extract_docs_linksr     s:   
 %KE M))Ir*gs#**W- KK
B/ 	MDxx'<<-G&1AGRI--r*C"))#.Awwz*HGGG$E#OOE"LL(UKL	MM. L% (()4 s   DD:9D:c                   K   t        | dt        d|fd|fg       d{   \  }}}|dk(  r*|j                  di       }|j                  dd	      }|r|S dS t        j	                  d
||||       y7 Qw)zReverse-lookup: given an obj_token, find its wiki node_token.

    Returns the wiki_token if the document belongs to a wiki space,
    or None if it doesn't or the API call fails.
    r   r   obj_typer   Nr   node
node_tokenr%   zE[Feishu-Comment] Wiki reverse lookup failed: code=%s msg=%s obj=%s:%s)rE   _WIKI_GET_NODE_URIr5   r+   r?   )r@   r   	obj_tokenr#   r$   r(   r   
wiki_tokens           r   _reverse_lookup_wiki_tokenr     s      *)9%
H'=> OD#t qyxx#XXlB/
'z1T1
NNZ\`begoqz{s   A3A1AA3c                  K   |D cg c]  }|d   dk(  s| }}|s|S |D ]  }|d   }t        | dt        d|fg       d{   \  }}}|dk(  rt|j                  di       }	|	j                  d	d
      }
|	j                  dd
      }|
r%|r#t        j	                  d||
|       |
|d<   ||d<   t        j                  d|       t        j                  d|||        |S c c}w 7 w)zResolve wiki links to their underlying document type and token.

    Mutates entries in *links* in-place: replaces ``doc_type`` and ``token``
    with the resolved values for wiki links.  Non-wiki links are unchanged.
    rq   wikir   r   r   Nr   r   r   r%   r   z+[Feishu-Comment] Wiki resolved: %s -> %s:%sresolved_typeresolved_tokenz0[Feishu-Comment] Wiki resolve returned empty: %sz=[Feishu-Comment] Wiki resolve failed: code=%s msg=%s token=%s)rE   r   r5   r+   r,   r?   )r@   r   l
wiki_linksr   r   r#   r$   r(   r   r   r   s               r   _resolve_wiki_nodesr     s     #>a
mv&=!>J> s']
 -E-z*+!
 
c4 1988FB'D HHZ4M!XXk26NA~ )6_%)7%&QS]^NNZ\`begqr)s, L5 ?
s!   C'C C )C'C%B#C'c                    | syddg}| D ]Z  }|j                  d|d         }|j                  d|d         }||k(  }|rdnd}|j                  d| d	| | d
|d   dd  d       \ dj                  |      S )z4Format resolved document links for prompt embedding.r%   z!Referenced documents in comments:r   rq   r   r   z (same as current document)z- :z (rv   Nrw   )r   )r5   r   r   )r   current_file_tokenlinesr   rtypertoken
is_currentsuffixs           r   _format_referenced_docsr	    s     45E I$z*:;*DM:11
2<."r%&&DK4D3EQGHI 99Ur            c                2    t        |       |k  r| S | d| dz   S )z#Truncate text for prompt embedding.Nz...)r   )r   r   s     r   	_truncater    s$    
4yE<%r   c                0   t        |       t        k  r| S t        |       }t               }|j                  d       |j                  |dz
         d|cxk  r|k  rn n|j                  |       t        t        |      z
  }|dz
  |dz   }}|dkD  rh|dk\  s||k  r^|dk\  r||vr|j                  |       |dz  }|dz  }|dkD  r||k  r||vr|j                  |       |dz  }|dz  }|dkD  r|dk\  rX||k  r^t	        |      D cg c]  }| |   	 c}S c c}w )z|Select up to _LOCAL_TIMELINE_LIMIT entries centered on target_index.

    Always keeps first, target, and last entries.
    r   r   )r   _LOCAL_TIMELINE_LIMITr   ra   sorted)timelinetarget_indexnselectedbudgetlohir   s           r   _select_local_timeliner  "  s     8}--HAuHLLOLLQL1\""S]2FA|a/B
1*"'R!V7r)LLaKF
aA:"q&Rx%7LLaKF
a 1*"'R!V "(!12AHQK222s   Dc                <   t        |       t        k  r| S t        |       }t               }d|cxk  r|k  rn n|j                  |       d|cxk  r|k  rn n|j                  |       t        t        |      z
  }|dz
  |dz   }}|dkD  rh|dk\  s||k  r^|dk\  r||vr|j                  |       |dz  }|dz  }|dkD  r||k  r||vr|j                  |       |dz  }|dz  }|dkD  r|dk\  rX||k  r^|s
| t         d S t	        |      D cg c]  }| |   	 c}S c c}w )z~Select up to _WHOLE_TIMELINE_LIMIT entries for whole-doc comments.

    Prioritizes current entry and nearest self reply.
    r   r   N)r   _WHOLE_TIMELINE_LIMITr   ra   r  )	r  current_indexnearest_self_indexr  r  r  r  r  r   s	            r   _select_whole_timeliner  A  s5    8}--HAuHMA]#""'("S]2FQ 1B
1*"'R!V7r)LLaKF
aA:"q&Rx%7LLaKF
a 1*"'R!V ../00!'!12AHQK222s   
Du=  
This is a Feishu document comment thread, not an IM chat.
Do NOT call feishu_drive_add_comment or feishu_drive_reply_comment yourself.
Your reply will be posted automatically. Just output the reply text.
Use the thread timeline above as the main context.
If the quoted content is not enough, use feishu_doc_read to read nearby context.
The quoted content is your primary anchor — insert/summarize/explain requests are about it.
Do not guess document content you haven't read.
Reply in the same language as the user's comment unless they request otherwise.
Use plain text only. Do not use Markdown, headings, bullet lists, tables, or code blocks.
Do not show your reasoning process. Do not start with "I will", "Let me", or "I'll first".
Output only the final user-facing reply.
If no reply is needed, output exactly NO_REPLY.
)r  referenced_docsc                   t        ||
      }d|  ddt        |       ddt        |       ddt        |d       ddd	| d
d| d| d| ddt        |       dt        |       dg}|D ].  \  }}}|rdnd}|j                  d| dt        |       |        0 |r|j                  |       |j                  d       |j                  t               dj                  |      S )z3Build the prompt for a local (quoted-text) comment.zThe user added a reply in "".Current user comment text: ""zOriginal comment text: "zQuoted content: "r"   GThis comment mentioned you (@mention is for routing, not task content).Document link: Current commented document:- file_type=- file_token=z- comment_id=r%   zCurrent comment card timeline (/
 entries): <-- YOU[] r   )r  r  r   r   _COMMON_INSTRUCTIONSr   )	doc_titledoc_urlrP   rQ   rL   
quote_textroot_comment_texttarget_reply_textr  r   r  r   r  r  r   r   is_selfmarkers                     r   build_local_comment_promptr7  t  s&     &h=H &i[3
&y1B'C&DAF
"9->#?"@B
Ij#67q9Q
'#%
yk"

|$

|$

)#h-#h-
SE #+ ?w&Bq	IdO#4VH=>? _%	LL	LL%&99Ur   )r  r  r   c        
           t        |||      }
d|  ddt        |       dddd| dd	| d
| ddt        |
       dt        |       dg
}|
D ].  \  }}}|rdnd}|j                  d| dt        |       |        0 |	r|j                  |	       |j                  d       |j                  t               dj                  |      S )z.Build the prompt for a whole-document comment.zThe user added a comment in "r"  r#  r$  z!This is a whole-document comment.r%  r&  r'  r(  r)  r%   z!Whole-document comment timeline (r*  r+  r,  r-  r.  r   )r  r  r   r   r/  r   )r0  r1  rP   rQ   comment_textr  r   r  r  r   r  r  r   r   r5  r6  s                   r   build_whole_comment_promptr:    s     &h?QRH (	{"5
&y'>&?qA+Q
'#%
yk"

|$

+CM?!CM?*UE #+ ?w&Bq	IdO#4VH=>? _%	LL	LL%&99Ur   c                     ddl m} m}  |        } ||      }ddl m}  |       }|s'|j	                  d      r	 ddlm}  ||d         }||fS ||fS # t        $ r Y ||fS w xY w)zIResolve model and provider credentials, same as gateway message handling.r   )_load_gateway_config_resolve_gateway_model)_resolve_runtime_agent_kwargsprovider)get_default_model_for_provider)gateway.runr<  r=  r>  r5   hermes_cli.modelsr@  	Exception)r<  r=  user_configmodelr>  runtime_kwargsr@  s          r   _resolve_model_and_runtimerG    s    H&(K";/E924N ^''
3	H2>*3MNE .  5.    	.  	s   A 	A! A!2   i  zDict[str, Dict]_session_cachec                    d|  d| S )Nzcomment-doc:r   )rQ   rP   s     r   _session_keyrL    s    )Aj\22r   c                Z   t         5  t        j                  |       }|g cddd       S t        j                         |d   z
  t
        kD  r(t        | = t        j                  d|        g cddd       S t        j                         |d<   t        |d         cddd       S # 1 sw Y   yxY w)z1Load conversation history for a document session.Nlast_accessz$[Feishu-Comment] Session expired: %smessages)	_session_cache_lockrI  r5   _timetime_SESSION_TTL_Sr+   r,   r;   )keyentrys     r   _load_session_historyrV    s    	 
'""3'=
' 
'
 ::<%..?s#KK>D
' 
'  %zz|mE*%&
' 
' 
's   B!?B!3$B!!B*c                ^   |D cg c](  }|j                  d      dv r|j                  d      r|* }}t        |      t        kD  r
|t         d }t        5  |t	        j
                         dt        | <   t        j                  d| t        |             ddd       yc c}w # 1 sw Y   yxY w)zISave conversation history for a document session (keeps last N messages).role>   user	assistantr'   N)rO  rN  z0[Feishu-Comment] Session saved: %s (%d messages))	r5   r   _SESSION_MAX_MESSAGESrP  rQ  rR  rI  r+   r,   )rT  rO  r   cleaneds       r   _save_session_historyr]    s     55=11aeeI6F 	
G 
 7|++0012	 [ ::<
s 	FSQX\Z[ [[ [s   -B?B##B,c                d   ddl m} t        j                  d       ddlm} ddlm}  ||        ||       	 t               \  }}t        j                  d||j                  d      |j                  d      xs dd	d
        |rt        |      ng }|r t        j                  dt        |      |        |||j                  d      |j                  d      |j                  d      |j                  d      |j                  d      ddddddg      }	t        j                  dt        |       t        |             |	j                  | |xs d	      }
|
j                  d      xs dj                         }|
j                  dd      }t        j                  d|t        |      |d	d        |r |
j                  dg       }|rt        ||       | |d	        |d	       S # t        $ r0}t        j                  d|       Y d	}~ |d	        |d	       yd	}~ww xY w#  |d	        |d	       w xY w)zCreate an AIAgent with feishu tools and run the prompt.

    If *session_key* is provided, loads/saves conversation history for
    cross-card memory within the same document.

    Returns the agent's final response text, or empty string on failure.
    r   )AIAgentzR[Feishu-Comment] _run_comment_agent: injecting lark client into tool thread-locals)
set_clientzE[Feishu-Comment] _run_comment_agent: model=%s provider=%s base_url=%sr?  base_urlr%   NrH  zO[Feishu-Comment] _run_comment_agent: loaded %d history messages from session %sapi_keyapi_modecredential_poolT   
feishu_docfeishu_drive)rE  ra  rb  r?  rc  rd  
quiet_modeskip_context_filesskip_memorymax_iterationsenabled_toolsetsz[[Feishu-Comment] _run_comment_agent: calling run_conversation (prompt=%d chars, history=%d))conversation_historyfinal_response	api_callszR[Feishu-Comment] _run_comment_agent: done api_calls=%d response_len=%d response=%s   rO  z5[Feishu-Comment] _run_comment_agent: agent failed: %s)	run_agentr_  r+   r,   tools.feishu_doc_toolr`  tools.feishu_drive_toolrG  r5   rV  r   run_conversationr   r]  rC  	exception)promptr@   session_keyr_  set_doc_clientset_drive_clientrE  rF  historyagentrz   rA   ro  new_messageses                  r   _run_comment_agentr~    s    "
KKdeBF6V, : <~[>--j9N<N<Nz<Z<`^`bece;f	h 9D'4KKiGk3 #''
3"&&y1#''
3#''
3*../@A#*N;
 	qKW	/''W_PT'UJJ/06B==?JJ{A.	hs8}htn	> !::j"5L%k<@
 	t  PRSTt 	ts*   FG! !	H*H H HH H/NO_REPLYadd_comment	add_reply)r   c                 K   t         j                  d       t        |      }|t         j                  d       yt         j                  d       |d   }|d   }|d   }|d   }|d	   }|d
   }	|d   }
|r|r||k(  rt         j	                  d|       y|	r|r |	|k7  rt         j	                  d|	xs d       y|
r|
t
        vrt         j	                  d|
       y|r|r|st         j                  d       yt         j                  d|
||||       ddlm}m}m	}m
}  |       } ||||      }|j                  dv r+ ||      r#t        | ||       d{   }|r |||||      }|j                  st         j                  d||       y |||      s-t         j                  d||j                  |j                         yt         j                  d||j                  |j                         |r#t        j                   t#        | |||d             t         j                  d       t        j                   t%        | ||            }t        j                   t'        | |||            }t        j(                  ||       d{   \  }}|j+                  dd      }|j+                  dd      }t-        |j+                  d             }t         j                  d!||       t         j                  d"|       |rt         j                  d#       t/        | ||       d{   }g }d}d$}d$}|D ]  }|j+                  d%i       }t1        |t2              r	 t5        j6                  |      }|j+                  d&g       } | D ]X  }!t=        |!      }"t?        |!      }#|r|"|k(  nd'}$tA        |      }%|jC                  |"|#|$f       |"|k(  rtE        |!|      }|%}|$sW|%}Z  |s1tG        tI        tK        |                  D ]  \  }&\  }"}#}$|$r|#}|&} n t         j                  d(tA        |      |||r|dd) nd       g }'|D ][  }|j+                  d%i       }(t1        |(t2              r	 t5        j6                  |(      }(|'jM                  |(j+                  d&g              ] tO        |'      })|)rtQ        | |)       d{   })tS        |)|      }*tU        ||||||||||**
      }+n{t         j                  d+       tW        | ||||,       d{   } |j+                  d-d      },g }d}-d}.d$}/tK        |       D ]r  \  }&}!t=        |!      }"t?        |!      }#|r|"|k(  nd'}$|jC                  |"|#|$f       |&dk(  rtE        |!|      }-|!j+                  dd      }0|0s_|0|k(  setE        |!|      }.|&}/t |.s6|r4tG        tI        tK        |                  D ]  \  }&\  }"}#}$|"|k(  s|#}.|&}/ n t         j                  d.tA        |      |/|,r|,dd/ nd|-r|-dd/ nd|.r|.dd/ nd       tO        |       })|)rtQ        | |)       d{   })tS        |)|      }*tY        ||||||,|-|.|||/|*0      }+t         j                  d1tA        |+             t         j	                  d2|+       t[        ||      }1t        j\                         }2|2j_                  dt`        |+| |1       d{   }3|3rtb        |3v rt         j                  d3       nt         j                  d4tA        |3      |3dd5        t         j                  d6||       te        | ||||3|       d{   }4|4rt         j                  d7       nt         jg                  d8       |rti        | |||d       d{    t         j                  d9       y7 E7 7 # t4        j8                  t:        f$ r i }Y Zw xY w# t4        j8                  t:        f$ r i }(Y pw xY w7 57 7 7 *7 7 uw):a  Full orchestration for a drive comment event.

    1. Parse event + filter (self-reply, notice_type)
    2. Add OK reaction
    3. Fetch doc meta + comment details in parallel
    4. Branch on is_whole: build timeline
    5. Build prompt, run agent
    6. Deliver reply
    zG[Feishu-Comment] ========== handle_drive_comment_event START ==========Nz7[Feishu-Comment] Dropping malformed drive comment eventz5[Feishu-Comment] [Step 0/5] Event parsed successfullyrP   rQ   rL   rM   rT   rU   rR   z6[Feishu-Comment] Skipping self-authored event: from=%sz<[Feishu-Comment] Skipping event not addressed to self: to=%sz(empty)z([Feishu-Comment] Skipping notice_type=%sz2[Feishu-Comment] Missing required fields, skippingz?[Feishu-Comment] Event: notice=%s file=%s:%s comment=%s from=%sr   )load_configresolve_ruleis_user_allowedhas_wiki_keys>   topwildcard)r   z6[Feishu-Comment] Comments disabled for %s:%s, skippingz4[Feishu-Comment] User %s denied (policy=%s, rule=%s)z:[Feishu-Comment] Access granted: user=%s policy=%s rule=%sr^   )rP   rQ   rM   r_   zJ[Feishu-Comment] [Step 2/5] Parallel fetch: doc meta + comment batch_queryru   Untitledrv   r%   r   z6[Feishu-Comment] Comment context: title=%s is_whole=%sz;[Feishu-Comment] [Step 3/5] Building timeline (is_whole=%s)zA[Feishu-Comment] Fetching whole-document comments for timeline...r  r   r   FzQ[Feishu-Comment] Whole timeline: %d entries, current_idx=%d, self_idx=%d, text=%srw   )
r0  r1  rP   rQ   r9  r  r   r  r  r   z3[Feishu-Comment] Fetching comment thread replies...r   r   zV[Feishu-Comment] Local timeline: %d entries, target_idx=%d, quote=%s root=%s target=%sr   )r0  r1  rP   rQ   rL   r2  r3  r4  r  r   r  r   zE[Feishu-Comment] [Step 4/5] Prompt built (%d chars), running agent...z [Feishu-Comment] Full prompt:
%sz;[Feishu-Comment] Agent returned NO_REPLY, skipping deliveryz.[Feishu-Comment] Agent response (%d chars): %srp  zI[Feishu-Comment] [Step 5/5] Delivering reply (is_whole=%s, comment_id=%s)z-[Feishu-Comment] Reply delivered successfullyz([Feishu-Comment] Failed to deliver replyzE[Feishu-Comment] ========== handle_drive_comment_event END ==========)5r+   r,   r]   r?   rV   _ALLOWED_NOTICE_TYPES-plugins.platforms.feishu.feishu_comment_rulesr  r  r  r  match_sourcer   enabledpolicyr/   ensure_futurerk   r{   r   gatherr5   rY   r   r8   r=   r-   r4   r6   r   r   r   r   r   r   reversedr;   r   r   r   r   r	  r:  r   r7  rL  get_running_looprun_in_executorr~  _NO_REPLY_SENTINELr   rg   rn   )5r@   r(   r   parsedrP   rQ   rL   rM   rT   rU   rR   r  r  r  r  comments_cfgruler   	meta_taskcomment_taskdoc_metacomment_detailr0  r1  r   whole_commentsr  current_textr  r  wcr   r   r   r   r   r5  idxr   all_raw_repliesrl	doc_linksref_docs_textrv  r2  	root_texttarget_textr  ridsess_keylooprA   r   s5                                                        r   handle_drive_comment_eventr  `  s0     KKYZ&t,F~PQ
KKGH%J{#I%Jj!H.)L%J'K )EM|\,:+ESU_Ulclm{*???MYjKL
KKIY
J xw=Li<D //M,4O5fiTT
iPZ[D<<LiYcd4.JLZ^ZeZegkgxgxy
KKLl\`\g\gimiziz{ %#!"	
 KK\]%%FJ	:I ((FJ	:FL &-^^I|%LLHnWj1Ill5"%GN&&z23H
KK@8 KKMxXWX26:yQQ02  	-Bb1J*c*$!%J!7J !nnY3G 
-(+*1-3?3,.U(mdG 45,&#9!\#JL$'M),&
-	-( +3D89L4M+N ''Cw#'L$%M	 	gM=2D)5L"%9	F
   	:Bb)B"c"BB ""266)R#89	: (8	1&)DDI/	:F+!%%'1)
 	IJ,J	:$
 

 $''4
	g& 
	!DAq$Q'C&q)D/;sl*GOOS$01Av21lC	%%
B'Csh4QE 
	! x+3D89L4M+N ''Cw,&"&K#$L	 	lM<'1JsOy&/IcrNY(3K$		D (0	1&)DDI/	:F+!!!')%%)
 KKWY\]cYde
LL4f= Iz2H##%D)) &&( H )X5QRDc(mU]^b_bUcd 	_aikuv-J	:x
 
 KKGHLLCD %!
 	
 	
 KKWXa U>  M  R ,,i8 $!#J$B ,,i8 B
 E&
N E2
	
s  E_3^D,_3>^?B_3^3_3^#A*_31_3 A_3_/?_3._#/A_3>_&?B_3_3A _3A_3)_)*B_3_,A1_34_/5A_3;_1<_3_3_3^>:_3=^>>_3_ _3_  _3&_3)_3,_3/_31_3)NNN)r   r=   r   r=   )r(   r   returnzOptional[Dict[str, Any]])r@   r   rP   r=   rQ   r=   rM   r=   r_   r=   r  rY   )r@   r   rP   r=   rQ   r=   r  Dict[str, Any])
r@   r   rP   r=   rQ   r=   rL   r=   r  r  )r@   r   rP   r=   rQ   r=   r  List[Dict[str, Any]])r@   r   rP   r=   rQ   r=   rL   r=   r   r=   r  r  )r   r=   r  r=   )r@   r   rP   r=   rQ   r=   rL   r=   r   r=   r  zTuple[bool, int])
r@   r   rP   r=   rQ   r=   r   r=   r  rY   )r   r=   r   intr  z	List[str])r@   r   rP   r=   rQ   r=   rL   r=   r   r=   r   rY   r  rY   )r   r  r  r=   )r%   )r   r  r   r=   r  r=   )r   r  r  List[Dict[str, str]])r@   r   r   r=   r   r=   r  zOptional[str])r@   r   r   r  r  r  )r   r  r  r=   r  r=   )r   r=   r   r  r  r=   )r  List[Tuple[str, str, bool]]r  r  r  r  )r  r  r  r  r  r  r  r  )r0  r=   r1  r=   rP   r=   rQ   r=   rL   r=   r2  r=   r3  r=   r4  r=   r  r  r   r=   r  r  r   r=   r  r=   )r0  r=   r1  r=   rP   r=   rQ   r=   r9  r=   r  r  r   r=   r  r  r  r  r   r=   r  r=   )r  zTuple[str, dict])rQ   r=   rP   r=   r  r=   )rT  r=   r  r  )rT  r=   rO  r  r  None)rv  r=   r@   r   rw  r=   r  r=   )r@   r   r(   r   r   r=   r  r  )N__doc__
__future__r   r/   r-   loggingtypingr   r   r   r   r   	getLoggerrX   r+   r   rE   r]   rh   rk   rn   rx   r   r   r   r   r   r{   r   r   r   r   r   r   r   r   _REPLY_CHUNK_SIZEr   r   r   r   r   re_recompiler   r   r   r   r   r	  _PROMPT_TEXT_LIMITr  r  r  r  r  r   r/  r7  r:  rG  	threadingrR  rQ  r[  rS  LockrP  rI  __annotations__rL  rV  r]  r~  r  r  r  rK  r   r   <module>r     s@  , #    3 3			8	$.#V+d J // / 	/
 / / 
/p %% % 	%
 % % 
%X @ W E X Y G '' '-0''T   00 0-00>A00f(( (-0((Z !@@ @-0@>A@@ @FP
 -0>AILD -08;	:   ): $((( ( 	(
 ( ( 
(`04@  !S[[(  : B+.*### #N <>58(     '9  3)33 !3> 3) 3 3  3 !	 3F 
EG 8 ** * 	*
 * * * * * ** * * * 	*l  && & 	&
 & & *& & & & & 	&\!2   $inn& "$ $3'[$<F    '4  46FYFYFY-0FY	FYr   