+
    jHàjx¤  ã                   óš  € R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RIHt ^ RI	H
t
 ^ RIHtHtHtHt ^ RIHtHtHt ^ RIHt ^ RIHt ^ R	IHt ^ R
IHtHtHtHtHtH t H!t!H"t"H#t# ^ RI$H%t%H&t&H't' ^ RI(H)t* ^ RI+H,t- ^ RI.H/t/ ^ RI0H1t2H3t4H5t6 ]! 4       t7]! RR4      t8]! RR4      t9]! RR4      t:]! RR4      t;]! RR4      t<]! RR4      t=]! RR4      t>]! RR4      t?]! RR4      t@]! RR4      tA]! RR4      tBR R  ltCR! R" ltDR# R$ ltER% R& ltFR' tGR‡R( R) lltHRˆR* R+ lltIR,tJR- R. ltK]R/ R0 l4       tLR1 R2 ltMR‰R3 R4 lltNR5 R6 ltOR7 R8 ltPR9 R: ltQR; R< ltRR= R> ltSRŠR@ RA lltTRB RC ltURD RE ltVRFtWRG RH ltXRI RJ ltYRˆRK RL lltZR‹RM RN llt[R?t\RO RP lt]RQ RR lt^RS RT lt_0 RŒmt`RˆRU RV lltaRˆRW RX lltbRˆRY RZ lltcRˆR[ R\ lltdRˆR] R^ llte^<tf]7PÏ                  R_4      R‡R` Ra ll4       th]7PÏ                  Rb4      RˆRc Rd ll4       ti]7PÏ                  Re4      R‹Rf Rg ll4       tj]7P×                  R_4      RˆRh Ri ll4       tl]7PÏ                  Rj4      RˆRk Rl ll4       tm]7PÝ                  Rb4      RˆRm Rn ll4       to]7P×                  Ro4      RˆRp Rq ll4       tp]7P×                  Rr4      RˆRs Rt ll4       tq]7P×                  Ru4      RˆRv Rw ll4       tr]7Pç                  Rb4      RˆRx Ry ll4       tt]7P×                  Rz4      R{ R| l4       tuR} R~ ltv]7PÏ                  R4      RˆR€ R� ll4       twR‚ Rƒ ltx]7P×                  R„4      R�R… R† ll4       tyR# )Ža  Cron dashboard routes.

The ``*_sync`` workers, profile resolution and the threadpool wrapper
(``_run_cron_dashboard_io``) live in web_server_cron and are reached through the
late-binding seam so ``monkeypatch.setattr(web_server_cron, ...)`` keeps working.
N)Úcontextmanager)Údatetime)ÚPath)ÚAnyÚDictÚListÚOptional)Ú	APIRouterÚHTTPExceptionÚRequest)ÚJSONResponse)Úlate)Úcfg_get)	Ú_create_cron_job_syncÚ_cron_optional_textÚ_cron_string_listÚ_mutate_cron_for_profileÚ _normalize_dashboard_cron_scriptÚ!_raise_if_cron_registration_errorÚ_run_cron_dashboard_ioÚ%_validate_dashboard_cron_context_fromÚ&_validate_dashboard_cron_effective_job)ÚAutomationBlueprintInstantiateÚCronJobCreateÚCronJobUpdate)Úlog)Úget_timezone)Úlegacy_cron_run_job)Úget_hermes_homeÚreset_hermes_home_overrideÚset_hermes_home_overrideÚ_find_cron_job_profilezhermes_cli.web_server_cronÚ_fire_cron_job_for_profileÚ_forward_cron_fire_to_gatewayÚ_gateway_intentionally_stoppedÚ!_notify_cron_provider_for_profileÚ_call_cron_for_profileÚload_configzhermes_cli.configÚ_cron_profile_dictsÚ_cron_profile_homeÚ_open_session_db_for_profilezhermes_cli.web_server_sessionsÚ_config_profile_scopezhermes_cli.web_server_profilesc                ó$   € V ^8„  d   QhR\         /# )é   Úreturn©r
   )Úformats   "Ú@/home/ubuntu/.hermes/hermes-agent/hermes_cli/web_routers/cron.pyÚ__annotate__r2   2   s   € ÷ Bñ Bœñ Bó    c                  ó   € \        R RR7      # )é”  zJob not found©Ústatus_codeÚdetailr/   © r3   r1   Ú_job_not_foundr:   2   s   € Ü S°ÔAÐAr3   c                ó€   € V ^8„  d   QhR\         \        \        3,          R\        R\         \        \        3,          /# )r-   ÚupdatesÚprofile_homer.   )ÚdictÚstrr   r   )r0   s   "r1   r2   r2   6   s6   € ÷ ñ ¬t´C¼°H­~ð ÌTð ÔVZÔ[^Ô`cÐ[cÕVdñ r3   c                óº  € \        T ;'       g    / 4      pR F  pW29   g   K  \        W#,          4      W#&   K   	  RV9   d   \        VR,          V4      VR&   RV9   d   \        VR,          RR7      VR&   RV9   d    \        VR,          4      ;'       g    RVR&   RV9   d   \        VR,          4      VR&   R	 F  pW29   g   K  \        W#,          4      W#&   K   	  V# )
züNormalize dashboard JSON into cron.jobs.update_job's storage shape.

Stays in the dashboard adapter layer on purpose: cron/jobs.py is the source
of truth for scheduling; this only translates form payloads into shapes the
core functions already accept.
ÚscriptÚbase_urlT)Ústrip_trailing_slashÚdeliverÚlocalÚfailure_deliver)ÚmodelÚproviderÚworkdir)Úcontext_fromÚenabled_toolsets)r>   r   r   r   )r<   r=   Ú
normalizedÚkeys   &&  r1   Ú!_normalize_dashboard_cron_updatesrN   6   sÜ   € ô �g—m�m Ó$€JÛ/ˆØÖÜ1°*µ/ÓBˆJ‹Oñ 0ð �:ÔÜ?À
È8Õ@TÐVbÓcˆ
�8ÑØ�ZÔÜ!4°ZÀ
Õ5KÐbfÔ!gˆ
�:ÑØ�JÔÜ 3°J¸yÕ4IÓ J× UÐ UÈgˆ
�9ÑØ˜JÔ&ô )<¸JÐGXÕ<YÓ(Zˆ
Ð$Ñ%Û3ˆØÖÜ/°
µÓ@ˆJ‹Oñ 4ð Ðr3   c                óh   € V ^8„  d   QhR\         R\        \         ,          R\        \         ,          /# ©r-   Újob_idÚprofiler.   ©r?   r   )r0   s   "r1   r2   r2   Q   s)   € ÷ *ñ *œsð *¬X´c­]ð *¼xÌ½}ñ *r3   c                óÈ   a € V'       dP   \        VRR4      p\        ;QJ d    V 3R lV 4       F  '       g   K   RM	  RM! V 3R lV 4       4      '       d   V# \        S 4      # )u‡  Profile that holds ``job_id`` (its jobs.json and the state.db with its run sessions).

``profile`` is a caller *hint*, not proof of ownership: the Desktop lists
jobs cross-profile (``?profile=all``) while its per-item calls carry the
ambient active profile. Trusting that hint opened a profile that does not
hold the job, so the job's run-session lookup matched nothing and
answered ``200 {"runs": []}`` â€” the UI rendered "No runs yet" for a job
that had run many times (#115345). A hint that does hold the job still
wins, so deliberately scoped lookups (the same job id in two profiles,
e.g. a copied jobs.json) keep reading the named profile.
Ú	list_jobsTc              3   ó‚   <"  € T F4  qP                  R 4      S8H  ;'       g    VP                  R4      S8H  x € K6  	  R# 5i)ÚidÚnameN)Úget)Ú.0ÚjrQ   s   & €r1   Ú	<genexpr>Ú%_job_owner_profile.<locals>.<genexpr>_   s4   øé € ÐNÉÀA�u‰u�T‹{˜fÑ$×?Ð?¨¯©¨f«¸Ñ(?Ô?Ëùs   ƒ?¢?F)r&   Úanyr!   )rQ   rR   Újobss   f& r1   Ú_job_owner_profiler`   Q   sG   ø€ ÷ Ü% g¨{¸DÓAˆß‹3ÔNÉÓN�3�3Š3ÔNÉÓN×NÒNØˆNÜ! &Ó)Ð)r3   c                óR   € V ^8„  d   QhR\         R\        \         ,          R\         /# rP   rS   )r0   s   "r1   r2   r2   d   s%   € ÷ ñ œð ¤x´¥}ð ¼ñ r3   c                ó@   € \        W4      pV'       g   \        4       hV# )a  Owning profile for the get/update/pause/resume/trigger/delete family; 404 when no profile
holds the job. Same hint validation as the run lookup, so a wrong-profile hint from the
cross-profile list cannot 404 (or act on the wrong store for) a job the server can locate.)r`   r:   )rQ   rR   Úselecteds   && r1   Ú_job_profilerd   d   s   € ô " &Ó2€HßÜÓÐØ€Or3   c                 ó*   € V '       g   \        4       hV # ©N)r:   )Újobs   &r1   Ú_foundrh   n   s   € ßÜÓÐØ€Jr3   c                ó$   € V ^8„  d   QhR\         /# ©r-   rR   ©r?   )r0   s   "r1   r2   r2   t   s   € ÷ (*ñ (*¤#ñ (*r3   c                 óB  € T ;'       g    R P                  4       pVP                  4       R 8w  d   \        VRR4      # . p\        4        FN  p\	        VP                  R4      ;'       g    R4      pV'       g   K1   VP                  \        VRR4      4       KP  	  / p. pV F²  p\        V\        4      '       g   K  VP                  R4      ;'       g    VP                  R4      pV'       d   VR8X  d   VP                  V4       Kg  VP                  V4      p	V	e3   VP                  R	4      '       g   K•  V	P                  R	4      '       d   K®  WuV&   K´  	  \        VP                  4       4      V,           #   \         d    \        P                  ! RT4        EKT  i ; i)
ÚallrU   TrX   Ú z'Failed to list cron jobs for profile %srW   rQ   ÚunknownÚis_default_profile)ÚstripÚlowerr&   r(   r?   rY   ÚextendÚ	ExceptionÚ_logÚ	exceptionÚ
isinstancer>   ÚappendÚlistÚvalues)
rR   Ú	requestedÚall_jobsÚitemrX   Úby_idÚunkeyedrg   ÚjidÚexistings
   &         r1   Ú_list_cron_jobs_syncr‚   t   s\  € Ø×!Ð!˜E×(Ñ(Ó*€IØ‡�Ó˜EÔ!Ü% i°¸dÓCÐCð &(€HÜ#Ö%ˆÜ�4—8‘8˜FÓ#×)Ð) rÓ*ˆßÙð	LØ�O‰OÔ2°4¸ÀdÓKÖLñ &ð (*€EØ$&€GÛˆÜ˜#œt×$Ò$ÙØ�g‰g�d‹m×0Ð0˜sŸw™w xÓ0ˆ÷ �c˜YÔ&Ø�N‰N˜3ÔÙØ—9‘9˜S“>ˆØÒØ�G‰GÐ(×)Ô)°(·,±,Ð?S×2TÔ2Tà�#‹Jñ! ô" �—‘“Ó 'Õ)Ð)øô- ô 	LÜ�NŠNÐDÀd×KÐKð	Lús   Á;E7Å7"FÆFc                óF   € V ^8„  d   QhR\         R\        \         ,          /# ©r-   rQ   rR   rS   )r0   s   "r1   r2   r2   Ÿ   s"   € ÷ \ñ \œsð \¬X´c­]ñ \r3   c                 ó@   € \        \        \        W4      R V 4      4      # )Úget_job)rh   r&   rd   ©rQ   rR   s   &&r1   Ú_get_cron_job_syncrˆ   Ÿ   s   € ÜÔ(¬°fÓ)FÈ	ÐSYÓZÓ[Ð[r3   z%Y-%m-%d_%H-%M-%Sc                óR   € V ^8„  d   QhR\         \        ,          R\        R\        /# )r-   rR   rQ   r.   )r   r?   r   )r0   s   "r1   r2   r2   ¦   s%   € ÷ 	;ñ 	;¤8¬C¥=ð 	;¼#ð 	;Ä$ñ 	;r3   c                óº   € V '       d    \        V 4      w  r#M
\        4       p\        V4      R,          R,          V,          #   \         d    \        4       p L7i ; i)uI   Output docs live under the job's home â€” resolve it even without a hint.ÚcronÚoutput)r)   rt   Ú_get_hermes_homer   )rR   rQ   Ú_r=   s   &&  r1   Ú_cron_output_runs_dirr�   ¦   sU   € çð	.Ü0°Ó9‰OˆAˆ|ô (Ó)ˆÜ�Ó Õ&¨Õ1°FÕ:Ð:øô	 ô 	.Ü+Ó-ŠLð	.ús   ŠA ÁAÁAc                ó:   € V ^8„  d   QhR\         \        ,          /# rj   ©r   r?   )r0   s   "r1   r2   r2   ³   s   € ÷ +ñ +œx¬�}ñ +r3   c              #  óN  "  € V '       g   Rx € R#  \        V 4      w  r\        T4      P                  4       \	        4       P                  4       8X  d   Rx € R# \        \        T4      4      p Tx € \        T4       R#   \         d	    Rx €  R# i ; i  \        T4       i ; i5i)aÃ  Keep reads in the owner's profile context for the whole run-history build.

Filename stems, the execution ledger and ``hermes_time``'s configured zone all
resolve through the *current* ``get_hermes_home()``; without this scope a
cross-profile listing decodes another profile's docs with the dashboard's
zone (an hours-off timestamp) and reads the dashboard's executions.db. The
dashboard's own profile needs no override (its home is already current).
N)r)   rt   r   Úresolver�   Ú_set_hermes_home_overrider?   Ú_reset_hermes_home_override)rR   rŽ   r=   Útokens   &   r1   Ú_owner_home_scoper—   ²   s•   é € ÷ ØŠ
ÙðÜ,¨WÓ5‰ˆô ˆLÓ×!Ñ!Ó#Ô'7Ó'9×'AÑ'AÓ'CÔCØŠ
ÙÜ%¤c¨,Ó&7Ó8€Eð+ØÒä# EÖ*øô ô ØŠ
Úðûô 	$ EÕ*üs@   ‚B%’A? ŸAB%Á.B Á2B%Á?BÂB%ÂBÂB%ÂB"Â"B%c                óF   € V ^8„  d   QhR\         R\        \        ,          /# )r-   Úpathr.   )r   r   Úfloat)r0   s   "r1   r2   r2   Î   s   € ÷ *ñ *¤Tð *¬h´u­oñ *r3   c                ó
  €  \         P                  ! V P                  \        4      p\        4       pTe!   TP                  TR7      P                  4       # TP                  4       P                  4       #   \         d     R# i ; i)u  Epoch seconds for an output filename's wall time.

save_job_output writes the stem with hermes_time.now() â€” the configured
IANA timezone (HERMES_TIMEZONE / config), falling back to server-local.
Read it back with that same ZONE, not a fixed offset snapshot of today's
local offset: a snapshot is wrong when the configured zone differs from
the server's, and off by an hour for a historical file across a DST
transition. With no configured zone, astimezone() on the naive datetime
resolves the offset in effect on the filename's own date.
N)Útzinfo)	r   ÚstrptimeÚstemÚ_CRON_OUTPUT_FILENAME_FORMATÚ
ValueErrorÚ_get_timezoneÚreplaceÚ	timestampÚ
astimezone)r™   ÚnaiveÚtzs   &  r1   Ú_cron_output_run_timestampr§   Î   sr   € ðÜ×!Ò! $§)¡)Ô-IÓJˆô 
‹€BØ	‚~Ø�}‰} Bˆ}Ó'×1Ñ1Ó3Ð3Ø×ÑÓ×'Ñ'Ó)Ð)øô ô Úðús   ‚%A3 Á3BÂBc                ó<   € V ^8„  d   QhR\         R\        R\        /# )r-   r™   Ú	max_charsr.   )r   Úintr?   )r0   s   "r1   r2   r2   ã   s!   € ÷ 5ñ 5¤4ð 5´Cð 5Ä#ñ 5r3   c                 ó   €  V P                  R RR7      p\        P                  ! RRT4      P	                  4       p\        T4      T8:  d   T# TRT^,
           P                  4       R,           #   \         d     R# i ; i)z	utf-8-sigr¢   )ÚencodingÚerrorsrn   z\s+Ú Nu   â€¦)Ú	read_textÚOSErrorÚreÚsubrq   ÚlenÚrstrip)r™   r©   ÚrawÚpreviews   &&  r1   Ú_cron_output_run_previewr·   ã   sz   € ðØ�n‰n k¸)ˆnÓDˆô �fŠf�V˜S #Ó&×,Ñ,Ó.€GÜ
ˆ7ƒ|�yÔ ØˆØ�?�Y •]Ð#×*Ñ*Ó,¨uÕ4Ð4øô ô Úðús   ‚A. Á.A=Á<A=c                ó~   € V ^8„  d   QhR\         \        \        \        3,          ,          R\         \        ,          /# ©r-   rg   r.   )r   r>   r?   r   rš   )r0   s   "r1   r2   r2   î   s+   € ÷ ñ ¤h¬t´C¼°H­~Õ&>ð Ä8ÌEÅ?ñ r3   c                 ót  € \        V \        4      '       g   R # V P                  R4      p\        V\        \        34      '       d   \	        V4      # \        V\
        4      '       d@   VP                  4       pV'       g   R #  \        P                  ! V4      P                  4       # R #   \         d     R # i ; i)NÚlast_run_at)rw   r>   rY   rª   rš   r?   rq   r   Úfromisoformatr£   r    )rg   rµ   Útexts   &  r1   Ú_cron_job_last_run_timestampr¾   î   s‘   € Ü�cœ4× Ò ÙØ
�'‰'�-Ó
 €CÜ�#œœU�|×$Ò$Ü�S‹zÐÜ�#”s×ÒØ�y‰y‹{ˆßÙð	Ü×)Ò)¨$Ó/×9Ñ9Ó;Ð;ñ øô ô 	Úð	ús   Â#B( Â(B7Â6B7c                óh   € V ^8„  d   QhR\         \        \        \        3,          ,          R\        /# r¹   )r   r>   r?   r   )r0   s   "r1   r2   r2   ÿ   s'   € ÷ ,ñ ,¤8¬D´´c°­NÕ#;ð ,Äñ ,r3   c                 óê   € \        V \        4      '       g   R # \        V P                  R4      ;'       g    R 4      P	                  4       pV'       g   R # VP                  RR4      P                  4       # )rn   Úlast_statusrŽ   r®   )rw   r>   r?   rY   rq   r¢   Úupper)rg   Ústatuss   & r1   Ú_cron_output_status_labelrÄ   ÿ   sV   € Ü�cœ4× Ò ÙÜ�—‘˜Ó'×-Ð-¨2Ó.×4Ñ4Ó6€FßÙØ�>‰>˜#˜sÓ#×)Ñ)Ó+Ð+r3   c          
      ó€   € V ^8„  d   QhR\         R\        R\        \        ,          R\        \        \        3,          /# )r-   Ú
started_atÚtitler¶   r.   )rš   r?   r   r>   r   )r0   s   "r1   r2   r2     s9   € ÷ ñ ¤Uð ´3ð ÄÌ#Åð ÔSWÔX[Ô]`ÐX`ÕSañ r3   c                 óR   € R TRT;'       g    RRRRV RV RV R^ R	^ R
^ R^ RRRRRRRR/# )rÇ   r¶   NÚsourceÚcron_outputrÆ   Úlast_activeÚended_atÚinput_tokensÚoutput_tokensÚmessage_countÚtool_call_countrG   ÚcwdÚarchivedFÚ	is_activer9   )rÆ   rÇ   r¶   s   &&&r1   Ú_cron_output_run_rowrÔ     sX   € à�Ø�7—?�?˜dØ�-Ø�jØ�zØ�JØ˜Ø˜Ø˜Ø˜1Ø�ØˆtØ�EØ�Uðð r3   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# )r-   r½   r.   )r   r   rš   )r0   s   "r1   r2   r2     s   € ÷ ñ œð ¤¬¥ñ r3   c                 óè   € \        V \        4      '       d   V P                  4       '       g   R #  \        P                  ! V P                  4       4      P                  4       #   \         d     R # i ; irf   )rw   r?   rq   r   r¼   r£   r    )r½   s   &r1   Ú_iso_to_epochr×     sS   € Ü�dœC× Ò ¨¯
©
¯ªÙðÜ×%Ò% d§j¡j£lÓ3×=Ñ=Ó?Ð?øÜô Úðús   °1A" Á"A1Á0A1c                óh   € V ^8„  d   QhR\         R\        \        \         \        3,          ,          /# ©r-   Úcanonical_job_idr.   )r?   ry   r>   r   )r0   s   "r1   r2   r2   $  s'   € ÷ ñ ´ð ¼¼TÄ#ÄsÀ(½^Õ8Lñ r3   c                ót  €  ^ RI Hp V! V ^dR7      p. pT F÷  p\        TP	                  R4      ;'       g    R4      R9  d   K.  TP                  R\        TP	                  R4      ;'       g    R4      R\        TP	                  R4      4      ;'       g    RR\        TP	                  R4      4      R\        TP	                  R4      ;'       g    R4      R	\        TP	                  R	4      ;'       g    R4      P                  4       /4       Kù  	  TP                  R
 RR7       T#   \         d    . u # i ; i)uh  Terminal execution-ledger rows for the job, newest first.

Each script-only fire creates exactly one ledger row (claimed â†’ completed /
failed), so the ledger â€” not filename proximity to ``last_run_at`` â€” is the
per-attempt record that pairs an output doc with its real status. Read inside
the owner-home scope so it opens the OWNER's cron/executions.db.
)Úlist_executions)rQ   ÚlimitrÃ   rn   rW   Ú
claimed_atç        Úfinished_atÚerrorc                 ó   € V R ,          # )rÞ   r9   ©Úrs   &r1   Ú<lambda>Ú+_owner_profile_executions.<locals>.<lambda>=  s   €   ,¦r3   T©rM   Úreverse)Ú	completedÚfailedro   )	Úcron.executionsrÜ   rt   r?   rY   rx   r×   rq   Úsort)rÚ   rÜ   ÚrowsÚterminalÚrows   &    r1   Ú_owner_profile_executionsrð   $  s  € ðÝ3áÐ&6¸cÔBˆð &(€HÛˆÜˆs�w‰w�xÓ ×&Ð& BÓ'Ð/QÔQÙØ�‰Ø”#�c—g‘g˜d“m×)Ð) rÓ*Øœ-¨¯©°Ó(=Ó>×EÐEÀ#Øœ=¨¯©°Ó)?Ó@Ø”c˜#Ÿ'™' (Ó+×1Ð1¨rÓ2Ø”S˜Ÿ™ Ó)×/Ð/¨RÓ0×6Ñ6Ó8ð
ö 	ñ ð ‡M�MÑ/¸€MÔ>Ø€Oøô ô ØŠ	ðús   ‚D' Ä'D7Ä6D7ç     Àr@c                ój   € V ^8„  d   QhR\         \        \        3,          R\        R\        R\        /# )r-   ÚattemptrÆ   Úgrace_secondsr.   )r>   r?   r   rš   Úbool)r0   s   "r1   r2   r2   A  s:   € ÷ Bñ BÜ”#”s�(�^ðBÜ).ðBÜ?DðBä	ñBr3   c                ó˜   € VR,           \         P                  ! V R,          4      8  d   R# V R,          pVRJ ;'       g    WV,           8*  # )a¹  Whether an output doc's timestamp falls inside a ledger attempt's window.

``save_job_output`` runs after the script finishes but before
``finish_execution`` closes the attempt, and both clocks are
``hermes_time.now()`` in the owner's zone, so a doc written by an attempt
lands within ``[claimed_at, finished_at + grace]`` of THAT attempt (a still-
open attempt has no ``finished_at``). Older attempts closed before this doc
was written, which is what keeps a fast-firing job's status from bleeding
onto an earlier run's row. The doc's filename keeps whole seconds only, so
the claim is floored to its second: an attempt claimed at ``:00.500`` that
writes at ``:00.750`` files its doc as ``:00``.
gü©ñÒMbP?rÞ   Frà   N)ÚmathÚfloor)ró   rÆ   rô   Úfinishs   &&& r1   Ú_execution_containsrú   A  sF   € ð �EÕœDŸJšJ w¨|Õ'<Ó=Ô=ÙØ�]Õ#€FØ�Tˆ>×AÐA˜Z°MÕ+AÑAÐAr3   c                óH   € V ^8„  d   QhR\         R\         R\         R\         /# )r-   rÃ   rá   Úfallbackr.   rk   )r0   s   "r1   r2   r2   V  s.   € ÷ `ñ `¤Cð `´ð `¼sð `Äsñ `r3   c                 ó¶   € T ;'       g    R P                  RR4      P                  4       pVR8X  d   V# V'       g   V# VR8X  d   V'       d   V RV 2# V RV 2# )rn   rŽ   r®   ÚUNKNOWNÚFAILEDõ    Â· )r¢   rÂ   )rÃ   rá   rü   Úlabels   &&& r1   Ú_execution_status_titler  V  se   € Ø�\ˆ\�r×"Ñ" 3¨Ó,×2Ñ2Ó4€EØ�	ÔØˆßØˆØ%*¨hÔ%6¿5ˆeˆW�D˜˜Ð Ð_È%ÈÐPTÐU]ÐT^ÐG_Ð_r3   c                ó4  € V ^8„  d   QhR\         \        \        \        3,          ,          R\        R\         \        ,          R\        R\
        \        \        \        \        3,          ,          \        \        \        \        3,          ,          3,          /# )r-   rg   rÚ   rR   rÝ   r.   )r   r>   r?   r   rª   Útuplery   )r0   s   "r1   r2   r2   _  sv   € ÷ @ñ @Ü	”$”sœC�x•.Õ	!ð@äð@ô ”c�]ð@ô ð	@ô
 Œ4””Sœ#�X•Õ¤¤d¬3´¨8¥nÕ!5Ð5Õ6ñ@r3   c           	     ó|  € \        W!4      p \        R VP                  R4       4       R RR7      p\	        V4      p. pVRV  Fq  p\        V4      p	V	f    VP                  4       P                  p	\        V4      p
TP                  RR	V R
VP                   2/\        Yš;'       g    RV
4      C4       Ks  	  V'       g;   \        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       d   Wv3# \        V 4      pVf   . V3# Rp
\        V \        4      '       d3   \!        V P#                  R4      ;'       g    R4      P%                  4       p
\'        V 4      ;'       g    RpV
'       d   V RV
 2pRR	V R2/\        W¼V
4      C.V3#   \         d    . p ELti ; i  \         d    Rp	 ELIi ; i)a{  SessionDB-less run records for jobs that never create agent sessions: ``(docs, attempts)``.

Script-only (no_agent) jobs deliberately skip SessionDB (cron/scheduler.run_job),
so their completed runs are invisible to the run-history endpoint. Their
output docs (one .md per fire under cron/output/<job_id>/) and the execution
ledger (one row per fire) are the only per-run records. ``docs`` are rows in
/api/sessions shape with source='cron_output', newest first, so the frontend
reuses SessionInfo; ids use a cron_output: prefix that can never collide with a
SessionDB session id. ``attempts`` are the job's terminal ledger rows, newest
first. A doc never records its execution, so which attempt it stands for is
decided by ``_reconcile_cron_runs`` beside the sessions, never here, and never
from ``last_run_at`` proximity, which mislabels an older doc when the newest
run's doc is missing.
c              3   óT   "  € T F  qP                  4       '       g   K  Vx € K   	  R # 5irf   )Úis_file)rZ   r™   s   & r1   r\   Ú)_list_cron_output_runs.<locals>.<genexpr>v  s   é € ÐHÑ5�d¿¹¿�TŠTÓ5ùs   ‚(ž
(z*.mdc                 ó   € V P                   # rf   )rX   )r™   s   &r1   rå   Ú(_list_cron_output_runs.<locals>.<lambda>w  s   € ˜TŸYšYr3   Trç   Nrß   rW   úcron_output:Ú:úScript-only runc              3   ó8   "  € T F  qR ,          R9   x € K  	  R# 5i)rÃ   N©ré   rê   r9   )rZ   ró   s   & r1   r\   r  Œ  s   é € ÐXÉxÀG˜8Õ$Ð(?Ö?Ëxùs   ‚Frn   Ú
last_errorr   z:latest)r�   ÚsortedÚglobr°   rð   r§   ÚstatÚst_mtimer·   rx   rž   rÔ   r^   r¾   rw   r>   r?   rY   rq   rÄ   )rg   rÚ   rR   rÝ   Ú
output_dirÚfilesÚattemptsÚdocsr™   rÆ   r¶   Ú	latest_tsrÇ   s   &&&&         r1   Ú_list_cron_output_runsr  _  sÒ  € ô( ' wÓA€JðÜÙH˜jŸo™o¨fÔ5ÓHÙ&Øô
ˆô )Ð)9Ó:€HØ!#€DØ�f�u“ˆÜ/°Ó5ˆ
ØÒð!Ø!ŸY™Y›[×1Ñ1�
ô +¨4Ó0ˆØ�‰Ø�LÐ!1Ð 2°!°D·I±I°;Ð?ð
ä" :×/KÐ/KÐ:KÈWÓUð
ö 	ñ ÷ �s‹sÑXÉxÓX�s�sŠsÑXÉxÓX×XÒXØˆ~Ðô
 -¨SÓ1€IØÒØ�8ˆ|Ðà€GÜ�#”t×ÒÜ�c—g‘g˜lÓ+×1Ð1¨rÓ2×8Ñ8Ó:ˆÜ% cÓ*×?Ð?Ð.?€EßØ�'˜˜g˜YÐ'ˆà�Ð-Ð.¨gÐ6ðä
˜y°Ó
9ðð ð 	ðð øôE ô Ø‹ðûô ô !Ø “
ð!ús#   �%F ÁF*ÆF'Æ&F'Æ*F;Æ:F;g      @c                óh   € V ^8„  d   QhR\         R\        \        \         \        3,          ,          /# rÙ   )r?   r   r>   r   )r0   s   "r1   r2   r2   §  s'   € ÷ 
ñ 
¬sð 
´xÄÄSÌ#ÀXÅÕ7Oñ 
r3   c                óD   €  ^ RI Hp V! V 4      #   \         d     R# i ; i)z¡The job's claimed/running ledger attempt under a live owner, or None (fail closed).

Must run inside the owner-home scope so it reads the OWNER's executions.db.
)Úlive_inflight_executionN)rë   r  rt   )rÚ   r  s   & r1   Ú_live_inflight_executionr  §  s(   € ð
Ý;á&Ð'7Ó8Ð8øÜô Úðús   ‚ �žc                ó–   € V ^8„  d   QhR\         \        \        3,          R\        \         \        \        3,          ,          R\        /# )r-   ÚsessionÚinflightr.   ©r>   r?   r   r   rõ   )r0   s   "r1   r2   r2   ´  s<   € ÷ `ñ `œ4¤¤S �>ð `´X¼dÄ3ÌÀ8½nÕ5Mð `ÔRVñ `r3   c                ó„  € V'       d   V P                  R4      e   R# V P                  R4      '       d   V R,          VP                  R4      8H  # \        VP                  R4      4      p \        V P                  R4      ;'       g    ^ 4      pTRJ;'       d    Y2\
        ,
          8¬  #   \        \        3 d     R# i ; i)a&  Whether the scheduler still OWNS this never-closed run session (#88443).

``is_active`` is a 300s activity window, so a live run inside a long tool call
(no heartbeat, no message) reads inactive exactly like a zombie whose process
died. Ownership is the durable answer: the job's in-flight attempt is held by a
live process and this session is that attempt's run. A unified-runtime run names
its execution, so that is exact; a legacy run must have started under the claim,
so an older never-closed run of the same job predates it and stays a zombie.
rÌ   NFÚcron_execution_idrW   rÞ   rÆ   )rY   r×   rš   Ú	TypeErrorr    Ú_OWNERSHIP_CLAIM_GRACE_SECONDS)r   r!  rÞ   rÆ   s   &&  r1   Ú_run_owned_byr'  ´  s¬   € ÷ �w—{‘{ :Ó.Ò:ÙØ‡{�{Ð&×'Ò'ØÐ*Õ+¨x¯|©|¸DÓ/AÑAÐAÜ˜xŸ|™|¨LÓ9Ó:€JðÜ˜7Ÿ;™; |Ó4×9Ð9¸Ó:ˆ
ð ˜TÐ!×_Ð_ jÔA_Õ4_Ñ&_Ð_øô ”zÐ"ô Úðús   Á+B* ÂB* Â*B?Â>B?c                óŠ   € V ^8„  d   QhR\         \        \        3,          R\        \        ,          R\        \        ,          /# )r-   r   rR   r.   r"  )r0   s   "r1   r2   r2   Ê  s9   € ÷ Hñ H¤d¬3´¨8¥nð H¼xÌ½}ð HÔX`ÔaeÕXfñ Hr3   c                ód  € V P                  R4      R8w  d   R# V P                  R4      ;'       g    \        V P                  R4      4      pV'       g   R# V P                  R4      e   R# \        V4      ;_uu_ 4        \        V \	        V4      4      uuRRR4       #   + '       g   i     R# ; i)aÓ  ``scheduler_owned`` for one session row, or None when it is not a cron run session.

The session-detail endpoint stamps this so a client re-checking a run it
already has open (a restored tab, a send) gets the same answer as the runs list.
A unified-runtime run's ``local-*`` id says nothing, so the caller stamps the
row's ``cron_job_id`` / ``cron_execution_id`` (``SessionDB.cron_run_identity``)
first; a legacy ``cron_{job_id}_{timestamp}`` id still names its job.
rÉ   r‹   NÚcron_job_idrW   rÌ   F)rY   r   r—   r'  r  )r   rR   rQ   s   && r1   Úcron_run_scheduler_ownedr+  Ê  s   € ð ‡{�{�8Ó Ô&ÙØ�[‰[˜Ó'×QÐQÔ+>¸w¿{¹{È4Ó?PÓ+Q€FßÙØ‡{�{�:ÓÒ*ÙÜ	˜7×	#Õ	#Ü˜WÔ&>¸vÓ&FÓG÷ 
$×	#×	#Ó	#ús   Á>BÂB/	c                óR   € V ^8„  d   QhR\         R\        \         ,          R\        /# ©r-   rQ   rR   rÝ   ©r?   r   rª   )r0   s   "r1   r2   r2   Þ  s+   € ÷ 5nñ 5n¤Sð 5n´8¼Cµ=ð 5nÔPSñ 5nr3   c                ó   € \        W4      pT pRpV'       d?   \        VRV 4      pV'       d*   VP                  R4      '       d   \        VR,          4      p \	        ^\        \        V4      ^d4      4      p\        V4      ;_uu_ 4        \        VRR7      p VP                  WF^ R7      pVP                  4        \        P                  ! 4       p	\        V4      p
V F…  pVP                  R4      RJ ;'       d,    W›P                  RVP                  R	^ 4      4      ,
          R
8  VR&   \        Wº4      VR&   \!        VP                  R4      4      VR&   V'       g   K�  W;R&   K‡  	  \#        WTW64      w  rÍRRR4       R\%        XXXWF4      RV/#   \        \        3 d    ^p EL.i ; i  TP                  4        i ; i  + '       g   i     LQ; i)uÞ  Run history for a cron job, newest first: agent sessions PLUS script-only fires.

Agent runs are ordinary ``source='cron'`` sessions: a legacy
``cron_{job_id}_{timestamp}`` id, or a unified-runtime ``local-*`` id bound to
this job by its scheduler admission. Backed by ``SessionDB.list_cron_job_runs``
â€” bounded index range scans, so cost scales with this job's runs, not total
cron history. Script-only (no_agent) jobs never write sessions; their
per-fire output docs (``_list_cron_output_runs``) fill the gaps. The two
representations are reconciled PER EXECUTION, not by branch: a job can
switch modes without changing its id (update_job supports no_agent on an
existing record), so a surviving historical agent session must not hide
newer script-only fires and a recent session must not hide older script
output. Rows carrying the same execution (an agent fire writes BOTH a
session and an output doc) are de-duplicated by execution id, or by
timestamp window for a legacy session that names none; script-only rows
that match no session are added in. All owner-scoped reads (SessionDB,
output docs, execution ledger, filename timezone) run inside the owner's
profile scope so the dashboard's own zone/store is never borrowed
cross-profile.
Nr†   rW   T)Ú	read_only)rÝ   ÚoffsetrÌ   rË   rÆ   i,  rÓ   Úscheduler_ownedrÒ   rR   ÚrunsrÝ   )r`   r&   rY   r?   ÚmaxÚminrª   r%  r    r—   r*   Úlist_cron_job_runsÚcloseÚtimer  r'  rõ   r  Ú_reconcile_cron_runs)rQ   rR   rÝ   rc   Ú	canonicalrg   Úlimit_nÚdbÚsession_runsÚnowr!  Úsr  r  s   &&&           r1   Ú_list_cron_job_runs_syncr@  Þ  s—  € ô* " &Ó2€Hà€IØ
€CßÜ$ X¨y¸&ÓAˆß�3—7‘7˜4—=’=Ü˜C �I›ˆIðÜ�aœœS ›Z¨Ó-Ó.ˆô 
˜8×	$Õ	$Ü)¨(¸dÔCˆð	Ø×0Ñ0°ÐRSÐ0ÓTˆLà�H‰HŒJä�iŠi‹kˆÜ+¨IÓ6ˆÛˆAØŸU™U :Ó.°$Ð6×uÐu¸CÇ%Á%ÈÐWX×W\ÑW\Ð]iÐklÓWmÓBnÕ<nÐruÑ;uˆAˆk‰NÜ#0°Ó#=ˆAÐÑ Ü  §¡ zÓ!2Ó3ˆAˆj‰Mß‰xØ'�)“ñ ô 0°ÀÓR‰ˆ÷! 
%ð$ Ô(¨°t¸XÀyÓZÐ\cÐelÐmÐmøô+ ”zÐ"ô Ø‹ðûð �H‰H�Jú÷ 
%×	$úsD   ÁF Â	F=ÂF(Â+AF=Ã:AF=ÅF=ÆF%Æ$F%Æ(F:Æ:F=Æ=G	c                ó@  € V ^8„  d   QhR\         \        \        \        3,          ,          R\         \        \        \        3,          ,          R\         \        \        \        3,          ,          R\        R\        R\         \        \        \        3,          ,          /# )r-   r=  r  r  rÚ   rÝ   r.   )ry   r>   r?   r   rª   )r0   s   "r1   r2   r2     sx   € ÷ <ñ <Ü”tœC¤˜H•~Õ&ð<ä
Œt”Cœ�H�~Õ
ð<ô ”4œœS˜•>Õ"ð<ô ð	<ô
 ð<ô 
Œ$Œs”Cˆx�.Õñ<r3   c                óH  aa€ R R lpV  Uu/ uF%  qfP                  R4      '       g   K  VR,          VbK'  	  upoV  Uu. uF  qfP                  R4      '       d   K  VNK  	  pp\        V4       UU	u. uF  w  r‰V	R,          R9   g   K  W‰3NK  	  p
pp	V
 UU	u0 uF  w  r‰V	R,          S9   g   K  VkK  	  ppp	\        4       p\        V 4      pV EF  p\	        VP                  R4      ;'       g    ^ 4      oV
 UU	u. uF$  w  r‰WŒ9  g   K  \        V	S4      '       g   K!  W‰3NK&  	  ppp	V'       g#   V! SV 4      '       g   VP                  V4       K„  \        VVV3R lR7      w  ppVP                  V4       VP                  V4       VR,          S9   g   V! SV4      '       d   KÚ  VP                  / VCR	\        VR,          VR
,          VR	,          4      /C4       EK  	  V
 F„  w  ppW‹9   d   K  VR,          ;'       g
    VR,          pV! VV4      '       d   K9  VR
,          pTP                  RRV RV 2/\        T\        VR,          VR4      T;'       g    R4      C4       K†  	  VP                  R RR7       VRV # u upi u upi u up	pi u up	pi u up	pi )a/  One row per execution, newest first: each terminal ledger attempt is its session, else the
output doc it wrote, else a metadata row from the ledger.

An agent fire writes BOTH a session and an output doc; sessions carry the richer
record (tokens, message counts, chat navigation), so the session wins and is never
dropped. A unified-runtime session names its execution, so that pairing is exact.
A doc only has a window guess (``_execution_contains``), so docs pair with attempts
only once every attempt's session is known (``_doc_writer``), one doc per attempt:
the doc folds into its writer's session, or into a legacy session (which names no
execution) by time, and otherwise is its writer's row with the ledger status. A doc
no attempt claims is matched against every session by time, and kept if none matches.
c                ót   € V ^8„  d   QhR\         R\        \        \        \        3,          ,          R\
        /# )r-   Údoc_tsÚsessionsr.   )rš   ry   r>   r?   r   rõ   )r0   s   "r1   r2   Ú*_reconcile_cron_runs.<locals>.__annotate__-  s3   € ÷ bñ bœð b¬¬d´3¼°8­nÕ)=ð bÄ$ñ br3   c                 óx   a € \         ;QJ d    V 3R  lV 4       F  '       g   K   R# 	  R# ! V 3R  lV 4       4      # )c              3   óF   <"  € T F  p\        SV\        4      x € K  	  R # 5irf   ©Ú_doc_matches_sessionÚ_SESSION_DOC_MATCH_SECONDS)rZ   r?  rD  s   & €r1   r\   Ú8_reconcile_cron_runs.<locals>.matches.<locals>.<genexpr>.  s"   øé € ÐaÑX`ÐSTÔ'¨°Ô3M×NÐNÓX`ùs   ƒ!TF)r^   )rD  rE  s   f&r1   ÚmatchesÚ%_reconcile_cron_runs.<locals>.matches-  s.   ø€ ß‹sÔaÑX`Óa�sŒsÐaŠsÐaˆsÔaÑX`ÓaÓaÐar3   r$  rÃ   rW   rÆ   c                 óf   <€ \        SV ^,          SP                  V ^,          R,          4      4      # )é   rW   )Ú_doc_writerrY   )ÚcÚby_executionrD  s   &€€r1   rå   Ú&_reconcile_cron_runs.<locals>.<lambda>?  s+   ø€ ´{À6È1ÈQÍ4ÐQ]×QaÑQaÐbcÐdeÕbfÐgkÕblÓQmÔ7nr3   )rM   rÇ   rá   rà   rÞ   r  z:exec:r  Nc                 óJ   € \        V P                  R 4      ;'       g    ^ 4      # )rÆ   ©rš   rY   rã   s   &r1   rå   rT  U  s   € œe A§E¡E¨,Ó$7×$<Ð$<¸1Ô=r3   Trç   r  )rY   Ú	enumerateÚsetry   rš   rú   rx   r5  Úaddr  rÔ   rì   )r=  r  r  rÚ   rÝ   rM  r?  ÚlegacyÚindexÚarî   ÚrepresentedÚpairedÚmergedÚdocÚ
candidatesró   Úwhenrá   rS  rD  s   &&&&&              @@r1   r9  r9    s‚  ù€ õ&bñ 8DÓb±|°!ÇuÁuÐM`×GaÔ-�AÐ)Õ*¨AÒ-±|Ñb€LÙ%ÓH™�A¯U©UÐ3F×-G�aˆa™€FÐHÜ+4°XÔ+>ÔiÑ+>™x˜uÀ!ÀHÅ+ÐQhÑBh”
�“
Ñ+>€HÑiÙ)1ÔM©™X˜U°Q°tµWÀÑ5L—5�5©€KÑMÜ“%€FÜ�,Ó€FäˆÜ�s—w‘w˜|Ó,×1Ð1°Ó2ˆÙ19ô Q±¡X UØÑ,ô !Ü1DÀQÈ×1Oô !�u“j±ˆ
ñ QçÙ˜6 <×0Ò0Ø—‘˜cÔ"ÙÜ˜ZÕ-nÔo‰ˆˆwØ�
‰
�5ÔØ�‰˜ÔØ�4�=˜LÔ(©G°F¸F×,CÒ,CÙØ�‰Ðr˜Ðr˜gÔ'>¸wÀxÕ?PÐRYÐZaÕRbÐdgÐhoÕdpÓ'qÑr×sñ ó$ #‰ˆˆwØÔÙØ�}Õ%×>Ð>¨°Õ)>ˆÙ�4˜× Ò ÙØ˜Õ ˆØ�‰Ø�LÐ!1Ð 2°&¸¸Ð@ð
ä"ØÔ-¨g°hÕ.?ÀÐHYÓZÐ\a×\iÐ\iÐeiókð
ö 	ñ #ð ‡K�KÑ=Àt€KÔLØ�&�5ˆ>ÐùòM cùÚHùÛiùÛMùóQs?   �JªJ¿JÁJÁ2JÂ	JÂJÂ/JÃ<
JÄJÄJc          	      ó¢   € V ^8„  d   QhR\         R\        \        \        3,          R\        \        \        \        3,          ,          R\
        /# )r-   rD  ró   r   r.   )rš   r>   r?   r   r   r  )r0   s   "r1   r2   r2   Y  s?   € ÷ ñ œð ¬¬S´#¨X­ð ÄÌ$ÌsÔTWÈxÍ.ÕAYð Ô^cñ r3   c                óˆ   € VR,          pVRJ;'       d    W8„  VRJ ;'       g    \        W\        4      '       * VR,          ) 3# )a‡  Sort key over a doc's candidate attempts, likeliest writer first.

``save_job_output`` runs before ``finish_execution``, so an attempt that closed before
the doc's second only reaches it through the grace. Among the rest, an attempt whose
own session spans the doc beats an overlapping one that has none (a manual run claimed
while a tick's run was still going); then the newest claim wins.
rà   NrÞ   rI  )rD  ró   r   rù   s   &&& r1   rQ  rQ  Y  sP   € ð �]Õ#€Fà�dÐ×.Ð.˜v™Ø�4ˆ×`Ð`Ô3°FÔE_Ó`Ô`Ø	�Õ	Ððð r3   c                ój   € V ^8„  d   QhR\         R\        \        \        3,          R\         R\        /# )r-   rD  r   rô   r.   )rš   r>   r?   r   rõ   )r0   s   "r1   r2   r2   i  s8   € ÷ 
Bñ 
B¤ð 
B´´c¼3°hµð 
BÔPUð 
BÔZ^ñ 
Br3   c                ó  € \        VP                  R4      ;'       g    ^ 4      p\        VP                  R4      ;'       g    VP                  R4      ;'       g    T4      pW2,
          T u;8*  ;'       d    WB,           8*  # u # )u@  Whether an output doc belongs to a session's run.

The doc is written when the run FINISHES, so its filename timestamp sits
at the run's end â€” compare against the session's full span, not its start:
``[started_at - grace, last_active + grace]`` (``last_active`` covers the
still-open case where ``ended_at`` is None).
rÆ   rÌ   rË   rV  )rD  r   rô   ÚstartÚends   &&&  r1   rJ  rJ  i  sj   € ô �'—+‘+˜lÓ+×0Ð0¨qÓ1€EÜ
�—‘˜JÓ'×NÐN¨7¯;©;°}Ó+E×NÐNÈÓ
O€CØÕ  F×AÔA¨cÕ.AÑAÐAÑAÐAr3   c                óR   € V ^8„  d   QhR\         R\        R\        \         ,          /# ©r-   rQ   ÚbodyrR   ©r?   r   r   )r0   s   "r1   r2   r2   y  s%   € ÷ ñ ¤#ð ¬]ð ÄXÌcÅ]ñ r3   c                 óè  € \        W4      p \        V4      w  rE\        \        VR V 4      4      p\	        VP
                  V4      pRV9   d   \        VP                  R4      V4       \        P                  V4      '       d%   / VCVCpRV9   d   RV9  d   RVR&   \        V4       \        VRW4      p	\        T	4      #   \         d    h \         d   p
\        R\        T
4      R7      T
hRp
?
ii ; i)r†   rJ   ÚskillsÚskillNÚ
update_jobé�  r6   )rd   r)   rh   r&   rN   r<   r   rY   Ú_EXECUTION_FIELDSÚintersectionr   r   r
   r    r?   )rQ   rk  rR   rc   Úprofile_namer=   r�   r<   Ú	effectiverg   Úexcs   &&&        r1   Ú_update_cron_job_syncrw  y  së   € Ü˜FÓ,€HðGÜ%7¸Ó%AÑ"ˆÜÔ0°¸yÈ&ÓQÓRˆÜ3°D·L±LÀ,ÓOˆØ˜WÔ$Ü1°'·+±+¸nÓ2MÈ|Ô\Ü×)Ñ)¨'×2Ò2Ø/˜8Ð/ wÐ/ˆIØ˜7Ô" w°gÔ'=Ø%)�	˜'Ñ"Ü2°9Ô=Ü& |°\À6ÓSˆô
 �#‹;Ðøô	 ô ØÜô GÜ¨´C¸³HÔ=À3ÐFûðGús$   �A5B? Â1B? Â?C1ÃC1ÃC,Ã,C1c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   Ž  s"   € ÷ `ñ `¤ð `¬x¼­}ñ `r3   c                 ó@   € \        \        \        W4      R V 4      4      # )Ú	pause_job©rh   r   rd   r‡   s   &&r1   Ú_pause_cron_job_syncr|  Ž  s   € ÜÔ*¬<¸Ó+HÈ+ÐW]Ó^Ó_Ð_r3   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   ’  s"   € ÷ añ a¤#ð a´¼µñ ar3   c                 ó@   € \        \        \        W4      R V 4      4      # )Ú
resume_jobr{  r‡   s   &&r1   Ú_resume_cron_job_syncr€  ’  s   € ÜÔ*¬<¸Ó+HÈ,ÐX^Ó_Ó`Ð`r3   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   –  s   € ÷ ;ñ ;¤3ð ;´¼#µñ ;r3   c                 óª  € \        W4      p\        \        VR V 4      4      pVP                  RR4      '       * ;'       g    VP                  R4      R8H  p\	        W#R,          VR7      p\        VRVR,          4      pV'       d(   VP                  R4      VP                  R4      8w  d   V# V'       g   \        R	R
R7      hV'       d   V# / VCRRRR/C# )Úresolve_job_refÚenabledTÚstateÚpausedrW   )Úforcer†   r»   i™  z:Job is already running or was claimed by another schedulerr6   Fré   )rd   rh   r&   rY   r"   r
   )rQ   rR   rc   rg   r‡  ÚranÚ	refresheds   &&     r1   Ú_trigger_cron_job_syncrŠ  –  sÂ   € Ü˜FÓ,€HÜ
Ô'¨Ð2CÀVÓLÓ
M€Cð
 —‘˜	 4Ó(Ô(×HÐH¨C¯G©G°GÓ,<ÀÑ,H€EÜ
$ X°4­yÀÔ
F€CÜ& x°¸CÀ½IÓF€Iß�Y—]‘] =Ó1°S·W±W¸]Ó5KÔKØÐßÜ¨Ð4pÔqÐqßØÐð ;ˆcÐ:�9˜e W¨kÑ:Ð:r3   c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   ¬  s   € ÷ ñ ¤#ð ´¼µñ r3   c                 ó´   € \        W4      p \        VR V 4      pT'       g   \        4       hRR/#   \         d   p\        R\	        T4      R7      ThRp?ii ; i)Ú
remove_jobrq  r6   NÚokT)rd   r   r    r
   r?   r:   )rQ   rR   rc   Úremovedrv  s   &&   r1   Ú_delete_cron_job_syncr�  ¬  s_   € Ü˜FÓ,€HðGÜ*¨8°\À6ÓJˆ÷ ÜÓÐØ�$ˆ<Ðøô	 ô GÜ¨´C¸³HÔ=À3ÐFûðGús   �0 °A»AÁAz/api/cron/jobsc                ó$   € V ^8„  d   QhR\         /# rj   rk   )r0   s   "r1   r2   r2   ¾  s   € ÷ Gñ G¤#ñ Gr3   c              ƒ   ó>   "  € \        \        V 4      G R j  x€L
 #  L5irf   )r   r‚   )rR   s   &r1   Úlist_cron_jobsr“  ½  s   é € ä'Ô(<¸gÓF×FÐFÑFùó   ‚–—z/api/cron/jobs/{job_id}c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   Ã  s"   € ÷ Mñ Mœsð M¬X´c­]ñ Mr3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   rˆ   r‡   s   &&r1   Úget_cron_jobr—  Â  s   é € ä'Ô(:¸FÓL×LÐLÑLùr”  z/api/cron/jobs/{job_id}/runsc                óR   € V ^8„  d   QhR\         R\        \         ,          R\        /# r-  r.  )r0   s   "r1   r2   r2   È  s+   € ÷ Zñ Z¤Sð Z´8¼Cµ=ð ZÔPSñ Zr3   c              ƒ   ó@   "  € \        \        WV4      G R j  x€L
 #  L5irf   )r   r@  )rQ   rR   rÝ   s   &&&r1   r6  r6  Ç  s   é € ä'Ô(@À&ÐSXÓY×YÐYÑYùó   ‚—˜c                óF   € V ^8„  d   QhR\         R\        \        ,          /# ©r-   rk  rR   )r   r   r?   )r0   s   "r1   r2   r2   Í  s"   € ÷ Nñ N¤ð N¼Ä½ñ Nr3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   r   )rk  rR   s   &&r1   Úcreate_cron_jobrž  Ì  s   é € ä'Ô(=¸tÓM×MÐMÑMùr”  z/api/cron/delivery-targetsc                ó:   € V ^8„  d   QhR\         \        ,          /# rj   r‘   )r0   s   "r1   r2   r2   Ò  s   € ÷  ñ  ¬X´c­]ñ  r3   c              ƒ  ó0  "  € RRRRRRRR/.p ^ R	I Hp \        V 4      ;_uu_ 4        VP                  V! 4       4       RRR4       RV/#   + '       g   i     RT/# ; i  \         d    h \
         d    \        P                  ! R
4        RT/# i ; i5i)u±  Delivery targets for the cron dropdown: implicit ``local`` plus the
configured gateway platforms (a platform without a cron home channel is
still listed with ``home_target_set: false`` so the UI can say so).

``cron_delivery_targets()`` reads each platform's home channel through
``get_secret``, which fails closed once this process hosts more than one
profile home (the dashboard/desktop ``serve`` backend flips multi-profile
hosting on the first ``?profile=`` request). The read must therefore run
inside the profile scope, exactly like the sibling cron routes â€” otherwise
the poll raises ``UnscopedSecretError`` on every tick and the dropdown
silently loses every configured platform.rW   rE   rX   zLocal (save only)Úhome_target_setTÚhome_env_varN©Úcron_delivery_targetsz%GET /api/cron/delivery-targets failedÚtargets)Úcron.scheduler_deliveryr¤  r+   rs   r
   rt   ru   rv   )rR   r¥  r¤  s   &  r1   Úget_cron_delivery_targetsr§  Ñ  s¤   é € ð �g˜vÐ':Ð<MÈtÐUcÐeiÐjÐk€Gð@ÝAä" 7×+Õ+Ø�N‰NÑ0Ó2Ô3÷ ,ð �wÐÐ÷ ,Ö+ð �wÐÐûô	 ô ØÜô @Ü�ŠÐ>Õ?Ø�wÐÐð@üsP   ‚B�A! ¨A¿A! ÁBÁA	ÁA! ÁBÁA! Á!BÁ6BÂBÂBÂBc                óR   € V ^8„  d   QhR\         R\        R\        \         ,          /# rj  rl  )r0   s   "r1   r2   r2   ì  s*   € ÷ Vñ V¤#ð V¬]ð VÄXÌcÅ]ñ Vr3   c              ƒ   ó@   "  € \        \        WV4      G R j  x€L
 #  L5irf   )r   rw  )rQ   rk  rR   s   &&&r1   Úupdate_cron_jobrª  ë  s   é € ä'Ô(=¸vÈWÓU×UÐUÑUùrš  z/api/cron/jobs/{job_id}/pausec                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   ñ  s"   € ÷ Oñ O¤ð O¬x¼­}ñ Or3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   r|  r‡   s   &&r1   Úpause_cron_jobr­  ð  s   é € ä'Ô(<¸fÓN×NÐNÑNùr”  z/api/cron/jobs/{job_id}/resumec                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   ö  ó"   € ÷ Pñ P¤#ð P´¼µñ Pr3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   r€  r‡   s   &&r1   Úresume_cron_jobr±  õ  ó   é € ä'Ô(=¸vÓO×OÐOÑOùr”  z/api/cron/jobs/{job_id}/triggerc                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2   û  s"   € ÷ Qñ Q¤3ð Q´¼#µñ Qr3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   rŠ  r‡   s   &&r1   Útrigger_cron_jobrµ  ú  s   é € ä'Ô(>ÀÓP×PÐPÑPùr”  c                óF   € V ^8„  d   QhR\         R\        \         ,          /# r„   rS   )r0   s   "r1   r2   r2      r¯  r3   c              ƒ   ó>   "  € \        \        W4      G R j  x€L
 #  L5irf   )r   r�  r‡   s   &&r1   Údelete_cron_jobr¸  ÿ  r²  r”  z/api/cron/firec                ó$   € V ^8„  d   QhR\         /# )r-   Úrequest)r   )r0   s   "r1   r2   r2     s   € ÷ WPñ WP¤Wñ WPr3   c              ƒ  ód  "  € ^ RI Hp V P                  P                  RR4      pVP	                  R4      '       d   VR,          P                  4       MRp\        P                  ! \        4      G Rj  x€L
 pV! 4       ! T\        VRRR	RR
7      \        VRRRRR
7      ;'       g    R\        VRRRRR
7      ;'       g    RR7      pVf   \        RR/RR7      #  V P                  4       G Rj  x€L
 p\        V\        4      '       d   T;'       g    / P                  R4      MRpV'       g   \        RR/RR7      # \        \         V4      G Rj  x€L
 pV'       g   \        RRRV/^ÈR7      # \#        W‡V4      G Rj  x€L
 p	V	fŠ    \        \$        VRVR4      G Rj  x€L
  \        \*        V4      G Rj  x€L
 '       d-   \&        P,                  ! RW‡4       \        RRRRRVR V/^ÈR7      # \        RR!RVR V/R"R#\/        \0        4      /R$7      # V	w  r«\        V\        4      '       d   VP3                  RV4       V
R"8X  d   R#\/        \0        4      /MRp\        WºVR$7      #  ELï ELw  \         d    / p EL…i ; i EL, EL Lä  \         d    \&        P(                  ! RTRR7        EL	i ; i Lù5i)%u  Chronos managed-cron fire webhook (NAS -> agent) â€” gateway forwarder.

Gated by the NAS-minted JWT (path is in ``PUBLIC_API_PATHS``), not the
dashboard cookie. Execution belongs to the GATEWAY process (it owns the live
platform adapters relay-fronted and E2EE targets need), so the fire is
forwarded to the gateway api_server's own ``/api/cron/fire`` on loopback
and its response passed through (the gateway re-verifies the JWT). Gateway
unreachable -> 503 so NAS retries; deliberately NO local-execution fallback.
)Úget_fire_verifierÚAuthorizationrn   zBearer :é   NNNr‹   ÚchronosÚexpected_audience©ÚdefaultÚnas_jwks_urlÚ
portal_url)r–   rÀ  Újwks_or_keyÚissuerrá   zinvalid fire tokeni‘  )r7   rQ   zmissing job_idrq  rÃ   ÚgoneÚnote_fire_forward_failurez¾scheduled fire could not be forwarded to the gateway api_server (127.0.0.1 loopback unreachable); the gateway process may be down or its api_server adapter not bound (missing API_SERVER_KEY)z&could not stamp last_fire_error for %sT©Úexc_infoz™cron fire dropped: gateway for profile %r is deliberately stopped (desired_state=stopped); job %s will resume via Chronos reconcile on next gateway startÚgateway_stoppedr8   zMgateway deliberately stopped; fire dropped, jobs re-arm on next gateway startrR   zgateway unreachable; retryi÷  zRetry-After)r7   Úheaders)Ú%plugins.cron_providers.chronos.verifyr¼  rÌ  rY   Ú
startswithrq   ÚasyncioÚ	to_threadr'   r   r   Újsonrt   rw   r>   r   r!   r#   r&   ru   Údebugr$   Úinfor?   Ú_CRON_FIRE_RETRY_AFTER_SECONDSÚ
setdefault)rº  r¼  Úauthr–   ÚcfgÚclaimsrk  rQ   rR   Ú	forwardedr7   Úgateway_bodyrÌ  s   &            r1   Úcron_fire_webhookrÛ    s°  é € õ Hà�?‰?×Ñ˜°Ó3€DØ $§¡°	× :Ò :ˆD��H�N‰NÔÀ€Eä×!Ò!¤+Ó.×
.€CÙÔ ØÜ! # v¨yÐ:MÐWYÔZÜ˜C ¨°NÈBÔO×WÐWÐSWÜ�s˜F I¨|ÀRÔH×PÐPÈDô	€Fð ‚~Ü˜WÐ&:Ð;ÈÔMÐMðØ—\‘\“^×#ˆô ,6°d¼D×+AÒ+Aˆd�jˆj�b×Ñ˜hÔ'Àt€FßÜ˜WÐ&6Ð7ÀSÔIÐIô +Ô+AÀ6ÓJ×J€GßÜ˜X v¨x¸Ð@ÈcÔRÐRä3°GÀTÓJ×J€IØÒð	XÜ(Ü&ØØ+Øð+ó	÷ 	ð 	ô" (Ô(FÈÓP×PÖPÜ�IŠIð:ð ô	ô  àÐ/ØÐmØ˜fØ˜wð	ð  ôð ô ØÐ2°H¸fÀiÐQXÐYØØ"¤CÔ(FÓ$GÐHô
ð 	
ð
 !*Ñ€KÜ�,¤×%Ò%Ø×Ñ ¨&Ô1àFQÐUXÔFXˆ}œcÔ"@ÓAÑBÐ^b€GÜ˜ÀwÔOÐOòQ /ò $øÜô Ø‹ðúò Kò Kñ	øô ô 	XÜ�JŠJÐ?ÀÐRV×WÐWð	Xúñ QùsÍ   ‚A0J0Á2I!Á31J0Â%J0Â>J0ÃI' Ã-I$Ã.I' Ã2J0ÄJ0Ä-"J0ÅI;ÅJ0Å J0Å<I>Å=J0ÆJ ÆJÆJ Æ!J0Æ4J.Æ5	J0Æ?B#J0É$I' É'I8É4J0É7I8É8J0É>J0ÊJ Ê$J+Ê'J0Ê*J+Ê+J0c                óF   € V ^8„  d   QhR\         \        ,          R\        /# )r-   rR   r.   )r   r?   ry   )r0   s   "r1   r2   r2   _  s   € ÷ ñ ¤X¬c¥]ð ´tñ r3   c                ó(  € ^ RI HpHp \        V 4      ;_uu_ 4        V! 4       pRp ^ RIHp V! 4        Uu. uF$  qfP                  R4      '       g   K  VR,          NK&  	  ppRR.VOpRRR4       . pX FY  p	V! V	4      p
X'       d6   V
P                  R
. 4       F  pVP                  R4      R8X  g   K  WKR&   K!  	  VP                  V
4       K[  	  V# u upi   \         d    \        P                  ! RRR	7        L•i ; i  + '       g   i     L¡; i)a  Built-in + ``profile``'s plugin blueprints, with the ``deliver`` slot's options rewritten
from that profile's configured gateway platforms. Plugin discovery can import plugin code, so
this runs off the event loop, inside the profile scope that picks the plugin manager.)Úblueprint_catalog_entryÚlist_blueprintsNr£  rW   ÚoriginrE   z?cron_delivery_targets unavailable; using static deliver optionsTrÉ  ÚfieldsrX   rD   Úoptions)Úcron.blueprint_catalogrÞ  rß  r+   r¦  r¤  rY   rt   ru   rÒ  rx   )rR   rÞ  rß  ÚcatalogÚdeliver_optionsr¤  ÚtÚ	platformsÚentriesrä   ÚentryÚfs   &           r1   Ú_blueprint_catalog_syncrë  _  sõ   € ÷ Pä	˜w×	'Õ	'Ù!Ó#ˆØˆð	iÝEá*?Ô*AÓQÑ*A QÇUÁUÈ4Ç[œ˜˜4Ÿ˜Ñ*AˆIÐQØ'¨Ð=°9Ð=ˆO÷ 
(ð €GÛˆÙ'¨Ó*ˆßØ—Y‘Y˜x¨Ö,�Ø—5‘5˜“= IÖ-Ø#2�i“Lñ -ð 	�‰�uÖñ ð €Nùò Røäô 	iÜ�JŠJÐXÐcg×hð	iú÷ 
(×	'ús?   œ
D§C¶CÁCÁ CÃCÃ#C>Ã;DÃ=C>Ã>DÄD	z/api/cron/blueprintsc                ó:   € V ^8„  d   QhR\         \        ,          /# rj   r‘   )r0   s   "r1   r2   r2   |  s   € ÷ <ñ <¬´­ñ <r3   c              ƒ  óØ   "  €  R\        \        V 4      G Rj  x€L
 /#  L  \         d    h \         d2   p\        P
                  ! R4       \        R\        T4      R7      hRp?ii ; i5i)zPBlueprint catalog (built-ins + the profile's plugin blueprints) as form schemas.Ú
blueprintsNzGET /api/cron/blueprints failediô  r6   )r   rë  r
   rt   ru   rv   r?   )rR   Úes   & r1   Úlist_cron_blueprintsrð  {  s]   é € ð<ØÔ$:Ô;RÐT[Ó$\×\Ð]Ð]Ñ\øÜô ØÜô <Ü�ŠÐ8Ô9Ü¨´C¸³FÔ;Ð;ûð<üs5   ‚A*„  ˜™  �A*ž   A'µA'¶,A"Á"A'Á'A*c                óF   € V ^8„  d   QhR\         R\        \         ,          /# )r-   rM   rR   rS   )r0   s   "r1   r2   r2   ‡  s   € ÷ "ñ "¤ð "¬h´s­mñ "r3   c                 ó‚   € ^ RI Hp \        V4      ;_uu_ 4        V! V 4      uuRRR4       #   + '       g   i     R# ; i)é    )Úget_blueprintN)rã  rô  r+   )rM   rR   rô  s   && r1   Ú_blueprint_for_profilerõ  ‡  s'   € Ý4ä	˜w×	'Õ	'Ù˜SÓ!÷ 
(×	'×	'Ó	'ús   š-­>	z /api/cron/blueprints/instantiatec                ó0   € V ^8„  d   QhR\         R\        /# rœ  )r   r?   )r0   s   "r1   r2   r2   �  s   € ÷ <ñ <Ô&Dð <Ìsñ <r3   c              ƒ  ó~  "  €  ^ RI HpHp \        \        V P
                  V4      G Rj  x€L
 pVf   \        RRV P
                   2R7      h V! W@P                  4      pTP                  RR4       \        P                  ! \        TR3/ TB p\        T4      G Rj  x€L
 p\        \        T4      G Rj  x€L
  T#  L‘  T d   p\        R\        T4      R7      ThRp?ii ; i LF L0  \         d    h \         d=   p	\        T	4       \         P"                  ! R	4       \        R
\        T	4      R7      hRp	?	ii ; i5i)zDFill a blueprint's slots and create the cron job (form-submit path).)ÚBlueprintFillErrorÚfill_blueprintNr5   zUnknown blueprint: r6   i¦  rà  Ú
create_jobz,POST /api/cron/blueprints/instantiate failedrq  )rã  rø  rù  r   rõ  Ú	blueprintr
   rz   r?   ÚpopÚ	functoolsÚpartialr&   r%   rt   r   ru   rv   )
rk  rR   rø  rù  rû  Úspecrv  Ú_createÚcreatedrï  s
   &&        r1   Úinstantiate_blueprintr  Ž  s  é € ð<ßMä0Ô1GÈÏÉÐY`Óa×aˆ	ØÒÜ¨CÐ:MÈdÏnÉnÐM]Ð8^Ô_Ð_ð	KÙ! )¯[©[Ó9ˆDð
 	�‰�˜4Ô ô ×#Ò#Ô$:¸GÀ\ÑZÐUYÑZˆÜ.¨wÓ7×7ˆä$Ô%FÈÓP×PÐPØˆñ! bøð
 "ô 	KÜ¨C¼¸C»ÔAÀsÐJûð	Kúñ 8áPøäô ØÜô <Ü)¨!Ô,Ü�ŠÐEÔFÜ¨´C¸³FÔ;Ð;ûð<üs€   ‚D=„&C( ªB<«"C( ÁB> Á =C( ÂC$ÂC( Â5C&Â6C( Â;D=Â<C( Â>C!ÃCÃC!Ã!C( Ã&C( Ã(D:Ã=D:Ã>7D5Ä5D:Ä:D=)rm   rf   )é´   )rñ   )Né   >   ro  ÚpromptrA   rn  Úno_agentrÁ  )zÚ__doc__rÏ  rý  r÷   r±   r8  Ú
contextlibr   r   Úpathlibr   Útypingr   r   r   r   Úfastapir	   r
   r   Úfastapi.responsesr   Úhermes_cli.web_depsr   Úhermes_cli.configr   Úhermes_cli.web_server_cronr   r   r   r   r   r   r   r   r   Úhermes_cli.web_modelsr   r   r   Úhermes_cli.web_routers._commonr   ru   Úhermes_timer   r¡   Úhermes_state_keysr   Úhermes_constantsr   r�   r   r•   r    r”   Úrouterr!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   r:   rN   r`   rd   rh   r‚   rˆ   rŸ   r�   r—   r§   r·   r¾   rÄ   rÔ   r×   rð   rú   r  r  r&  r  r'  r+  r@  rK  r9  rQ  rJ  rr  rw  r|  r€  rŠ  r�  rÔ  rY   r“  r—  r6  Úpostrž  r§  Úputrª  r­  r±  rµ  Údeleter¸  rÛ  rë  rð  rõ  r  r9   r3   r1   Ú<module>r     s™  ðñó Û Û Û 	Û Ý %Ý Ý ß ,Ó ,ç 5Ñ 5Ý *å $Ý %÷÷ õ ÷ _Ñ ^Ý 6Ý 5Ý 1÷ñ ñ 
‹€áÐ6Ð8TÓUÐ Ù!Ð">Ð@\Ó]Ð Ù $Ð%DÐFbÓ cÐ Ù!%Ð&FÐHdÓ!eÐ Ù$(Ð)LÐNjÓ$kÐ !ÙÐ6Ð8TÓUÐ Ù�=Ð"5Ó6€ÙÐ0Ð2NÓOÐ ÙÐ.Ð0LÓMÐ Ù#Ð$BÐDdÓeÐ ÙÐ4Ð6VÓWÐ õBõõ6*õ&ò÷(*÷V\ð  3Ð õ	;ð ô+ó ð+õ6*÷*5õõ",õõ&õ÷:Bõ*`õ@ðJ "%Ð õ
õ`÷,H÷(5nðr #Ð õ<õ~õ 
Bò HÐ ÷÷*`÷a÷;÷,ð "$Ð ð ‡�ÐÓöGó ðGð ‡�Ð%Ó&öMó 'ðMð ‡�Ð*Ó+öZó ,ðZð ‡�ÐÓöNó ðNð ‡�Ð(Ó)ö ó *ð ð2 ‡�Ð%Ó&öVó 'ðVð ‡�Ð,Ó-öOó .ðOð ‡�Ð-Ó.öPó /ðPð ‡�Ð.Ó/öQó 0ðQð ‡�Ð(Ó)öPó *ðPð ‡�ÐÓôWPó ðWPõtð8 ‡�Ð"Ó#ö<ó $ð<õ"ð ‡�Ð/Ó0ö<ó 1ò<r3   