From 9ee540a5e29f1a7b03e80e15f12b10acacbd0a4b Mon Sep 17 00:00:00 2001 From: txbxxx <79389617+txbxxx@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:29:59 -0700 Subject: [PATCH] fix(install): promote croniter to a core dependency Cron is a built-in Hermes feature (CLI `hermes cron`, `cronjob` agent tool, gateway ticker, scheduler in cron/scheduler.py) but croniter has been gated behind the [cron] optional extra. Users who do a plain `pip install hermes-agent` can create jobs via /cron but any recurring cron schedule silently returns next_run_at=None (HAS_CRONITER=False), which then gets wrapped into a 'state=error' message only after a tick. Move croniter into core dependencies so scheduled jobs work out of the box on any install path. The [cron] extra is kept as an empty passthrough so existing `pip install hermes-agent[cron]` installs and the [all]/[termux] extras continue to resolve. Also update the now-stale user-facing error message in `compute_next_run()` that still tells users to install `hermes-agent[cron]`. Salvaged from #17234 (authored by @txbxxx) with a corrected premise: the original PR claimed [cron] wasn't in [all], but it is (pyproject.toml line 112). The real UX problem is the plain no-extras install path, which this fix addresses. --- cron/jobs.py | 7 ++++--- pyproject.toml | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cron/jobs.py b/cron/jobs.py index 4258c479f6..6376260828 100644 --- a/cron/jobs.py +++ b/cron/jobs.py @@ -313,9 +313,10 @@ def compute_next_run(schedule: Dict[str, Any], last_run_at: Optional[str] = None elif schedule["kind"] == "cron": if not HAS_CRONITER: logger.warning( - "Cannot compute next run for cron schedule %r: 'croniter' " - "is not installed. Install the 'cron' extra (pip install " - "'hermes-agent[cron]') to re-enable recurring cron jobs.", + "Cannot compute next run for cron schedule %r: 'croniter' is " + "not installed. croniter is a core dependency as of v0.9.x; " + "reinstall hermes-agent or run 'pip install croniter' in your " + "runtime env.", schedule.get("expr"), ) return None diff --git a/pyproject.toml b/pyproject.toml index 9dd757c005..f1132e8d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,8 @@ dependencies = [ "firecrawl-py>=4.16.0,<5", "parallel-web>=0.4.2,<1", "fal-client>=0.13.1,<1", + # Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter). + "croniter>=6.0.0,<7", # Text-to-speech (Edge TTS is free, no API key needed) "edge-tts>=7.2.7,<8", # Skills Hub (GitHub App JWT auth — optional, only needed for bot identity) @@ -42,7 +44,7 @@ daytona = ["daytona>=0.148.0,<1"] vercel = ["vercel>=0.5.7,<0.6.0"] dev = ["debugpy>=1.8.0,<2", "pytest>=9.0.2,<10", "pytest-asyncio>=1.3.0,<2", "pytest-xdist>=3.0,<4", "mcp>=1.2.0,<2", "ty>=0.0.1a29,<0.0.22", "ruff"] messaging = ["python-telegram-bot[webhooks]>=22.6,<23", "discord.py[voice]>=2.7.1,<3", "aiohttp>=3.13.3,<4", "slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4", "qrcode>=7.0,<8"] -cron = ["croniter>=6.0.0,<7"] +cron = [] # croniter is now a core dependency; this extra kept for back-compat slack = ["slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4"] matrix = ["mautrix[encryption]>=0.20,<1", "Markdown>=3.6,<4", "aiosqlite>=0.20", "asyncpg>=0.29", "aiohttp-socks>=0.10,<1"] cli = ["simple-term-menu>=1.0,<2"]