mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-09 16:08:31 +00:00
feat: add scheduled tasks MVP (#3898)
* feat: add scheduled tasks MVP
* fix: harden scheduled task execution semantics
* feat(scheduled-tasks): preset-driven schedule form with timezone and live preview
Replace the raw cron input with a preset Select (hourly/daily/weekly/monthly/custom)
plus structured inputs (time picker, weekday toggles, day-of-month), datetime-local
for one-time tasks, a timezone selector defaulting to the browser timezone, and a
live human-readable preview. Reuses one ScheduledTaskScheduleInput for create and
edit; backend contract unchanged; zero new deps (pure Intl + DST-safe offset helpers).
* feat(scheduled-tasks): full-page i18n + recipe templates + E2E locale pin
Localize the rest of the scheduled-tasks page (filters, detail pane, actions,
edit form, run list, enum values) via t.scheduledTasks.* in en/zh. Add four
built-in recipe templates (GitHub Trending, news digest, issue triage, weekly
report) exposed as a chip row that pre-fills title + prompt + schedule. Pin
Playwright locale to en-US so E2E selectors stay stable against i18n. No backend
change, no new deps.
* fix(scheduled-tasks): idempotent 0003 migration, update head constants, future-date once test
Merge with main surfaced three CI failures:
- 0003_scheduled_tasks create_table collided with legacy test seeds that
build from full metadata; guard with inspector.has_table so the revision
no-ops when the table already exists (0004/0005 are already idempotent via
_helpers.py).
- persistence bootstrap concurrency/regression tests pinned HEAD to main's
0002_runs_token_usage; bump to the new head 0005_scheduled_task_thread_nullable.
- once-task router test used a fixed past run_at and tripped the
must-be-in-the-future validation; use a future date.
* address review: ok-check, 502 for trigger failure, mock fields, migration filename, doc fences
- fetchThreadScheduledTasks now checks response.ok like the other fetchers.
- trigger endpoint returns 502 (not 409) when dispatch fails outright, so
clients can distinguish a real conflict from a server-side failure.
- E2E mock normalizes scheduled-task objects with context_mode/last_thread_id
and nullable thread_id, matching the backend contract the UI renders against.
- Rename 0002_scheduled_tasks.py -> 0003_scheduled_tasks.py to match its
revision id (file was renamed in spirit already; filename now follows).
- CONFIGURATION.md: close the Tool Groups yaml fence and drop the stray fence
after the Scheduler notes so the sections render correctly.
* fix(scheduled-tasks): harden lease, poller, config, and frontend UX after review
* fix(scheduled-tasks): harden run lifecycle, overlap skip, non_interactive gating, and DST conversion after review
- defer a once task's terminal status to the run-completion hook; the task
stays running until the real outcome, and a startup sweep cancels once
tasks orphaned by a crash (launch-time 'completed' could stick forever)
- record interrupted runs as a distinct 'interrupted' run status with a
readable message; an interrupted once task ends 'cancelled', not 'failed'
- enforce overlap_policy=skip for fresh_thread_per_run via an active-run
pre-check (same-thread ConflictError can never fire across fresh threads)
- protect terminal run statuses from the late launch-path 'running' write
- honor context.non_interactive only for internally-authenticated callers;
arbitrary clients can no longer strip ask_clarification
- fix DST-stale timezone offset in zonedLocalToUtcIso by re-deriving the
offset at the resolved instant (once tasks fired an hour late around
spring-forward and the create->edit round-trip diverged)
- drop dead ScheduledTaskRunRepository.update_by_run_id; share one Gateway
API error helper between channels and scheduled-tasks frontends
* fix(scheduled-tasks): close review round-3 gaps in guards, concurrency, and API ergonomics
- scrub internal-only context keys (non_interactive) from the assembled run
config for non-internal callers: gating body.context alone left the same
key smuggle-able through the free-form body.config copied verbatim by
build_run_config
- guard update_after_launch with protect_terminal so the launch bookkeeping
write cannot clobber a once task already finalized by a fast-failing run's
completion hook (parent-row sibling of the run-row guard)
- reject a manual trigger while the task has an active run (409) instead of
launching a duplicate concurrent run on fresh_thread_per_run
- re-arm a terminal once task to enabled when PATCH pushes run_at into the
future; previously the endpoint returned 200 with a next_run_at that could
never be claimed
- make max_concurrent_runs a real global cap: each poll claims only into the
remaining budget of active (queued/running) scheduled runs
- paginate GET /scheduled-tasks/{id}/runs (limit<=200, offset) and push the
thread filter of /threads/{id}/scheduled-tasks into SQL
- stamp context.user_id on scheduler-launched runs, matching IM channels, so
user-scoped guardrail providers see the owning user
---------
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
6060d95ee0
commit
4fc08b4f15
65 changed files with 9392 additions and 29 deletions
2136
docs/superpowers/plans/2026-07-01-scheduled-tasks-mvp.md
Normal file
2136
docs/superpowers/plans/2026-07-01-scheduled-tasks-mvp.md
Normal file
File diff suppressed because it is too large
Load diff
596
docs/superpowers/specs/2026-07-01-scheduled-tasks-mvp-design.md
Normal file
596
docs/superpowers/specs/2026-07-01-scheduled-tasks-mvp-design.md
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
# DeerFlow Scheduled Tasks MVP Design
|
||||
|
||||
**Date**: 2026-07-01
|
||||
**Status**: Approved for implementation
|
||||
**Scope**: First-class scheduled-task management for DeerFlow web workspace
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
DeerFlow main does not ship a real scheduled-task product surface today. The repository already has internal timers, worker pools, and run persistence, but users cannot create, inspect, pause, resume, trigger, or delete durable background tasks from the product.
|
||||
|
||||
This creates three concrete problems:
|
||||
|
||||
1. Users cannot automate recurring DeerFlow work such as daily summaries, periodic follow-ups, or recurring repo triage from the normal workspace.
|
||||
2. Existing cron-related PRs prove demand, but they either cut scope too broadly or start from the wrong interaction surface, which makes them hard to merge and harder to operate safely.
|
||||
3. Without a management surface, any future chat-created schedule would be operationally unsafe because users would have no first-class place to inspect or stop unattended jobs.
|
||||
|
||||
The first implementation must solve the operational and product-control problem before natural-language schedule creation.
|
||||
|
||||
## Solution Summary
|
||||
|
||||
Build a scheduled-task MVP with these hard boundaries:
|
||||
|
||||
1. **Durable backend resource**: add a `scheduled_task` resource with DB-backed persistence and DB-backed task-run history.
|
||||
2. **Shared execution path**: scheduled executions must launch through the existing DeerFlow run lifecycle, not a parallel agent path.
|
||||
3. **Workspace management page**: add a first-class page at `/workspace/scheduled-tasks` for list/detail/create/edit/pause/resume/trigger/delete.
|
||||
4. **Execution context mode is explicit**: every task chooses whether runs reuse an existing thread or create a fresh thread per occurrence.
|
||||
5. **Minimal schedule surface**: MVP supports `once` and `cron`, but not `interval`.
|
||||
6. **Opt-in runtime gate**: background scheduling remains disabled by default and requires explicit config enablement.
|
||||
|
||||
## Explicit Non-Goals
|
||||
|
||||
The MVP intentionally does **not** include:
|
||||
|
||||
1. Conversation-created schedules or a `schedule_task` tool.
|
||||
2. Text-only notification jobs.
|
||||
3. Channel, IM, or GitHub dispatch targets.
|
||||
4. Goal-backed scheduled work.
|
||||
5. Retry/dead-letter orchestration.
|
||||
6. Distributed leader election beyond a single enabled scheduler instance with DB lease claims.
|
||||
7. Intervals shorter than 60 seconds for user-created tasks.
|
||||
|
||||
These exclusions are not optional polish cuts. They are what keeps the first PR reviewable.
|
||||
|
||||
## Chosen Architecture
|
||||
|
||||
### Why this shape
|
||||
|
||||
This MVP combines the right parts of prior DeerFlow cron attempts without inheriting their problems:
|
||||
|
||||
1. Keep the **execution discipline** from the narrower backend MVPs: scheduler decides *when* to run, existing run services decide *how* to run.
|
||||
2. Keep the **durable task identity + task history** shape from broader implementations.
|
||||
3. Put **management UI before chat-created scheduling**, because users need a reliable control plane before background automation can be created from conversation.
|
||||
|
||||
### Resource Model
|
||||
|
||||
The MVP introduces two durable entities:
|
||||
|
||||
1. `scheduled_tasks`
|
||||
2. `scheduled_task_runs`
|
||||
|
||||
`scheduled_tasks` is the durable trigger definition. `scheduled_task_runs` is the execution ledger per occurrence.
|
||||
|
||||
This keeps schedule identity separate from DeerFlow `runs`, which already model one concrete execution attempt.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As a DeerFlow user, I want to create a one-time task that can run in a fresh thread, so periodic automation does not silently accumulate old context.
|
||||
2. As a DeerFlow user, I want to create a recurring cron task that can either reuse a thread or create a fresh thread per run, so I can choose between continuity and isolation explicitly.
|
||||
3. As a DeerFlow user, I want to see next run time, last run result, and last error at a glance, so I know whether automation is healthy.
|
||||
4. As a DeerFlow user, I want to pause and resume a task, so I can stop automation without deleting configuration.
|
||||
5. As a DeerFlow user, I want to trigger a task manually, so I can test or re-run it on demand.
|
||||
6. As a DeerFlow user, I want to inspect task run history, so I can audit what happened.
|
||||
7. As a DeerFlow user, I want tasks to be owner-scoped, so no other user can list or mutate my automations.
|
||||
8. As a maintainer, I want scheduler execution to reuse existing run-launch code, so scheduled runs do not become a second runtime stack.
|
||||
|
||||
## MVP Product Shape
|
||||
|
||||
### Supported Task Kinds
|
||||
|
||||
Only one execution kind is supported in MVP:
|
||||
|
||||
- `task_type = "agent"`
|
||||
- `dispatch_type = "thread"`
|
||||
|
||||
That means every scheduled task is defined as:
|
||||
|
||||
- context mode
|
||||
- optional target thread id
|
||||
- title
|
||||
- prompt override
|
||||
- schedule definition
|
||||
- runtime policy
|
||||
|
||||
When it fires, DeerFlow launches a normal run, but the execution thread is selected by `context_mode`.
|
||||
|
||||
These are fixed MVP semantics, not user-editable API fields and not persisted schema columns. The first PR must behave as if every task implicitly carries those values, without prematurely generalizing the contract.
|
||||
|
||||
### Supported Schedule Kinds
|
||||
|
||||
The user-facing MVP supports:
|
||||
|
||||
1. `once`
|
||||
2. `cron`
|
||||
|
||||
The MVP does **not** support `interval` because:
|
||||
|
||||
1. it adds another schedule parser path,
|
||||
2. it enlarges frontend validation,
|
||||
3. it increases edge-case surface around cadence drift and minimum interval enforcement,
|
||||
4. it is not required to prove the scheduler architecture.
|
||||
|
||||
If later added, `interval` can be layered on the same resource model.
|
||||
|
||||
### Execution Context Rule
|
||||
|
||||
The MVP supports two execution-context modes:
|
||||
|
||||
1. `fresh_thread_per_run` — default. Each scheduled occurrence creates a fresh DeerFlow thread.
|
||||
2. `reuse_thread` — optional. Each scheduled occurrence reuses an existing thread.
|
||||
|
||||
This is deliberate:
|
||||
|
||||
1. recurring digests, summaries, and automation jobs should not silently accumulate context forever;
|
||||
2. follow-up and reminder use cases still need an explicit reuse mode;
|
||||
3. the scheduler definition stays separate from the execution thread used by each occurrence.
|
||||
|
||||
## Backend Design
|
||||
|
||||
### Persistence Layout
|
||||
|
||||
Add harness-owned persistence packages:
|
||||
|
||||
- `backend/packages/harness/deerflow/persistence/scheduled_tasks/`
|
||||
- `backend/packages/harness/deerflow/persistence/scheduled_task_runs/`
|
||||
|
||||
Add ORM registration in:
|
||||
|
||||
- `backend/packages/harness/deerflow/persistence/models/__init__.py`
|
||||
|
||||
Add Alembic migration under:
|
||||
|
||||
- `backend/packages/harness/deerflow/persistence/migrations/versions/`
|
||||
|
||||
### `scheduled_tasks` schema
|
||||
|
||||
Fields:
|
||||
|
||||
- `id`: string primary key
|
||||
- `user_id`: owner user id, indexed
|
||||
- `thread_id`: nullable target thread id, indexed
|
||||
- `context_mode`: `fresh_thread_per_run | reuse_thread`
|
||||
- `assistant_id`: nullable assistant id snapshot
|
||||
- `title`: user-visible task title
|
||||
- `prompt`: explicit prompt to send when the task runs
|
||||
- `schedule_type`: `once | cron`
|
||||
- `schedule_spec`: JSON payload
|
||||
- `timezone`: IANA timezone
|
||||
- `status`: `enabled | paused | running | completed | failed | cancelled`
|
||||
- `overlap_policy`: fixed to `skip` in MVP, still persisted explicitly
|
||||
- `misfire_policy`: fixed to `run_once` in MVP, still persisted explicitly
|
||||
- `next_run_at`: UTC timestamp, indexed
|
||||
- `last_run_at`: nullable UTC timestamp
|
||||
- `last_run_id`: nullable DeerFlow run id
|
||||
- `last_thread_id`: nullable DeerFlow thread id from the latest execution
|
||||
- `last_error`: nullable text
|
||||
- `lease_owner`: nullable string
|
||||
- `lease_expires_at`: nullable UTC timestamp
|
||||
- `run_count`: integer
|
||||
- `max_runs`: nullable integer
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
Not included in MVP schema:
|
||||
|
||||
- `dispatch_type`
|
||||
- `dispatch_target`
|
||||
- `task_type`
|
||||
- `sandbox_profile`
|
||||
- `trust_policy`
|
||||
- `credential_scope`
|
||||
|
||||
Reason: those are real future needs, but introducing dormant columns now weakens the first implementation and invites half-implemented policy behavior. The first PR should store only what it truly enforces.
|
||||
|
||||
### `scheduled_task_runs` schema
|
||||
|
||||
Fields:
|
||||
|
||||
- `id`: string primary key
|
||||
- `task_id`: foreign-key-like indexed link to `scheduled_tasks.id`
|
||||
- `thread_id`: indexed for efficient thread-level lookup
|
||||
- `run_id`: nullable DeerFlow run id
|
||||
- `scheduled_for`: UTC timestamp
|
||||
- `trigger`: `scheduled | manual`
|
||||
- `status`: `queued | running | success | failed | skipped`
|
||||
- `error`: nullable text
|
||||
- `started_at`: nullable UTC timestamp
|
||||
- `finished_at`: nullable UTC timestamp
|
||||
- `created_at`
|
||||
|
||||
This run ledger is distinct from DeerFlow `runs` because:
|
||||
|
||||
1. a scheduled occurrence may fail before a DeerFlow run is created,
|
||||
2. an overlap skip still deserves audit visibility,
|
||||
3. manual and scheduled triggers need explicit occurrence records.
|
||||
|
||||
### Repository APIs
|
||||
|
||||
Create two repositories:
|
||||
|
||||
1. `ScheduledTaskRepository`
|
||||
2. `ScheduledTaskRunRepository`
|
||||
|
||||
Required repository behavior:
|
||||
|
||||
- create/get/list/update/delete tasks
|
||||
- owner-scoped search
|
||||
- claim due tasks atomically
|
||||
- update lease / clear lease
|
||||
- record status transitions
|
||||
- insert run history rows
|
||||
- list task run history
|
||||
|
||||
Atomic due-claim API must operate in one DB transaction:
|
||||
|
||||
- find due enabled tasks
|
||||
- skip tasks with live unexpired lease
|
||||
- set `lease_owner`, `lease_expires_at`, and temporary `status="running"`
|
||||
- return claimed rows
|
||||
|
||||
The scheduler service must not implement claim logic in Python-only in-memory filters.
|
||||
|
||||
## Scheduler Runtime Design
|
||||
|
||||
### Location
|
||||
|
||||
Runtime service lives under:
|
||||
|
||||
- `backend/app/scheduler/`
|
||||
|
||||
Reason: it needs app-layer dependencies and shared run-launch services. Harness persistence remains app-agnostic.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
The scheduler starts during FastAPI lifespan only when config enables it.
|
||||
|
||||
Suggested config section:
|
||||
|
||||
```yaml
|
||||
scheduler:
|
||||
enabled: false
|
||||
poll_interval_seconds: 5
|
||||
lease_seconds: 120
|
||||
max_concurrent_runs: 3
|
||||
min_interval_seconds: 60
|
||||
```
|
||||
|
||||
There is no separate `leader` toggle in MVP. The DB lease is the operational guard. If deployments later require multi-instance topology, a leader dimension can be added in hardening.
|
||||
|
||||
### Execution flow
|
||||
|
||||
For each poll cycle:
|
||||
|
||||
1. fetch up to `max_concurrent_runs` due tasks via repository claim,
|
||||
2. for each claimed task:
|
||||
- create `scheduled_task_runs` row with `status=queued`,
|
||||
- compute and persist the next schedule before or immediately after launch,
|
||||
- dispatch a normal DeerFlow run through shared run-launch helper,
|
||||
- persist `last_run_id`, `last_run_at`, `run_count`, task-run status, and error fields,
|
||||
- release lease.
|
||||
|
||||
### Shared run-launch helper
|
||||
|
||||
MVP must extract or reuse a non-router helper based on existing logic in:
|
||||
|
||||
- [backend/app/gateway/services.py](/Users/nowcoder/Desktop/auto-code-work/deer-flow/.worktrees/scheduled-tasks-mvp/backend/app/gateway/services.py)
|
||||
- [backend/app/gateway/routers/thread_runs.py](/Users/nowcoder/Desktop/auto-code-work/deer-flow/.worktrees/scheduled-tasks-mvp/backend/app/gateway/routers/thread_runs.py)
|
||||
|
||||
Required property:
|
||||
|
||||
- manual API trigger and background scheduler trigger both call the same launch helper.
|
||||
|
||||
The helper takes:
|
||||
|
||||
- target thread id
|
||||
- target assistant id
|
||||
- prompt content
|
||||
- authenticated owner context
|
||||
- origin metadata indicating `scheduled_task_id` and `scheduled_trigger`
|
||||
|
||||
### Overlap semantics
|
||||
|
||||
MVP uses one fixed overlap rule:
|
||||
|
||||
- if the target thread already has a pending/running run, record the occurrence as `skipped`, update `next_run_at`, and do not launch another run.
|
||||
|
||||
This is intentionally narrower than exposing multiple overlap policies in the first PR.
|
||||
|
||||
### Misfire semantics
|
||||
|
||||
MVP uses one fixed misfire rule:
|
||||
|
||||
- `run_once`
|
||||
|
||||
If the scheduler was down and multiple occurrences were missed, only the latest eligible missed occurrence runs when the scheduler comes back.
|
||||
|
||||
Reason:
|
||||
|
||||
1. avoids backlog explosion,
|
||||
2. avoids unreviewed catch-up storms,
|
||||
3. keeps first implementation deterministic.
|
||||
|
||||
### One-time task completion
|
||||
|
||||
For `once` tasks:
|
||||
|
||||
- successful dispatch marks task `completed`
|
||||
- dispatch failure marks task `failed`
|
||||
- task remains visible and queryable from UI/history after completion or failure
|
||||
|
||||
MVP uses soft retention, not destructive deletion.
|
||||
|
||||
### Cron semantics
|
||||
|
||||
Cron rules:
|
||||
|
||||
1. accept exactly 5 fields
|
||||
2. reject 6-field cron with seconds
|
||||
3. store explicit IANA timezone
|
||||
4. compute `next_run_at` in UTC
|
||||
5. normalize weekday semantics consistently and test them explicitly
|
||||
|
||||
The implementation must not silently depend on an ambiguous day-of-week interpretation.
|
||||
|
||||
## API Design
|
||||
|
||||
Add REST routes under `/api/scheduled-tasks`.
|
||||
|
||||
### Routes
|
||||
|
||||
- `GET /api/scheduled-tasks`
|
||||
- `POST /api/scheduled-tasks`
|
||||
- `GET /api/scheduled-tasks/{task_id}`
|
||||
- `PATCH /api/scheduled-tasks/{task_id}`
|
||||
- `POST /api/scheduled-tasks/{task_id}/pause`
|
||||
- `POST /api/scheduled-tasks/{task_id}/resume`
|
||||
- `POST /api/scheduled-tasks/{task_id}/trigger`
|
||||
- `DELETE /api/scheduled-tasks/{task_id}`
|
||||
- `GET /api/scheduled-tasks/{task_id}/runs`
|
||||
- `GET /api/threads/{thread_id}/scheduled-tasks`
|
||||
|
||||
There is intentionally **no** dispatch-target discovery endpoint in MVP because the only target is an owned thread.
|
||||
|
||||
### Request validation
|
||||
|
||||
Create:
|
||||
|
||||
- title required
|
||||
- prompt required
|
||||
- thread id required and must be owner-accessible
|
||||
- `schedule_type` required
|
||||
- `once` requires run timestamp
|
||||
- `cron` requires valid 5-field cron
|
||||
- timezone required
|
||||
|
||||
Update:
|
||||
|
||||
- allow title/prompt/schedule/timezone changes
|
||||
- disallow owner/thread reassignment across users
|
||||
- disallow mutation while task is in temporary `running` state if that would invalidate schedule semantics
|
||||
|
||||
### Authorization
|
||||
|
||||
Owner checks are mandatory for:
|
||||
|
||||
- list
|
||||
- detail
|
||||
- run history
|
||||
- patch
|
||||
- pause
|
||||
- resume
|
||||
- trigger
|
||||
- delete
|
||||
- thread-scoped list
|
||||
|
||||
This should reuse existing auth patterns from thread/runs routers rather than inventing a new access scheme.
|
||||
|
||||
## Frontend Design
|
||||
|
||||
### Navigation
|
||||
|
||||
Add new workspace nav item:
|
||||
|
||||
- `/workspace/scheduled-tasks`
|
||||
|
||||
This belongs beside existing high-level workspace surfaces in `WorkspaceNavChatList`, not hidden under settings.
|
||||
|
||||
### Main page
|
||||
|
||||
Add page:
|
||||
|
||||
- `frontend/src/app/workspace/scheduled-tasks/page.tsx`
|
||||
|
||||
The page includes:
|
||||
|
||||
1. list table/cards
|
||||
2. filter bar
|
||||
3. create-task button
|
||||
4. detail drawer or side panel
|
||||
|
||||
### List columns
|
||||
|
||||
- title
|
||||
- thread title
|
||||
- schedule summary
|
||||
- status
|
||||
- next run
|
||||
- last run
|
||||
- last result
|
||||
- actions
|
||||
|
||||
### Filters
|
||||
|
||||
MVP filters:
|
||||
|
||||
- status
|
||||
- schedule type
|
||||
- thread
|
||||
|
||||
No owner filter is needed in MVP because tasks are already owner-scoped.
|
||||
|
||||
### Create/edit form
|
||||
|
||||
Fields:
|
||||
|
||||
- title
|
||||
- thread selector
|
||||
- prompt textarea
|
||||
- schedule type: `once | cron`
|
||||
- once datetime picker
|
||||
- cron input
|
||||
- timezone selector
|
||||
|
||||
Validation:
|
||||
|
||||
- prompt non-empty
|
||||
- title non-empty
|
||||
- once datetime must be in the future
|
||||
- cron must be valid before submit
|
||||
|
||||
### Detail view
|
||||
|
||||
Displays:
|
||||
|
||||
- full prompt
|
||||
- thread link
|
||||
- raw schedule config
|
||||
- last error
|
||||
- run history list
|
||||
- actions: pause/resume/trigger/delete/edit
|
||||
|
||||
### Thread-level entry point
|
||||
|
||||
Thread chat pages gain a visible entry point to view schedules for the current thread.
|
||||
|
||||
MVP behavior:
|
||||
|
||||
- small button/link in thread page header opens filtered scheduled-task page for current thread
|
||||
|
||||
It does not need a full embedded task manager in-thread. Reusing the main page keeps the first PR smaller.
|
||||
|
||||
## State and Data Fetching
|
||||
|
||||
Frontend adds a small `scheduled-tasks` API layer under:
|
||||
|
||||
- `frontend/src/core/scheduled-tasks/`
|
||||
|
||||
Recommended pieces:
|
||||
|
||||
- typed request/response models
|
||||
- list/detail/run-history fetchers
|
||||
- mutations for create/update/pause/resume/trigger/delete
|
||||
- React Query hooks
|
||||
|
||||
This should follow the same shape the repo already uses for threads and feedback, not ad-hoc local fetch calls sprinkled through components.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Backend
|
||||
|
||||
Explicit failures that must surface cleanly:
|
||||
|
||||
1. missing or deleted thread
|
||||
2. unauthorized owner access
|
||||
3. invalid cron
|
||||
4. invalid timezone
|
||||
5. task already paused/resumed
|
||||
6. trigger rejected due to active in-flight thread run
|
||||
7. scheduler launch failure before run creation
|
||||
|
||||
Failure must never cause infinite immediate retry loops.
|
||||
|
||||
### Frontend
|
||||
|
||||
Users should see:
|
||||
|
||||
1. inline form validation errors
|
||||
2. mutation toasts for pause/resume/trigger/delete
|
||||
3. visible failed state in task row
|
||||
4. visible `last_error` in details
|
||||
|
||||
The UI must not show a healthy-looking task row when the last scheduler attempt failed.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Backend unit tests
|
||||
|
||||
1. valid and invalid cron expressions
|
||||
2. valid and invalid timezone handling
|
||||
3. weekday normalization semantics
|
||||
4. next-run computation across timezone boundaries and DST-sensitive cases
|
||||
5. one-time schedule status transitions
|
||||
6. due-task claim logic and lease expiry
|
||||
7. overlap skip behavior
|
||||
8. misfire `run_once` behavior
|
||||
|
||||
### Backend integration tests
|
||||
|
||||
1. CRUD API with owner isolation
|
||||
2. thread-scoped task list route
|
||||
3. pause/resume/trigger/delete flows
|
||||
4. manual trigger creates a normal DeerFlow run through shared launch helper
|
||||
5. scheduler loop claims each due task once
|
||||
6. dispatch failure writes task and task-run errors correctly
|
||||
7. deleted thread does not hot-loop retries
|
||||
|
||||
### Frontend unit tests
|
||||
|
||||
1. scheduled-task nav item renders and routes
|
||||
2. list renders status/next run/last result
|
||||
3. create dialog validates form state
|
||||
4. action buttons settle correctly after API response
|
||||
5. detail drawer renders history and last error
|
||||
|
||||
### Frontend E2E
|
||||
|
||||
Playwright with mocked APIs:
|
||||
|
||||
1. list page loads
|
||||
2. create task from UI
|
||||
3. pause/resume/trigger/delete flows
|
||||
4. thread header link navigates to filtered scheduled-task view
|
||||
|
||||
### Real-path validation
|
||||
|
||||
Required before claiming feature complete:
|
||||
|
||||
1. start backend and frontend
|
||||
2. create a one-time task due soon from the real UI
|
||||
3. observe row move through live status updates
|
||||
4. confirm linked DeerFlow run exists
|
||||
5. confirm completed/failure state is visible in the management page
|
||||
|
||||
## Documentation Updates Required
|
||||
|
||||
If code lands, update:
|
||||
|
||||
1. `README.md` with feature overview and enablement note
|
||||
2. `AGENTS.md` and `backend/AGENTS.md` with scheduler/runtime ownership and commands if architecture changes
|
||||
3. config docs for new `scheduler` section
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
1. Scheduled runs reuse the existing run lifecycle.
|
||||
2. Harness persistence does not import `app.*`.
|
||||
3. Due-task claim logic is atomic.
|
||||
4. No hot loop after dispatch failure.
|
||||
5. Day-of-week semantics are explicit and tested.
|
||||
6. Owner checks cover list/detail/history/mutate/trigger/delete.
|
||||
7. UI shows failing state honestly.
|
||||
8. Background scheduler remains opt-in.
|
||||
9. Thread-level entry point does not introduce duplicate management UI logic.
|
||||
10. Chat-created scheduling remains absent from MVP.
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Backend persistence and repository layer
|
||||
2. Schedule parser / next-run computation
|
||||
3. Shared run-launch helper
|
||||
4. Scheduler service and API
|
||||
5. Frontend API layer and page
|
||||
6. Thread header entry point
|
||||
7. E2E and real-path validation
|
||||
|
||||
This order is mandatory because the frontend cannot be implemented against an unstable backend contract.
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Scheduled-tasks page: full i18n + recipe templates — design
|
||||
|
||||
Follow-up to the preset-driven schedule form (commit `1ca27a73`). Scope kept
|
||||
front-end only and backend-contract-free so it stays reviewable inside PR #3898.
|
||||
|
||||
## Background
|
||||
|
||||
The preset form i18n'd the schedule section but left the rest of the
|
||||
scheduled-tasks page in hard-coded English (filters, detail pane, actions, edit
|
||||
form, run list). A `zh-CN` user saw a half-English page right next to the
|
||||
i18n'd schedule input. Separately, the page still required users to hand-write
|
||||
prompts for the headline use cases (GitHub Trending, news digest, issue triage,
|
||||
weekly report).
|
||||
|
||||
## Goals
|
||||
|
||||
1. Every visible string on `/workspace/scheduled-tasks` goes through
|
||||
`t.scheduledTasks.*` (en + zh).
|
||||
2. One-click "quick create" via four built-in recipe templates that pre-fill
|
||||
title + prompt + schedule.
|
||||
3. No backend change. No new dependency.
|
||||
|
||||
## Design
|
||||
|
||||
### Full i18n
|
||||
|
||||
Added to the `scheduledTasks` i18n section (types + en-US + zh-CN):
|
||||
|
||||
- `create`, `context`, `filters`, `detail`, `actions`, `edit` — UI labels.
|
||||
- `status`, `runTrigger`, `runStatus` — enum maps so list/detail/run rows render
|
||||
localized values instead of raw `enabled` / `manual` / `success`.
|
||||
- `recipes` — recipe labels (below).
|
||||
|
||||
The page maps raw enum values through small lookup helpers
|
||||
(`statusLabel`, `scheduleTypeLabel`, `contextModeLabel`, `runTriggerLabel`,
|
||||
`runStatusLabel`) that fall back to the raw value if unknown. E2E selectors
|
||||
switched from exact English strings to case-insensitive regex so they survive
|
||||
the i18n capitalization change (`cron · enabled` → `Cron · Enabled`).
|
||||
|
||||
### Recipe templates
|
||||
|
||||
New `frontend/src/core/scheduled-tasks/recipes.ts` exports `RECIPES: Recipe[]`
|
||||
with `{ id, icon, titleKey, prompt, schedule }`. Four starters:
|
||||
|
||||
| id | icon | schedule | prompt gist |
|
||||
|---|---|---|---|
|
||||
| `trending` | 🔥 | daily 09:00 | web_search GitHub Trending, summarize top 10 |
|
||||
| `news` | 📰 | daily 09:00 | web_search top tech news, 5-item digest |
|
||||
| `issues` | 🏷️ | daily 09:00 | triage open issues in `{{repo}}` (user fills placeholder) |
|
||||
| `weekly` | 📅 | weekly Mon 09:00 | weekly report |
|
||||
|
||||
`schedule.timezone` is intentionally `""` so the `ScheduledTaskScheduleInput`
|
||||
falls back to the browser timezone when applied. Recipe titles/descriptions live
|
||||
in i18n (`recipes.{id}.{title,desc}`); the file only stores the prompt + schedule.
|
||||
|
||||
### Wiring into the create form
|
||||
|
||||
- A `createNonce` counter state keys the create-form `ScheduledTaskScheduleInput`
|
||||
(`key={createNonce}`). Bumping the nonce forces the component to remount so the
|
||||
recipe's schedule is re-initialized into its `useState`, not just emitted.
|
||||
- `applyRecipe(recipe)` sets title + prompt + createSchedule + contextMode +
|
||||
bumps the nonce.
|
||||
- A chip row (`data-testid="schedule-recipes"`) renders above the form fields.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm check` 0 errors; `pnpm test` all green (403 unit tests).
|
||||
- Real browser (chrome-devtools, zh-CN): 4 recipe chips render, clicking
|
||||
「每周周报」pre-fills title + prompt + preset (weekly) + preview
|
||||
`每周 周一 09:00 (Asia/Shanghai)`.
|
||||
- Playwright `scheduled-tasks` suite re-verified with regex selectors.
|
||||
|
||||
## Non-goals (next session)
|
||||
|
||||
- Backend dispatch targets / `task_type` (RFC phase 2) — kept out because it
|
||||
changes schema/API/executor and would muddy this PR's review.
|
||||
- Backend-stored / user-authored recipes — front-end built-ins are enough for
|
||||
the first cut (YAGNI).
|
||||
Loading…
Add table
Add a link
Reference in a new issue