Skip to content

feat(web-shell): add a Scheduled Tasks management page#6348

Merged
wenshao merged 8 commits into
QwenLM:mainfrom
wenshao:feat/web-shell-scheduled-tasks
Jul 6, 2026
Merged

feat(web-shell): add a Scheduled Tasks management page#6348
wenshao merged 8 commits into
QwenLM:mainfrom
wenshao:feat/web-shell-scheduled-tasks

Conversation

@wenshao

@wenshao wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a "Scheduled tasks" page to the Web Shell for managing durable cron tasks against the current workspace. The sidebar gains a clock entry that opens a full-pane page (it replaces the chat area, it is not a modal overlay) listing each task with an enable/disable toggle, a delete and a run-now action, and a human-readable schedule. "New scheduled task" opens a modal with a schedule builder (daily / weekdays / weekly / hourly / every-N-minutes / custom cron) and a live preview. "Create via chat" returns to the chat and pre-fills the composer so the user can describe the task in natural language and the agent creates it through its cron_create tool. On the daemon side, new CRUD routes read and write the existing per-project scheduled_tasks.json; task firing continues to be handled by the session-side scheduler.

Why it's needed

qwen-code already ships a durable cron engine (cron_create / cron_list / cron_delete backed by scheduled_tasks.json), but until now the only way to see or manage those tasks was through chat tool-calls. This gives Web Shell users a visual place to review, create, toggle, edit, and delete their recurring tasks — the "Scheduled tasks" affordance other agent desktops offer — while reusing the existing engine instead of adding a second one.

Reviewer Test Plan

How to verify

Run the daemon-backed Web Shell and open the page: npm run dev:daemon, then open the printed http://localhost:5173/?token=… URL.

  • Click the clock icon in the sidebar footer → the chat area is replaced by the "定时任务 / Scheduled Tasks" page (a full-pane view, not a modal; a back arrow returns to chat).
  • Click "New scheduled task" → a modal opens. Choose "Weekdays", time 12:30, enter a prompt, click Create → the modal closes and a card appears showing "Weekdays at 12:30" and "Never run".
  • Toggle the card's switch off → it persists to disk, and the scheduler skips tasks with enabled: false.
  • Click "Create via chat" → returns to chat with the composer pre-filled ("Help me set up a recurring scheduled task…"); describe a task and the agent creates it via cron_create, after which it shows on the page.

Automated checks run locally on this branch:

  • Daemon CRUD contract via curl: GET empty → POST 201 → PATCH enabled:false persists to scheduled_tasks.json → invalid cron 400 → DELETE 200 → DELETE again 404 → 51st create 409 (max-tasks cap).
  • Playwright smoke (zh-CN): page opens (role=dialog count 0), create is a modal (role=dialog count 1) that closes after Create, card renders "工作日 12:30", toggle true→false, "Create via chat" returns to chat with the composer pre-filled. Zero console errors.
  • Unit tests: core cron 131 (incl. new enabled-skip and name/enabled round-trip), cli route 9, web-shell sidebar 29. tsc typecheck and eslint clean; full npm run build green.

Evidence (Before & After)

Before: there was no Scheduled Tasks page — durable tasks were only reachable via chat tool-calls.

After — the page (replaces chat), the create modal, and "Create via chat":

Scheduled Tasks page

New scheduled task modal

Create via chat pre-fills the composer

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

npm run dev:daemon (vite dev server + daemon) and a built daemon (node packages/cli/dist/index.js serve --web).

Risk & Scope

  • Main risk or tradeoff: the page reads/writes the per-project scheduled_tasks.json; firing still requires a live agent session holding the durable lock, so this PR intentionally does not add an always-on daemon-level scheduler.
  • Not validated / out of scope: daemon-resident always-on firing; execution history; one-shot (non-recurring) creation from the UI; "Create via chat" depends on the model choosing cron_create(durable: true), so a session-only task would not appear on the page.
  • Breaking changes / migration notes: none. DurableCronTask gains optional name / enabled fields; existing task files load unchanged and tool-created tasks keep firing.

Linked Issues

中文说明

这个 PR 做了什么

给 Web Shell 增加一个「定时任务」管理页,用于管理当前工作区的持久化 cron 任务。侧边栏新增一个时钟入口,点开后是一个占据右侧主区域的整页(它替换聊天区,而不是浮层弹窗),以卡片列出每个任务,带启用/停用开关、删除、立即运行,以及人类可读的计划描述。「新建定时任务」打开一个弹窗,内含计划构造器(每天 / 工作日 / 每周 / 每小时 / 每 N 分钟 / 自定义 cron)和实时预览。「通过聊天创建」会切回聊天并预填输入框,用户用自然语言描述任务,由 agent 通过它的 cron_create 工具创建。daemon 侧新增的 CRUD 路由读写既有的按项目 scheduled_tasks.json;任务的实际触发仍由会话侧的调度器负责。

为什么需要

qwen-code 已经内置了一套持久化 cron 引擎(cron_create / cron_list / cron_delete,由 scheduled_tasks.json 持久化),但此前查看或管理这些任务的唯一方式是聊天里的工具调用。这个改动给 Web Shell 用户一个可视化的地方来查看、创建、开关、编辑、删除周期任务——即其它 agent 桌面端常见的「定时任务」入口——并且复用既有引擎,而不是再造一套。

复审测试计划

如何验证

跑起 daemon 版 Web Shell 并打开页面:npm run dev:daemon,然后打开打印出的 http://localhost:5173/?token=…

  • 点侧边栏底部的时钟图标 → 聊天区被「定时任务」整页替换(是整页视图而非弹窗;左上返回箭头回到聊天)。
  • 点「新建定时任务」→ 弹出弹窗。选「工作日」、时间 12:30、填提示词、点创建 → 弹窗关闭,出现一张卡片,显示「工作日 12:30」和「尚未运行」。
  • 关掉卡片上的开关 → 已落盘持久化,调度器会跳过 enabled: false 的任务。
  • 点「通过聊天创建」→ 切回聊天且输入框已预填(「帮我创建一个长期保留的定时任务……」);描述任务后由 agent 用 cron_create 创建,之后会出现在页面上。

本分支上本地跑过的自动化检查:

  • curl 验 daemon CRUD 契约:GET 空 → POST 201 → PATCH enabled:false 落盘 scheduled_tasks.json → 非法 cron 400 → DELETE 200 → 再 DELETE 404 → 第 51 个创建 409(任务上限)。
  • Playwright 冒烟(zh-CN):整页打开(role=dialog 计数 0)、创建是弹窗(role=dialog 计数 1)且创建后关闭、卡片渲染「工作日 12:30」、开关 true→false、「通过聊天创建」切回聊天且输入框已预填。零 console 错误。
  • 单测:core cron 131(含新增的 enabled 跳过与 name/enabled 往返)、cli 路由 9、web-shell 侧边栏 29。tsc 类型检查与 eslint 通过;完整 npm run build 全绿。

证据(前后对比)

之前:没有定时任务页——持久化任务只能通过聊天工具调用触达。

之后——整页(替换聊天)、创建弹窗、以及「通过聊天创建」:

(见上方英文部分的三张截图。)

测试平台

macOS ✅;Windows ⚠️ 未测;Linux ⚠️ 未测。

运行环境(可选)

npm run dev:daemon(vite dev + daemon)以及构建后的 daemon(node packages/cli/dist/index.js serve --web)。

风险与范围

  • 主要风险/取舍:页面读写按项目的 scheduled_tasks.json;触发仍需要一个持有持久锁的活跃 agent 会话,因此本 PR 有意不引入常驻 daemon 级调度器。
  • 未验证 / 范围外:daemon 常驻触发;执行历史;UI 里创建一次性(非周期)任务;「通过聊天创建」依赖模型选择 cron_create(durable: true),若建成仅本次会话的任务则不会出现在页面上。
  • 破坏性变更 / 迁移说明:无。DurableCronTask 新增可选 name / enabled 字段;既有任务文件照常加载,工具创建的任务照常触发。

Add a "Scheduled tasks" page to the Web Shell for managing durable cron tasks against the current workspace.

- Sidebar entry opens a full-pane page (replaces the chat area, not a modal) listing tasks with enable/disable toggle, delete, run-now, and human-readable schedules.
- "New scheduled task" opens a modal with a schedule builder (daily / weekdays / weekly / hourly / every-N-minutes / custom cron) and a live preview.
- "Create via chat" returns to the chat and primes the composer so the agent creates the task through its cron_create tool.
- Daemon CRUD routes (GET/POST/PATCH/DELETE /scheduled-tasks) read/write the existing per-project scheduled_tasks.json; task firing stays with the session-side scheduler.
- Extend DurableCronTask with optional name/enabled (backward compatible); the scheduler skips tasks with enabled:false.
- Add /scheduled-tasks to the vite dev-server proxy allowlist so the page works under npm run dev:daemon.
@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present and filled in, including bilingual description, reviewer test plan with step-by-step verification, before/after evidence (screenshots), and risk/scope assessment.

Problem: this is a feature addition, not a bug fix. The gap is real — qwen-code ships a durable cron engine (cron_create/cron_list/cron_delete backed by scheduled_tasks.json), but until now the only way to manage those tasks was through chat tool-calls. A visual management page is a legitimate usability improvement that other agent desktops already offer.

Direction: aligned. This reuses the existing cron engine rather than introducing a parallel scheduler — the right call. The Web Shell already proxies daemon routes, and adding a CRUD surface on top of the existing file format is the natural extension. The scope is a single coherent feature (page + daemon routes + core field additions), not unrelated bolt-ons.

Approach: the scope feels right for the feature. Core changes are minimal (optional name/enabled fields on DurableCronTask, scheduler skips disabled tasks, durable-only cap instead of combined cap). The PATCH route supports more fields than the UI currently uses (cron, prompt, recurring in addition to enabled/name), but that's a complete CRUD API — reasonable forward-looking design, not scope creep. One small note: parseNameField exists both in the route file and could theoretically live in core for reuse, but the route version has HTTP-specific error codes, so local duplication is fine.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必填章节齐全,包括双语说明、复审测试计划(含逐步验证步骤)、前后对比证据(截图)、以及风险与范围评估。

问题:这是一个功能新增,不是 bug 修复。差距确实存在 — qwen-code 已经内置了持久化 cron 引擎(cron_create/cron_list/cron_delete,由 scheduled_tasks.json 支撑),但此前管理这些任务的唯一方式是聊天里的工具调用。一个可视化管理页面是合理的可用性改进,其他 agent 桌面端已经提供了类似功能。

方向:对齐。复用了既有的 cron 引擎而不是引入并行调度器 — 正确的做法。Web Shell 已经代理了 daemon 路由,在既有文件格式之上添加 CRUD 接口是自然的扩展。范围是一个完整的特性(页面 + daemon 路由 + core 字段新增),不是无关的拼凑。

方案:范围合理。core 改动最小化(在 DurableCronTask 上新增可选的 name/enabled 字段,调度器跳过 disabled 任务,持久化专用上限取代混合上限)。PATCH 路由支持的字段比 UI 目前使用的多(除了 enabled/name 还有 cron、prompt、recurring),但这是完整的 CRUD API — 合理的前瞻性设计,不是范围蔓延。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Code Review

I formed an independent proposal before reading the diff: to add a scheduled-tasks management page, I'd add optional enabled and name fields to DurableCronTask, add daemon CRUD routes reading/writing the existing scheduled_tasks.json, build a React component with a schedule builder, and wire it into the sidebar. The PR's approach matches this almost exactly.

Core changes (256 lines, well under 500-line threshold): Backward-compatible. DurableCronTask gains two optional fields (name?: string, enabled?: boolean). The isValidTask validator handles absent-as-valid for both. The scheduler's loadFileTasks correctly filters enabled === false tasks alongside unparseable-cron ones, making the toggle effective via the reconcile path (disabled task not in filtered set → job removed from live map). The durable-only cap (durableJobCount instead of this.jobs.size) is a genuine fix — session-only jobs were incorrectly crowding out durable loads from disk, meaning a route-accepted 201 could silently never fire.

Route (scheduled-tasks.ts): Thorough validation — cron (both parseCron and nextFireTime for impossible schedules like Feb 30), prompt length, name length, type checks. Cap checked under the write lock. The re-enable logic correctly resets lastFiredAt to the creation minute, preventing catch-up fires for intentionally paused tasks. No path traversal risk — task ids are generated internally, and PATCH/DELETE use findIndex on the in-memory array, not file paths.

UI: Clean separation — pure schedule helpers (buildCron/describeCron/describeLastRun) in a framework-free module with comprehensive tests. The React component handles loading states, stale-data protection (monotonic reloadSeqRef), and unmount guards. i18n covers both EN and ZH.

Cross-package exports: readCronTasks, updateCronTasks, removeCronTasks, getCronFilePath, generateCronTaskId exported from @qwen-code/qwen-code-core for the daemon route. Necessary and appropriate.

Tests: 205 tests across 7 files, all passing. Good coverage of edge cases: corrupt files, disabled-task lifecycle, cap enforcement, non-divisor minute intervals, URL-encoded task IDs.

Real-Scenario Testing

Built the project (npm run build ✓) and started the daemon. Tested the full CRUD contract via curl:

=== Daemon startup (capture-pane) ===

$ node packages/cli/dist/index.js serve --web --port 0
qwen serve: daemon log → ~/.qwen/debug/daemon/serve-*.log
qwen serve: Web Shell UI served from .../packages/web-shell/dist
qwen serve listening on http://127.0.0.1:36313 (mode=http-bridge, workspace=...)
qwen serve: bound to workspace "..."
qwen serve: startup timing: processToListenMs=110 runQwenServeToListenMs=30
qwen serve: bearer auth disabled (loopback default).
[DAEMON] deferred runtime: fallback timer fired, starting
[DAEMON] ideEnvPresent=false ... daemon workspace roots initialized
qwen serve: session reaper started (interval 60000ms, idle threshold 1800000ms)
qwen serve: /acp WebSocket transport enabled on /acp

=== GET (empty) ===
{ "v": 1, "tasks": [] }

=== POST (create: weekdays 12:30, name "Digest") ===
{ "id": "rosou0fl", "name": "Digest", "cron": "30 12 * * 1-5",
  "prompt": "summarize the day", "recurring": true, "enabled": true,
  "createdAt": 1783309431570, "lastFiredAt": 1783309380000 }

=== PATCH (disable) ===
{ ... "enabled": false ... }

=== PATCH (re-enable) ===
{ ... "enabled": true, "lastFiredAt": 1783309380000 }  ← reset to creation minute, no catch-up fire

=== DELETE ===
{ "deleted": true, "id": "rosou0fl" }

=== DELETE again → 404 ===
{ "error": "Task not found", "code": "task_not_found" }  HTTP 404

=== POST invalid cron → 400 ===
{ "error": "Cron expression must have exactly 5 fields, got 3: \"not a cron\"",
  "code": "invalid_cron" }  HTTP 400

=== POST impossible cron (Feb 30) → 400 ===
{ "error": "No matching fire time found within 4 years for: \"0 0 30 2 *\"",
  "code": "invalid_cron" }  HTTP 400

All CRUD operations behave as specified. Typecheck and build clean.

中文说明

代码审查

在阅读 diff 之前,我独立构思了方案:在 DurableCronTask 上添加可选的 enabledname 字段,新增 daemon CRUD 路由读写现有的 scheduled_tasks.json,构建一个带计划构造器的 React 组件,并接入侧边栏。PR 的方案与此几乎完全一致。

Core 改动(256 行,远低于 500 行阈值): 向后兼容。调度器正确过滤 enabled === false 的任务。持久化专用上限(而非混合上限)是一个真正的修复 — 会话级任务之前会错误地挤掉磁盘上的持久化任务。

路由: 验证全面 — cron(含不可能日期如2月30日)、提示词长度、名称长度、类型检查。重新启用时正确重置 lastFiredAt 以防止补发。

UI: 干净的分离 — 纯函数式计划辅助器、React 组件处理加载态和过期数据保护、i18n 覆盖中英文。

测试: 7 个文件共 205 个测试,全部通过。

真实场景测试

构建了项目,启动 daemon,通过 curl 测试了完整 CRUD 契约。所有操作行为正确。Typecheck 和 build 全绿。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Stage 3 Reflection

This PR does what it says: adds a visual management page for durable cron tasks that already exist in the codebase. The motivation is clear (chat-only management is inconvenient), the implementation reuses existing infrastructure instead of creating a parallel system, and the core changes are backward-compatible optional fields with well-tested scheduler logic.

Going back to my independent proposal: the PR matches it closely. The one improvement over my initial thinking is the durable-only cap fix — I would have kept the combined cap and missed the silent-failure scenario where session-only jobs crowd out durable loads. That's a meaningful correctness win embedded in what looks like a feature PR.

The code is straightforward. No over-abstraction, no speculative features, no drive-by refactors. The 27 files / 2748 additions look large, but the bulk is test code (205 tests), i18n strings (EN + ZH), and CSS — the actual logic surface is tight. Every change in the diff serves the stated goal.

Real-scenario testing confirms the CRUD contract works end-to-end against a running daemon. Unit tests pass (205/205). Typecheck and build are clean.

One minor observation for the author's consideration (not a blocker): the PATCH route currently supports updating cron/prompt/recurring in addition to enabled/name, but the UI only uses toggle and delete. If those extra PATCH fields aren't planned for near-term UI use, they could be deferred to keep the API surface minimal. But a complete CRUD is also defensible.

Approved. ✅

中文说明

Stage 3 反思

这个 PR 做了它声明的事情:为代码库中已有的持久化 cron 任务添加可视化管理页面。动机明确(仅聊天管理不方便),实现复用了既有基础设施而非创建并行系统,core 改动是向后兼容的可选字段加上经过充分测试的调度器逻辑。

回到我的独立方案:PR 与之高度一致。一个改进是持久化专用上限修复 — 我会保留混合上限而错过会话级任务挤掉持久化加载的静默失败场景。这是一个嵌在功能 PR 里有意义的正确性修复。

代码直截了当。无过度抽象、无投机性功能、无顺手重构。27 个文件 / 2748 行新增看起来很多,但大部分是测试代码(205 个测试)、i18n 字符串(中英文)和 CSS — 实际逻辑面很紧凑。diff 里每个改动都服务于声明的目标。

真实场景测试确认 CRUD 契约在运行中的 daemon 上端到端工作。单元测试通过(205/205)。Typecheck 和 build 全绿。

一个小观察供作者参考(不是阻断项):PATCH 路由目前支持更新 cron/prompt/recurring(除了 enabled/name),但 UI 只用了开关和删除。如果这些额外的 PATCH 字段近期没有 UI 使用计划,可以延后以保持 API 面最小化。不过完整的 CRUD 也是合理的。

已通过 ✅

Qwen Code · qwen3.7-max

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Request Changes to Comment: self-PR; CI still running.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/services/cronScheduler.ts
@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Suggestions — commit caadb8d6f

File Issue Suggested fix
WebShellSidebar.test.tsx:248 Missing click test for the new onOpenScheduledTasks sidebar button — the existing onOpenDaemonStatus has 2 click tests (expanded + collapsed) but the new clock button has 0 Add a describe('scheduled tasks entry') block mirroring the daemon-status tests
scheduled-tasks.test.ts:220 PATCH handler length-cap branches (cron > 200 chars, prompt > 100K chars) have no test — POST covers the same limits but PATCH doesn't Add a PATCH test sending cron: 'x'.repeat(201) and asserting 400 / invalid_cron
ScheduledTasksDialog.tsx:449 "Run now" button fires the prompt on disabled tasks with no confirmation — contradicts the semantic of the enable/disable toggle Add disabled={!task.enabled} on the button, or a window.confirm when the task is off
ScheduledTasksDialog.tsx:189 Delete confirmation passes task.prompt (up to 100K chars) to window.confirm — produces an unreadable/hanging dialog Truncate the label: rawLabel.length > 60 ? rawLabel.slice(0, 57) + '...' : rawLabel
cronTasksFile.ts:276 isValidTask strict type check on optional enabled/name fields — "enabled": null (common in hand-edited JSON) fails validation and makes the entire file unreadable Coerce wrong-type optional fields to absent rather than rejecting the entry
scheduled-tasks.ts:42 MAX_SCHEDULED_TASKS = 50 duplicates MAX_JOBS = 50 across packages with no compile-time coupling — drift risk if either is changed independently Export a shared constant from cronTasksFile.ts and import in both locations

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] cron_list tool doesn't surface new name/enabled fields

In packages/core/src/tools/cron-list.ts:74, the fileTasks.map mapping drops the new name and enabled fields added by this PR. A disabled task appears in cron_list output indistinguishable from an enabled one — the agent cannot tell that a disabled task will never fire. This creates a divergence between the management UI and the agent's view.

Suggested fix: add name: task.name and enabled: task.enabled !== false to the mapping.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/core/src/index.ts
Comment thread packages/cli/src/serve/routes/scheduled-tasks.test.ts
- cron_list: surface name/enabled so the agent can tell a disabled durable task from an active one (a disabled task no longer looks identical to an active one).
- core: export only the tasks-file functions the daemon route actually uses (drop unused addCronTask / getCronFilePath / CRON_TASKS_DISPLAY_PATH from the public barrel).
- CronScheduler: warn when a durable reload fails and the prior view is kept, since a just-disabled or -deleted task can keep firing until the next successful reload.
- Extract the schedule helpers (buildCron / describeCron / parseHhmm / describeLastRun) into a pure module and add unit tests for them.
- Add route tests for PATCH cron/prompt/recurring, empty-patch rejection, and POST field-length / boolean-type validation.
@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Re: cron_list doesn't surface the new name/enabled fields (from the change-request review) — fixed in 76c4744b.

cron_list now includes name and enabled for durable tasks and marks a disabled task with a , disabled status tag in both the LLM content and the display output (e.g. abc123 — 0 9 * * 1 (recurring) [durable, disabled]: my task), so the agent can no longer mistake a disabled task for an active one. Output for tasks that carry neither field (tool-created durable tasks and session-only jobs) is byte-for-byte unchanged, so existing cron_list tests still pass.

中文

关于 cron_list 未暴露新的 name/enabled 字段(来自 change-request 复审)——已在 76c4744b 修复。

cron_list 现在会为持久任务带上 nameenabled,并在 LLM 内容与展示输出里都用 , disabled 状态标记标出被禁用的任务(例如 abc123 — 0 9 * * 1 (recurring) [durable, disabled]: my task),这样 agent 不会再把禁用任务误当作活跃任务。对于不带这两个字段的任务(工具创建的持久任务、以及仅会话内的任务),输出逐字节不变,因此既有的 cron_list 测试仍然通过。

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Request Changes to Comment: self-PR; CI still running.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/tools/cron-list.ts
Comment thread packages/webui/src/daemon/workspace/actions.ts
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts Outdated
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
@wenshao wenshao dismissed qwen-code-ci-bot’s stale review July 5, 2026 22:44

Addressed in 76c4744: cron_list now surfaces name/enabled and tags disabled tasks with a , disabled marker (see PR comment). All four inline review threads are resolved and CI (Test ubuntu) is green. Dismissing this stale pre-fix review.

Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/core/src/tools/cron-list.ts
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts Outdated
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/web-shell/client/components/dialogs/scheduledTasksSchedule.ts Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Request Changes to Comment: self-PR.

Comment thread packages/web-shell/client/App.tsx Outdated
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
wenshao added 2 commits July 6, 2026 07:47
- Log CRUD errors server-side (writeStderrLine) in each route catch block, matching the other daemon routes.
- Share one id generator (generateCronTaskId in cronTasksFile) between the scheduler and the daemon route instead of duplicating it.
- describeCron: recognize cron day-of-week 7 as an alternate notation for Sunday.
- Reset the builder time to :00 when switching to the hourly frequency (its time picker is hidden, so it no longer silently carries the daily minute).
- Tests: cron_list name/disabled output; route Feb-30 impossible-cron and corrupt-file 500 read-failure; describeCron dow=7.
- Run now: report sendPrompt rejections via the toast/error path instead of dropping the promise.
- Block chat interaction while the full-pane Scheduled Tasks view is open, so the covered composer can't receive keystrokes/Escape.
- Guard reload() with a request-sequence id so a slow load can't overwrite a newer list after a mutation.
- Re-enabling a task that had genuinely fired resumes from now instead of catching up work paused while it was disabled.
- Restrict "every N minutes" to divisors of 60 (a non-divisor */N fires more often than the label claims).
- Show a Repeats / Runs once label on each card so tool-created one-shots aren't mistaken for repeating schedules.
- Return generic 500 client messages (no internal file path); the detail is logged server-side.
- Tests: SDK scheduled-task methods (method/URL/id-encoding/headers/errors); route re-enable behavior both ways.
@wenshao wenshao dismissed qwen-code-ci-bot’s stale review July 6, 2026 01:00

All inline threads from this review are fixed and resolved across 18a04af + caadb8d (server-side logging, cron_list name/disabled + tests, id-generator dedup, GET/Feb-30 route tests, hourly-minute reset, dow=7). CI (Test ubuntu) is green on the head commit. Dismissing this stale review pinned to an older commit.

Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts Outdated
Comment thread packages/core/src/services/cronTasksFile.ts
Comment thread packages/core/src/tools/cron-list.ts Outdated
Comment thread packages/web-shell/client/components/dialogs/ScheduledTasksDialog.tsx Outdated
Comment thread packages/web-shell/client/App.tsx
Comment thread packages/web-shell/client/components/dialogs/ScheduledTasksDialog.tsx Outdated
- Route error logs interpolate the actual task id instead of the literal ":id".
- cron_list returnDisplay includes the task name (matching llmContent) so terminal /cron list shows UI-assigned names.
- Truncate the delete-confirm label so an unnamed task's long prompt doesn't blow up the confirm() dialog.
- Cap the create-form prompt textarea at MAX_PROMPT_LENGTH and drop the dead typeof-window guard.
- Test generateCronTaskId (format + near-uniqueness).

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downgraded from Request Changes to Comment: self-PR.

Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/web-shell/client/components/dialogs/scheduledTasksSchedule.ts Outdated
Comment thread packages/cli/src/serve/routes/scheduled-tasks.test.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: self-PR. Reviewed — no blockers. Suggestion-level recommendations are in the Suggestion summary comment below.

— qwen3.7-max via Qwen Code /review

- Re-enable now resumes any recurring task from now (stamp on every false→true), not only ones that had already fired — a task disabled before its first run no longer catch-up-fires the slot it was paused through.
- describeCron applies the same divisor-of-60 check as buildCron, so a hand-edited/persisted */45 falls back to the raw expression instead of a misleading "every 45 minutes".
- Strengthen the corrupt-file route test to assert the generic client message and no leaked file path.
- Tests: recurring-disabled-before-first-run and one-shot re-enable; describeCron non-divisor fallback.
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
Comment thread packages/core/src/index.ts
Comment thread packages/cli/src/serve/routes/scheduled-tasks.ts
wenshao added 2 commits July 6, 2026 10:58
Seed a pre-fields task (no name/enabled) directly to disk and assert the GET response normalizes it to name:null / enabled:true, guarding backward compatibility with existing scheduled_tasks.json files.
The daemon route accepts up to MAX_JOBS durable tasks on disk, but the scheduler previously capped durable loads against its combined job map (session-only + durable). A session holding session-only cron jobs could push the map to MAX_JOBS and make loadFileTasks silently skip durable tasks the route had already accepted — a create that returned 201 would then never fire.

Cap durable installs against a durable-only count instead, and share one MAX_JOBS constant between the scheduler and the daemon route, so a successful create is always loadable. Adds a scheduler test that 40 session-only jobs no longer crowd out 20 durable loads.

@BZ-D BZ-D left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I re-reviewed the two latest commits. The durable-load cap now uses a durable-only budget and shares MAX_JOBS with the route, which addresses the created-but-not-loadable edge case I flagged. The added legacy normalization test also covers the backward-compatible name/enabled defaults.

Verified locally on the latest head (62485aa829ce1836660c7472a397a780cfde99f5):

  • packages/cli: npx vitest run src/serve/routes/scheduled-tasks.test.ts (19 passed)
  • packages/core: npx vitest run src/services/cronScheduler.test.ts src/services/cronTasksFile.test.ts src/tools/cron-list.test.ts (143 passed)
  • packages/web-shell: schedule helper/sidebar tests (37 passed)
  • packages/webui: scheduled task actions test (6 passed)
  • packages/core: build + typecheck passed
  • packages/webui: typecheck passed

I did not find additional blocking issues.

@wenshao

wenshao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao wenshao added this pull request to the merge queue Jul 6, 2026
Merged via the queue into QwenLM:main with commit 9a63c03 Jul 6, 2026
41 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants