- AsyncIOScheduler now runs in Europe/Bucharest so cron strings in jobs.json
match local wall-clock time.
- New add_shell_job() validates name, cron, command list, channel, report_on
(always|changes|never), and optional timeout (1..3600s). Existing add_job()
stays untouched for the Claude path.
- _execute_job dispatches on job['kind'] (default 'claude'); legacy jobs
without the field still route to the Claude executor. Refactored the
Claude path into _execute_claude_job; new _execute_shell_job runs
subprocess with _safe_env + PROJECT_ROOT cwd.
- Shell semantics: non-zero exit always forwards stderr (trimmed to 500 ch)
as '[cron:NAME] exit CODE: STDERR' regardless of report_on. On exit 0,
'always' forwards stdout (trimmed to 1500 ch), 'never' stays silent, and
'changes' parses the GSTACK-CRON marker (^GSTACK-CRON: changes=\d+$) and
forwards stdout only when N>0; missing/malformed marker logs a warning
and stays silent.
- Timeout honoured per-job (falls back to JOB_TIMEOUT=300s).