+
    jHàj,  ã                  ó  € 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Ht ]P                  ! ]4      t^<tRtRt]P&                  ! R]P(                  4      t/ ]P/                  R&^4      C]P/                  R'^<4      C]P/                  R(R4      C]P/                  R)R	4      CtR
]R3R]^ 3R]R3R]R3R]R3R]^ 3/tR R ltR R lt]	 ! R R4      4       tR R lt Rt!R R lt"R R lt#R  R! lt$ ! R" R#4      t%R$ R% lt&. R*Ot'R# )+uÀ  Session heartbeats â€” recurring re-entry prompts for the current session.

Session-scoped and in-process (CLI or gateway must be running); durable cross-process scheduling stays
``hermes cron``. Invariants (mirrors goals.py): injection is a plain user message â€” no system-prompt
mutation or toolset swap, so prompt caching stays intact â€” and a real user message always wins:
heartbeats only fire into an idle session with an empty input queue.)ÚannotationsN)ÚasdictÚ	dataclass)ÚAnyÚOptionalg      @uÞ   [Heartbeat â€” recurring instruction, fires every {interval}]
{prompt}

If there is nothing meaningful to do or report for this instruction right now, reply briefly that nothing has changed and stop â€” do not invent work.zh^\s*(?:every\s+)?(\d+(?:\.\d+)?)\s*(s|sec|secs|seconds?|m|min|mins|minutes?|h|hr|hrs|hours?|d|days?)\s*$é  é€Q ÚpromptÚ Úinterval_secondsÚstatusÚactiveÚ
created_atç        Úlast_fired_atÚ
fire_countc               ó    € V ^8„  d   QhRRRR/# )é   ÚtextÚstrÚreturnzOptional[int]© )Úformats   "Ú9/home/ubuntu/.hermes/hermes-agent/hermes_cli/heartbeat.pyÚ__annotate__r   -   s   € ÷ 	=ñ 	=˜ð 	= ñ 	=ó    c                ó  € V '       d   \         P                  V 4      MRpV'       g   R# \        \        VP	                  ^4      4      \
        VP	                  ^4      P                  4       ,          ,          4      pV\        8  d   R# T# )z¯Parse ``10m`` / ``every 2h`` / ``every 90 minutes`` into seconds.

None when not an interval; below ``MIN_INTERVAL_SECONDS`` returns -1 so callers can tell "too small" apart.
Néÿÿÿÿ)Ú_INTERVAL_REÚmatchÚintÚfloatÚgroupÚ_UNIT_SECONDSÚlowerÚMIN_INTERVAL_SECONDS)r   ÚmÚsecondss   &  r   Úparse_intervalr(   -   sc   € ÷
 %)Œ×Ñ˜4Ô ¨d€AßÙÜ”%˜Ÿ™ ›
Ó#¤m°A·G±G¸A³J×4DÑ4DÓ4FÕ&GÕGÓH€GØÔ/Ô/ˆ2Ð<°WÐ<r   c               ó    € V ^8„  d   QhRRRR/# )r   r'   r    r   r   r   )r   s   "r   r   r   9   s   € ÷ pñ p˜Sð p Sñ pr   c                óP   a € \        S 4      o Rp\        V 3R lV 4       S  R24      # )u.   Human-readable interval (``600`` â†’ ``10m``).c              3  ób   <"  € T F$  w  rSV,          ^ 8X  g   K  SV,           V 2x € K&  	  R# 5i)é    Nr   )Ú.0ÚunitÚsuffixr'   s   &  €r   Ú	<genexpr>Ú"format_interval.<locals>.<genexpr>=   s1   øé € Ð_Á%±,°$È7ÐUYÍ>Ð]^ÑK^Ô-�G˜t•OÐ$ V HÕ-Ã%ùs   ƒ/š/Ús))r   Úd)r   Úh)é<   r&   )r    Únext)r'   Úunitss   f r   Úformat_intervalr8   9   s.   ø€ ä�'‹l€GØ2€EÜÔ_Á%Ó_ÐdkÐclÐlmÐanÓoÐor   c                  ó¦   € ] tR t^@t$ RtR]R&   R]R&   RtR]R&   RtR	]R
&   RtR	]R&   ^ t	R]R&   R R lt
]R R l4       tRR R lltR R ltRtR# )ÚHeartbeatStatez#Serializable per-session heartbeat.r   r	   r    r   r   r   r   r!   r   r   r   c               ó   € V ^8„  d   QhRR/# ©r   r   r   r   )r   s   "r   r   ÚHeartbeatState.__annotate__K   s   € ÷ <ñ <˜ñ <r   c                	óD   € \         P                  ! \        V 4      R R7      # )F)Úensure_ascii)ÚjsonÚdumpsr   ©Úselfs   &r   Úto_jsonÚHeartbeatState.to_jsonK   s   € Ü�zŠzœ& ›,°UÔ;Ð;r   c               ó    € V ^8„  d   QhRRRR/# )r   Úrawr   r   r:   r   )r   s   "r   r   r=   O   s   € ÷ uñ u˜Cð u Nñ ur   c                	óà   € \         P                  ! V4      pT ! R/ \        P                  4        UUUu/ uF)  w  pw  rEY4! VP	                  V4      ;'       g    T4      bK+  	  upppB # u upppi )Nr   )r@   ÚloadsÚ_STATE_FIELDSÚitemsÚget)ÚclsrG   ÚdataÚnameÚcoerceÚdefaults   &&    r   Ú	from_jsonÚHeartbeatState.from_jsonN   s]   € ä�zŠz˜#‹ˆÙÑtÔ]j×]pÑ]pÔ]rÕsÑ]rÑBYÀ$ÑHYÈ�d˜F 4§8¡8¨D£>×#<Ð#<°WÓ=Ò=Ñ]rÓsÑtÐtùÔss   ³#A)ÁA)Nc               ó    € V ^8„  d   QhRRRR/# )r   ÚnowúOptional[float]r   Úboolr   )r   s   "r   r   r=   S   s   € ÷ xñ x˜/ð x°Tñ xr   c                	ó  € V P                   R 8w  g$   V P                  '       d   V P                  ^ 8:  d   R# Vf   \        P                  ! 4       MTV P                  ;'       g    V P
                  ,
          V P                  8¬  # )r   F)r   r	   r   Útimer   r   )rC   rU   s   &&r   Úis_dueÚHeartbeatState.is_dueS   s\   € Ø�;‰;˜(Ô"¨$¯+¯+¨+¸×9NÑ9NÐRSÔ9SÙØ"š{”—	’	”°¸×8JÑ8J×8]Ð8]ÈdÏoÉoÕ^Ðbf×bwÑbwÑwÐwr   c               ó   € V ^8„  d   QhRR/# r<   r   )r   s   "r   r   r=   X   s   € ÷ uñ u˜sñ ur   c                	ój   € \         P                  \        V P                  4      V P                  R 7      # ))Úintervalr	   )ÚHEARTBEAT_PROMPT_TEMPLATEr   r8   r   r	   rB   s   &r   Úrender_promptÚHeartbeatState.render_promptX   s*   € Ü(×/Ñ/¼È×I^ÑI^Ó9_Ðhl×hsÑhsÐ/ÓtÐtr   r   ©N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__annotations__r   r   r   r   rD   ÚclassmethodrR   rZ   r`   Ú__static_attributes__r   r   r   r:   r:   @   se   ‡ á-àƒKØÓØ€FˆCÓØ€J�ÓØ€M�5ÓØ€J�Óõ<ð ôuó ðu÷x÷
uñ ur   r:   c               ó   € V ^8„  d   QhRR/# )r   r   zOptional[Any]r   )r   s   "r   r   r   \   s   € ÷ ñ ˜ñ r   c                 ó|   €  ^ RI Hp  V ! 4       #   \         d"   p\        P	                  RT4        Rp?R# Rp?ii ; i)zePersistence goes through the goals module's per-HERMES_HOME cached SessionDB (one shared connection).)Ú_get_session_dbz1HeartbeatManager: SessionDB bootstrap failed (%s)N)Úhermes_cli.goalsrm   Ú	ExceptionÚloggerÚdebug)Ú	_goals_dbÚexcs     r   rm   rm   \   s4   € ðÝAÙ‹{ÐøÜô Ü�‰ÐHÈ#ÔNÝûðús   ‚ �;š6¶;z
heartbeat:c               ó    € V ^8„  d   QhRRRR/# )r   Ú
session_idr   r   úOptional[HeartbeatState]r   )r   s   "r   r   r   i   s   € ÷ Iñ I˜sð IÐ'?ñ Ir   c                óž  € V '       d   \        4       MR pVf   R #  VP                  \        V ,           4      p T'       d   \        P                  T4      MR pTe   TP                  R8X  d   R # T#   \         d"   p\        P                  RT4        R p?R # R p?ii ; i  \         d"   p\        P                  RY4        R p?R # R p?ii ; i)Nz%HeartbeatManager: get_meta failed: %sz=HeartbeatManager: could not parse stored heartbeat for %s: %sÚcleared)
rm   Úget_metaÚ_META_PREFIXro   rp   rq   r:   rR   Úwarningr   )ru   ÚdbrG   rs   Ústates   &    r   Úload_heartbeatr~   i   s«   € ß(ŒÔ	¨d€BØ	‚zÙðØ�k‰kœ,¨Õ3Ó4ˆðß14”×(Ñ(¨Ô-¸$ˆð ’= E§L¡L°IÔ$=ˆ4ÐHÀ5ÐHøô ô Ü�‰Ð<¸cÔBÝûðûô
 ô Ü�‰ÐVÐXbÔhÝûðús4   œA1 ¹B  ÁB  Á1BÁ<BÂBÂ CÂ+CÃCc               ó    € V ^8„  d   QhRRRR/# )r   r|   r   r   rW   r   )r   s   "r   r   r   z   s   € ÷ 
ñ 
 3ð 
¨4ñ 
r   c                ó¸   € V P                  \        4       F-  w  r \        P                  V4      P                  R8X  d    R# K/  	  R#   \
         d      R# i ; i)u*  True when *db* holds an ACTIVE ``heartbeat:*`` row â€” or one that cannot be parsed (unknown, so
the caller keeps its full sweep). ``clear``/``pause`` keep their rows (status ``cleared``/``paused``),
so key existence alone is not "active". Read errors propagate: "unavailable" is the caller's call.r   TF)Úlist_meta_prefixrz   r:   rR   r   ro   )r|   Ú_keyrG   s   &  r   Ústore_has_active_heartbeatrƒ   z   s[   € ð ×(Ñ(¬Ö6‰	ˆð	Ü×'Ñ'¨Ó,×3Ñ3°xÔ?Úñ @ñ 7ñ øô ô 	Ûð	ús   œ$A	Á	AÁAc               ó$   € V ^8„  d   QhRRRRRR/# )r   ru   r   r}   r:   r   ÚNoner   )r   s   "r   r   r   ‡   s&   € ÷ Cñ C˜sð C¨>ð C¸dñ Cr   c                ó  € V '       g   R # \        4       pVf   ^ RIHp V! RRV 4       R #  VP                  \        V ,           VP                  4       4       R #   \         d"   p\        P                  RT4        R p?R # R p?ii ; i)N)Ú_warn_dropped_writeÚHeartbeatManagerÚ	heartbeatz%HeartbeatManager: set_meta failed: %s)	rm   rn   r‡   Úset_metarz   rD   ro   rp   rq   )ru   r}   r|   r‡   rs   s   &&   r   Úsave_heartbeatr‹   ‡   sk   € ßÙÜ	Ó	€BØ	‚zÝ8ÙÐ.°¸ZÔHÙðCØ
�‰”L :Õ-¨u¯}©}«Ö?øÜô CÜ�‰Ð<¸c×BÒBûðCús   ¬+A ÁBÁ$B Â Bc                  óÀ   € ] tR t^•tRtR R lt]R R l4       tR R ltR R	 lt	R
 R lt
R R ltRR/R R lltR R ltR R ltR R ltRR R lltR R ltRtR# )rˆ   zõPer-session heartbeat state + due-tick decisions; the surface CLI + gateway talk to.

Drivers (CLI thread / gateway task) call :meth:`due_prompt` on a poll cadence while the session is
idle; a non-None return is the user-role message to inject.
c               ó   € V ^8„  d   QhRR/# )r   ru   r   r   )r   s   "r   r   ÚHeartbeatManager.__annotate__œ   s   € ÷ =ñ = 3ñ =r   c                	ó@   € Wn         \        V4      V n        R V n        R # rb   )ru   r~   Ú_stateÚ_last_claim)rC   ru   s   &&r   Ú__init__ÚHeartbeatManager.__init__œ   s   € Ø$ŒÜ0>¸zÓ0JˆŒØ8<ˆÖr   c               ó   € V ^8„  d   QhRR/# ©r   r   rv   r   )r   s   "r   r   rŽ   ¢   s   € ÷ ñ Ð/ñ r   c                	ó   € V P                   # rb   )r�   rB   s   &r   r}   ÚHeartbeatManager.state¡   s   € à�{‰{Ðr   c               ó   € V ^8„  d   QhRR/# ©r   r   rW   r   )r   s   "r   r   rŽ   ¥   s   € ÷ Vñ V˜tñ Vr   c                	ó`   € V P                   R J;'       d    V P                   P                  R9   # )N>   r   Úpaused©r�   r   rB   s   &r   Úhas_heartbeatÚHeartbeatManager.has_heartbeat¥   s)   € Ø�{‰{ $Ð&×UÐU¨4¯;©;×+=Ñ+=ÐAUÑ+UÐUr   c               ó   € V ^8„  d   QhRR/# r™   r   )r   s   "r   r   rŽ   ¨   s   € ÷ Jñ J˜4ñ Jr   c                	ó`   € V P                   R J;'       d    V P                   P                  R8H  # )Nr   rœ   rB   s   &r   Ú	is_activeÚHeartbeatManager.is_active¨   s(   € Ø�{‰{ $Ð&×IÐI¨4¯;©;×+=Ñ+=ÀÑ+IÐIr   c               ó   € V ^8„  d   QhRR/# r<   r   )r   s   "r   r   rŽ   «   s   € ÷ 
Qñ 
Q˜Sñ 
Qr   c                	ó  € V P                   pVf   R# \        VP                  4      pVP                  '       d   RVP                   R2MRpVP                  R8X  dw   \        ^ \        VP                  ;'       g    VP                  VP                  ,           \        P                  ! 4       ,
          4      4      pRV RV RV R	VP                   2# VP                  R
8X  d   RMRpV RVP                   RV V R	VP                   2# )Nz@No heartbeat. Set one with /heartbeat every <interval> <prompt>.z, fired ô   Ã—r
   r   u   â™¥ Heartbeat (every z, next in ~r2   z): r›   u   â�¸ zHeartbeat (z, every )r�   r8   r   r   r   Úmaxr    r   r   rY   r	   )rC   r2   ÚeveryÚfiredÚnext_inÚicons   &     r   Ústatus_lineÚHeartbeatManager.status_line«   sè   € Ø�K‰KˆØŠ9ÙUÜ × 2Ñ 2Ó3ˆØ/0¯|¯|¨|�(˜1Ÿ<™<˜.¨Ñ+ÀˆØ�8‰8�xÔÜ˜!œS !§/¡/×"AÐ"A°Q·\±\ÀQ×EWÑEWÕ!WÔZ^×ZcÒZcÓZeÕ!eÓfÓgˆGØ*¨5¨'°¸W¸IÀQÀuÀgÈSÐQR×QYÑQYÐPZÐ[Ð[ØŸ™ XÔ-‰v°2ˆØ��{ 1§8¡8 *¨H°U°G¸E¸7À#ÀaÇhÁhÀZÐPÐPr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r	   r   r   r    r   r:   r   )r   s   "r   r   rŽ   ·   s!   € ÷ 
ñ 
˜#ð 
°ð 
¸ñ 
r   c                	óR  € T;'       g    R P                  4       pV'       g   \        R4      h\        V4      pV\        8  d   \        R\         R24      h\	        WR\
        P
                  ! 4       R7      V n        \        V P                  V P                  4       V P                  # )r
   zheartbeat prompt is emptyzinterval must be at least r2   r   )r	   r   r   r   )	ÚstripÚ
ValueErrorr    r%   r:   rY   r�   r‹   ru   )rC   r	   r   s   &&&r   ÚsetÚHeartbeatManager.set·   s‡   € Ø—,�,˜B×%Ñ%Ó'ˆßÜÐ8Ó9Ð9ÜÐ/Ó0ÐØÔ2Ô2ÜÐ9Ô:NÐ9OÈqÐQÓRÐRÜ$¨FÐ^fÜ04·	²	³ô=ˆŒä�t—‘¨¯©Ô4Ø�{‰{Ðr   ÚreanchorFc               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   r³   rW   r   rv   r   )r   s   "r   r   rŽ   Ã   s"   € ÷ ñ  #ð °Dð ÐE]ñ r   c               	óú   € V P                   '       g   R # WP                   n        V'       d%   \        P                  ! 4       V P                   n        \	        V P
                  V P                   4       V P                   # rb   )r�   r   rY   r   r‹   ru   )rC   r   r³   s   &&$r   Ú_set_statusÚHeartbeatManager._set_statusÃ   sI   € Ø�{�{ˆ{ÙØ#�‰ÔßÜ(,¯	ª	«ˆD�K‰KÔ%Ü�t—‘¨¯©Ô4Ø�{‰{Ðr   c               ó   € V ^8„  d   QhRR/# r•   r   )r   s   "r   r   rŽ   Ì   s   € ÷ *ñ *Ð/ñ *r   c                	ó$   € V P                  R 4      # )r›   ©r¶   rB   s   &r   ÚpauseÚHeartbeatManager.pauseÌ   s   € Ø×Ñ Ó)Ð)r   c               ó   € V ^8„  d   QhRR/# r•   r   )r   s   "r   r   rŽ   Ï   s   € ÷ 9ñ 9Ð0ñ 9r   c                	ó(   € V P                  R RR7      # )r   T)r³   rº   rB   s   &r   ÚresumeÚHeartbeatManager.resumeÏ   s   € à×Ñ °4ÐÓ8Ð8r   c               ó   € V ^8„  d   QhRR/# r™   r   )r   s   "r   r   rŽ   Ó   s   € ÷ ñ �tñ r   c                	ó:   € V P                  R 4      RJpRV n        V# )rx   N)r¶   r�   )rC   rx   s   & r   ÚclearÚHeartbeatManager.clearÓ   s#   € Ø×"Ñ" 9Ó-°TÐ9ˆØˆŒØˆr   Nc               ó    € V ^8„  d   QhRRRR/# )r   rU   rV   r   zOptional[str]r   )r   s   "r   r   rŽ   Ø   s   € ÷ !ñ !˜oð !¸ñ !r   c                óL  € V P                   pVe   VP                  V4      '       g   R# VP                  VP                  3V n        Ve   TM\
        P
                  ! 4       Vn        V;P                  ^,          un        \        V P                  V4       VP                  4       # )a  Return the injection prompt if the heartbeat is due, else None.

The fire is recorded immediately (before the turn runs) so overlapping polls or a long turn can never
double-fire the same tick. Missed ticks coalesce: the anchor resets to NOW, not the theoretical
schedule.
N)	r�   rZ   r   r   r‘   rY   r‹   ru   r`   )rC   rU   r2   s   && r   Ú
due_promptÚHeartbeatManager.due_promptØ   sr   € ð �K‰KˆØŠ9˜AŸH™H SŸMšMÙØŸO™O¨Q¯\©\Ð:ˆÔØ!$¢™#´d·i²i³kˆŒØ	�Š˜Õ�Ü�t—‘¨Ô*Ø�‰Ó Ð r   c               ó   € V ^8„  d   QhRR/# r™   r   )r   s   "r   r   rŽ   è   s   € ÷ ñ ˜dñ r   c                ó^  € V P                   V P                  r!Ve   Vf   R# \        V P                  4      pVeE   VP                  R8w  g4   VP
                  VP                  3VP
                  VP                  38w  d   R# Vw  Vn        Vn        RV n         \        V P                  V4       R# )u+  Rewind the fire recorded by the last :meth:`due_prompt` whose turn never started, so the tick stays
due for the next poll instead of being silently consumed. Mirrors ``LoopManager.abandon_tick``. Skipped
(False) when the persisted state moved on â€” a pause/resume/clear that landed in between wins.NFr   T)r‘   r�   r~   ru   r   r   r   r‹   )rC   Úclaimr2   Úcurrents   &   r   Úabandon_fireÚHeartbeatManager.abandon_fireè   s•   € ð ×#Ñ# T§[¡[ˆqØŠ=˜AšIÙÜ  §¡Ó1ˆØŠ?˜gŸn™n°Ô8¸W×=RÑ=RÐT[×TfÑTfÐ<gØ—‘ §¡ðl/ô =/áØ(-Ñ%ˆŒ˜œØˆÔÜ�t—‘¨Ô*Ùr   )r‘   r�   ru   rb   )rc   rd   re   rf   rg   r’   Úpropertyr}   r�   r¡   r«   r±   r¶   r»   r¿   rÃ   rÇ   rÍ   rj   r   r   r   rˆ   rˆ   •   sa   † ñõ=ð
 ôó ðõVõJõ
Qõ
ð¸5÷ õ*õ9õ÷
!÷ ñ r   rˆ   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úold_session_idr   Únew_session_idr   rW   r   )r   s   "r   r   r   ù   s!   € ÷ ñ °ð Àcð Èdñ r   c                ó  € V '       d   V'       d   W8X  d   R#  \        V 4      pVe   \        V4      e   R# \        W4       RVn        \        W4       R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)z˜Carry a heartbeat across a compression session rotation (copy to child, archive parent, never raise).

Same shape as ``goals.migrate_goal_to_session``.
FNrx   Tz&HeartbeatManager: migration failed: %s)r~   r‹   r   ro   rp   rq   )rÑ   rÒ   r}   rs   s   &&  r   Úmigrate_heartbeat_to_sessionrÔ   ù   sn   € ÷
 §°>Ô3SÙð
Ü˜~Ó.ˆØŠ=œN¨>Ó:ÒFÙÜ�~Ô-Ø ˆŒÜ�~Ô-ÙøÜô Ü�‰Ð=¸sÔCÝûðús   šA ¸A ÁBÁ"A>Á>B)r2   ÚsecÚsecsÚsecondr'   )r&   ÚminÚminsÚminuteÚminutes)r4   ÚhrÚhrsÚhourÚhours)r3   ÚdayÚdays)
r_   r%   ÚPOLL_SECONDSrˆ   r:   r8   r~   rÔ   r(   r‹   )(rg   Ú
__future__r   r@   ÚloggingÚrerY   Údataclassesr   r   Útypingr   r   Ú	getLoggerrc   rp   r%   râ   r_   ÚcompileÚ
IGNORECASEr   ÚdictÚfromkeysr#   r   r    r!   rJ   r(   r8   r:   rm   rz   r~   rƒ   r‹   rˆ   rÔ   Ú__all__r   r   r   Ú<module>rî      sZ  ðñHõ #ã Û Û 	Û ß )ß  à	×	Ò	˜8Ó	$€àÐ Ø€ðYð ð �zŠzØoÐqs×q~Ñq~ó@€ðØ
‡m�mÐ=¸qÓAðà
‡m�mÐ=¸rÓBðð ‡m�mÐ7¸Ó>ðð ‡m�mÐ(¨%Ó0ð	€ð ˆs�BˆiÐ+¨c°1¨X°xÀ#ÀxÀØ�5˜#�, °%¸°¸|ÈcÐSTÈXð€õ	=õpð ÷uð uó ðuõ6ð €õIõ"
õC÷añ aõHò(‚r   