feat(schedule): add explicit-time-zone cron reminders

This commit is contained in:
pku-xht
2026-08-06 23:48:57 +08:00
committed by Tianyi Cui
parent 9abecc103d
commit 31c0c6a9f6
23 changed files with 1833 additions and 92 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write examples/web-schedule/README.md
README.md: 906e191a4ae96b17d17b4dbf5ebea24ee18f3cb4
README.zh.md: 9a05730a16935f238c835c9aab8a058e2a4e717a
README.md: 5018ebf0905ea7713d4aabe4f15f686ac269f23e
README.zh.md: 119728513f85ec72e3b04e127ec297acd9229a9e

View File

@@ -8,7 +8,7 @@ This overlay opts one `dsh web` process into durable Schedule reminders without
dsh web --patch examples/web-schedule/cordis.yml
```
The current overlay supports one-shot reminders created with a positive whole-number `after_seconds` or an absolute `at` target, plus fixed-rate `every_seconds` reminders at intervals of at least 300 seconds. The model manages them through `schedule_create`, `schedule_list`, and `schedule_delete`; every result identifies the delivery mode as `session-local`.
The current overlay supports one-shot reminders created with a positive whole-number `after_seconds` or an absolute `at` target, fixed-rate `every_seconds` reminders at intervals of at least 300 seconds, and restricted five-field `cron` reminders paired with an explicit IANA `time_zone`. The model manages them through `schedule_create`, `schedule_list`, and `schedule_delete`; every result identifies the delivery mode as `session-local`.
An `at` target is either a strict RFC 3339 date-time with `Z` or a numeric offset, or a local `{ date, time, time_zone? }` value. The overlay loads time-context so the model sees the current date, local time, Session zone, and request-zone relationship before calling the tool. A local value may omit `time_zone` only when the current browser zone agrees with the immutable zone captured when that Session was created.
@@ -16,8 +16,8 @@ The browser samples its zone for each create or prompt operation. Resuming the S
The original Session log owns each reminder. A live root Agent waits, retries after it becomes idle, and records a durable dispatch receipt in the Web conversation. Closing the process or leaving the Session cold stops its in-memory timer without deleting the record; reopening that same Session restores the wait and delivers an overdue reminder. Merely reading cold history never activates it, and a fork does not inherit its parent's reminders.
Fixed-rate reminders remain anchored to their first target. A late wake or restart skips the missed backlog and presents only each record's latest due occurrence, then advances to its first future target. All overdue fixed-rate records share one model follow-up when the 300-second recurring gate opens, while each keeps its own durable dispatch, next target, and Web receipt. One-shot reminders bypass that gate.
Fixed-rate reminders remain anchored to their first target. Cron reminders use the stored UTC target as a history-stable baseline while current IANA tzdata determines only newer matches and the next target. A late wake or restart skips the missed backlog and presents only each record's latest due occurrence. All overdue Every and Cron records share one model follow-up when the 300-second recurring gate opens, while each keeps its own durable dispatch, next target, and Web receipt. One-shot reminders bypass that gate.
Create and actual delete operations acknowledge success only after Session persistence confirms their event prefix. A reminder receipt likewise appears only after its dispatch is durable. Schedule does not provide browser, operating-system, email, SMS, or other external notification, and the best-effort model follow-up is not a delivery acknowledgement.
Cron rules are not accepted by this layer.
Cron accepts only numeric minute, hour, day-of-month, month, and day-of-week fields using wildcards, integers, increasing lists/ranges, or steps. Day-of-month and day-of-week cannot both be restricted; nominal intervals under five minutes, names, macros, seconds, years, Quartz operators, local defaults, abbreviations, and numeric zone offsets are rejected. DST gaps are skipped, overlaps use the first instant, and the locked calendar evaluator never owns a timer or callback.

View File

@@ -8,7 +8,7 @@
dsh web --patch examples/web-schedule/cordis.yml
```
当前 overlay 支持使用正整数 `after_seconds` 或绝对时间 `at` 目标创建的一次性提醒,也支持间隔至少为 300 秒的固定频率 `every_seconds` 提醒。模型通过 `schedule_create``schedule_list``schedule_delete` 管理它们;每个结果都会把交付模式标为 `session-local`
当前 overlay 支持使用正整数 `after_seconds` 或绝对时间 `at` 目标创建的一次性提醒间隔至少为 300 秒的固定频率 `every_seconds` 提醒,以及与显式 IANA `time_zone` 配对的受限五字段 `cron` 提醒。模型通过 `schedule_create``schedule_list``schedule_delete` 管理它们;每个结果都会把交付模式标为 `session-local`
`at` 目标可以是带 `Z` 或数值偏移量且严格符合 RFC 3339 的日期时间,也可以是本地 `{ date, time, time_zone? }` 值。此 overlay 会加载时间上下文让模型在调用工具前看到当前日期、本地时间、Session 时区及其与请求时区的关系。只有当前浏览器时区与创建该 Session 时捕获且不可变的时区一致,本地值才可省略 `time_zone`
@@ -16,8 +16,8 @@ dsh web --patch examples/web-schedule/cordis.yml
每条提醒由原 Session 日志拥有。live 根 Agent 会等待,在恢复 idle 后重试,并在 Web 会话中记录持久 dispatch 回执。关闭进程或让 Session 保持 cold 会停止内存 timer但不会删除记录重新打开同一个 Session 会恢复等待并交付逾期提醒。仅查看 cold 历史不会激活提醒fork 也不会继承父 Session 的提醒。
固定频率提醒始终锚定其首个目标。延迟唤醒或重启会跳过错过期间的积压,只呈现每条记录最近一次到期的 occurrence,随后推进到该记录的第一个未来目标。300 秒周期性门控开放时,所有 overdue 固定频率记录共享一次模型 follow-up但每条记录仍保有自己的持久 dispatch、下一个目标和 Web 回执。一次性提醒会绕过该门控。
固定频率提醒始终锚定其首个目标。Cron 提醒以已存储的 UTC 目标作为在 history 中保持稳定的 baseline当前 IANA tzdata 只决定比该 baseline 更新的 match 与下一个目标。延迟唤醒或重启会跳过错过期间的积压,只呈现每条记录最近一次到期的 occurrence。300 秒周期性门控开放时,所有 overdue Every 与 Cron record 共享一次模型 follow-up但每条记录仍保有自己的持久 dispatch、下一个目标和 Web 回执。一次性提醒会绕过该门控。
创建和实际删除操作只有在 Session persistence 确认对应事件前缀后才会确认成功。提醒回执同样只在 dispatch 持久化后出现。Schedule 不提供浏览器、操作系统、邮件、短信或其他外部通知best-effort 模型 follow-up 也不构成交付确认。
本层不接受 cron 规则
Cron 只接受数值分钟、小时、月中日期、月份与星期字段;各字段可使用 wildcard、整数、递增列表区间或 step。月中日期与星期字段不能同时受限名义间隔短于 5 分钟的规则以及名称、macro、秒、年份、Quartz operator、本地默认值、缩写和数值时区偏移都会被拒绝。系统会跳过夏令时空档并在重叠时段使用第一个时刻版本锁定的日历求值器绝不会拥有 timer 或 callback