+
    kHàj)  ã                  ó~  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIH	t	H
t
 ^ RIHt ^ RIHt ]P                  ! ]4      t^ t^2t^t^tRtRt ! R R]4      t ! R	 R
]4      t]P6                  ! R]P8                  4      tR R ltR R ltR R lt R R lt!R R lt" ! R R4      t#Rs$R]%R&   R R lt&R R lt'R# )u  Signal attachment rate-limit scheduler: process-wide token-bucket simulator mirroring the per-account
attachment rate limit signal-cli/Signal-Server enforce. Producers (``SignalAdapter.send_multiple_images``
and the ``send_message`` tool's Signal path) call ``acquire(n)`` before an attachment send; on a 429 they
call ``feedback(retry_after, n)`` so the model recalibrates from the server's authoritative hint.
Concurrent calls serialize through an ``asyncio.Lock`` â€” FIFO fairness across sessions sharing one daemon.)ÚannotationsN)ÚAnyÚOptional)Út)Úparse_retry_after_secondsg      $@c                  ó6   a € ] tR t^tRtRR V 3R llltRtV ;t# )ÚSignalRateLimitErroru¿   Raised by ``SignalAdapter._rpc`` for rate-limit responses when ``raise_on_rate_limit=True``.
``retry_after`` is the server's per-token Retry-After in seconds (signal-cli â‰¥ v0.14.3) or None.c               ó$   € V ^8„  d   QhRRRRRR/# )é   ÚmessageÚstrÚretry_afterúOptional[float]ÚreturnÚNone© )Úformats   "ÚO/home/ubuntu/.hermes/hermes-agent/plugins/platforms/signal/signal_rate_limit.pyÚ__annotate__Ú!SignalRateLimitError.__annotate__    s!   € ÷ 'ñ ' ð '°/ð 'ÈTñ 'ó    c                	ó2   <€ \         SV `  V4       W n        R # ©N)ÚsuperÚ__init__r   )Úselfr   r   Ú	__class__s   &&&€r   r   ÚSignalRateLimitError.__init__    s   ø€ Ü‰Ñ˜Ô!Ø&Ör   )r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r   Ú__static_attributes__Ú__classcell__)r   s   @r   r   r      s   ø† ñj÷'÷ 'ð 'r   r   c                  ó   € ] tR t^%tRtR# )ÚSignalSchedulerErrorr   N)r   r   r    r!   r#   r   r   r   r&   r&   %   s   † Ûr   r&   z$Retry after (\d+(?:\.\d+)?)\s*secondc               ó    € V ^8„  d   QhRRRR/# )r
   Úerrr   r   r   r   )r   s   "r   r   r   .   s   € ÷ Nñ N˜ð N ñ Nr   c                óz   € \        V \        4      '       d   \        V P                  R R4      4      # \        V 4      # )r   Ú )Ú
isinstanceÚdictr   Úget)r(   s   &r   Ú_error_messager.   .   s-   € Ü*4°S¼$×*?Ò*?Œ3ˆs�w‰w�y "Ó%Ó&ÐMÄSÈÃXÐMr   c               ó    € V ^8„  d   QhRRRR/# )r
   r(   r   r   r   r   )r   s   "r   r   r   2   s   € ÷ Hñ H cð H¨oñ Hr   c                óh  € \        V \        4      '       dÒ   V P                  R4      ;'       g    / P                  R4      ;'       g    / P                  R4      ;'       g    . pV Uu. uFN  p\        V\        4      '       g   K  VP                  R4      '       g   K4  \        VP                  R4      4      NKP  	  ppV Uu. uF
  qDf   K  VNK  	  up;p'       d   \	        V4      # \
        P                  \        V 4      4      pV'       d   \        VP                  ^4      4      # R# u upi u upi )u  Per-token Retry-After from a signal-cli rate-limit error, or None. Sources, in order:
``error.data.response.results[*].retryAfterSeconds`` (signal-cli â‰¥ v0.14.3), then "Retry after N
seconds" parsed from the message (RetryLaterException wrapped as AttachmentInvalidException).ÚdataÚresponseÚresultsÚretryAfterSecondsN)	r+   r,   r-   r   ÚmaxÚ_RETRY_AFTER_REÚsearchr.   Úgroup)r(   r3   ÚrÚ
candidatesÚcÚmatchs   &     r   Ú_extract_retry_after_secondsr=   2   sü   € ô �#”t×ÒØ—G‘G˜F“O×)Ð) r×.Ñ.¨zÓ:×@Ð@¸b×EÑEÀiÓP×VÐVÐTVˆÙU\ó MÑU\ÐPQÜ# A¤t×,ô LØ12·±Ð7J×1Kô LÔ/°·±Ð6IÓ0JÖKÑU\ˆ
ð Má%/ÓA¡Z Ÿ!˜!¡ZÑAÐAˆ:ÖAÜ�z“?Ð"Ü×"Ñ"¤>°#Ó#6Ó7€Eß8=Ô$ U§[¡[°£^Ó4ÐGÀ4ÐGùòMùâAs   Á(D*ÂD*ÂD*ÃD/ÃD/c               ó    € V ^8„  d   QhRRRR/# )r
   r(   r   r   Úboolr   )r   s   "r   r   r   @   s   € ÷ rñ r Sð r¨Tñ rr   c                ó,  a€ \        V \        4      '       d   V P                  R4      \        8X  d   R# \	        V 4      pVP                  4       oRV9   ;'       g;    \        ;QJ d    V3R lR 4       F  '       g   K   R# 	  R# ! V3R lR 4       4      # )u  True if a signal-cli RPC error reflects a rate-limit failure: the typed ``RATELIMIT_ERROR`` code
(â‰¥ v0.14.3), legacy ``[429]`` / ``RateLimitException`` substrings, or libsignal-net's
``RetryLaterException`` / "Retry after N seconds" leaked through AttachmentInvalidException.ÚcodeTz[429]c              3  ó,   <"  € T F	  qS9   x € K  	  R # 5ir   r   )Ú.0ÚsÚ	msg_lowers   & €r   Ú	<genexpr>Ú._is_signal_rate_limit_error.<locals>.<genexpr>H   s   øé € Ð$qÑ=p¸¨)¦^Ó=pùs   ƒF)Ú	ratelimitÚretrylaterexceptionzretry after)r+   r,   r-   ÚSIGNAL_RPC_ERROR_RATELIMITr.   ÚlowerÚany)r(   r   rE   s   & @r   Ú_is_signal_rate_limit_errorrM   @   sr   ø€ ô �#”t×Ò §¡¨£Ô4NÔ!NÙÜ˜SÓ!€GØ—‘“€IØ�gÑ×qÐq§£Ô$qÑ=pÓ$q§¤Ðq¢Ðq Ô$qÑ=pÓ$qÓ!qÐqr   c               ó    € V ^8„  d   QhRRRR/# )r
   ÚsecondsÚfloatr   r   r   )r   s   "r   r   r   K   s   € ÷ Kñ K˜%ð K Cñ Kr   c           
     ó¢   € \        RV 4      pV^Z8  d   \        R\        V4      R7      # \        R\        ^\        V^<,          4      4      R7      # )z9Human-friendly wait label for user-facing pacing notices.ç        zplatform.signal.wait_seconds)Úcountzplatform.signal.wait_minutes)r5   r   Úround)rO   rD   s   & r   Ú_format_waitrU   K   sk   € äˆC�Ó€AØ@AÀBÄŒ1Ð+´5¸³8Ô<ð  KÌAÐNlÔtwÐxyô  |Að  BCð  FHõ  BHó  |Ió  uJô  MKð  Kr   c               ó    € V ^8„  d   QhRRRR/# )r
   Únum_attachmentsÚintr   rP   r   )r   s   "r   r   r   Q   s   € ÷ Nñ N¨#ð N°%ñ Nr   c                ó:   € V ^ 8:  d   R# \        RRV ,          4      # )zÃHTTP timeout for a Signal ``send`` RPC: signal-cli uploads attachments serially inside the call, so
the default 30s truncates large batches mid-upload (phantom failure). 5s/attachment, 60s floor.g      >@g      N@g      @)r5   )rW   s   &r   Ú_signal_send_timeoutrZ   Q   s"   € ð # aÔ'ˆ4ÐM¬S°°s¸_Õ7LÓ-MÐMr   c                  óž   € ] tR t^WtRt]! ]4      ]! ]4      3R R lltRR R llt	R R lt
R	 R
 ltR R ltR R ltR R ltR R ltRtR# )ÚSignalAttachmentSchedulerur  Process-wide token-bucket simulator for Signal attachment sends: up to ``capacity`` tokens (default
50 = Signal's server bucket), one per attachment, refilling at ``refill_rate``/s (calibrated from the
server's per-token Retry-After once a 429 has been observed; default 1 token / 4s). ``acquire(n)``
calls serialize through an ``asyncio.Lock`` â€” FIFO across sessions.c               ó$   € V ^8„  d   QhRRRRRR/# )r
   ÚcapacityrP   Údefault_retry_afterr   r   r   )r   s   "r   r   Ú&SignalAttachmentScheduler.__annotate__]   s#   € ÷ $ñ $ ð $Ø&+ð$Ø_cñ$r   c                	óÜ   € \        V4      V n        \        V4      V n        R \        V4      ,          V n        \        P
                  ! 4       V n        \        P                  ! 4       V n	        R# )ç      ð?N)
rP   r^   ÚtokensÚrefill_rateÚtimeÚ	monotonicÚlast_refillÚasyncioÚLockÚ_lock)r   r^   r_   s   &&&r   r   Ú"SignalAttachmentScheduler.__init__]   sG   € ä˜h›ˆŒÜ˜H“oˆŒØ¤Ð':Ó!;Õ;ˆÔÜŸ>š>Ó+ˆÔÜ—\’\“^ˆŽ
r   Nc               ó    € V ^8„  d   QhRRRR/# )r
   Únowr   r   rP   r   )r   s   "r   r   r`   e   s   € ÷ ñ  _ð Àñ r   c                ó$  € Vf   \         P                  ! 4       MTV P                  ,
          pV^ 8”  dS   V P                  V P                  8  d8   \        V P                  V P                  W P                  ,          ,           4      # V P                  # )z@Tokens the bucket would hold at ``now``, without mutating state.)re   rf   rg   rc   r^   Úminrd   )r   rm   Úelapseds   && r   Ú_projected_tokensÚ+SignalAttachmentScheduler._projected_tokense   sb   € à'*¢{”4—>’>Ô#¸¸t×?OÑ?OÕOˆØ�QŒ;˜4Ÿ;™;¨¯©Ô6Ü�t—}‘} d§k¡k°G×>NÑ>NÕ4NÕ&NÓOÐOØ�{‰{Ðr   c               ó   € V ^8„  d   QhRR/# ©r
   r   r   r   )r   s   "r   r   r`   l   s   € ÷ ñ ˜ñ r   c                	óh   € \         P                  ! 4       pV P                  V4      V n        Wn        R # r   )re   rf   rq   rc   rg   )r   rm   s   & r   Ú_refillÚ!SignalAttachmentScheduler._refilll   s&   € Ü�nŠnÓˆØ×,Ñ,¨SÓ1ˆŒØÖr   c               ó    € V ^8„  d   QhRRRR/# ©r
   ÚnrX   r   rP   r   )r   s   "r   r   r`   q   s   € ÷ Cñ C˜sð C uñ Cr   c                ód   € WP                  4       ,
          pV^ 8:  d   R# W P                  ,          # )uÈ   Seconds until ``n`` tokens would be available (lock-free, informational â€” decides whether to emit
a pacing notice *before* a possibly-blocking ``acquire``; races vs. concurrent acquires are benign).rR   )rq   rd   )r   rz   Údeficits   && r   Úestimate_waitÚ'SignalAttachmentScheduler.estimate_waitq   s/   € ð ×,Ñ,Ó.Õ.ˆØ ”lˆsÐB¨×2BÑ2BÕ(BÐBr   c               ó    € V ^8„  d   QhRRRR/# ry   r   )r   s   "r   r   r`   w   s   € ÷  ñ  ˜sð   uñ  r   c           
   ƒ  ó  "  € V^ 8:  d   R# WP                   8”  d   \        RV RV P                    R24      hRRr2 V P                  ;_uu_4       GRj  x€L
  V P                  4        V P                  V8¼  dE   V'       d   V^ 8”  d"   \
        P                  RWP                  V4       TuuRRR4      GRj  x€L
  # WP                  ,
          pRRR4      GRj  x€L
  XV P                  ,          pV'       dA   \
        P                  RWQV P                  W@P                  R	V P                  ,          4       R
p\        P                  ! V4      G Rj  x€L
  W%,          pEK)   EL L® L�  + GRj  x€L 
 '       g   i     L¤; i L25i)uÁ  Block until at least ``n`` tokens are available; return the seconds slept. Does **not** deduct
tokens â€” the bucket is a read-only model of server-side capacity; call ``report_rpc_duration()``
after the RPC to sync. The lock is released during ``asyncio.sleep`` so other callers interleave,
and the loop re-checks after each sleep in case the deadline was pessimistic. Signal's server is
ground truth and will 429 (â†’ requeue) if the model drifts.rR   z'Signal scheduler was called requesting z tokens (max is Ú)TNzNSignal scheduler: tokens sufficient for %d (remaining=%.1f, total_slept=%.1fs)uk   Signal scheduler: pausing %.1fs for %d tokens (available=%.1f, deficit=%.1f, refill=%.4f/s â‰ˆ %.1fs/token)rb   F)r^   r&   rj   rv   rc   ÚloggerÚdebugrd   Úinforh   Úsleep)r   rz   Útotal_sleptÚ
first_passr|   Úwaits   &&    r   ÚacquireÚ!SignalAttachmentScheduler.acquirew   s&  é € ð �Œ6ÙØ�}‰}ÔÜ&Ð)PÐQRÐPSÐScÐdh×dqÑdqÐcrÐrsÐ'tÓuÐuØ"% t�ZØØ—z—z—z“zØ—‘”Ø—;‘; !Ô#ß%¨°q¬ÜŸ™Ð%uØ%&¯©°[ôBà&÷ "—z’zð Ÿk™k�/�÷ "—zð ˜T×-Ñ-Õ-ˆDßÜ—‘ð =Ø>BÀtÇ{Á{ÐT[×]mÑ]mØ $×"2Ñ"2Õ2ô4ð #�
Ü—-’- Ó%×%Ð%ØÕ‹K÷ "—z—z—zúñ &ùs…   ‚AF	ÁE$ÁF	Á)E+Â)E+Â+F	Â8E'Â9F	Â>E+ÃF	ÃE)ÃF	Ã;AF	ÅFÅF	Å'F	Å)F	Å+F	Å1E4Å2
F	Å=F	Å?	F	c               ó$   € V ^8„  d   QhRRRRRR/# )r
   Úrpc_durationrP   Ún_attachmentsrX   r   r   r   )r   s   "r   r   r`   ”   s"   € ÷ %ñ %°eð %ÈCð %ÐTXñ %r   c           
   ƒ  óþ  "  € V^ 8:  d   R# V P                   ;_uu_4       GRj  x€L
  V P                  p\        RV\        V4      ,
          4      V n        \        P
                  ! 4       V n        RRR4      GRj  x€L
  \        P                  V^
8”  d   V^8”  d   \        P                  M\        P                  RW!XV P                  VV P                  4       R#  L¸ Lc  + GRj  x€L 
 '       g   i     Lz; i5i)a"  Deduct ``n_attachments`` tokens for a completed send RPC. No refill is credited for the upload
window: Signal's server checks the bucket at RPC start and resumes refill only after the response,
so crediting it causes cumulative drift that eventually triggers 429s. Advances ``last_refill``.NrR   u€   Signal scheduler: RPC for %d att took %.1fs â€” tokens %.1f â†’ %.1f (deducted=%d, no upload refill credited, refill=%.4fsâ�»Â¹))rj   rc   r5   rP   re   rf   rg   r‚   ÚlogÚloggingÚINFOÚDEBUGrd   )r   rŒ   r�   Útoken_befores   &&& r   Úreport_rpc_durationÚ-SignalAttachmentScheduler.report_rpc_duration”   sª   é € ð ˜AÔÙØ—:—:—:“:ØŸ;™;ˆLÜ˜c <´%¸Ó2FÕ#FÓGˆDŒKÜ#Ÿ~š~Ó/ˆDÔ÷ —:ô 	�
‰
 <°"Ô#4¸ÈÔ9J”7—<’<ÔPW×P]ÑP]ð2Ø3@ÐP\Ð^b×^iÑ^iÐkxØ×#Ñ#ö	%÷	 —:—:”:üsL   ‚"C=¤C¥C=¨AC!Á0C=Á;CÁ<A"C=ÃC=Ã!C:	Ã'C*Ã(
C:	Ã3C:	Ã5C=c               ó$   € V ^8„  d   QhRRRRRR/# )r
   r   r   Ún_attemptedrX   r   r   r   )r   s   "r   r   r`   £   s!   € ÷ ,ñ , Oð ,À#ð ,È$ñ ,r   c                óâ   € V'       dF   V^ 8”  d?   R\        V4      ,          ;q0P                  8w  d   \        P                  RW14       W0n        RV n        \
        P                  ! 4       V n        R# )z–Apply server feedback after a 429: empty the bucket and, when ``retry_after`` (per-token refill
window) is present, calibrate ``refill_rate`` from it.rb   zaSignal scheduler: calibrating refill_rate to %.4f tokens/sec (server retry_after=%.1fs per token)rR   N)rP   rd   r‚   r„   rc   re   rf   rg   )r   r   r—   Únew_rates   &&& r   ÚfeedbackÚ"SignalAttachmentScheduler.feedback£   sW   € ÷ ˜;¨œ?¸CÄ%ÈÓBTÕ<TÐ0T°×YiÑYiÔ/iÜ�K‰Kð %Ø&.ô=à'ÔØˆŒÜŸ>š>Ó+ˆÖr   c               ó   € V ^8„  d   QhRR/# )r
   r   r,   r   )r   s   "r   r   r`   ­   s   € ÷ xñ x�tñ xr   c                ó  € R\        V P                  4       ^4      R\        V P                  4      R\        V P                  ^4      RV P                  ^ 8”  d   \        RV P                  ,          ^4      /# \        R4      /# )z\Current scheduler state for diagnostic logging (read-only, doesn't advance ``last_refill``).rc   r^   rd   Úrefill_seconds_per_tokenrb   Úinf)rT   rq   rX   r^   rd   rP   )r   s   &r   ÚstateÚSignalAttachmentScheduler.state­   s}   € àœ% × 6Ñ 6Ó 8¸!Ó<¸jÌ#ÈdÏmÉmÓJ\Øœu T×%5Ñ%5°qÓ9Ø*ÐPT×P`ÑP`ÐcdÔPd¬E°#¸×8HÑ8HÕ2HÈ!Ó,Lðxð 	xäjoÐpuÓjvðxð 	xr   )rj   r^   rg   rd   rc   r   )r   r   r    r!   r"   rP   Ú!SIGNAL_RATE_LIMIT_BUCKET_CAPACITYÚ%SIGNAL_RATE_LIMIT_DEFAULT_RETRY_AFTERr   rq   rv   r}   r‰   r”   rš   r    r#   r   r   r   r\   r\   W   sL   † ñMñ
 */Ð/PÓ)QÙ.3Ð4YÓ.Z÷$÷õõ
Cõ õ:%õ,÷xñ xr   r\   z#Optional[SignalAttachmentScheduler]Ú
_schedulerc               ó   € V ^8„  d   QhRR/# )r
   r   r\   r   )r   s   "r   r   r   ·   s   € ÷ ñ Ð0ñ r   c                 óÖ   € \         f]   \        4       s \        P                  R\	        \         P
                  4      \         P                  R\         P                  ,          4       \         # )z?Return the process-wide scheduler, creating it on first access.uM   Signal scheduler: created (capacity=%d tokens, refill=%.4f/s â‰ˆ %.1fs/token)rb   )r¤   r\   r‚   r„   rX   r^   rd   r   r   r   Úget_schedulerr§   ·   sK   € ô ÒÜ.Ó0ˆ
Ü�‰ÐcÜœ
×+Ñ+Ó,¬j×.DÑ.DÀcÌJ×LbÑLbÕFbô	däÐr   c               ó   € V ^8„  d   QhRR/# rt   r   )r   s   "r   r   r   Á   s   € ÷ ñ ˜$ñ r   c                 ó
   € Rs R# )zVDrop the cached scheduler so the next ``get_scheduler`` builds a fresh one. Test-only.N)r¤   r   r   r   Ú_reset_schedulerrª   Á   s	   € ð ‚Jr   éûÿÿÿ)(Ú__conditional_annotations__r"   Ú
__future__r   rh   r�   Úrere   Útypingr   r   Ú
agent.i18nr   Úagent.retry_utilsr   Ú	getLoggerr   r‚   ÚSIGNAL_MAX_ATTACHMENTS_PER_MSGr¢   r£   ÚSIGNAL_RATE_LIMIT_MAX_ATTEMPTSÚ$SIGNAL_BATCH_PACING_NOTICE_THRESHOLDrJ   Ú	Exceptionr   r&   ÚcompileÚ
IGNORECASEr6   r.   r=   rM   rU   rZ   r\   r¤   Ú__annotations__r§   rª   )r¬   s   @r   Ú<module>rº      sÍ   øðôpõ #ã Û Û 	Û ß  å Ý 7à	×	Ò	˜8Ó	$€à!#Ð Ø$&Ð !Ø()Ð %Ø!"Ð Ø'+Ð $ØÐ ô'˜9ô 'ô	˜9ô 	ð —*’*ÐDÀbÇmÁmÓT€õNõHõrõKõN÷Zxñ Zxðz 37€
Ð/Ó 6õ÷r   