feat(todo): add timezone-aware resume_at triggers - #4347
Conversation
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
d920df4 to
6098862
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
基线 0933d48ef 的 Todo resume_when 不能表达精确日期:同一个 resume_at:2026-09-15T09:00:00+08:00 在真实 CLI dry-run 中被判为不支持。若日期只存在于自然语言描述,managed Turn、heartbeat、Dashboard 与 Lark 无法获得同源的到期事实,可能提前选中任务,或依赖人工重新唤醒。最小正确修复不是新增定时服务或解析自然语言,而是在既有 resume_when owner 中增加一个带显式时区的 typed variant;本 PR 正是沿这一边界实现。
改动思路
不可约的用户意图仍只有 Todo 上的 resume_when=resume_at:<timezone-aware-rfc3339>;evaluated_at、resume_ready、generation 与 receipt 都由该值和一次 runtime tick 捕获的时钟派生,不能独立编辑。Python active-state 边界在写入前验证并规范化,TypeScript resume_condition reducer 仍是 readiness 决策 owner;quota、managed Turn、Dashboard 与 Lark 只消费同一 projection。未来时间得到 generation 0 且没有 receipt,到期得到 generation 1 与稳定的 todo_resume_receipt_v0。该 receipt 只证明时间条件满足,不授予执行 Todo 外部动作的权限。
仓库搜索确认复用了现有 normalize_todo_resume_when、condition reducer、summary、quota 与 personal-workspace 路径,没有引入第二套 scheduler、store 或 message queue。Python/TypeScript 的重复仅限跨运行时的 wire-format 边界验证,并由同一组正常/边界样例约束。
具体改动
精确 head:609886257f7ca7f617b2998bf87e06521d25bdba。完整 diff 为 32 个文件、+693/-49;其中约 249 行为 production source,262 行 tests/fixtures,180 行双语 docs,51 行 packaged Dashboard 生成物。
关键代码讲解
loopx/control_plane/todos/resume_condition.ts:84的resumeTimestamp要求显式 offset,将时间规范为 UTC,并在 offset 换算后拒绝超出 1000..9999 年的值。它只解析,不写状态、不调度工作。loopx/control_plane/todos/resume_condition.ts:403的conditionFor在同一次 tick 的时钟上判定 future/due,生成 typed condition 与 content-addressed receipt;原有条件分支保持不变。loopx/control_plane/todos/active_state_todo_parser.py:56的parse_active_state_todos把同样的支持范围放在 Python 写入边界,非法/naive/越界时间在持久化前失败。apps/presentation/dashboard/src/views/dashboard-page.tsx:709的personalTodoResumeReceiptId及相邻 mapping 只投影 canonical backend facts;到期后 active Todo 仍显示 resume condition,前端不自行判断 readiness。
用户路径已经同时覆盖 CLI/managed Turn、Dashboard 与 Lark 的共享状态。文档说明 UTC 规范化、无时区拒绝、receipt 语义以及 successor_replan_required 与执行授权的区别。
对主干的风险
我主动构造的最强反例有两个:一是“语法合法的本地时间经 offset 转 UTC 后越出宿主日期范围”,普通 2026 fixtures 不会发现;二是 due 后 Todo 变 active,旧的 deferred-only UI guard 会隐藏仍有用的恢复条件。当前 head 已在 Python/TypeScript 同时加入换算后范围检查,并将 UI 条件改为 deferred || resumeWhen;对应极端年份回归测试和 packaged/development smoke 均通过。
本地证据:focused TypeScript 18 cases、focused Python 24 cases;完整 control-plane 为 1495 passed、1 skipped、0 failed;build:chat、packaged/development personal-workspace smokes 与 git diff --check 均通过。默认路径通过 exact tag 隔离:没有 resume_at 的 Todo 不新增 required field、prompt、state、quota 或 side effect。残余风险是 host wall-clock 被人工校正时 readiness 的观测时点会变化;本实现是 reducer projection,不宣称 timer/monotonic-clock 保证。
我的整体评价
同一真实 CLI 输入在基线为 exit 1/unsupported,在本 head 为 exit 0/planned,并读回规范化的 resume_at:2026-09-15T01:00:00Z,且 dry-run 无写入;这是预期且已验证的 observable semantic change。机制留在既有 owner,范围与重复出现的精确日期需求相称,没有扩大 actor 或执行 authority。revision-3 结构化结果校验通过,未发现阻塞问题。
Verdict: APPROVE for exact head 609886257f7ca7f617b2998bf87e06521d25bdba. Because this is author-owned, this comment records the approval conclusion rather than a formal GitHub approval. Publication is gated on all required GitHub checks completing successfully and the remote head remaining unchanged.
动机
LoopX 现有 Todo 恢复条件无法表达一个精确日期。自然语言日期可能继续留在可执行 Todo 中,而 CLI、heartbeat 与托管 Turn 没有同源的到期证明。该切片把日期恢复收敛到既有
resume_whenowner,不新增调度器或消息队列。改动
resume_at:<timezone-aware-rfc3339-timestamp>,强制显式时区并规范为 UTC;无时区、非法日期与非法 offset 在写入前失败。todo_resume_receipt_v0;重复 tick 和进程重启复现同一回执。agent_scope_wait/successor_replan_required,回执不授予执行权限,也不隐式 reopen Todo。English summary
Adds a strict, timezone-aware one-shot
resume_atTodo condition to the existing resume owner. Equivalent instants normalize to UTC; due reads emit one stable content-addressed receipt across ticks and restarts. CLI, heartbeat quota, managed Turn, frontend, and Lark share the same projection and retain explicit lifecycle authority.验证
PATH="$PWD/.venv/bin:$PATH" npm run test:control-plane— 1495 passed, 1 skippednpm run typecheck:control-planegit diff --checkFixes #4346