diff --git a/README.md b/README.md index f336b3f36..b4632db9e 100644 --- a/README.md +++ b/README.md @@ -1792,6 +1792,7 @@ Current MVP capabilities: - Persist a due execution as `queued` when its reused thread or the global execution budget is busy, then launch it when capacity is available; queued occurrences survive Gateway restarts and fail after `scheduler.queue_timeout_seconds` - Freeze a task's definition while an occurrence is `queued`, `launching`, or `running`, so a durable occurrence cannot silently pick up a different prompt, thread, or schedule; transitioning a task to paused or deleting it cancels an existing waiting occurrence, while `launching`/`running` work must finish before those mutations are retried and an explicit manual trigger may still wait and run without resuming a paused schedule - Pause, resume, trigger, inspect history, and delete tasks +- Search task titles or prompts, combined with status/type filters and the current thread scope. - Execute scheduled work through the normal DeerFlow run lifecycle - Browse execution history in pages of 50; older pages pause automatic refresh, with an explicit return to the latest runs. Counts appear only after a successful read; loading and failed reads are not reported as zero runs. diff --git a/README_zh.md b/README_zh.md index f9490deda..cf9aa4100 100644 --- a/README_zh.md +++ b/README_zh.md @@ -849,6 +849,7 @@ DeerFlow 现在在 workspace 里内置了一个一等的定时任务(scheduled 当前 MVP 能力: - 在 `/workspace/scheduled-tasks` 管理任务 +- 支持按任务标题或提示词搜索,可与状态、类型筛选及当前会话范围组合使用 - 每个定时任务可以选择复用同一个 thread 及其历史对话,也可以选择每次运行新建一个 thread - 每个任务可以固定使用 `lead_agent`(默认)或当前用户已有的自定义 agent;未知名字会被拒绝 - 将现有任务复制到创建表单中作为可编辑草稿,不复制运行历史 diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index dad982307..a367edad0 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -95,6 +95,11 @@ do not use HTML `maxLength`, which counts UTF-16 code units instead. - **Path alias**: `@/*` maps to `src/*`. - **Components**: `ui/` and `ai-elements/` are generated from registries (Shadcn, MagicUI, React Bits, Vercel AI SDK) — don't manually edit these. +Scheduled-task list search filters the current authorized query result by title or +prompt, composing with status/type filters and thread scope. Selection must derive +from the filtered list so hidden tasks cannot remain actionable. Keep literal +matching in `core/scheduled-tasks/search.ts`; clearing search retains other filters. + Single-run schedule edits retain the mounted task's original `run_at` while its wall time and timezone match. The parent echoes edits through `initial`; retain a stable snapshot and reset the parent draft during render before remounting with a task key when switching tasks. Use the resolved timezone consistently for the snapshot and displayed wall time. Component and scheduled-task E2E tests cover DST folds and timestamp precision. ## Environment diff --git a/frontend/src/app/workspace/scheduled-tasks/page.tsx b/frontend/src/app/workspace/scheduled-tasks/page.tsx index 7c2a53aab..df8c10fc9 100644 --- a/frontend/src/app/workspace/scheduled-tasks/page.tsx +++ b/frontend/src/app/workspace/scheduled-tasks/page.tsx @@ -49,6 +49,7 @@ import { } from "@/core/scheduled-tasks/hooks"; import { RECIPES, type Recipe } from "@/core/scheduled-tasks/recipes"; import { useScheduledTaskRunHistory } from "@/core/scheduled-tasks/run-history"; +import { matchesScheduledTaskQuery } from "@/core/scheduled-tasks/search"; import type { ScheduledTask, ScheduledTaskRun, @@ -143,6 +144,7 @@ export default function ScheduledTasksPage() { const [typeFilter, setTypeFilter] = useState< "all" | "once" | "cron" | "interval" >("all"); + const [taskSearch, setTaskSearch] = useState(""); const [formError, setFormError] = useState(null); const [editing, setEditing] = useState(false); const [editTaskId, setEditTaskId] = useState(undefined); @@ -184,7 +186,9 @@ export default function ScheduledTasksPage() { const filteredData = (data ?? []).filter((task) => { const statusPass = statusFilter === "all" || task.status === statusFilter; const typePass = typeFilter === "all" || task.schedule_type === typeFilter; - return statusPass && typePass; + return ( + statusPass && typePass && matchesScheduledTaskQuery(task, taskSearch) + ); }); const selectedTask = filteredData.find((task) => task.id === selectedTaskId) ?? filteredData[0]; @@ -465,6 +469,20 @@ export default function ScheduledTasksPage() { {st.detail.loadFailed}: {queryError.message} ) : null} +
+ setTaskSearch(event.target.value)} + /> + {taskSearch && ( + + )} +