fix(cron-recipes): pre-release hardening — honest cadences, strict slot names, surface-aware UX
Review fixes for the Cron Recipes stack before release: - hydration-move: */90 in the cron minute field silently wraps to hourly (croniter-verified) — 90/120-minute options never fired at their stated cadence. Replaced with an hour-field step (0 9-17/2 * * 1-5) and an interval_hours slot whose options (1/2/3h) all fire as labeled. - fill_recipe: reject unknown slot names. A typo'd 'tiem=07:15' used to silently create the job at the 08:00 default; now it 422s on the dashboard form and errors on the slash/deep-link paths with the valid slot list. - deliver slot: non-strict enum (options are suggestions, scheduler validates downstream) so slack/whatsapp/etc. users aren't locked out; GET /api/cron/recipes rewrites its options from cron_delivery_targets() so the dashboard form only offers configured platforms; help text no longer claims dashboard-created jobs deliver to 'the chat you set this up from' (the endpoint strips origin — they go to the home channel). - gateway: success/accept messages no longer point at /cron (cli_only); surface-aware hint instead. Conversational fill now sends the 'Setting up X — I'll ask you a couple of things…' ack before the agent turn, matching the CLI experience. - important-mail catalog entry: reference the urgency classifier by module path (python3 -m cron.scripts.classify_items) instead of baking an absolute host path into the job prompt — stale after relocation and nonexistent on remote terminal backends. cron/scripts is now a real package and ships in the wheel (pyproject packages.find). - export_recipe: interval schedules round-trip again — parse_schedule stores 'minutes' but the renderer only read 'seconds', so every interval job exported as the silent '0 9 * * *' fallback. - skills_hub install: say so when a recipe suggestion is dropped (latched dedup or pending cap) instead of printing nothing. Targeted tests: 58 cron/recipe + 261 web_server pass; E2E-validated all 14 recipes fill+parse, hydration cadences via croniter, typo rejection on slash + endpoint paths, surface-aware hints, and interval export round-trip.
This commit is contained in:
+15
-7
@@ -307,11 +307,19 @@ def _schedule_to_string(schedule: Any) -> str:
|
||||
kind = schedule.get("kind")
|
||||
if kind == "cron" and schedule.get("expr"):
|
||||
return str(schedule["expr"])
|
||||
if kind == "interval" and schedule.get("seconds"):
|
||||
secs = int(schedule["seconds"])
|
||||
if secs % 3600 == 0:
|
||||
return f"every {secs // 3600}h"
|
||||
if secs % 60 == 0:
|
||||
return f"every {secs // 60}m"
|
||||
return f"every {secs}s"
|
||||
if kind == "interval":
|
||||
# parse_schedule stores interval periods as "minutes"; tolerate a
|
||||
# legacy/foreign "seconds" form too.
|
||||
if schedule.get("minutes"):
|
||||
mins = int(schedule["minutes"])
|
||||
if mins % 60 == 0:
|
||||
return f"every {mins // 60}h"
|
||||
return f"every {mins}m"
|
||||
if schedule.get("seconds"):
|
||||
secs = int(schedule["seconds"])
|
||||
if secs % 3600 == 0:
|
||||
return f"every {secs // 3600}h"
|
||||
if secs % 60 == 0:
|
||||
return f"every {secs // 60}m"
|
||||
return f"every {secs}s"
|
||||
return "0 9 * * *" # safe daily fallback
|
||||
|
||||
Reference in New Issue
Block a user