mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-08 22:00:46 +08:00
Keeps retrigger for consumed jobs only; agents can advance or delay nextRunAt safely. Co-authored-by: Cursor <cursoragent@cursor.com>
4.7 KiB
4.7 KiB
cron_reschedule 原语详设
状态:已在本包落地(rescheduleJob + cron_reschedule + POST /jobs/:id/reschedule)
包:dsh-ops-cron(本仓库可读源码,非混淆服务)
背景:一次性 at 任务在仍有 nextRunAt(pending)时,cron_retrigger 会拒绝;Agent 只能删重建,体验差。
1. 结论
改 pending 可以做,且应做。
语义是「把预定的那一次触发改期/提前」,不是「再开一次并发 run」。比放开 cron_retrigger 更精确、更安全。
| 方案 | 做法 | 评价 |
|---|---|---|
| A | 改 cron_retrigger:有 pending 时也接受,行为=抢占改期 |
可行,但混淆「已消费再触发」与「未到期改期」 |
| B(推荐) | 新增 cron_reschedule |
不碰 retrigger 语义;最小侵入、向后兼容 |
2. 现状(代码事实)
isRetriggerable/retriggerJob:one-shot 仅当nextRunAt == null且lastStatus终态才可 retrigger(lib/monitor.js、lib/host.js)。- pending 拒绝文案已写明:
one-shot still has a pending next run; wait or edit the schedule instead of retrigger。 - 侧栏 / HTTP 已能改期:
PATCH /jobs/:id→updateJob({ schedule });若 patch 了schedule,会经createJobRecord重算nextRunAt(host.js约 698–700 行:仅当未改 schedule/enabled 才保留旧 next)。 - 缺口:Agent 工具面没有
cron_update/cron_reschedule,只有 create / list / pause / resume / retrigger / delete。
因此:不必「热改运行中混淆服务」;在本包加工具(可选再加 host 薄封装)即可落地。
3. 推荐 API:cron_reschedule
3.1 签名
cron_reschedule({
id | task_id: string, // 必填
after_minutes?: number, // ≥0;与 at 二选一(优先 after_minutes)
at?: string, // ISO 或与 cron_create 一致的本地时间语义
timezone?: string // 可选;默认沿用 job.schedule.timezone
})
3.2 行为
- 仅
schedule.kind === 'at'。 - 仅当 仍有 pending next(
nextRunAt != null)且 无 active run(queued/running)。 - 计算新触发时刻
T(after_minutes=0→ 立即 due;>0→ now+N;at→ 解析后的绝对时间)。 T必须 ≥ now(允许 0 表示立刻进入 due,由现有 tick/run-now路径消费;不要另开第二条 pending)。- 写回:
schedule = { kind: 'at', at: ISO(T), timezone }nextRunAt = Tenabled保持不变(paused 允许改 next,不隐式 resume)
- 不调用
dispatchRun(除非产品明确要fire_now=true;默认不火,交给调度器)。 - 返回
{ ok, job, nextRunAt, previousNextRunAt }。
3.3 拒绝条件(稳定 error.code)
| 条件 | code |
|---|---|
| 非 one-shot | INVALID_RESCHEDULE |
nextRunAt == null(已消费) |
INVALID_RESCHEDULE → 提示改用 cron_retrigger |
| 已有 queued/running | ALREADY_RUNNING |
| 时间非法 / 过去 | INVALID_RESCHEDULE |
| 周期 cron | INVALID_RESCHEDULE(周期改期另议,勿混进本原语) |
3.4 与 retrigger 分工(写进 tool description + skill)
| 状态 | 用哪个 |
|---|---|
one-shot 已跑完(next=n/a,retriggerable=true) |
cron_retrigger |
one-shot 仍在等(有 nextRunAt) |
cron_reschedule |
| recurring 立刻多跑一次 | cron_retrigger(不改 cron 表达式) |
4. 实现落点(本仓库)
lib/host.js:新增rescheduleJob(jobId, opts, identity)(或在updateJob外包一层校验);可复用scheduleFromArgs/nextFire。lib/tools.js:注册cron_reschedule;更新scheduled-tasksskill 文案(禁止删重建来改期)。- HTTP(可选):
POST /jobs/:id/reschedule,与 PATCH 并存;Agent 走工具即可。 - 测试:
host.test.js/tools.test.js- pending → after_minutes=1 →
nextRunAt前移 - pending + active run → 拒绝
- consumed → 拒绝并指向 retrigger
- cron kind → 拒绝
- paused pending → 只改 next,仍
enabled=false
- pending → after_minutes=1 →
工作量小:调度内核已支持「改 schedule ⇒ 新 next」;主要是 Agent 可发现原语 + 边界校验。
5. 明确不做
- 不对 pending one-shot 放开无条件
cron_retrigger(避免与「已消费再触发」心智冲突)。 - 不引入第二条并发 pending。
- 不把 reschedule 做成隐式 start(paused 保持 paused)。
6. 临时绕过(原语未上线前)
- 人:侧栏编辑任务时间 → Save(已走 PATCH)。
- Agent:无工具时只能 delete + create(应在 skill 里标明为 workaround,待
cron_reschedule替换)。