Commit 0d859c2
feat(ui): add scheduled task management (#354)
## Summary
- Add a Scheduled Tasks experience with direct creation, a consolidated
cadence editor, active/paused controls, edit flows, irreversible-delete
confirmation, and a disabled “Snooze — coming soon” affordance.
- Add an occurrence-focused Upcoming timeline with current/all-agent
scope, synchronized task-sidebar filtering, balanced previews for
frequent schedules, run-now flows, and exact skip/unskip actions.
- Use the generated TypeScript schedule SDK, label scheduled runs
consistently in the task sidebar, and keep scheduled task titles stable
without appending occurrence timestamps.
## UX details
- Create schedules immediately without a confirmation step; show
animated pending/success feedback with Edit and auto-dismiss.
- Group upcoming runs into Today, Tomorrow, and Later; show 10 by
default with at most 3 per schedule, then allow expansion to the 10
occurrences returned per schedule.
- Keep occurrence actions scoped to Skip/Unskip and Edit schedule; keep
Pause/Resume, Delete, Run now, and Skip next run in the Schedules view.
- Keep schedule names consistent with agent naming semantics:
auto-generated kebab-case names are displayed and copied to scheduled
task metadata.
## Test plan
- [x] `npm --prefix agentex-ui run typecheck`
- [x] `npm --prefix agentex-ui run lint`
- [x] `npm --prefix agentex-ui run test:run --
hooks/use-safe-search-params.test.tsx lib/agent-run-schedules.test.ts
lib/schedule-utils.test.ts`
- [x] Local smoke test with two registered agents, cross-agent schedule
scope, upcoming schedule details, and schedule actions
- [x] Pre-commit Agentex UI lint hooks
https://github.com/user-attachments/assets/decc0659-8c22-43db-b13d-55a26bc0a3e4
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR delivers a full Scheduled Tasks UI surface: inline schedule
creation with animated feedback, an Upcoming timeline grouped by
Today/Tomorrow/Later, a Schedules list with pause/resume toggle,
skip/unskip per-occurrence, run-now flows, edit and delete modals, and a
feature flag (`ENABLE_AGENT_RUN_SCHEDULES`) that gates the entire
experience.
- New `agentex-ui/components/scheduled-tasks/` module (~1 400 lines)
provides the page, composer, cadence picker, upcoming list, schedules
list, modals, and shared helpers; routing is handled entirely via URL
search params (`view`, `schedule_scope`, `agent_name`).
- `lib/schedule-utils.ts` owns cadence serialization, validation,
display, and the `scheduleToCadence` round-trip used by the edit form.
- `agentex/src/temporal/activities/scheduled_agent_run_activities.py`
removes `_format_fire_time` and the per-occurrence timestamp from
`task_metadata.display_name`, keeping task names stable across retries.
<details><summary><h3>Confidence Score: 5/5</h3></summary>
Safe to merge; the feature is flag-gated and all CRUD flows, cache
invalidation, and error paths look correct.
Additive, flag-gated change. TanStack Query cache keys are
well-structured, mutation error handling surfaces toasts via onError,
and search-param routing clears stale VIEW values on task selection. The
two findings only affect externally-created sub-minute or complex-cron
schedules that the UI itself never produces.
agentex-ui/lib/schedule-utils.ts — specifically scheduleToCadence
(sub-minute round-trip) and describeCadence (missing complex-cron
fallback).
</details>
<details><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| agentex-ui/lib/schedule-utils.ts | New utility module for cadence
serialization, validation, and display. scheduleToCadence silently
coerces sub-minute interval_seconds (e.g. 30 s → 1 min) and
describeCadence lacks the raw-expression fallback its inline comment
promises for complex external crons. |
| agentex-ui/hooks/use-agent-run-schedules.ts | New TanStack Query hooks
for schedule CRUD and actions. Cache key structure is consistent,
invalidation logic is correct, and error toasts are surfaced via
onError. Limit is 50 and is surfaced in the UI. |
| agentex-ui/components/scheduled-tasks/scheduled-tasks-page.tsx | Main
page orchestrating scope/agent/view state. Correctly wires
search-param-driven scope selection and shows ScheduleComposer only for
the current-agent scope. |
| agentex-ui/components/scheduled-tasks/all-schedules-list.tsx |
Schedule list with inline pause/resume toggle and overflow menu.
Functionally correct; idempotent API calls handle the case where
row-level and menu-level mutation pending states don't reflect each
other. |
| agentex-ui/components/scheduled-tasks/upcoming-schedule-list.tsx |
Upcoming runs timeline with Today/Tomorrow/Later grouping. Run-now/skip
sequential flow is handled correctly with .catch(() => undefined) to
suppress uncaught-rejection noise while onError toasts surface failures.
|
| agentex-ui/components/scheduled-tasks/schedule-modals.tsx | Delete
confirmation and edit modals. Name normalization/sanitization on blur,
cadence round-trip via scheduleToCadence, and inline delete from the
edit modal all look correct. |
| agentex/src/temporal/activities/scheduled_agent_run_activities.py |
Removes per-occurrence timestamp from display_name (now just
schedule.name), removes the unused _format_fire_time helper, and keeps
_extract_fire_time/regex for UI occurrence-time parsing. |
</details>
<details><summary><h3>Flowchart</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Page Load] --> B{agentRunSchedulesEnabled?}
B -->|false| C[Chat / Home only]
B -->|true| D{view=scheduled_tasks?}
D -->|no| E{taskID param?}
E -->|yes| F[ChatView]
E -->|no| G[HomeView]
D -->|yes| H[ScheduledTasksPage]
H --> I{scheduleScope?}
I -->|ALL| J[useAgentRunSchedulesForAgents all agents]
I -->|CURRENT| K{agent selected?}
K -->|no| L[Empty State: Select an agent]
K -->|yes| M[useAgentRunSchedules current agent]
J --> N[useAgentRunScheduleDetailsForItems one req per schedule]
M --> N
N --> O{scheduleView?}
O -->|upcoming| P[UpcomingScheduleList Today/Tomorrow/Later]
O -->|all| Q[AllSchedulesList sorted by next run]
M --> R[ScheduleComposer create new schedule]
```
</a>
<a href="#gh-dark-mode-only">
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Page Load] --> B{agentRunSchedulesEnabled?}
B -->|false| C[Chat / Home only]
B -->|true| D{view=scheduled_tasks?}
D -->|no| E{taskID param?}
E -->|yes| F[ChatView]
E -->|no| G[HomeView]
D -->|yes| H[ScheduledTasksPage]
H --> I{scheduleScope?}
I -->|ALL| J[useAgentRunSchedulesForAgents all agents]
I -->|CURRENT| K{agent selected?}
K -->|no| L[Empty State: Select an agent]
K -->|yes| M[useAgentRunSchedules current agent]
J --> N[useAgentRunScheduleDetailsForItems one req per schedule]
M --> N
N --> O{scheduleView?}
O -->|upcoming| P[UpcomingScheduleList Today/Tomorrow/Later]
O -->|all| Q[AllSchedulesList sorted by next run]
M --> R[ScheduleComposer create new schedule]
```
</a>
</details>
<sub>Reviews (8): Last reviewed commit: ["Merge branch 'main'
into
jerome/schedule..."](b2fde1c)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=43852411)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent e2c8a22 commit 0d859c2
29 files changed
Lines changed: 3229 additions & 62 deletions
File tree
- agentex-ui
- app
- components
- primary-content
- scheduled-tasks
- task-sidebar
- hooks
- lib
- agentex/src/temporal/activities
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
11 | 16 | | |
12 | 17 | | |
13 | 18 | | |
| |||
19 | 24 | | |
20 | 25 | | |
21 | 26 | | |
22 | | - | |
| 27 | + | |
23 | 28 | | |
24 | 29 | | |
25 | 30 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
20 | 26 | | |
21 | 27 | | |
22 | 28 | | |
| |||
84 | 90 | | |
85 | 91 | | |
86 | 92 | | |
| 93 | + | |
87 | 94 | | |
88 | 95 | | |
89 | 96 | | |
| |||
115 | 122 | | |
116 | 123 | | |
117 | 124 | | |
118 | | - | |
| 125 | + | |
119 | 126 | | |
| 127 | + | |
120 | 128 | | |
121 | 129 | | |
122 | 130 | | |
| |||
Lines changed: 37 additions & 26 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | | - | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| 20 | + | |
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
21 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| |||
56 | 61 | | |
57 | 62 | | |
58 | 63 | | |
59 | | - | |
| 64 | + | |
60 | 65 | | |
61 | 66 | | |
62 | 67 | | |
63 | 68 | | |
64 | | - | |
| 69 | + | |
65 | 70 | | |
66 | 71 | | |
67 | 72 | | |
68 | 73 | | |
69 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
70 | 77 | | |
71 | 78 | | |
72 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
73 | 82 | | |
74 | 83 | | |
75 | 84 | | |
| |||
81 | 90 | | |
82 | 91 | | |
83 | 92 | | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
104 | 115 | | |
105 | 116 | | |
106 | 117 | | |
| |||
0 commit comments