
    sj]             7          d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZmZ ddlmZ  ej        e          ZdZdZej                            d e ee          j        j                             ddlmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z( dd
Z)g dZ*g dZ+dZ,de, dfde, dfde, dfde, dfde, ddfgZ-ddl.m/Z0 dZ1dZ2de3d	e4fdZ5dede3d	e4fd Z6d!ed	efd"Z7d!ed	efd#Z8d!ed	efd$Z9d!ed	e:ee;e         f         fd%Z<d!ed	efd&Z=d'ed	e:eef         fd(Z>d	eeeef                  fd)Z?d*eee
f         d+ee         d	ee         fd,Z@d*eee
f         d	efd-ZAdd.ee         d/ee
         d	ee         fd0ZBd1d2d3ee
         d4e4d	ee         fd5ZCd3e
d	ee         fd6ZDd7ee         d	ee         fd8ZEd9ee
         d:ee
         d	ee         fd;ZFd<ee         d	ee         fd=ZGd*eee
f         d	eee
f         fd>ZH	 dd*eee
f         d?ee         d	eee
f         fd@ZI	 dd*eee
f         d?ee         d	eee
f         fdAZJddCedDe3d	ee         fdEZK	 	 dd*eee
f         dFee         d?ee         d	eeee
f                  fdGZLdHeeeee         f                  dIe4d	eee                  fdJZM	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddKedCee         d!ee         dLee         dMee         dNee3         d7ee         dOe4d.ee         d/eee                  dPee         d9ee         d:ee         dQee         d<ee         dHeeeee         f                  dIee4         dReee                  dSee         dTee4         dUee4         dVee         dWee         dXee         dYedFee         d	ef6dZZNd[d\d]i dKd^d_d`dCd^dad`d!d^dbd`dLd^dcd`dMd^ddd`dNdedfd`d7d^dgd`d/dhdid^idjdkd<d^dl e             dmd`dVd^dn e             dod`dWd^dpd`dTdqd1drdsdHdhdid^idtdkdIdqdud`dRdhdid^idvdkdSd^dwd`dUdqdxd`dKgdydzZOd	e4fd{ZPdd|lQmRZRmSZS  eRjT        d[d[eOd} ePd~           dS )z
Cron job management tools for Hermes Agent.

Expose a single compressed action-oriented tool to avoid schema/context bloat.
Compatibility wrappers remain for direct Python callers and legacy tests.
    N)Path)AnyDictListOptionalUnion)display_hermes_homeg      $@g     @)AmbiguousJobReferenceclaim_job_for_fireeffective_job_stateget_jobis_job_runnable	list_jobsmark_job_runparse_schedule	pause_job
remove_jobresolve_job_ref
resume_job
update_jobreturnc                  J    	 ddl m}   |              dS # t          $ r Y dS w xY w)u   Tell the active cron scheduler provider the job set changed (no-op for
    the built-in). Best-effort — never lets a provider error break the tool.r   _notify_provider_jobs_changedN)cron.schedulerr   	Exceptionr   s    7/home/agent/.hermes/hermes-agent/tools/cronjob_tools.py"_notify_provider_jobs_changed_safer   9   sP    @@@@@@%%'''''   s    
"")zJignore\s+(?:\w+\s+)*(?:previous|all|above|prior)\s+(?:\w+\s+)*instructionsprompt_injectionzdo\s+not\s+tell\s+the\s+userdeception_hidezsystem\s+prompt\s+overridesys_prompt_overridez<disregard\s+(your|all|any)\s+(instructions|rules|guidelines)disregard_rules)zKcat\s+[^\n]*(\.env|credentials|\.netrc|\.pgpass|id_rsa|id_ed25519|id_ecdsa)read_secrets)authorized_keysssh_backdoor)z/etc/sudoers|visudosudoers_mod)zrm\s+-rf\s+/destructive_root_rm)r   r!   r#   r%   z:\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)\w*\}?z curl\s+[^\n]*https?://[^\s"\'`]*exfil_curl_urlz wget\s+[^\n]*https?://[^\s"\'`]*exfil_wget_urlzIcurl\s+[^\n]*(?:--data(?:-raw|-binary|-urlencode)?|-d|--form|-F)\s+[^\n]*exfil_curl_dataz(wget\s+[^\n]*--post-(?:data|file)=[^\n]*exfil_wget_postzHcurl\s+[^\n]*(?:-H|--header)\s+["\']Authorization:\s*(?:Bearer|token)\s+z["\']exfil_curl_auth_header)INVISIBLE_CHARS))i  i )i &  i'  )i #  i#  )i i )   r2   i  cpc                 D     t           fdt          D                       S )Nc              3   >   K   | ]\  }}|cxk    o|k    nc V  d S N ).0lohir3   s      r   	<genexpr>z_is_emoji_cp.<locals>.<genexpr>   s;      GG&"brR~~~~2~~~~GGGGGG    )any_EMOJI_NEIGHBOUR_CP_RANGES)r3   s   `r   _is_emoji_cpr?      s'    GGGG,FGGGGGGr<   textidxc                 8   |dz
  }|dk    rGt          | |                   t          k    r)|dz  }|dk    rt          | |                   t          k    )|dz   }|t          |           k     rTt          | |                   t          k    r6|dz  }|t          |           k     rt          | |                   t          k    6|dk    oV|t          |           k     oCt          t          | |                             o!t          t          | |                             S )zGReturn True when the ZWJ at text[idx] appears inside an emoji sequence.   r   )ord_VARIATION_SELECTOR_CPlenr?   )r@   rA   leftrights       r   _zwj_has_emoji_neighbourrI      s   7D
!))DJ+AAA	 !))DJ+AAA!GE
#d))

DK 0 04J J J
 #d))

DK 0 04J J J 		 	+ec$ii' 	+T$Z))	+T%[))**r<   promptc                     d| vr| S g }t          |           D ]1\  }}|dk    rt          | |          r|                    |           2d                    |          S )N   ‍ )	enumeraterI   appendjoin)rJ   cleanedrA   chs       r   _strip_legitimate_emoji_zwjrS      st    vGV$$  R>>6vsCC>r777r<   c                 X    t          j        dt           dd| t           j                  S )u  Strip the GitHub `Authorization: token $GITHUB_TOKEN` auth-header
    pattern so it doesn't trip the broader curl-auth-header exfil rule.

    Allows the bundled GitHub skill fallback without opening a blanket
    exemption for arbitrary Authorization-header exfiltration.

    Uses ``re.sub`` so EVERY occurrence is scrubbed, not just the first — a
    cron job that loads 2+ GitHub skills (e.g. github-issues +
    github-pr-workflow + github-code-review) contains several such blocks,
    and the old ``re.search`` + single ``str.replace`` left the rest to trip
    the exfil_curl_auth_header detector on every run. The trailing
    ``[^\s;&|$`]*`` consumes only the URL path — never whitespace, command
    separators, or subshell openers — so a payload smuggled onto the same
    line (``;``, ``&&``, ``|``, ``$(...)``, backticks) survives the strip
    and is still scanned. The host must be exactly ``api.github.com``
    followed by ``/``, whitespace, quote, or end: lookalike authorities
    (``api.github.com.evil.com``, ``api.github.com@evil.com``) are not the
    trusted construct and fall through to the exfil detectors, while
    legitimately quoted bare-host URLs stay exempt.
    zBcurl\s+[^\n;&|$`]*(?:-H|--header)\s+["\']Authorization:\s*token\s+zJ["\']\s+["\']?https://api\.github\.com(?::\d+)?(?:/|\s|$|["\'])[^\s;&|$`]*z curl https://api.github.com/user)flags)resub_CRON_SECRET_VAR_RE
IGNORECASE)rJ   s    r   _strip_cron_safe_constructsrZ      sC    * 6	QNa 	Q 	Q 	Q*m   r<   c                 l    t          |           }t          D ]}||v rdt          |          ddc S dS )zReturn an error string if the prompt contains invisible-unicode
    injection markers (ZWJ inside legitimate emoji sequences is allowed).
    z-Blocked: prompt contains invisible unicode U+04Xz (possible injection).rM   )rS   _CRON_INVISIBLE_CHARSrD   )rJ   prompt_for_invisible_scanchars      r   _check_invisible_unicoder`      sY     !<F C C% i i,,,h3t99hhhhhhh -2r<   c                    | s| g fS t                      }g }t          |           D ]v\  }}|t          v rS|dk    r&t          | |          r|                    |           :|                    dt          |          d           a|                    |           wd                    |          t          |          fS )u>  Strip invisible-unicode characters from *prompt*, preserving the ZWJ
    that lives inside legitimate emoji sequences.

    Returns ``(cleaned_prompt, removed_codepoints)`` where ``removed_codepoints``
    is the sorted list of ``U+XXXX`` labels that were stripped (empty when the
    prompt was already clean). Used by the skills-attached cron path, where the
    skill body is already vetted at install time by ``skills_guard.py`` — a
    stray zero-width space in a code example should be sanitized, not turned
    into a hard block that permanently kills the job.
    rL   zU+r\   rM   )	setrN   r]   rI   rO   addrD   rP   sorted)rJ   removedrQ   rA   rR   s        r   _strip_invisible_unicoderf      s      rz GGV$$  R&&&X~~":63"G"G~r"""KK*SWW***+++r777VG__,,r<   c                    t          |           }t          |          }|r|S t          D ]-\  }}t          j        ||t          j                  rd| dc S .t          D ]-\  }}t          j        ||t          j                  rd| dc S .dS )u}  Scan the USER-SUPPLIED cron prompt for critical threats.

    Strict pattern set — used at job create/update time and as a runtime
    defense-in-depth for prompts authored before the scanner existed.
    The user prompt is small and directive; bare `cat .env` or `rm -rf /`
    there is a smoking gun, not prose. Returns an error string when
    blocked, else empty string.
    (Blocked: prompt matches threat pattern 'D'. Cron prompts must not contain injection or exfiltration payloads.rM   )rZ   r`   _CRON_THREAT_PATTERNSrV   searchrY   _CRON_EXFIL_COMMAND_PATTERNS)rJ   prompt_to_scaninvisible_errpatternpids        r   _scan_cron_promptrq     s     188N,^<<M - H H9Wnbm<< 	H Hc  H  H  H  H  H  H	H4 H H9Wnbm<< 	H Hc  H  H  H  H  H  H	H2r<   	assembledc                 6   t          |           \  }}|r<t                              dt          |          d                    |                     t          |          }t          D ]/\  }}t          j        ||t          j	                  r
|d| dfc S 0|dfS )u  Scan an ASSEMBLED cron prompt that includes loaded skill content.

    Looser pattern set — only catches unambiguous prompt-injection
    directives. Drops command-shape patterns (cat .env, rm -rf /,
    authorized_keys, /etc/sudoers) because they false-positive on
    legitimate skill markdown that *describes* attack commands in
    security postmortems and runbooks.

    Invisible unicode is SANITIZED, not blocked. Skill bodies are
    user-curated and already scanned at install time by
    ``skills_guard.py``; a stray zero-width space in a code example
    (common in copy-pasted unicode docs) should not permanently kill the
    job. The offending codepoints are stripped and logged, the cleaned
    prompt is returned. The hard block remains for raw user prompts via
    ``_scan_cron_prompt`` — that path is the actual injection surface.

    Returns ``(cleaned_prompt, error)``; ``error`` is empty when the
    prompt passed (after sanitization).
    zaCron skill-assembled prompt: stripped %d invisible-unicode char(s) (%s) from vetted skill contentz, rh   ri   rM   )
rf   loggerwarningrF   rP   rZ   _CRON_SKILL_ASSEMBLED_PATTERNSrV   rk   rY   )rr   rQ   re   rm   ro   rp   s         r   _scan_cron_skill_assembledrw     s    ( 0	::GW 
5LL$))G,,	
 	
 	

 199N6 Q Q9Wnbm<< 	Q  Qs  Q  Q  Q  Q  Q  Q  Q	QB;r<   c                     ddl m}   | d          } | d          }|r|r | d          pd }|rR|dk    rL | d          pd }|r=t          |          t          |          k    rt                              d|           d }|rt                              d	|||           || | d
          pd | | d          pd  | d          pd dS d S )Nr   get_session_envHERMES_SESSION_PLATFORMHERMES_SESSION_CHAT_IDHERMES_SESSION_THREAD_IDslackHERMES_SESSION_MESSAGE_IDzWCron origin: dropping synthetic per-message Slack thread_id=%s (== creation message id)z+Cron origin captured thread_id=%s for %s:%sHERMES_SESSION_CHAT_NAMEHERMES_SESSION_USER_IDHERMES_SESSION_SCOPE_ID)platformchat_id	chat_name	thread_iduser_idscope_id)gateway.session_contextrz   strrt   debug)rz   origin_platformorigin_chat_idr   
message_ids        r   _origin_from_envr   <  sJ   777777%o&?@@O$_%=>>N -
> -
#O$>??G4	  	!G33()DEEMJ !c)nnJ??<=F   !	 	LL=?N  
 (%()CDDL" ''?@@HD ((ABBJd)
 
 	
, 4r<   jobuser_deliverc                     |pd                                                                 dk    rdS 	 ddlm}  ||           rdS n(# t          $ r |                     d          rY dS Y nw xY w	 dS )uU  Return an informational notice when a created job won't deliver anywhere.

    TUI/CLI sessions cannot be captured as a cron ``origin`` (no
    ``HERMES_SESSION_PLATFORM``/``CHAT_ID`` is set for them), so a
    ``deliver="origin"`` request — or an omitted ``deliver`` that defaults to
    origin-or-local — produces a job that runs and saves output to
    ``last_output`` but is never delivered back into the session. This is by
    design (there is no live-delivery channel for local sessions), but silently
    dropping the user's "tell me when it runs" intent is the trap reported in
    #51568. Surface it at create time so the agent can relay it instead of
    promising a delivery that never happens.

    Returns ``None`` when the user explicitly asked for ``local`` (no surprise),
    or when the job resolves to a real delivery target.
    rM   localNr   )_resolve_delivery_targetsoriginuQ  This is a local-only cron job: its output is saved (view it with cronjob(action='list')) but will NOT be delivered back into this session — CLI/TUI sessions have no live-delivery channel. To be notified when it runs, recreate or update the job with deliver set to a gateway-connected platform, e.g. deliver='telegram' or deliver='all'.)striplowerr   r   r   get)r   r   r   s      r   _local_delivery_noticer   q  s    " 	!!##))++w66t<<<<<<$$S)) 	4	   778 	44	 	
	R s   A A)(A)c                     |                      d          pi                      d          }|                      d          pi                      dd          }|dS |dk    r
|dk    rdndS |r| d	| n| d
S )Nrepeattimes	completedr   foreverrC   oncez1/1/z timesr   )r   r   r   s      r   _repeat_displayr     s    WWX$"))'22E""(b--k1==I}yzz"avvU2%.Di!!%!!!u4D4D4DDr<   skillskillsc                     || r| gng }n(t          |t                    r|g}nt          |          }g }|D ]@}t          |pd                                          }|r||vr|                    |           A|S )NrM   )
isinstancer   listr   rO   )r   r   	raw_items
normalizeditemr@   s         r   _canonical_skillsr     s    ~$,UGG"			FC	 	  !H		LL	J $ $4:2$$&& 	$D
**d###r<   Fstrip_trailing_slashvaluer   c                    | d S t          |                                           }|r|                    d          }|pd S )Nr   )r   r   rstrip)r   r   r@   s      r   _normalize_optional_job_valuer     sF    }tu::D  {{3<4r<   c                     | dS t          | t          t          f          r%d | D             }|rd                    |          ndS t	          |                                           }|pdS )u  Normalize a user-supplied ``deliver`` value to the canonical string form.

    The cron schema documents ``deliver`` as a string (``"local"``, ``"origin"``,
    ``"telegram"``, ``"telegram:chat_id[:thread_id]"``, or comma-separated combos).
    Some callers — MCP clients passing arrays, scripts building the payload as a
    list — supply ``["telegram"]``.  ``create_job``/``update_job`` store it as-is,
    and the scheduler's ``str(deliver).split(",")`` then serializes the list to
    the literal ``"['telegram']"`` which is not a known platform.  Flatten lists
    / tuples at the API boundary so storage is always a string.  Returns ``None``
    for ``None``/empty so callers can treat it as "not supplied".
    Nc                     g | ]D}t          |                                          #t          |                                          ES r7   r   r   r8   ps     r   
<listcomp>z,_normalize_deliver_param.<locals>.<listcomp>  s9    AAAA#a&&,,..AQAAAr<   ,)r   r   tuplerP   r   r   )r   partsr@   s      r   _normalize_deliver_paramr     sq     }t%$'' 2AAAAA"'1sxxT1u::D<4r<   deliverc                 ^   ddl m ddlm}  | dd                    s| S dt          ffd| 
             S d	 t	          |                               d
          D             }fd|D             }t                      fd|D             }|rd
                    |          ndS )uH  Resolve ``origin`` to a concrete target for cron-context creates.

    A job created FROM a cron run must never store the literal ``origin``:
    the creating session is ephemeral, so by fire time there is no origin to
    resolve and the scheduler would fall back to guessing a home channel.
    Resolve at create time instead, using the creating run's own concrete
    delivery target — the ``HERMES_CRON_AUTO_DELIVER_*`` contextvars that
    ``run_job`` publishes per run (already per-job-safe under the parallel
    pool). Rules:

    * Not a cron-context session → returned unchanged (chat/CLI creates keep
      today's fire-time ``origin`` semantics, byte-identical).
    * ``origin`` element (or an omitted value, which the scheduler treats as
      origin) → replaced with ``platform:chat_id[:thread_id]`` from the
      creating run's target; ``local`` when the creating run has no concrete
      target (e.g. its own deliver is ``local``).
    * Every other element (``local``, ``all``, explicit ``platform:...``)
      passes through verbatim, including inside comma lists.
    r   ry   )is_truthy_valueHERMES_CRON_SESSIONrM   r   c                       dd                                           }  dd                                           }| r|sdS  dd                                           }|r
|  d| d| S |  d| S )N!HERMES_CRON_AUTO_DELIVER_PLATFORMrM    HERMES_CRON_AUTO_DELIVER_CHAT_IDr   "HERMES_CRON_AUTO_DELIVER_THREAD_ID:r   )r   r   r   rz   s      r   _creator_targetz6_resolve_cron_context_deliver.<locals>._creator_target  s    "?#FKKQQSS!/"DbIIOOQQ 	w 	7#O$H"MMSSUU	 	766669666&&W&&&r<   Nc                 ^    g | ]*}|                                 |                                 +S r7   r   r   s     r   r   z1_resolve_cron_context_deliver.<locals>.<listcomp>  s-    EEE117799EQWWYYEEEr<   r   c                 X    g | ]&}|                                 d k    r
             n|'S )r   r   )r8   r   r   s     r   r   z1_resolve_cron_context_deliver.<locals>.<listcomp>  s8    QQQaQWWYY(%:%:!!!QQQr<   c                 F    g | ]}|v                      |          |S r7   )rc   )r8   r   seens     r   r   z1_resolve_cron_context_deliver.<locals>.<listcomp>  s,    DDDA!t))txx{{)a)))r<   )r   rz   utilsr   r   splitrb   rP   )r   r   r   resolveduniquer   rz   r   s        @@@r   _resolve_cron_context_deliverr     s	   ( 877777%%%%%%???+@"EEFF 'S ' ' ' ' ' '    EEG 2 23 7 7EEEEQQQQ5QQQH DDDDDDDDF%/388F4/r<   providerbase_urlc                    t          |d          }|sdS t          |           }|s	 dS 	 ddlm}m}m} ddlm} ddlm}m	}	 n# t          $ r	 d	|d
cY S w xY w|                                dk    rdS  ||          rb	  ||          }
n# t          $ r d}
Y nw xY w|
r! |	|
pi                     dd                    nd}|r |||          rdS d|d|d|pd dS 	  ||          }n# t          $ r |}Y nw xY wt          |t                    r|                    |          nd} |	|rt          |dd          nd          }|r |||          rdS d|d|dS )a  Reject pairing a named provider's stored credential with an off-host base_url.

    The cron tool is model-callable, so a prompt-injected job could set a real
    provider plus an attacker ``base_url``; on fire the scheduler resolves that
    provider's stored API key and sends it to the URL, exfiltrating the
    credential (CWE-200/CWE-522). Allow a ``base_url`` override only when it
    cannot leak a stored secret: no override at all, a configured custom/byok
    provider that carries its own endpoint+key, or an override whose host
    matches the named provider's own endpoint.

    Returns an error string if blocked, else None (valid).
    Tr   Nzwbase_url override requires an explicit provider. Set provider to a configured custom provider to use a custom endpoint.r   )has_named_custom_providerresolve_requested_provider_get_named_custom_provider)PROVIDER_REGISTRY)base_url_host_matchesbase_url_hostnamez2Unable to validate base_url override for provider z
; refused.customr   rM   z	base_url z is not allowed for provider z_. A named custom provider's stored credential may only be sent to its own configured endpoint (unknownz).inference_base_urlz. A named provider's stored credential may only be sent to its own endpoint; use a configured custom provider (provider="custom") for a custom base_url.)r   hermes_cli.runtime_providerr   r   r   hermes_cli.authr   r   r   r   r   r   r   r   r   getattr)r   r   buprovr   r   r   r   r   r   r3   cfg_hostr   pconfig
known_hosts                  r   _validate_cron_base_urlr     s    
'xd	K	K	KB t(22D 

C	
 	

W	
 	
 	
 	
 	
 	
 	
 	
 	
 	

 	655555BBBBBBBBB W W WVDVVVVVVW zz||x t  && 

	++D11BB 	 	 	BBB	HJR$$bhB^^J%C%CDDDPR 	--b(;; 	4> > >4 > >$,$9	> > >	

--d33   1;Hc1J1JT##H---PTG""QX#`774H"#M#M#M^`aaJ ++B
;; t	WB 	W 	Wt 	W 	W 	Ws5   A AA B BB"C. .C=<C=scriptc                 ^   | r|                                  sdS ddlm} |                                  }|                    d          st	          |          dk    r|d         dk    rd|d	S dd
lm}  |            dz  }|                    dd            |||z  |          }|rd|S dS )a3  Validate a cron job script path at the API boundary.

    Scripts must be relative paths that resolve within HERMES_HOME/scripts/.
    Absolute paths and ~ expansion are rejected to prevent arbitrary script
    execution via prompt injection.

    Returns an error string if blocked, else None (valid).
    Nr   )get_hermes_home)r   ~   rC   r   zXScript path must be relative to ~/.hermes/scripts/. Got absolute or home-relative path: z@. Place scripts in ~/.hermes/scripts/ and use just the filename.)validate_within_dirscriptsT)parentsexist_okz9Script path escapes the scripts directory via traversal: )r   hermes_constantsr   
startswithrF   tools.path_securityr   mkdir)r   r   rawr   scripts_dircontainment_errors         r   _validate_cron_script_pathr   O  s      t000000
,,..C ~~j!! 
c#hh!mmA#N36N N N	
 877777!/##i/KdT222++K#,={KK 
OOO	
 4r<   c                 ,    t                               d          pd          }t                               d                               d                    }t                               d          pd          }t                               d          p|d d         p|r|d	         ndp|pd
          }i d|d|d|r|d	         nd d|dt          |          dk    r|d d         dz   n|d                     d          d                     d          d                     d          d                     d          pddt	                     d                     dd          d                     d          d                     d          d                     d          d                     d          d                     d          d                     dd          t                                          d                               d           d!}                     d"          r d"         |d"<                        d#          r d#         |d#<                        d$          r d$         |d$<                        d%          r d%         |d%<                        d&          r d&         |d&<                        d'          rd|d'<                        d(          r d(         |d(<                        d)          r d)         |d)<                        d*          pg }t          |t                     r|g}t           fd+|D                       rd|d,<    fd-|D             }|r||d*<   |S ).NrJ   rM   r   r   idr   name2   r   zcron jobjob_idprompt_previewd   z...modelr   r   scheduleschedule_display?r   r   r   next_run_atlast_run_atlast_statuslast_delivery_errorlast_fire_errorenabledT	paused_atpaused_reason)stater  r	  r   reasoning_effortmonitor_scriptmonitor_urlmonitor_stateno_agentenabled_toolsetsworkdircontext_fromc              3      K   | ]T}t          |                                                                          d k    p|                    d          k    V  UdS )selfr   Nr   r   r   r   r8   rr   s     r   r;   z_format_job.<locals>.<genexpr>  sV      
W
Wa3q66<<>>!!V+AqCGGDMM/A
W
W
W
W
W
Wr<   
continuityc                     g | ]T}t          |                                                                          d k    9|                    d          k    R|US )r  r   r  r  s     r   r   z_format_job.<locals>.<listcomp>  s\       q66<<>>!!V++SWWT]]0B0B 	
0B0B0Br<   )r   r   r   rF   r   r   r   r=   )r   rJ   r   r   r   resultstored_refsexternal_refss   `       r   _format_jobr  v  s1   ""(b))Fswww//1B1BCCF+),,Fswwvf&"+fv2M&))2fRXf\fggD& 	f.$ 	&	
 	#f++2C2C&#,.. 	!! 	CGGJ'' 	CGGJ'' 	CGG.//63 	/#&& 	3779g.. 	sww}-- 	sww}-- 	sww}-- 	sww'<==  	377#455!" 	3779d++#& %S))WW[))11+  F. wwx )x=x
ww!"" =%();%<!"
ww   9#&'7#8 
ww} 3 #M 2}
ww 7"%o"6
wwz "!z
ww!"" =%();%<!"
wwy +	Ny''.))/RK+s## $"m

W
W
W
W;
W
W
WWW $#|     M  /!.~Mr<   extra_promptc                    | d         }d}	 t          |d          }t          |t                    s.t          |          }|d}nt	          |          sd}nd}dd|d	S nv# t
          $ ri}t                              d
||           	 t          |dt          |                     n# t
          $ r Y nw xY wddt          |          d	cY d}~S d}~ww xY wt          ||          S )u  Execute a cron job immediately, outside the scheduler tick.

    Atomically claims the job first via ``claim_job_for_fire`` — the same
    at-most-once CAS the scheduler/external-provider fire path uses — so a
    concurrently-running gateway ticker cannot also fire it (the claim both
    blocks a duplicate fire and advances ``next_run_at`` for recurring jobs).
    If the claim is lost (another fire is in flight), this is a no-op.

    The actual firing is delegated to ``run_one_job`` — the single shared
    execute→save→deliver→mark body the ticker and external providers use — so
    failure delivery, ``[SILENT]`` handling, and live-adapter delivery stay
    identical across paths and can't drift.

    Returns {"claimed": bool, "success": bool, "error": str|None}.
    r   NT
return_job%Job no longer exists; nothing to run.1Job is paused/disabled; resume it before running.;Job is already being fired by the scheduler; not run again.Fclaimedsuccesserrorz1Failed to claim cron job %s for immediate run: %sr  )r   r   dictr   r   r   rt   r(  r   r   _run_claimed_job)r   r  r   claimed_job	refreshedreasones          r   _execute_job_nowr0    sA   $ YFKD(DAAA+t,, 	I
  I @$Y// WLV$HHH	I  D D DH&RSTTT	A//// 	 	 	D	ECFFCCCCCCCCD KlCCCCsB   AA! !
C+CB'&C'
B41C3B44C	CCc                 (   | d         }d}d}	 ddl m}m}m}  ||          sddddS d}|                     d	          }t          |t                    r$t          |                    d
          pd          nd}	 ddlm	}	  |	            n# t          $ r dY nw xY wt          j                    d}
Wt          |                     d          p|          dfd}t          j        |dd          }
|
                                 t          j                            d          }t#          |dd          }t%          |          r
 |            nd}|t#          |dd          nd}|t#          |dd          nd}	 	  || |||          }                                 |
|
                    t*          dz              n:#                                  |
|
                    t*          dz              w w xY wd} ||           n# d} ||           w xY wt-          |          pi }|                    d          dk    }dt/          |o|          |                    d          dS # t          $ r}t0                              d||           |r#	 ddl m}  ||           n# t          $ r Y nw xY w	 t5          |dt          |          |           n# t          $ r Y nw xY wddt          |          dcY d}~S d}~ww xY w) u  Fire an already-claimed job through the shared ``run_one_job`` body.

    Split out of ``_execute_job_now`` so the background dispatch path
    (``_try_dispatch_background_run``) can take the claim synchronously — so
    the tool response can report "paused"/"already firing" immediately — and
    hand the actual run to a daemon worker.

    Returns {"claimed": True, "success": bool, "error": str|None}.
    r   FNr   )release_running_jobrun_one_jobtry_register_running_jobTcJob is already running (a scheduler tick or another manual run is executing it); not started again.r%  
fire_claimbyrM   )get_activity_callbackr   r   c                  z   t          j                    }                     t                    st          j                    | z
  }|t          k    rt
                              d|           d S 	  d dt          |           d           n# t          $ r Y w xY w                    t                    d S d S )Nuu   cronjob run heartbeat ceiling reached for job '%s' (%.0fs) — stopping heartbeat; gateway watchdog regains authorityzcronjob: running job '' (z
s elapsed))	time	monotonicwait_CRON_RUN_HEARTBEAT_INTERVAL_CRON_RUN_HEARTBEAT_CEILINGrt   ru   intr   )startedelapsed_heartbeat_stopactivity_cbjob_names     r   _heartbeat_loopz)_run_claimed_job.<locals>._heartbeat_loop)  s    .**)../KLL !"n..8G!<<< 9 %g	   !#ZXZZ#g,,ZZZ    % ! ! ! !	!! *../KLL ! ! ! ! !s   /B 
BBzcronjob-run-heartbeat)targetdaemonr   zgateway.run_gateway_runner_refadapters_gateway_loop)rJ  loopr  rC   )timeoutr  ok
last_errorz-Failed to execute cron job %s immediately: %s)r2  )expected_fire_ownerr   N)r   r2  r3  r4  r   r   r*  r   tools.environments.baser8  r   	threadingEventThreadstartsysmodulesr   callablerb   rP   r>  r   boolrt   r(  r   )r   r  r   _registered
fire_ownerr2  r3  r4  claimr8  _heartbeat_threadrF  gateway_module
runner_refrunnerrJ  gateway_loop	processedr-  rN  r/  _releaserC  rD  rE  s                         @@@r   r+  r+    sz    YFKJW
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ('// 	 F	   %%3=eT3J3JTS4.B///PT
"	EEEEEE
 0/11KK 	 	 	KKK	 $/++ "3776??4f55H! ! ! ! ! ! ! !0 !* 0&,! ! !
 ##%%% 77^-BDII
!)*!5!5?48>8J76:t444PTAGASwv===Y]	(U'K(!-  	
  ##%%%$0%**3ORS3S*TTT  ##%%%$0%**3ORS3S*TTTT 1  K''''  K''''FOO)r	]]=))T1I,"--]]<00
 
 	
  
 
 
DfaPPP 
	
JJJJJJ       	A$.	      	 	 	D	 VV
 
 	
 	
 	
 	
 	
 	
-
s   I7 AI7 ?B I7 BI7 BC&I7 G 5H 7HH I7 H""AI7 7
LL J21L2
J?<L>J??L K$#L$
K1.L0K11LLL  r   	max_charsc                 V   	 ddl m}  |            | z  }t          |                    d                    }|sdS |d                             dd                                          }|sdS t          |          |k    r|d|         d	|d          d
z   }|S # t          $ r Y dS w xY w)a  Best-effort excerpt of the job's most recent saved output file.

    Included in the background-run completion block so the parent agent sees
    what the job actually produced without having to dig through
    ``~/.hermes/cron/output/``. Never raises.
    r   )get_cron_output_dirz*.mdNzutf-8replace)encodingerrorsu   
… (truncated; full output: ))	cron.jobsrh  rd   glob	read_textr   rF   r   )r   rf  rh  out_dirfilesr@   s         r   _latest_job_output_excerptrs    s    111111%%''&0w||F++,, 	4Ry""GI"FFLLNN 	4t99y  

#&Tb	&T&T&TTD   tts   7B 1B .+B 
B('B(
session_idc                 @   	 ddl m}  |            sdS n# t          $ r Y nw xY w| d         t          |                     d          p          	 ddlm}  |            }|rt                              d|           n2# t          $ r%}t          	                    d|           Y d}~nd}~ww xY w	 dd	l
m}  |d
          }n# t          $ r d
}Y nw xY w|s|rt          |          }|sdS 	 	 ddlm}	  |	            v rddddS n# t          $ r Y nw xY wt          d          t          t                     s.t#                    }
|
d}nt%          |
          sd}nd}dd|dS nw# t          $ rj}t                              d|           	 t)          dt          |                     n# t          $ r Y nw xY wdddt          |          dcY d}~S d}~ww xY wd
}	 ddl m}  |dd
          pd
}n# t          $ r Y nw xY w	 ddlm}m}  |            }nK# t          $ r>}t                              d|           t3                    }d|d<   |cY d}~S d}~ww xY w	 ddlm}  |            }n# t          $ r d}Y nw xY wt9          j                    |                     dd           d!t:          t          t<          f         ffd"} |d# d$ d%d&dd'|                     d(          ||rt          |          nd||||)          }|                    d*          dk    rdd|                    d+          d,S t                              d-|                    d.d/                     t3          |           }d|d<   |S )0uh  Claim ``job`` now, then fire it on the async-delegation daemon executor.

    A manual ``cronjob(action='run')`` used to execute the job synchronously
    on the calling agent's tool thread. A cron job is a full agent run that
    routinely takes minutes-to-hours, so the parent turn sat inside ONE tool
    call the whole time: uninterruptible (the interrupt flag is only checked
    between loop iterations) and serial (a batch of runs executed one by one).

    This dispatches the run like ``delegate_task``'s background mode: the tool
    returns immediately with a handle, the run executes on the shared async
    daemon executor, and a ``type="async_delegation"`` completion event
    re-enters the conversation as a fresh turn when the job finishes — riding
    the existing completion-queue rail (CLI drain + gateway watcher), which
    keeps message-role alternation legal and the prompt cache intact.

    The at-most-once claim is taken SYNCHRONOUSLY before dispatch so
    unrunnable jobs (paused / missing / already firing) report in the tool
    response immediately instead of as a delayed completion event.

    Returns
    -------
    None
        Background delivery unavailable on this session runtime (one-shot
        ``hermes -z``, stateless HTTP, Kanban worker, nested cron run).
        Caller falls back to the synchronous path unchanged.
    dict
        ``{"claimed": False, "success": False, "error": ...}`` — claim lost;
        same shape as ``_execute_job_now`` so the caller's existing response
        formatting applies.
        ``{"claimed": True, "dispatched": True, "delegation_id": ...}`` —
        run is executing in the background.
        ``{"claimed": True, "dispatched": False, "success": ..., "error": ...}``
        — dispatch pool was at capacity; the run executed inline (the claim
        was already taken and must not be stranded).
    r   )async_delivery_supportedNr   r   )recover_interrupted_executionszSReclaimed %d stale cron execution(s) from dead owner(s) before dispatching job '%s'z"Stale execution reclaim failed: %s)get_current_session_keyrM   )default)get_running_job_idsFr5  r%  Tr   r"  r#  r$  z2Failed to claim cron job %s for background run: %s)r&  
dispatchedr'  r(  ry   HERMES_UI_SESSION_ID)_current_origin_session_iddispatch_async_delegationzQcronjob run: async delegation registry unavailable (%s); running job '%s' inline.r)  r{  )_get_max_async_children   r   r   r   c                     t                    } t          t          j                    
z
  d          }t                    pi }d	 d dd|                     d          rdnd	 |                     d
          rd|                     d
           ndz   d dk    rdndz   g}|                    d          r|                    d|d                     t                    }|r*|                    d           |                    |           |                     d          rdnd
d                    |          |                     d
          d|dS )Nr)  r   
Cron job 'r:  z) finished its manual run.zResult: r'  rN  FAILEDr(  u    — rM   zDelivery target: r   z/ (output was delivered there by the job itself)z (output saved locally only)r  zNext scheduled run: z--- JOB OUTPUT ---r   
r   )statussummaryr(  	api_callsduration_seconds)r+  roundr;  r   r   rO   rs  rP   )resdurationr-  linesexcerptr,  r   r  r   rE  
started_ats        r   _runnerz-_try_dispatch_background_run.<locals>._runnerP  s   {FFFz1155FOO)r	HHHfHHHAswwy11?ttxAA-0WWW-=-=E)swww'')))2G))) g%% BA3		

 =='' 	LLLJ	-0HJJKKK,V44 	"LL-...LL!!!%(WWY%7%7DkkWyy''WWW%% (
 
 	
r<   zManual run of cron job 'r:  rm  ztTriggered via cronjob(action='run'). The job executed in its own fresh cron session; this block reports its outcome.cron_runr   )goalcontexttoolsetsroler   session_keyparent_session_idra  origin_ui_session_idorigin_session_idmax_async_childrenr  delegation_id)r&  r{  r  zGcronjob run: background pool unavailable (%s); running job '%s' inline.r(  rejected) r   rv  r   r   r   cron.executionsrw  rt   ru   r   tools.approvalrx  r   rz  r   r   r*  r   r   r(  r   rz   tools.async_delegationr}  r~  r+  tools.delegate_toolr  r;  r   r   info)r   rt  r  rv  rw  
_reclaimed	_reap_excrx  r  rz  r-  r.  r/  r  rz   r}  r~  r  r  r  	max_asyncr  dispatchr,  r   r   rE  r  s     `                    @@@@@r   _try_dispatch_background_runr    s   RDDDDDD'')) 	4	    YF3776??,f--HFBBBBBB3355
 	NN.	    F F F 	99EEEEEEEEF::::::--b999    &: &
 *oo  t&Y
	::::::,,....$$J	   /  	 	 	D	
 )DAAA+t,, 	II @$Y// WLV$HHH	I  Y Y YI6STUUU	A//// 	 	 	D	uQTUVQWQWXXXXXXXXY ;;;;;;./ErJJPb   	
 	
 	
 	
 	
 	
 	
 	

 7688   '()8	
 	
 	
 "+LIII$|??????++--		   			 Jggi))G
T#s(^ 
 
 
 
 
 
 
 
 
 
 
: )(>>>V>>>B ggg-7A#j///T1+$  H" ||H--%\\/::
 
 	
 KKQWj))8   c===F F<Ms    
((.B 
B6B11B6:C CC8D E6 
DE6 DAE6 6
G* G%F<;G%<
G	G%G		G%G*%G*0H 
HHH) )
I133I,&I1,I15J JJr  r  c                 4   t          | t                    r,|                                 r|                                 gng }n| rd | D             }ng }t          d |D                       }|r|s|                    d           n|s|rd |D             }|pdS )a  Translate the ``continuity`` flag into the ``context_from`` list.

    ``continuity=True`` ensures ``"self"`` is present (the job's own previous
    output is injected each run); ``continuity=False`` removes any
    ``"self"``/own-id entry. Other entries are preserved untouched.
    c                     g | ]D}t          |                                          #t          |                                          ES r7   r   r8   js     r   r   z%_apply_continuity.<locals>.<listcomp>  s9    GGG1AGAGGGr<   c              3   F   K   | ]}|                                 d k    V  dS )r  Nr   r8   r  s     r   r;   z$_apply_continuity.<locals>.<genexpr>  s/      55117799&555555r<   r  c                 B    g | ]}|                                 d k    |S )r  r   r  s     r   r   z%_apply_continuity.<locals>.<listcomp>  s*    777a17799#6#6#6#6#6r<   N)r   r   r   r=   rO   )r  r  refshas_selfs       r   _apply_continuityr    s     ,$$ )5););)=)=E""$$%%2	 GGGGG5555555H 8( 8F 8H 8774777<4r<   actionr   r   r   include_disabledr   r.  r  r  r  attach_to_sessionr  r  r  task_idc                    ~	 | pd                                                                 }|dk    r`|st          dd          S t          ||	          }t	          |          }|r|st          dd          S n|s|st          dd          S |r"t          |          }|rt          |d          S |r"t          |          }|rt          |d          S |r"t          |          }|rt          |d          S t          ||          } | rt          | d          S |rdd	lm	}! t          |t                    r|gn|}"|"D ]d}#t          |#t                    r+|#                                                                 d
k    rB |!|#          st          d|# dd          c S e|t          ||          }ddlm}$m}% 	  |%dai d|pdd|d|d|dt!          t#          |                    dt%                      d|dt'          |
          dt'          |          dt'          |d          dt'          |          d|d|pddt'          |          d|d |d!t'          |          d"t'          |          d#|}&nH# |$$ r@}'|'                                }(t          |(                    d$          fd%di|(cY d}'~'S d}'~'ww xY wd&|&d          d'})t-          |&t#          |                    }*|*r|) d(|* })t/          j        d|&d)         |&d         |&                    d*          |&                    dg           |&d+         t5          |&          |&                    dd,          |&d-         t7          |&          |)d.d/0          S |d1k    rAd2 t9          |3          D             }+t/          j        dt;          |+          |+d4d/0          S |st          d5| d6d          S 	 t=          |          }&nM# t>          $ r@}'t/          j        dt          |'          d7 |'j         D             d8d/0          cY d}'~'S d}'~'ww xY w|&st/          j        dd9| d:d;d/0          S |&d)         }|d<k    rutC          |          },|,st          d=| d6d          S tE                       t/          j        dd&|&d          d>||&d         |&                    d+          d?d@d/0          S |dAk    rEtG          ||B          }-tE                       t/          j        dt7          |-          dCd/0          S |dDk    rCtI          |          }-tE                       t/          j        dt7          |-          dCd/0          S |dEv r|pd}.|.r"t          |.          }|rt          |d          S tK          |&||.F          }/|/|/                    dG          rjtE                       t7          t          |          pd)|i          }0d|0dH<   dI|0dJ<   |/                    dK          |0dK<   t/          j        d|0dLdMd/0          S |/|/ntM          |&|.N          }1|1                    dOd          rtE                       t7          t          |          pd)|i          }0|1                    dOd          |0dH<   |1                    d%d          |0dP<   |1                    dOd          s|1                    d$          pdQ|0dR<   n |1                    d$          r|1d$         |0dS<   t/          j        d|0dCd/0          S |dTk    rYi }2|'t          |          }|rt          |d          S ||2d<   |||2d<   |t!          t#          |                    |2d<   |	|$t          ||	          }||2d<   |r|d         nd|2d*<   |
t'          |
          |2d<   |t'          |          |2d<   |t'          |d          |2d<   |||2d#<   d|2v r|2d         n|&                    d          }3d|2v r|2d         n|&                    d          }4t          |3|4          } | rt          | d          S |:|r"t          |          }|rt          |d          S |rt'          |          nd|2d<   |:|r"t          |          }|rt          |d          S |rt'          |          nd|2d!<   ||rt'          |          nd|2d"<   ||Wd!|2v r|2d!         n|&                    d!          }5d"|2v r|2d"         n|&                    d"          }6|5r|6rt          dUd          S |||$|&                    d          pg }7dV |7D             }"nMt          |t                    r,|                                 r|                                 gng }"ndW |D             }"|t          |"|          pg }"|"rFdd	lm	}! |"D ]=}#|#                                d
k    r |!|#          st          d|# dd          c S >|"pd|2d<   ||pd|2d<   |t	          |          |2d <   |t'          |          pd|2d<   |Wt	          |          }8|8rAd|2v r|2                    d          n|&                    d          }9|9st          dXd          S |8|2d<   |8|dk    rdn|}:tO          |&                    d          pi           };|:|;dY<   |;|2d<   |PtQ          |          }<|<|2d<   |<                    dZ|          |2d+<   |&                    d[          d\k    r
d]|2d[<   d|2d^<   |2st          d_d          S tS          ||2          }-tE                       t/          j        dt7          |-          dCd/0          S t          d`|  d6d          S # tT          $ r(}=t          t          |=          d          cY d}=~=S d}=~=ww xY w)bz!Unified cron job management tool.rM   createzschedule is required for createF)r'  uF   create with no_agent=True requires a script — the script is the job.z3create requires either prompt or at least one skillr   )r   r  zcontext_from job 'z>' not found. Use cronjob(action='list') to see available jobs.N)CronSchedulerRegistrationError&create_job_with_scheduler_registrationrJ   r   r   r   r   r   r   r   r   r   Tr   r   r  r  r  r  r  r  r  r  r(  r'  r  z
' created. r   r   r   r   r  )r'  r   r   r   r   r   r   r   r  r   messager   )indentr   c                 ,    g | ]}t          |          S r7   )r  )r8   r   s     r   r   zcronjob.<locals>.<listcomp>E  s     ]]]K$$]]]r<   )r  )r'  countjobszjob_id is required for action ''c                     g | ]H}|d          |                     d          |                     d          |                     d          dIS )r   r   r   r  )r   r   r   r  r   )r8   ms     r   r   zcronjob.<locals>.<listcomp>R  sd            #$D'$%EE&MM().@(A(A+,55+?+?	      r<   )r'  r(  matcheszJob with ID or name 'z8' not found. Use cronjob(action='list') to inspect jobs.)r'  r(  removezFailed to remove job 'z
' removed.)r   r   r   )r'  r  removed_jobpause)r.  )r'  r   resume>   runrun_nowtrigger)rt  r  r{  executed
backgroundexecution_moder  u   The job is running in the background. You and the user can keep working; its outcome re-enters the conversation as a new message when it finishes. Do not wait or poll — just continue.)r'  r   noter)  r&  execution_successz4Already being fired by the scheduler; not run again.execution_skippedexecution_errorupdateu]   monitor_script and monitor_url are mutually exclusive — clear one before setting the other.c                     g | ]D}t          |                                          #t          |                                          ES r7   r   r  s     r   r   zcronjob.<locals>.<listcomp>!  s9    OOOqAOCFFLLNNOOOr<   c                     g | ]D}t          |                                          #t          |                                          ES r7   r   r  s     r   r   zcronjob.<locals>.<listcomp>%  s9    SSSqCFFLLNNSCFFLLNNSSSr<   ziCannot set no_agent=True on a job without a script. Set `script` in the same update, or on the job first.r   displayr
  paused	scheduledr  zNo updates provided.zUnknown cron action 'r7   )+r   r   
tool_errorr   rZ  rq   r   r   rn  r   r   r   r  r   r  r  r   r   r   r   to_dictpopr   jsondumpsr   r   r  r   rF   r   r
   r  r   r   r   r   r  r0  r*  r   r   r   )>r  r   rJ   r   r   r   r   r  r   r   r   r   r   r.  r   r  r  r  r  r  r  r  r  r  r  rt  r   canonical_skills	_no_agent
scan_errorscript_errormonitor_errorbase_url_error_get_jobr  ref_idr  r  r   exc_partial_create_message_local_noticer  re   updatedr  bgr  exec_resultupdateseff_providereff_base_urleff_mon_scripteff_mon_urlexistingtarget_no_agenteffective_scriptnormalized_repeatrepeat_stateparsed_scheduler/  s>                                                                 r   cronjobr    s   : 	Z1l))++1133
!! T!"CUSSSS0??XI  h %1 %     h$4 h!"Wafgggg A.v66
 A%j%@@@@  C9&AA C%lEBBBB  D :> J J  D%mUCCCC 5XxHHN A!.%@@@@  999999)3L#)F)FX~~L"  F "&#.. !6<<>>3G3G3I3IV3S3S #8F++ )P P P P$)         %0zJJ       
T<<   !<R%X  "6	
 :099   ,--- ,+ 8>>> ;8DDD ;8Z^____ 9@@@ ". &6%=  :'BBB!" 'Y#$ '8&7%& $A#P#P#P'( !>k J J J)4 &6%558 2 T T T;;==!(,,w"7"7SSS(SSSSSSSST C3v;BBBO238PQX8Y8YZZM G%4"F"F}"F"F:#!$iK WWW--!ggh33 #$6 7-c22"wwy'::#&}#5&s++.     " ]]	K[0\0\0\]]]D:$TDQQZ[\\\\ 	^M
MMMW\]]]]	!&))CC$ 	 	 	:$ XX    "%               	"  	:!  -EF  -E  -E  -E  F  F   
 T!! ((G U!"D6"D"D"DeTTTT.000:#CCKCCC$ #F$'GG,>$?$?$ $        v666G.000:${77K7KLLUVWWWW!! ((G.000:${77K7KLLUVWWWW666 ">TL A.|<<
 A%j%@@@@ .
  B ~"&&"6"6~2444$WV__%FvGG%)z"+7'(*,&&*A*A'z#'%E		 	    " n%cEEE  y%00 52444 !BT6NCCF!,E!B!BF:*5//)U*K*KF&'??9e44 A.9oog.F.F /J *++ )) A,7,@():$v>>qIIII!!&(G!.v66
 A%j%@@@@$*!"&"%B,W55& &	" !U%6#4UF#C#C $4!:J#T#3A#6#6PT  #@#G#G #&CH&M&M
##&CHcg&h&h&h
#+ /?*+ (2W'<'<
###''*BUBU  (2W'<'<
###''*BUBU  5\<PPN A!.%@@@@! G#=f#E#EL# G),FFFFMS$]$A&$I$I$IY]!)! H$>~$N$NM$ H)-GGGGES]1.AAAY] () & CNW1+>>>SW & )[-D1AW1L1LG,--RURYRYZjRkRk  /<w.F.FGM**CGGTaLbLb  " k %> %   
 ':+A  '"ww~66<"HOOHOOODDc22 T5A5G5G5I5IQL..0011rDDSSLSSSD),T:>>D"D ======"& 	 	!<<>>V33$'x// #-!TV !T !T !T(-$ $ $    +/,$'+.>.F$*+ ,/34E/F/F+," &C7%K%K%St	"# #'x.." @HG@S@Sw{{8'<'<'<Y\Y`Y`aiYjYj$+ )T$)      
 '6
#!,2aKKDDV!#CGGH$5$5$;<<(9W%$0!#"0":":&5
#.=.A.A)X.V.V*+777##x//'2GG$)-GI& I!"8%HHHH 11G.000:${77K7KLLUVWWWW;&;;;UKKKK 1 1 1#a&&%0000000001s/  An. 3n. 9n. #n. 3#n. #n. ;"n. Bn. &n. CJ	 n. 	K5K	Kn. 	KCn. An. n. 1P  n. 
Q5Q Qn. Q!n. -3n. !An. 0A
n. ;An. ,n. 1Bn. C4n. 9,n. &C>n. %%n. ;n. Bn. Cn. "Bn. 1B#n. An. n. .
o 8oo o r  u  Manage scheduled cron jobs with a single compressed tool.

Use action='create' to schedule a new job from a prompt or one or more skills.
Use action='list' to inspect jobs.
Use action='update', 'pause', 'resume', 'remove', or 'run' to manage an existing job.

action='run' fires the job immediately in the BACKGROUND (like delegate_task): the call returns at once with a handle and the job's outcome re-enters the conversation as a new message when it finishes. Do not wait or poll after triggering a run — just continue. Optionally pass 'prompt' with action='run' to inject transient per-run context (appended to the job's stored prompt for that single fire only, never persisted).

To stop a job the user no longer wants: first action='list' to find the job_id, then action='remove' with that job_id. Never guess job IDs — always list first.

Jobs run in a fresh session with no current-chat context, so prompts must be self-contained.
If skills are provided on create, the future cron run loads those skills in order, then follows the prompt as the task instruction.
On update, passing skills=[] clears attached skills.

NOTE: The agent's final response is auto-delivered to the target. Put the primary
user-facing content in the final response. Cron jobs run autonomously with no user
present — they cannot ask questions or request clarification.

Scheduling from cron-run sessions is disabled by default and enabled via cron.allow_agent_scheduling in config.yaml. When enabled, jobs created from a cron run are user-owned in the same flat job table as every other job, and their delivery resolves to the creating job's own persistent target — never to the ephemeral cron-run session. Prefer updating an existing job (list first, then update by job_id) over creating near-duplicates.objectstringz~One of: create, list, update, pause, resume, remove, run. When action=create, the 'schedule' and 'prompt' fields are REQUIRED.)typedescriptionz+Required for update/pause/resume/remove/runzFor create: the full self-contained prompt. If skills are also provided, this becomes the task instruction paired with those skills. For run: optional transient context appended to the stored prompt for that single fire only (never persisted).a  REQUIRED for action=create. For create/update: '30m', 'every 2h', '0 9 * * *', or ISO timestamp. Examples: '30m' (every 30 minutes), 'every 2h' (every 2 hours), '0 9 * * *' (daily at 9am), '2026-06-01T09:00:00' (one-shot). You MUST include this field when action=create.zOptional human-friendly nameintegerzTOptional repeat count. Omit for defaults (once for one-shot, forever for recurring).a  Omit this parameter to auto-deliver back to the current chat and topic (recommended). Auto-detection preserves thread/topic context. Only set explicitly when the user asks to deliver somewhere OTHER than the current conversation. Values: 'origin' (same as omitting), 'local' (no delivery, save only), 'all' (fan out to every connected home channel), or platform:chat_id:thread_id for a specific destination. Combine with comma: 'origin,all' delivers to the origin plus every other connected channel. Examples: 'telegram:-1001234567890:17585', 'discord:#engineering', 'sms:+15551234567', 'all'. WARNING: 'platform:chat_id' without :thread_id loses topic targeting. 'all' resolves at fire time, so a job created before a channel was wired up will pick it up automatically once connected.arrayr  zOptional ordered list of skill names to load before executing the cron prompt. On update, pass an empty array to clear attached skills.)r  itemsr  a3  Optional path to a script that runs each tick. In the default mode its stdout is injected into the agent's prompt as context (data-collection / change-detection pattern). With no_agent=True, the script IS the job and its stdout is delivered verbatim (classic watchdog pattern). Relative paths resolve under zx/scripts/. ``.sh``/``.bash`` extensions run via bash, everything else via Python. On update, pass empty string to clear.zIOptional monitor-mode source script (same rules as `script`: relative to u^  /scripts/, .sh/.bash via bash, else Python). Each tick it runs FIRST and its output is hashed as exact bytes: UNCHANGED output suppresses the agent run entirely (no LLM, no delivery, recorded as a silent no_change tick); CHANGED output injects a MONITOR CHANGE DETECTED block (unified diff + new output) into the prompt before a normal agent run. The first tick always runs the agent (baseline). Scripts must emit STABLE output — no timestamps or random ordering — or every tick looks changed. Mutually exclusive with monitor_url; incompatible with no_agent=True. On update, pass empty string to clear.u  Optional http(s) URL used as the monitor source instead of a script — fetched with a bounded GET (30s timeout, 256KB cap) each tick. Same hash-suppression semantics as monitor_script. Mutually exclusive with monitor_script. On update, pass empty string to clear.booleanu_  Default: False (LLM-driven job — the agent runs the prompt each tick). Set True to skip the LLM entirely: the scheduler just runs ``script`` on schedule and delivers its stdout verbatim. No tokens, no agent loop, no model override honoured. 

REQUIREMENTS when True: ``script`` MUST be set (``prompt`` and ``skills`` are ignored). 

DELIVERY SEMANTICS when True: (a) non-empty stdout is sent verbatim as the message; (b) EMPTY stdout means SILENT — nothing is sent to the user and they won't see anything happened, so design your script to stay quiet when there's nothing to report (the watchdog pattern); (c) non-zero exit / timeout sends an error alert so a broken watchdog can't fail silently. 

WHEN TO USE True: recurring script-only pings where the script itself produces the exact message text (memory/disk/GPU watchdogs, threshold alerts, heartbeats, CI notifications, API pollers with a fixed output shape). WHEN TO USE False (default): anything that needs reasoning — summarize a feed, draft a daily briefing, pick interesting items, rephrase data for a human, follow conditional logic based on content.)r  ry  r  u  Optional job ID or list of job IDs whose most recent completed output is injected into the prompt as context before each run. Use this to chain cron jobs: job A collects data, job B processes it. Each entry must be a valid job ID (from cronjob action='list'); for a job's OWN previous output, prefer the `continuity` flag. Note: injects the most recent completed output — does not wait for upstream jobs running in the same tick. On update, pass an empty array to clear.a  When true, this recurring job carries continuity across runs: each run wakes up with the job's own most recent output injected into its prompt, so it can dedupe against what was already reported and continue where the last run left off (scouts, monitors, incremental digests). First run has no previous output and runs unchanged. On update, pass false to turn continuity off (other context_from entries are preserved). Default: false.u  Optional list of toolset names to restrict the job's agent to (e.g. ["web", "terminal", "file", "delegation"]). When set, only tools from these toolsets are loaded, significantly reducing input token overhead. When omitted, all default tools are loaded. Infer from the job's prompt — e.g. use "web" if it calls web_search, "terminal" if it runs scripts, "file" if it reads files, "delegation" if it calls delegate_task. On update, pass an empty array to clear.u1  Optional absolute path to run the job from. When set, AGENTS.md / CLAUDE.md / .cursorrules from that directory are injected into the system prompt, and the terminal/file/code_exec tools use it as their working directory — useful for running a job inside a specific project repo. Must be an absolute path that exists. When unset (default), preserves the original behaviour: no project context files, tools use the scheduler's cwd. On update, pass an empty string to clear. Jobs with workdir run sequentially (not parallel) to keep per-job directories isolated.u  When True, this job becomes CONTINUABLE: the user can reply to its delivery and the agent has the brief in context instead of asking 'what is that?'. On thread-capable platforms (Telegram topics, Discord/Slack threads) a dedicated thread is opened for the job and its replies; on DM-only platforms (WhatsApp/Signal) the brief is mirrored into the origin DM session. Use this for conversational recurring jobs the user will reply to — daily briefings, reminders that kick off follow-up work. Leave unset for fire-and-forget alerts/watchdogs. Overrides the global cron.mirror_delivery config for this one job. Only the origin chat is touched (never fan-out targets); no effect when deliver='local'.)r  
propertiesrequired)r   r  
parametersc                  P    ddl m}   | d          p | d          p
 | d          S )u  
    Check if cronjob tools can be used.

    Available in interactive CLI mode and gateway/messaging platforms.
    The cron system is internal (JSON file-based scheduler ticked by the gateway),
    so no external crontab executable is required.

    Session env vars must hold an explicit truthy string (``1``, ``true``,
    ``yes``, ``on``) — false-like values (``0``, ``false``, ``no``, ``off``)
    leave the tool disabled. Uses the shared ``env_var_enabled`` helper so
    every consumer of these flags agrees on the truthy set.
    r   env_var_enabledHERMES_INTERACTIVEHERMES_GATEWAY_SESSIONHERMES_EXEC_ASK)r   r  r  s    r   check_cronjob_requirementsr    sQ     &%%%%% 	,-- 	.?344	.?,--r<   )registryr  c           	         t          di d|                     dd          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d	|                     d	d
          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                     d          d|                    d          d|                    d          S )Nr  rM   r   rJ   r   r   r   r   r  Tr   r   r.  r   r  r  r  r  r  r  r  r  rt  r7   )r  r   )argskws     r   <lambda>r    s%   w      xx"%%% xx!!!  xx!!!  *%%%	 
 XXf  xx!!!  ###  "4d;;;  hhw  xx!!!   xx!!!! " xx!!!# $ XXn---% & 88L)))' ( "4555) * ###+ , *%%%- . xx 0111/ 0 HH]+++1 2 y!!!3 4 66,'''5  r<   u   ⏰)r   toolsetschemahandlercheck_fnemojirQ  )NNr6   )re  )NNNNNNFNNNNNNNNNNNNNNNNNN)U__doc__r  loggingrV   rW  rS  r;  pathlibr   typingr   r   r   r   r   r   r	   	getLogger__name__rt   r>  r?  pathinsertr   __file__parentrn  r
   r   r   r   r   r   r   r   r   r   r   r   r   r   rj   rv   rX   rl   tools.threat_patternsr1   r]   r>   rE   r@  rZ  r?   rI   rS   rZ   r`   r   r   rf   rq   rw   r   r   r   r   r   r   r   r   r   r  r0  r+  rs  r  r  r  CRONJOB_SCHEMAr  tools.registryr  r  registerr7   r<   r   <module>r      sC      				 



            3 3 3 3 3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0		8	$	$  $  )  33ttH~~,344 5 5 5                             "   R	 	 	 $" " "  T  ?)<>>@PQ>)<>>@PQgReggiz{F1DFFHYZkQdkkk  nF  G  ( K J J J J J    HS HT H H H H3 S T              <S S    -S -U3S	>-B - - - -:c c    ,# %S/    D2(4S>2 2 2 2 2j"S#X "hsm "PXY\P] " " " "JEc3h EC E E E E Xc] 8C= TXY\T]    $ Y^   # QU bjknbo    C HSM    *,08C= ,0Xc] ,0 ,0 ,0 ,0^MsmM'/}Mc]M M M M`$x} $# $ $ $ $N7T#s(^ 7S#X 7 7 7 7v 8<,D ,D	c3h,D'/},D	#s(^,D ,D ,D ,D` 8<f
 f
	c3hf
'/}f
	#s(^f
 f
 f
 f
R s s hsm    2 6:"&m m	c3hm%-c]m3-m d38nm m m m`5d3i01 d3i   4 ! " !""&""  48!%,0!#(,$(!%&* $5y1 y1y1SMy1 SMy1 sm	y1
 3-y1 SMy1 c]y1 y1 C=y1 T#Yy1 C=y1 smy1 smy1 SMy1 SMy1  5d3i01!y1" #y1$ tCy)%y1& c]'y1( tn)y1*  ~+y1, SM-y1. #/y10 sm1y12 3y14 5y16 	7y1 y1 y1 y1| y( g
   ` g

  L g
    U g
    p g
"  = #g
* !u +g
2    s 3g
:  (+  i ;g
D    e  Vi  Vi  Vk  Vk   e   e   e Eg
L    ak~k~  lA  lA   a   a   a Mg
T    j Ug
\ ! ]	 ]g
@  (+?	 Ag
\ !6 ]g
t  (+  @! !ug
~    S	 g
F  !  \" "Gg
P JUk k+A AHD    . 0 / / / / / / /  	 8 (
C" " " " " "r<   