+
    ӃiHd                     p   R t ^ RIt^ RIt^ RI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 ^ RIHtHtHtHt ]P&                  ! ]4      t^ RIHt  ^ RIHt Rt]! 4       t]R
,          t]R,          t]R,          t^xt R?R R llt!R R lt"R R lt#R R lt$R 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! R" llt+R# R$ lt,R% R& lt-RAR' R( llt.R) R* lt/RBR+ R, llt0R- R. lt1R@R/ R0 llt2R1 R2 lt3R3 R4 lt4R5 R6 lt5R@R7 R8 llt6R9 R: lt7R; R< lt8R= R> lt9R#   ] d    R	t Li ; i)Cz
Cron job storage and management.

Jobs are stored in ~/.hermes/cron/jobs.json
Output is saved to ~/.hermes/cron/output/{job_id}/{timestamp}.md
N)datetime	timedeltaPath)get_hermes_home)OptionalDictListAny)now)croniterTFcronz	jobs.jsonoutputc                ~    V ^8  d   QhR\         \        ,          R\         \        ,          R\        \        ,          /# )   skillskillsreturn)r   strr
   r	   )formats   "&/home/ubuntu/hermes-agent/cron/jobs.py__annotate__r   )   s0      # x} X\]`Xa     c                   Vf   V '       d   V .M. pM%\        V\        4      '       d   V.pM\        V4      p. pV FI  p\        T;'       g    R4      P                  4       pV'       g   K0  WS9  g   K8  VP	                  V4       KK  	  V# )zPNormalize legacy/single-skill and multi-skill inputs into a unique ordered list. )
isinstancer   liststripappend)r   r   	raw_items
normalizeditemtexts   &&    r   _normalize_skill_listr#   )   sv    ~$UG"		FC	 	 H	L	J4::2$$&4D*d#  r   c                t    V ^8  d   QhR\         \        \        3,          R\         \        \        3,          /# )r   jobr   )r   r   r
   )r   s   "r   r   r   :   s*      T#s(^ S#X r   c                    \        V 4      p\        VP                  R4      VP                  R4      4      pW!R&   V'       d
   V^ ,          MRVR&   V# )zLReturn a job dict with canonical `skills` and legacy `skill` fields aligned.r   r   N)dictr#   get)r%   r    r   s   &  r   _apply_skill_fieldsr)   :   sI    cJ":>>'#:JNN8<TUF!x'-&)4Jwr   c                $    V ^8  d   QhR\         /# r   pathr   )r   s   "r   r   r   C   s      d r   c                f     \         P                  ! V R4       R#   \        \        3 d     R# i ; i)z<Set directory to owner-only access (0700). No-op on Windows.i  N)oschmodOSErrorNotImplementedErrorr,   s   &r   _secure_dirr3   C   s-    
u() s    00c                $    V ^8  d   QhR\         /# r+   r   )r   s   "r   r   r   K   s      t r   c                     V P                  4       '       d   \        P                  ! V R4       R# R#   \        \        3 d     R# i ; i)z;Set file to owner-only read/write (0600). No-op on Windows.i  N)existsr.   r/   r0   r1   r2   s   &r   _secure_filer7   K   s=    ;;==HHT5! () s   3 3 AAc                     \         P                  RRR7       \        P                  RRR7       \        \         4       \        \        4       R# )z6Ensure cron directories exist with secure permissions.Tparentsexist_okN)CRON_DIRmkdir
OUTPUT_DIRr3    r   r   ensure_dirsr@   T   s5    NN4$N/TD1
r   c                0    V ^8  d   QhR\         R\        /# )r   sr   )r   int)r   s   "r   r   r   `   s     % %c %c %r   c                ,   V P                  4       P                  4       p \        P                  ! RV 4      pV'       g   \	        RV  R24      h\        VP                  ^4      4      pVP                  ^4      ^ ,          pR^R^<RR/pW$V,          ,          # )ud   
Parse duration string into minutes.

Examples:
    "30m" → 30
    "2h" → 120
    "1d" → 1440
zD^(\d+)\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days)$zInvalid duration: 'z''. Use format like '30m', '2h', or '1d'mhdi  )r   lowerrematch
ValueErrorrC   group)rB   rJ   valueunitmultiplierss   &    r   parse_durationrP   `   s     	
	AHH\^_`E.qc1XYZZAE;;q>!D3C.Kt$$$r   c                R    V ^8  d   QhR\         R\        \         \        3,          /# r   scheduler   )r   r   r
   )r   s   "r   r   r   u   s&     V VS VT#s(^ Vr   c           	     h   V P                  4       p T pV P                  4       pVP                  R4      '       d/   V R,          P                  4       p\        V4      pRRRVRRV R2/# V P	                  4       p\        V4      ^8  dt   \        ;QJ d#    R VR	,           4       F  '       d   K   R
M	  RM! R VR	,           4       4      '       d,   \        '       g   \        R4      h \        V 4       RRRT RT /# RV 9   g   \        P                  ! RV 4      '       dn    \        P                  ! V P                  RR4      4      pVP                   f   VP#                  4       pRRRVP%                  4       RRVP'                  R4       2/#  \        V 4      p\)        4       \+        VR7      ,           pRRRVP%                  4       RRV 2/#   \         d   p\        RT  RT 24      hRp?ii ; i  \         d   p\        RT  RT 24      hRp?ii ; i  \         d     Mi ; i\        RT R24      h)u  
Parse schedule string into structured format.

Returns dict with:
    - kind: "once" | "interval" | "cron"
    - For "once": "run_at" (ISO timestamp)
    - For "interval": "minutes" (int)
    - For "cron": "expr" (cron expression)

Examples:
    "30m"              → once in 30 minutes
    "2h"               → once in 2 hours
    "every 30m"        → recurring every 30 minutes
    "every 2h"         → recurring every 2 hours
    "0 9 * * *"        → cron expression
    "2026-02-03T14:00" → once at timestamp
zevery :   NNkindintervalminutesdisplayrE   c              3   P   "   T F  p\         P                  ! R V4      x  K  	  R# 5i)z^[\d\*\-,/]+$N)rI   rJ   ).0ps   & r   	<genexpr>!parse_schedule.<locals>.<genexpr>   s"      /8!!1%%ys   $&:N   NFTzOCron expressions require 'croniter' package. Install with: pip install croniterzInvalid cron expression 'z': Nr   exprTz^\d{4}-\d{2}-\d{2}Zz+00:00oncerun_atzonce at z%Y-%m-%d %H:%MzInvalid timestamp 'rX   zonce in zInvalid schedule 'z'. Use:
  - Duration: '30m', '2h', '1d' (one-shot)
  - Interval: 'every 30m', 'every 2h' (recurring)
  - Cron: '0 9 * * *' (cron expression)
  - Timestamp: '2026-02-03T14:00:00' (one-shot at time))r   rH   
startswithrP   splitlenallHAS_CRONITERrK   r   	ExceptionrI   rJ   r   fromisoformatreplacetzinfo
astimezone	isoformatstrftime_hermes_nowr   )	rS   originalschedule_lowerduration_strrX   partsedtrd   s	   &        r   parse_schedulery   u   sY   $ ~~HH^^%N   **|))+ .Jwy*
 	
 NNE
5zQ33 /4Ry333 /4Ry   |noo	KX FHx
 	
 h"((#8(CC	E''(8(8h(GHB yy ]]_",,.Xbkk2B&C%DE 	 *7!;;Ff&&((,
 	
;  	K8
#aSIJJ	K,  	E28*CsCDD	E   
XJ 'B 	C sC   (G  A+G0 >H G-G((G-0H;HHH"!H"c                0    V ^8  d   QhR\         R\         /# )r   rx   r   )r   )r   s   "r   r   r      s     $ $h $8 $r   c                   \        4       P                  pV P                  fO   \        P                  ! 4       P	                  4       P                  pV P                  VR7      P	                  V4      # V P	                  V4      # )a  Return a timezone-aware datetime in Hermes configured timezone.

Backward compatibility:
- Older stored timestamps may be naive.
- Naive values are interpreted as *system-local wall time* (the timezone
  `datetime.now()` used when they were created), then converted to the
  configured Hermes timezone.

This preserves relative ordering for legacy naive timestamps across
timezone changes and avoids false not-due results.
)rn   )rr   rn   r   r   ro   rm   )rx   	target_tzlocal_tzs   &  r   _ensure_awarer~      s`     $$I	yy<<>,,.55zzz*55i@@==##r   last_run_atc          	          V ^8  d   QhR\         \        \        3,          R\        R\        \        ,          R\        \        ,          /# )r   rS   r   r   r   )r   r   r
   r   r   )r   s   "r   r   r      s@      38n	 #	
 c]r   c                   V P                  R4      R8w  d   R# V'       d   R# V P                  R4      pV'       g   R# \        \        P                  ! V4      4      pWA\	        \
        R7      ,
          8  d   V# R# )zReturn a one-shot run time if it is still eligible to fire.

One-shot jobs get a small grace window so jobs created a few seconds after
their requested minute still run on the next tick. Once a one-shot has
already run, it is never eligible again.
rV   rc   Nrd   )seconds)r(   r~   r   rl   r   ONESHOT_GRACE_SECONDS)rS   r   r   rd   	run_at_dts   &&$  r   _recoverable_oneshot_run_atr      s`     ||Fv%\\(#Fh44V<=I),ABBBr   c                0    V ^8  d   QhR\         R\        /# rR   )r'   rC   )r   s   "r   r   r      s      T c r   c                    ^xpRpV P                  R4      pVR8X  d8   V P                  R^4      ^<,          pV^,          p\        V\        WR4      4      # VR8X  d   \        '       d    \	        4       p\        V R,          V4      pVP                  \        4      pVP                  \        4      p	\        W,
          P                  4       4      pV^,          p\        V\        WR4      4      # V#   \         d     T# i ; i)a  Compute how late a job can be and still catch up instead of fast-forwarding.

Uses half the schedule period, clamped between 120 seconds and 2 hours.
This ensures daily jobs can catch up if missed by up to 2 hours,
while frequent jobs (every 5-10 min) still fast-forward quickly.
i   rV   rW   rX   r   r`   )r(   maxminrj   rr   r   get_nextr   rC   total_secondsrk   )
rS   	MIN_GRACE	MAX_GRACErV   period_secondsgracer   r   firstseconds
   &         r   _compute_grace_secondsr      s     II<<Dz!i3b8!#9c%344v~,,		-CHV,c2DMM(+E]]8,F &.!?!?!ABN"a'Ey#e"788   		s   (BC. .C=<C=c                    V ^8  d   QhR\         \        \        3,          R\        \        ,          R\        \        ,          /# )r   rS   r   r   )r   r   r
   r   )r   s   "r   r   r     s4      tCH~ HSM U]^aUb r   c                    \        4       pV R,          R8X  d   \        WVR7      # V R,          R8X  dw   V R,          pV'       dC   \        \        P                  ! V4      4      pV\        VR7      ,           pVP                  4       # V\        VR7      ,           pVP                  4       # V R,          R8X  dG   \        '       g   R# \        V R	,          V4      pVP                  \        4      pVP                  4       # R# )
zc
Compute the next run time for a schedule.

Returns ISO timestamp string, or None if no more runs.
rV   rc   r   rW   rX   re   r   Nr`   )
rr   r   r~   r   rl   r   rp   rj   r   r   )rS   r   r   rX   lastnext_runr   s   &&     r   compute_next_runr     s     -C6!*8kRR	&	Z	'9% !7!7!DEDi88H !!## Yw77H!!##	&	V	#|(#.==*!!##r   c                \    V ^8  d   QhR\         \        \        \        3,          ,          /# r   r   r	   r   r   r
   )r   s   "r   r   r   @  s       4S#X' r   c                    \        4        \        P                  4       '       g   . #  \        \        RRR7      ;_uu_ 4       p \        P
                  ! V 4      pVP                  R. 4      uuRRR4       #   + '       g   i     R# ; i  \        P                   d     \        \        RRR7      ;_uu_ 4       p \        P                  ! T P                  4       RR7      pTP                  R. 4      pT'       d!   \        T4       \        P                  R4       TuuRRR4       u #   + '       g   i      R# ; i  \         d    . u u # i ; i\         d    . u # i ; i)	zLoad all jobs from storage.rutf-8encodingjobsNF)strictz8Auto-repaired jobs.json (had invalid control characters))r@   	JOBS_FILEr6   openjsonloadr(   JSONDecodeErrorloadsread	save_jobsloggerwarningrk   IOError)fdatar   s      r   	load_jobsr   @  s    M	)S733q99Q<D88FB' 4333  
	iw771zz!&&(59xx+dONN#]^ 8777  	I	 	sx   B	 (A5*
B	 5B	 B	 B	 	E!D=9A"D(
D=%E!(D:3D=:D==E
E!EE!E! E!c                \    V ^8  d   QhR\         \        \        \        3,          ,          /# )r   r   r   )r   s   "r   r   r   [  s       Dc3h( r   c           	        \        4        \        P                  ! \        \        P
                  4      RRR7      w  r \        P                  ! VRRR7      ;_uu_ 4       p\        P                  ! RV R\        4       P                  4       /V^R	7       VP                  4        \        P                  ! VP                  4       4       R
R
R
4       \        P                  ! V\        4       \!        \        4       R
#   + '       g   i     L<; i  \"         d+     \        P$                  ! T4       h   \&         d     h i ; ii ; i)zSave all jobs to storage..tmpz.jobs_dirsuffixprefixwr   r   r   
updated_at)indentN)r@   tempfilemkstempr   r   parentr.   fdopenr   dumprr   rp   flushfsyncfilenorm   r7   BaseExceptionunlinkr0   )r   fdtmp_pathr   s   &   r   r   r   [  s    M##I,<,<(=fU]^LBYYr311QIIvt\;=3J3J3LMqYZ[GGIHHQXXZ  2 	

8Y'Y 21  	IIh 	  		sH   !D A(C;2D ;D	D ED10E1D?<E>D??Ec                   V ^8  d   QhR\         R\         R\        \         ,          R\        \        ,          R\        \         ,          R\        \        \         \        3,          ,          R\        \         ,          R\        \
        \         ,          ,          R	\        \         ,          R
\        \         ,          R\        \         ,          R\        \         ,          R\        \         \        3,          /# )r   promptrS   namerepeatdeliveroriginr   r   modelproviderbase_urlscriptr   )r   r   rC   r   r
   r	   )r   s   "r   r   r   n  s     b bbb 3-b SM	b
 c]b T#s(^$b C=b T#Yb C=b smb smb SMb 
#s(^br   c                   \        V4      pVe
   V^ 8:  d   RpVR,          R8X  d   Vf   ^pVf   V'       d   RMRp\        P                  ! 4       P                  R,          p\	        4       P                  4       p\        Wg4      p\        V\        4      '       d   \        V4      P                  4       MRp\        V	\        4      '       d   \        V	4      P                  4       MRp\        V
\        4      '       d)   \        V
4      P                  4       P                  R4      MRpT;'       g    RpT;'       g    RpT;'       g    Rp\        V\        4      '       d   \        V4      P                  4       MRpT;'       g    RpT ;'       g    V'       d
   V^ ,          MR;'       g    Rp/ R	VbR
T;'       g    VR,          P                  4       bRV bRVbRV'       d
   V^ ,          MRbRVbRVbRVbRVbRVbRVP                  RV4      bRRVR^ /bRRbRRbRRbRRbRVbR \        V4      R!RR"RR#RR$VRV/Cp\        4       pVP                  V4       \        V4       V# )%a  
Create a new cron job.

Args:
    prompt: The prompt to run (must be self-contained, or a task instruction when skill is set)
    schedule: Schedule string (see parse_schedule)
    name: Optional friendly name
    repeat: How many times to run (None = forever, 1 = once)
    deliver: Where to deliver output ("origin", "local", "telegram", etc.)
    origin: Source info where job was created (for "origin" delivery)
    skill: Optional legacy single skill name to load before running the prompt
    skills: Optional ordered list of skills to load before running the prompt
    model: Optional per-job model override
    provider: Optional per-job provider override
    base_url: Optional per-job base URL override
    script: Optional path to a Python script whose stdout is injected into the
            prompt each run.  The script runs before the agent turn, and its output
            is prepended as context.  Useful for data collection / change detection.

Returns:
    The created job dict
NrV   rc   r   local:N   N/zcron jobidr   :N2   Nr   r   r   r   r   r   r   rS   schedule_displayrY   r   times	completedenabledTstate	scheduled	paused_atpaused_reason
created_atnext_run_atr   last_status
last_errorr   )ry   uuiduuid4hexrr   rp   r#   r   r   r   rstripr(   r   r   r   r   )r   rS   r   r   r   r   r   r   r   r   r   r   parsed_schedulejob_idr   normalized_skillsnormalized_modelnormalized_providernormalized_base_urlnormalized_scriptlabel_sourcer%   r   s   &&&&&&&&&&&&           r   
create_jobr   n  s   H %X.O fk v&(V^ $('ZZ\c"F
-
!
!
#C-e<-7s-C-Cs5z'')3=h3L3L#h---/RV?I(TW?X?X#h---/66s;^b'//4-55-55/9&#/F/FF))+D)11TbS7H03dbbXbLf11S)//1 	& 	#	
 	):"1% 	! 	' 	' 	# 	O 	O//	8D 	V
  	4!" 	#$ 	T%& 	'( 	c)* 	'8ttd7&7C< ;DKKdOJr   c                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# r   r   r   r   r   r   r
   )r   s   "r   r   r     s'      C HT#s(^4 r   c                b    \        4       pV F  pVR,          V 8X  g   K  \        V4      u # 	  R# )zGet a job by ID.r   N)r   r)   )r   r   r%   s   &  r   get_jobr     s/    ;Dt9&s++  r   c                h    V ^8  d   QhR\         R\        \        \        \        3,          ,          /# )r   include_disabledr   )boolr	   r   r   r
   )r   s   "r   r   r     s'       d38n1E r   c                    \        4        Uu. uF  p\        V4      NK  	  ppV '       g+   V Uu. uF  qP                  RR4      '       g   K  VNK   	  ppV# u upi u upi )z2List all jobs, optionally including disabled ones.r   T)r   r)   r(   )r   jr   s   &  r   	list_jobsr     sO    ,5K8Kq"KD8:4a55D#94:K 9:s   AAAc          	          V ^8  d   QhR\         R\        \         \        3,          R\        \        \         \        3,          ,          /# )r   r   updatesr   )r   r   r
   r   )r   s   "r   r   r     s6      s T#s(^ c3h8P r   c           
        \        4       p\        V4       EF^  w  r4VR,          V 8w  d   K  \        / VCVC4      pRV9   pRV9   g   RV9   dE   \        VP	                  R4      VP	                  R4      4      pWuR&   V'       d
   V^ ,          MRVR&   V'       db   VR,          pVP	                  RVP	                  RVP	                  R4      4      4      VR&   VP	                  R4      R	8w  d   \        V4      VR
&   VP	                  RR4      '       dC   VP	                  R4      R	8w  d-   VP	                  R
4      '       g   \        VR,          4      VR
&   WRV&   \        V4       \        W#,          4      u # 	  R# )zCUpdate a job by ID, refreshing derived schedule fields when needed.r   rS   r   r   Nr   rY   r   pausedr   r   T)r   	enumerater)   r#   r(   r   r   )	r   r   r   ir%   updatedschedule_changedr   updated_schedules	   &&       r   
update_jobr    s\   ;DD/t9%&8&8&89%0w'W"4 5gkk'6JGKKX`La b 1H7H03dGG&z2*1++" $$Y<N0OP+G&' {{7#x/)9:J)K&;;y$''GKK,@H,LU\U`U`anUoUo%5gj6I%JGM"Q$"47++5 "6 r   c          	          V ^8  d   QhR\         R\        \         ,          R\        \        \         \        3,          ,          /# )r   r   reasonr   r   )r   s   "r   r   r     s2     
 
c 
8C= 
HT#s(^<T 
r   c                V    \        V RRRRR\        4       P                  4       RV/4      # )z Pause a job without deleting it.r   Fr   r   r   r   )r  rr   rp   )r   r  s   &&r   	pause_jobr    s6    uX002V		
 r   c                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# r   r   )r   s   "r   r   r     s'      s xS#X7 r   c                |    \        V 4      pV'       g   R# \        VR,          4      p\        V RRRRRRRRR	V/4      # )
z=Resume a paused job and compute the next future run from now.NrS   r   Tr   r   r   r   r   )r   r   r  )r   r%   r   s   &  r   
resume_jobr    sM    
&/C"3z?3Kt[T;	
	 	r   c                h    V ^8  d   QhR\         R\        \        \         \        3,          ,          /# r   r   )r   s   "r   r   r   %  s'       c3h 8 r   c                    \        V 4      pV'       g   R# \        V RRRRRRRRR\        4       P                  4       /4      # )	z1Schedule a job to run on the next scheduler tick.Nr   Tr   r   r   r   r   )r   r  rr   rp   )r   r%   s   & r   trigger_jobr  %  sJ    
&/Ct[T;=224	
	 	r   c                0    V ^8  d   QhR\         R\        /# r   r   r   )r   s   "r   r   r   6  s      s t r   c                    \        4       p\        V4      pV Uu. uF  q3R,          V 8w  g   K  VNK  	  pp\        V4      V8  d   \        V4       R# R# u upi )zRemove a job by ID.r   TF)r   rh   r   )r   r   original_lenr   s   &   r   
remove_jobr  6  sO    ;Dt9L1t!w&0AAtD1
4y<$	 2s
   AAc                R    V ^8  d   QhR\         R\        R\        \         ,          /# )r   r   successerror)r   r   r   )r   s   "r   r   r   A  s%     ) ) )t )HSM )r   c                   \        4       p\        V4       EF;  w  rEVR,          V 8X  g   K  \        4       P                  4       pWeR&   V'       d   RMRVR&   V'       g   TMRVR&   VP	                  R4      '       d   VR,          P	                  R	^ 4      ^,           VR,          R	&   VR,          P	                  R
4      pVR,          R	,          pVe-   V^ 8  d&   W8  d    VP                  V4       \        V4        R# \        VR,          V4      VR&   VR,          f   RVR&   R	VR&   MVP	                  R4      R8w  d   RVR&   \        V4        R# 	  \        V4       R# )z
Mark a job as having been run.

Updates last_run_at, last_status, increments completed count,
computes next_run_at, and auto-deletes if repeat limit reached.
r   r   okr  r   Nr   r   r   r   rS   r   Fr   r   r   r   )r   r   rr   rp   r(   popr   r   )	r   r  r  r   r   r%   r   r   r   s	   &&&      r   mark_job_runr  A  s?    ;DD/t9-))+C!$)0gC-4$C wwx  -0]->->{A-NQR-RHk* H))'2M+6	$y7IHHQKdO "2#j/3!GC =!)!&I*G!X-*GdO? "B dOr   c                0    V ^8  d   QhR\         R\        /# r   r  )r   s   "r   r   r   m  s      S T r   c                X   \        4       pV F  pVR,          V 8X  g   K  VP                  R/ 4      P                  R4      pVR9  d    R# \        4       P                  4       p\	        VR,          V4      pV'       d(   WRP                  R4      8w  d   WRR&   \        V4        R#  R# 	  R# )u  Preemptively advance next_run_at for a recurring job before execution.

Call this BEFORE run_job() so that if the process crashes mid-execution,
the job won't re-fire on the next gateway restart.  This converts the
scheduler from at-least-once to at-most-once for recurring jobs — missing
one run is far better than firing dozens of times in a crash loop.

One-shot jobs are left unchanged so they can still retry on restart.

Returns True if next_run_at was advanced, False otherwise.
r   rS   rV   Fr   Tr   rW   )r   r(   rr   rp   r   r   )r   r   r%   rV   r   new_nexts   &     r   advance_next_runr  m  s     ;Dt977:r*..v6D//-))+C'J=HH(>>%-M"$  r   c                \    V ^8  d   QhR\         \        \        \        3,          ,          /# r   r   )r   s   "r   r   r     s#     L Ld4S>* Lr   c            	     d   \        4       p \        4       p\        P                  ! V4       Uu. uF  p\	        V4      NK  	  pp. pRpV EF  pVP                  RR4      '       g   K  VP                  R4      pV'       g   \        VP                  R/ 4      V VP                  R4      R7      pV'       g   Kn  WR&   Tp\        P                  RVP                  R	VR
,          4      V4       V F   p	V	R
,          VR
,          8X  g   K  WR&   Rp M	  \        \        P                  ! V4      4      p
W8:  g   K  VP                  R/ 4      pVP                  R4      p\        V4      pVR9   d   W
,
          P                  4       V8  d{   \        WP                  4       4      pV'       dZ   \        P                  RVP                  R	VR
,          4      VVV4       V F   p	V	R
,          VR
,          8X  g   K  WR&   Rp M	  EK  VP!                  V4       EK  	  V'       d   \#        V4       V# u upi )a;  Get all jobs that are due to run now.

For recurring jobs (cron/interval), if the scheduled time is stale
(more than one period in the past, e.g. because the gateway was down),
the job is fast-forwarded to the next future run instead of firing
immediately.  This prevents a burst of missed jobs on gateway restart.
Fr   Tr   rS   r   r   z:Job '%s' had no next_run_at; recovering one-shot run at %sr   r   rV   zSJob '%s' missed its scheduled time (%s, grace=%ds). Fast-forwarding to next run: %sr  )rr   r   copydeepcopyr)   r(   r   r   infor~   r   rl   r   r   r   rp   r   r   )r   raw_jobsr   r   due
needs_saver%   r   recovered_nextrjnext_run_dtrS   rV   r   r  s                  r   get_due_jobsr+    s    -C{H,0MM(,CD,Cq",CDD
CJwwy$''77=)8
B'GGM2N
 "!/%HKKLD	*
 d8s4y((6}%!%J	  $H$:$:8$DEwwz2.H<<'D
 +84E++1B0Q0Q0SV[0[ ,HmmoFKK:D	2   'd8s4y008}-)-J!	 '
 JJsOs v (JE Es   H-c                0    V ^8  d   QhR\         R\         /# )r   r   r   )r   )r   s   "r   r   r     s      C  r   c                   \        4        \        V ,          pVP                  RRR7       \        V4       \	        4       P                  R4      pW# R2,          p\        P                  ! \        V4      RRR7      w  rV \        P                  ! VRR	R
7      ;_uu_ 4       pVP                  V4       VP                  4        \        P                  ! VP                  4       4       RRR4       \        P                  ! Wd4       \!        V4       V#   + '       g   i     L3; i  \"         d+     \        P$                  ! T4       h   \&         d     h i ; ii ; i)zSave job output to file.Tr9   z%Y-%m-%d_%H-%M-%Sz.mdr   z.output_r   r   r   r   N)r@   r>   r=   r3   rr   rq   r   r   r   r.   r   writer   r   r   rm   r7   r   r   r0   )r   r   job_output_dir	timestampoutput_filer   r   r   s   &&      r   save_job_outputr2    s   M&(N5&&':;I [#44K##N(;FS]^LBYYr311QGGFOGGIHHQXXZ  2 	

8)[!  21  	IIh 	  		sI   ?!D$  AD&)D$ D!	D$ $E0EEEEEE)NN)N)
NNNNNNNNNN)F):__doc__r"  r   loggingr   r.   rI   r   r   r   pathlibr   hermes_constantsr   typingr   r   r	   r
   	getLogger__name__r   hermes_timer   rr   r   rj   ImportError
HERMES_DIRr<   r   r>   r   r#   r)   r3   r7   r@   rP   ry   r~   r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r+  r2  r?   r   r   <module>r=     s&       	 	  (  , , ,			8	$ *!L 
{"	 
 "%*Vr$& "&	6@H6&bJB
&")X8L^{  Ls   D) )	D54D5