+
    ƒWÝjê  ã                  ó   € R t ^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	 ]P                  ! ]4      tRtRt^tRtRt] ! R	 R
4      4       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 R ltR R ltR R ltR R ltR R  ltR# )!uû  Monitor-mode cron support â€” hash-suppressed change detection.

A monitor job attaches a cheap source (``monitor_script`` / ``monitor_url``) to an LLM cron job.
Each tick runs the source FIRST and hashes its EXACT output bytes (no timestamp/whitespace
normalization â€” scripts must emit stable output) against the hash from the last agent-triggering
tick: unchanged â†’ agent run suppressed (silent ``no_change`` run); changed/first run â†’ a "MONITOR
CHANGE DETECTED" block (capped unified diff + new output) is injected into the prompt; source
failure â†’ an ERROR, never a change, and the stored hash is left untouched. State:
``job["monitor_state"]`` in jobs.json (hash + last_changed_at) and
``OUTPUT_DIR/<job_id>/monitor_last_output.txt`` (for the diff).
)ÚannotationsN)Ú	dataclass)ÚOptionali   i@  i   zmonitor_last_output.txtc                  ó^   € ] tR t^!t$ RtR]R&   RtR]R&   RtR]R&   RtR]R	&   Rt	R]R
&   Rt
R# )ÚMonitorOutcomez(Result of one monitor-source evaluation.ÚboolÚokFÚchangedÚ	first_runNzOptional[str]Úcontext_blockÚerror© )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__annotations__r	   r
   r   r   Ú__static_attributes__r   ó    Ú@C:\Users\johnn\AppData\Local\hermes\hermes-agent\cron\monitor.pyr   r   !   s2   ‡ á2àƒHØ€GˆTÓØ€IˆtÓØ#'€M�=Ó'Ø€Eˆ=×r   r   c               ó    € V ^8„  d   QhRRRR/# )é   ÚoutputÚstrÚreturnr   )Úformats   "r   Ú__annotate__r   ,   s   € ÷ Pñ P ð P¨ñ Pr   c                ól   € \         P                  ! V P                  RRR7      4      P                  4       # )z@Hash the monitor output as exact UTF-8 bytes (no normalization).úutf-8Úreplace©Úerrors)ÚhashlibÚsha256ÚencodeÚ	hexdigest)r   s   &r   Úhash_monitor_outputr'   ,   s'   € ä�>Š>˜&Ÿ-™-¨¸	˜-ÓBÓC×MÑMÓOÐOr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úoldr   Únewr   r   )r   s   "r   r   r   1   s!   € ÷ 	ñ 	˜Cð 	 cð 	¨cñ 	r   c           
     óÚ   € RP                  \        P                  ! V P                  4       VP                  4       RRRR7      4      p\	        V4      \
        8”  d   VR\
         R,           pV# )zDUnified diff of old vs new monitor output, capped at MAX_DIFF_CHARS.Ú
ÚpreviousÚcurrentÚ )ÚfromfileÚtofileÚlinetermNz
... [diff truncated])ÚjoinÚdifflibÚunified_diffÚ
splitlinesÚlenÚMAX_DIFF_CHARS)r)   r*   Údiffs   && r   Úbuild_monitor_diffr:   1   s]   € à�9‰9Ü×ÒØ�N‰NÓ˜cŸn™nÓ.¸ÈIÐ`bô	
ó€Dô
 ˆ4ƒy”>Ô!Ø�O”^Ð$Ð'?Õ?ˆØ€Kr   c               ó   € V ^8„  d   QhRR/# )r   Újob_idr   r   )r   s   "r   r   r   =   s   € ÷ 8ñ 8˜3ñ 8r   c                ó4   € ^ RI Hp V! V 4      \        ,          # )é    )Ú_job_output_dir)Ú	cron.jobsr?   Ú_SNAPSHOT_FILENAME)r<   r?   s   & r   Ú_snapshot_pathrB   =   s   € Ý)á˜6Ó"Ô%7Õ7Ð7r   c               ó    € V ^8„  d   QhRRRR/# )r   r<   r   r   r   )r   s   "r   r   r   C   s   € ÷ ñ ˜cð  cñ r   c                óÎ   €  \        V 4      pVP                  4       '       d   VP                  R R7      #  R#   \         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)z	utf-8-sig©Úencodingz.Monitor: failed to read last output for %r: %sNr/   )rB   ÚexistsÚ	read_textÚ	ExceptionÚloggerÚwarning)r<   ÚpathÚexcs   &  r   Ú_read_last_outputrN   C   sa   € ðVÜ˜fÓ%ˆØ�;‰;�=Š=Ø—>‘>¨;�>Ó7Ð7ð ñ øô ô VÜ�‰ÐGÈ×UÐUÙûðVús   ‚28 ¸A$ÁAÁA$c               ó$   € V ^8„  d   QhRRRRRR/# )r   r<   r   r   r   ÚNoner   )r   s   "r   r   r   M   s&   € ÷ Yñ Y˜sð Y¨Cð Y°Dñ Yr   c                óÒ   €  ^ RI Hp \        V 4      pV! VP                  4       VP	                  VRR7       R#   \
         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)r>   )Ú_ensure_cron_dirr   rE   z1Monitor: failed to persist last output for %r: %sN)r@   rR   rB   ÚparentÚ
write_textrI   rJ   rK   )r<   r   rR   rL   rM   s   &&   r   Ú_write_last_outputrU   M   sU   € ðYÝ.ä˜fÓ%ˆÙ˜Ÿ™Ô%Ø�‰˜¨ˆÖ1øÜô YÜ�‰ÐJÈF×XÒXûðYús   ‚6: ºA&ÁA!Á!A&c               ó    € V ^8„  d   QhRRRR/# )r   Úurlr   r   útuple[bool, str]r   )r   s   "r   r   r   X   s   € ÷ 9ñ 9˜Cð 9Ð$4ñ 9r   c                óî  € ^ RI p\        V 4      P                  4       P                  R4      '       g	   RRV : 23#  VP                  P                  V RR/R7      pVP                  P                  V\        R7      ;_uu_ 4       pVP                  \        ^,           4      pRRR4       RXR\         P                  R	R
R7      3#   + '       g   i     L,; i  \         d   pRRT 23u Rp?# Rp?ii ; i)z:Bounded GET of a monitor URL. Returns (ok, body-or-error).NFzmonitor_url must be http(s): z
User-Agentzhermes-cron-monitor)Úheaders)ÚtimeoutTr   r    r!   zmonitor_url fetch failed: )zhttp://zhttps://)Úurllib.requestr   ÚlowerÚ
startswithÚrequestÚRequestÚurlopenÚURL_TIMEOUT_SECONDSÚreadÚMAX_URL_BYTESÚdecoderI   )rW   ÚurllibÚreqÚrespÚbodyrM   s   &     r   Ú_fetch_monitor_urlrj   X   sß   € ãäˆs‹8�>‰>Ó×&Ñ&Ð'>×?Ò?ØÐ5°c±WÐ=Ð=Ð=ð9Ø�n‰n×$Ñ$ S°<ÐAVÐ2WÐ$ÓXˆØ�^‰^×#Ñ# CÔ1DÐ#×EÔEÈØ—9‘9œ]¨QÕ.Ó/ˆD÷ Fà�T˜.œ=Ð)×0Ñ0°ÀÐ0ÓKÐKÐK÷ F×Eûô ô 9ØÐ2°3°%Ð8Ð8Õ8ûð9ús6   ¼AC ÂCÂ!#C ÃC	ÃC ÃC4Ã#C/Ã)C4Ã/C4c               ó$   € V ^8„  d   QhRRRRRR/# )r   ÚjobÚdictÚkeyr   r   r   )r   s   "r   r   r   g   s!   € ÷ (ñ (�ð (˜3ð ( 3ñ (r   c                óT   € V P                  V4      ;'       g    R P                  4       # )r/   )ÚgetÚstrip)rl   rn   s   &&r   Ú_fieldrr   g   s!   € Ø�G‰G�C‹L×Ð˜B×%Ñ%Ó'Ð'r   c               ó    € V ^8„  d   QhRRRR/# )r   rl   rm   r   rX   r   )r   s   "r   r   r   k   s   € ÷ Kñ K˜Tð KÐ&6ñ Kr   c                óÔ   € \        V R4      pV'       d5   ^ RIHp T! T\        V R4      ;'       g    RV P                  R4      R7      # \        V R4      pV'       d   \	        V4      # R# )	zCRun the job's monitor source (script or URL). Returns (ok, output).Úmonitor_script)Ú_run_job_scriptÚworkdirNÚinterpreter)rw   rx   Úmonitor_url)Fz6monitor job has neither monitor_script nor monitor_url)rr   Úcron.scheduler_scriptrv   rp   rj   )rl   ru   rv   ry   s   &   r   Ú_run_monitor_sourcer{   k   sd   € ä˜CÐ!1Ó2€Nßå9á˜~´v¸cÀ9Ó7M×7UÐ7UÐQUØ+.¯7©7°=Ó+AôCð 	Cä˜˜mÓ,€KßÜ! +Ó.Ð.ØJÐJr   c               ó    € V ^8„  d   QhRRRR/# )r   rl   rm   r   r   r   )r   s   "r   r   r   z   s   € ÷ Mñ M˜ð M $ñ Mr   c                óT   € \        \        V R 4      ;'       g    \        V R4      4      # )ru   ry   )r   rr   )rl   s   &r   Újob_has_monitorr~   z   s$   € Ü”�sÐ,Ó-×KÐK´¸¸]Ó1KÓLÐLr   c               ó    € V ^8„  d   QhRRRR/# )r   rl   rm   r   r   r   )r   s   "r   r   r   ~   s   € ÷ *cñ *c�tð *c ñ *cr   c                óX  € \        V P                  R4      ;'       g    R4      p\        V 4      w  r#V'       g   \        RVR7      # \	        V4      pV P                  R4      p\        V\        4      '       d   VP                  R4      MRpVe   WF8X  d   \        RRR	7      # VRJ pV'       d   RM
\        V4      pTp	\        V	4      \        8”  d   V	R\         R
,           p	RV	 R2p
V'       d   RV
,           pM\        Wƒ4      pRV R2V
,           p\        WV4       \        RRW{R7      # )u%  Run the monitor source and decide whether the agent should run.

On change (or first run) the new hash + snapshot are persisted BEFORE the agent runs â€” detection
time is the state boundary, so a failed agent run doesn't re-alert on the same content forever.
On failure nothing is persisted.
Úidr/   F)r   r   Úmonitor_stateÚlast_output_hashNT)r   r	   z
... [output truncated]z### Current output

```
z
```u‰   ## Monitor Baseline (first run)

This is the first observation of the monitored source â€” there is no previous output to diff against.

u€   ## MONITOR CHANGE DETECTED

The monitored source's output changed since the last run.

### Diff (previous â†’ current)

```diff
z
```

)r   r	   r
   r   )r   rp   r{   r   r'   Ú
isinstancerm   rN   r7   ÚMAX_OUTPUT_CHARSr:   Ú_persist_monitor_state)rl   r<   r   r   Únew_hashÚ	raw_stateÚ	last_hashr
   Ú
old_outputÚshown_outputr.   r   r9   s   &            r   Úcheck_monitorrŒ   ~   s3  € ô �—‘˜“×$Ð$ "Ó%€FÜ$ SÓ)�J€BßÜ ¨fÔ5Ð5ä" 6Ó*€HØ—‘˜Ó(€IÜ5?À	Ì4×5PÒ5P�	—‘Ð0Ô1ÐVZ€IàÒ Ô!6Ü ¨uÔ5Ð5à˜TÐ!€Iß ‘Ô&7¸Ó&?€Jà€LÜ
ˆ<ÓÔ+Ô+Ø#Ð$5Ô%5Ð6Ð9SÕSˆà+¨L¨>¸Ð?€Gßð6à8?õ@ñ 	ô " *Ó5ˆð;à;?¸&À	ðKàMTõUð 	ô ˜6¨VÔ4Ü˜T¨4¸9ÔbÐbr   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r<   r   r‡   r   r   rP   r   )r   s   "r   r   r   «   s.   € ÷ Sñ S 3ð S°#ð S¸sð SÀtñ Sr   c           	     óÐ   € ^ RI HpHp \        W4        V! V RRVRV! 4       P	                  4       //4       R#   \
         d"   p\        P                  RY4        Rp?R# Rp?ii ; i)r>   )Ú_hermes_nowÚ
update_jobr‚   rƒ   Úlast_changed_atz+Monitor: failed to persist state for %r: %sN)r@   r�   r�   rU   Ú	isoformatrI   rJ   rK   )r<   r‡   r   r�   r�   rM   s   &&&   r   r†   r†   «   sf   € ß1ä�vÔ&ðSÙØàØ&¨Ø%¡{£}×'>Ñ'>Ó'@ð"ðö	
øô ô SÜ�‰ÐDÀf×RÒRûðSús   •"9 ¹A%ÁA Á A%)r   Ú
__future__r   r4   r#   ÚloggingÚdataclassesr   Útypingr   Ú	getLoggerr   rJ   r8   r…   rb   rd   rA   r   r'   r:   rB   rN   rU   rj   rr   r{   r~   rŒ   r†   r   r   r   Ú<module>r˜      s£   ðñ
õ #ã Û Û Ý !Ý à	×	Ò	˜8Ó	$€ð €ØÐ ØÐ Ø€à.Ð ð ÷ ð  ó ð õPõ
	õ8õõYõ9õ(õKõMõ*c÷ZSr   