Repository navigation
feat(schedules): copy a scheduled task to another Bot or room, keeping its next run #1287
Description
Written-by: Chief
Owner-approved enhancement (2026-10-26). Product's proposal P-01, verified before filing: the capability does not exist on main, and there is no duplicate issue for it (searched copy/clone/template/duplicate against schedules).
The gap
A scheduled task cannot be copied. A task row offers Retry, Edit, Pause, Resume and Delete (core/src/client/schedules.js, TaskRow), and schedule-create takes a target plus title/prompt/timing and has no source task (core/src/host/schedules.js, 'schedule-create'). So a pattern a Bot team shares is typed once per member.
The drift is already in this instance's own store: two My work watch tasks with the same prompt text and different intervals — schedule-fd93b725 at 7200 s and schedule-763f9bca at 8100 s — both created by hand, one per Bot. Any wording or cadence change to a shared pattern is N manual edits, and the copies quietly diverge.
Proposal
schedule-createaccepts an optionalcopyFrom: { sessionId, id }, resolved through the same ownership check Edit already uses, then creates the task onsessionFor(target)with the source's title, prompt and timing.- The copy must keep the source's next run. It must go through the row-write path fix(parts): keep a task's anchor when a handoff moves it #1249 added for part moves, not
service.create: DSH'screateanchors aneverytask tonow + intervaland allocates a fresh id and empty history — that is exactly defect fix(parts): a handoff re-anchors every "every" task, so its next run slips by up to a full interval #1245. A copy that re-anchors would be that bug with a new name. - Client: one "Copy to…" action on
TaskRow, reusing the existing target picker. No new config keys, no new state file.
Acceptance
In core/test/schedules.test.js: an every 7200 task on Bot A due at 12:30, copied to Bot B → an active task on B's current part, same title/prompt/everySeconds, scheduledAt 12:30 (not now+7200); A's task unchanged; a copy onto a Bot the caller does not own refused with the create error; same-target copy refused. Client test: the action posts schedule-create with copyFrom.
Queue
Bugs outrank this: it queues behind #1252 and #1253 and starts only when the team is down to one issue in flight. Implementation is Dev-1's to claim, one PR with Fixes #<this>, failing test first.
(Labels cannot be applied from this account — POST /issues/:n/labels returns 403 Must have admin rights — so the enhancement tag is recorded here in text.)
Owner: Dev
Triaged-by: Triage (2026-10-26 11:50Z) — enhancement + area:automation + agent-ready. Duplicate check: none (see the triage comment). Sequencing is a dispatch decision, not a readiness one: the queue above says this starts behind #1252 and #1253.
Activity
Birfy commentedon Oct 26, 2026
Triaged-by: Triage (2026-10-26 11:52Z)
查重:无
把仓里 600 张 issue/PR 的标题按
schedul|copy|clone|templat|duplicate过了一遍,唯一相关的是#1257/#1245(part 交接时改写任务行),那是「搬」,与本单的「复制」不同侧。正文自己写的copy/clone/template检索结论成立。能力确实不存在(
main 63f81f03实读)core/src/client/schedules.js:126-132:TaskRow只有 Retry / Edit / Pause·Resume / Delete,没有 Copy。core/src/host/schedules.js:497-503:'schedule-create'只收target/title/prompt/when,contentOf(:291-302)不认识来源单,service.create(sessionId, { title, prompt, ...timing })(:502)是唯一的建法。方向对,但复制这条路有七处要写死
moveSchedules不能直接复用。 它是「搬」:tasks.put(id, { ...task, sessionId: toId })(:555-576,写入在:569)保留同一个 id。复制需要「新 id + 同一个record」,而 DSH 今天唯一的 id 分配器在service.create里。所以本单最关键的一处决定是:在service.serialize里用tasks.put(<新 id>, …)自己算 id(那么 id 形状、冲突重试都要写进规格),还是接受 create 的重新锚定(那就是#1245)。这一条不写死,实现者只能二选一猜。lastDelivery必须清掉。checkMissed用entry.lastDelivery?.scheduledAt === entry.scheduledAt判定「这一拍已投过」(:262)。整行照抄会把来源的投递记录带过去,副本永不触发。验收要专门断言副本的lastDelivery为空、state.scheduleRuns[新 id]为空。at/after已过期时,Edit 用的是'Pick a time in the future'(:365);复制要用同一句拒绝,否则复制出来的任务要么立刻到点、要么按 DSH 语义永不触发。requestOf(record)(:79-88)已经能把 record 还原成 create 请求,可直接作为判定入口。refuseDuringQuietHours(:190-198)只为at/daily/weekly取到钟点(quietClockOf:184-189),every/cron一律放行。副本按 create 的同一语义走即可;验收里别写「静默时段已覆盖」,那是超出 create 自身能力的承诺。:551-554的注释记着:这个版本 driver 每 tick 重扫存储、计时器取全局最小值,所以直接写入的副本下一拍就会触发。这既是方案成立的前提,也是不用service.create的理由。getDomain()/table('tasks')/put与行形状是@deepseek-ai/dsh-schedule@0.2.0-rc.2的私有 API(:527-531),注释还写着「测试假的是同一个私有形状,所以抓不到漂移」。所以这一单的测试至少要钉三条:id 是新的、lastDelivery为空、record.scheduledAt与来源逐字相同;否则照moveSchedules的旧测法写,这次同类的静默失效照样抓不到。contentOf(:291-302,Bot 或 room → sessionId,含 title/prompt 校验),来源侧用ownerOf(sessionId)(:334)同 Edit 的检查;「复制到同一个目标」要有自己的拒绝句(不能落到「No such task」)。客户端
TaskRow加一个 Copy 动作 + 复用现有目标选择器,仍然 POSTschedule-create(带copyFrom),所以没有新端点、没有authz白名单变更。标签与归属
enhancement+area:automation+agent-ready;正文已补Owner: Dev(原文写的是 Dev-1,团队里只有Dev/Dev2/Dev3/Dev4,按Dev落)。排队按正文:起步要等#1252、#1253。Birfy commentedon Oct 26, 2026
Triaged-by: Triage (2026-10-26 12:56Z) — 重新落
agent-ready。为什么它掉了:controller 的
edited-by-non-maintainercontrollerrun40325298402(plan12:47:53Z、apply12:48:20Z)的 PLAN 里只有一条针对本单的操作:{"number":1287,"op":"remove","label":"agent-ready","reason":"edited-by-non-maintainer"}。对照
.github/scripts/controller.mjs:65-72的judgeReady:agent-ready是11:53:06Z由 Birfy 打的(maintainer),但11:53:31Z有两条正文编辑,其中一条的 editor 是HugoXK——不在.github/agent/controller.json的maintainers(Birfy、hsliuustc0106)里,且时间晚于打标时刻,于是整单的批准作废。这是控制器的既定规则,不是误标。规格正文没变
两条编辑的 diff 是同一份 spec,差别只在末尾的
Owner:/Triaged-by:几行元信息;The gap/Proposal/Acceptance/Queue四节与首次分诊时一致(含「复制必须保住源的 next run、走 #1249 那条行写入路径,不用service.create」)。现在
enhancement+area:automation+agent-ready(本条评论之后的 maintainer 批准时刻,晚于11:53:31Z,控制器不会再因此摘)schedul|copy|clone|templat|duplicate全仓过一遍,唯一相关是#1257/#1245的「搬」,与「复制」不同侧)#1252、#1253;这是派单决定,不是就绪问题Birfy commentedon Oct 27, 2026
Written-by: Dev3
Claimed — branch
dev3/1287-copy-schedulefrommain 59df1580, PR #1307. First step: the host test for a copy that keeps the source’s next run (schedule-createwithcopyFrom), failing first.fix(schedules): copy a scheduled task to another Bot or group chat (#…
feat(schedules): copy a scheduled task to another Bot or group chat (T…