Capture Slack thread metadata for cron and message-tool deliveries so replies stay in the originating thread, and hydrate first thread mentions with recent Slack context.
Made-with: Cursor
PR #3125 added a top-level `oneOf` branch to `_CRON_PARAMETERS` to
advertise per-action required fields. OpenAI Codex/Responses rejects
`oneOf`/`anyOf`/`allOf`/`enum`/`not` at the root of function
parameters, so any agent that registers the cron tool now fails to
start with:
HTTP 400: Invalid schema for function 'cron': schema must have
type 'object' and not have 'oneOf'/'anyOf'/'allOf'/'enum'/'not'
at the top level.
Remove the top-level `oneOf`. The original intent of #3125 (stop LLMs
from looping on the #3113 contract mismatch) is preserved by:
- `validate_params` — runtime-enforces `message` for `action='add'`
and `job_id` for `action='remove'`
- field descriptions — each schema field already flags
"REQUIRED when action='...'" so the LLM sees the contract
The regression test is updated to lock the invariant in the other
direction: the top-level schema must not contain
`oneOf`/`anyOf`/`allOf`/`not`, and the REQUIRED hints must stay on
`message` and `job_id`.
Verified:
- tests/cron/ 70 passed
- tests/agent/test_loop_cron_timezone.py + tests/providers/ 232 passed
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The previous patch promoted `message` into top-level `required`, which solved
the `add` loop but broke `list` and `remove`: `ToolRegistry.prepare_call`
enforces `required` via `validate_params`, so `cron(action="list")` and
`cron(action="remove", job_id=...)` — both documented in `SKILL.md` — started
failing schema validation with the same "missing required message" shape that
#3113 describes for `add`.
Instead:
- Keep `required=["action"]` so `list`/`remove` stay callable.
- Prefix `message`'s description with `REQUIRED when action='add'.` and
`job_id`'s with `REQUIRED when action='remove'.` so LLMs see the real
per-action contract up front.
- Keep the improved runtime error message from the previous commit for the
case an LLM still omits `message` on `add`.
Also add `tests/cron/test_cron_tool_schema_contract.py` to lock in:
- `list` and `remove` pass schema validation with no `message`
- `add` with `message` passes
- `add` without `message` surfaces the actionable runtime error
- field descriptions carry the REQUIRED hints
- top-level `required` stays `["action"]`
Existing `tests/cron/test_cron_tool_list.py` cases bypass schema validation by
calling `_list_jobs()` / `_remove_job()` directly, which is why CI didn't catch
the regression; the new test goes through `ToolRegistry.prepare_call`.
Previously the JSON schema only required "action" but the runtime
rejected empty messages, causing LLM retry loops. Making "message"
required in the schema prevents the mismatch, and the improved error
message guides the LLM to retry with the correct parameters.
Fixes#3113
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Make cron list output render one-shot and run-state timestamps in the same timezone context used to interpret schedules. This keeps scheduling logic and user-facing time displays consistent.
Made-with: Cursor
Make cron use the configured agent timezone when a cron expression omits tz or a one-shot ISO time has no offset. This keeps runtime context, heartbeat, and scheduling aligned around the same notion of time.
Made-with: Cursor
Addresses review feedback: moves schedule formatting and state
formatting into dedicated static methods, removes duplicate
in-loop imports, and simplifies _list_jobs() to a clean loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_list_jobs() only displayed job name, id, and schedule kind (e.g. "cron"),
omitting the actual timing and run state. The agent couldn't answer
"when does this run?" or "did it run?" even though CronSchedule and
CronJobState had all the data.
Now surfaces:
- Cron expression + timezone for cron jobs
- Human-readable interval for every jobs
- ISO timestamp for one-shot at jobs
- Enabled/disabled status
- Last run time + status (ok/error/skipped) + error message
- Next scheduled run time
Fixes#1496
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
datetime.fromisoformat(at) raises ValueError for malformed strings,
which propagated uncaught and crashed the tool execution. Now catches
ValueError and returns a user-friendly error message instead.
When a cron job fires, the agent processes the scheduled message and
has access to the cron tool. If the original message resembles a
scheduling instruction (e.g. "remind me in 10 seconds"), the agent
would call cron.add again, creating an infinite feedback loop.
Add a cron-context flag to CronTool that blocks add operations during
cron job execution. The flag is set before process_direct() and cleared
in a finally block to ensure cleanup even on errors.
Fixes#1441
- Remove trailing whitespace and normalize blank lines
- Unify string quotes and line breaks for long lines
- Sort imports alphabetically across modules