Skip to content

Commit e36d4cd

Browse files
fix(schedules): align UI with exact skip contract
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3052608 commit e36d4cd

8 files changed

Lines changed: 13 additions & 62 deletions

File tree

agentex-ui/components/scheduled-tasks/scheduled-tasks-page.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ function ScheduleRow({
10161016
const pause = useScheduleAction({ baseURL, agentId, action: 'pause' });
10171017
const resume = useScheduleAction({ baseURL, agentId, action: 'resume' });
10181018
const isPaused = isSchedulePaused(schedule);
1019+
const nextRun = getNextRun(schedule);
10191020

10201021
return (
10211022
<article className="border-border/70 relative flex items-center justify-between gap-4 border-b px-4 py-3 last:border-b-0">
@@ -1024,7 +1025,7 @@ function ScheduleRow({
10241025
<div className="text-muted-foreground truncate text-sm">
10251026
{describeCadence(schedule)}
10261027
{showAgentName ? ` · ${agentName}` : ''}
1027-
{` · ${schedule.num_tasks_created} runs`}
1028+
{` · ${schedule.num_actions_taken} runs`}
10281029
</div>
10291030
</div>
10301031
<div className="relative flex shrink-0 items-center justify-end gap-2">
@@ -1055,6 +1056,7 @@ function ScheduleRow({
10551056
agentId={agentId}
10561057
onEdit={() => setIsEditing(true)}
10571058
includeRunNow={true}
1059+
{...(nextRun ? { scheduledTime: nextRun.toISOString() } : {})}
10581060
/>
10591061
</div>
10601062
{isEditing && (
@@ -1112,6 +1114,7 @@ function ScheduleOverflowMenu({
11121114
action: 'delete',
11131115
});
11141116
const isPaused = isSchedulePaused(schedule);
1117+
const canSkip = scheduledTime != null && !isPaused;
11151118

11161119
return (
11171120
<div ref={menuRef} className="relative">
@@ -1152,17 +1155,14 @@ function ScheduleOverflowMenu({
11521155
) : (
11531156
<MenuButton
11541157
onClick={() => {
1158+
if (!scheduledTime) return;
11551159
setIsOpen(false);
1156-
skip.mutate(
1157-
scheduledTime
1158-
? { scheduleId: schedule.id, scheduledTime }
1159-
: schedule.id
1160-
);
1160+
skip.mutate({ scheduleId: schedule.id, scheduledTime });
11611161
}}
1162-
disabled={skip.isPending}
1162+
disabled={skip.isPending || !canSkip}
11631163
>
11641164
<Play className="size-4" />
1165-
Skip run
1165+
Skip next run
11661166
</MenuButton>
11671167
)}
11681168
<MenuButton disabled title="Backend support needed">

agentex-ui/hooks/use-agent-run-schedules.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ScheduleActionInput =
3434
| string
3535
| {
3636
scheduleId: string;
37-
scheduledTime?: string;
37+
scheduledTime: string;
3838
};
3939

4040
export type AgentRunScheduleListItem = {
@@ -177,6 +177,9 @@ export function useScheduleAction({
177177
case 'resume':
178178
return agentRunSchedulesAPI.resume(baseURL, agentId, scheduleId);
179179
case 'skip':
180+
if (!scheduledTime) {
181+
throw new Error('scheduledTime is required to skip a run');
182+
}
180183
return agentRunSchedulesAPI.skip(
181184
baseURL,
182185
agentId,

agentex-ui/lib/agent-run-schedules.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export type AgentRunSchedule = {
2626
skipped_action_times: string[];
2727
last_action_time: string | null;
2828
num_actions_taken: number;
29-
num_tasks_created: number;
3029
};
3130

3231
export type AgentRunScheduleListResponse = {
@@ -152,7 +151,7 @@ export const agentRunSchedulesAPI = {
152151
baseURL: string,
153152
agentId: string,
154153
scheduleId: string,
155-
scheduledTime?: string
154+
scheduledTime: string
156155
) =>
157156
request<AgentRunSchedule>(
158157
baseURL,

agentex-ui/lib/schedule-utils.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ describe('scheduleToCadence', () => {
9999
skipped_action_times: [],
100100
last_action_time: null,
101101
num_actions_taken: 0,
102-
num_tasks_created: 0,
103102
} satisfies AgentRunSchedule;
104103

105104
it('converts weekly cron to editable cadence config', () => {

agentex/src/api/schemas/agent_run_schedules.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,6 @@ class AgentRunScheduleResponse(BaseModel):
160160
num_actions_taken: int = Field(
161161
0, description="Number of times the schedule has fired."
162162
)
163-
num_tasks_created: int = Field(
164-
0,
165-
description=(
166-
"Number of non-deleted tasks created by this schedule, including manual runs."
167-
),
168-
)
169163

170164

171165
class AgentRunScheduleListResponse(BaseModel):

agentex/src/domain/repositories/task_repository.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,6 @@ async def list_with_join(
9696
relationships=relationships,
9797
)
9898

99-
async def count_by_agent_id_and_task_metadata(
100-
self, *, agent_id: str, task_metadata: dict
101-
) -> int:
102-
"""Count non-deleted tasks for an agent matching JSONB metadata."""
103-
query = (
104-
select(func.count(TaskORM.id))
105-
.join(TaskAgentORM, TaskORM.id == TaskAgentORM.task_id)
106-
.where(
107-
TaskAgentORM.agent_id == agent_id,
108-
TaskORM.task_metadata.contains(task_metadata),
109-
TaskORM.status != TaskStatus.DELETED,
110-
)
111-
)
112-
async with self.start_async_db_session(allow_writes=False) as session:
113-
result = await session.execute(query)
114-
return result.scalar_one()
115-
11699
async def list_cleanup_candidate_ids(
117100
self,
118101
*,

agentex/src/domain/services/agent_run_schedule_service.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from src.domain.repositories.agent_run_schedule_repository import (
2929
DAgentRunScheduleRepository,
3030
)
31-
from src.domain.repositories.task_repository import DTaskRepository
3231
from src.domain.services.authorization_service import DAuthorizationService
3332
from src.utils.ids import orm_id
3433
from src.utils.logging import make_logger
@@ -73,13 +72,11 @@ def __init__(
7372
authorization_service: DAuthorizationService,
7473
schedule_repository: DAgentRunScheduleRepository,
7574
agent_repository: DAgentRepository,
76-
task_repository: DTaskRepository,
7775
):
7876
self.temporal_adapter = temporal_adapter
7977
self.authorization_service = authorization_service
8078
self.schedule_repository = schedule_repository
8179
self.agent_repository = agent_repository
82-
self.task_repository = task_repository
8380

8481
async def create_schedule(
8582
self,
@@ -450,12 +447,6 @@ async def _to_response(
450447
skipped_action_times: list[datetime] = []
451448
last_action_time: datetime | None = None
452449
num_actions_taken = 0
453-
num_tasks_created = (
454-
await self.task_repository.count_by_agent_id_and_task_metadata(
455-
agent_id=entity.agent_id,
456-
task_metadata={"schedule_id": entity.id},
457-
)
458-
)
459450

460451
# Live Temporal fields are best-effort and opt-in. ``include_live=False``
461452
# (list path) skips the describe RPC entirely and serves state from the
@@ -505,7 +496,6 @@ async def _to_response(
505496
skipped_action_times=skipped_action_times,
506497
last_action_time=last_action_time,
507498
num_actions_taken=num_actions_taken,
508-
num_tasks_created=num_tasks_created,
509499
)
510500

511501
@staticmethod

agentex/tests/unit/services/test_agent_run_schedule_service.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,11 @@ def service():
4949
)
5050
schedule_repository = AsyncMock()
5151
agent_repository = AsyncMock()
52-
task_repository = AsyncMock()
53-
task_repository.count_by_agent_id_and_task_metadata.return_value = 0
5452
return AgentRunScheduleService(
5553
temporal_adapter=temporal_adapter,
5654
authorization_service=authorization_service,
5755
schedule_repository=schedule_repository,
5856
agent_repository=agent_repository,
59-
task_repository=task_repository,
6057
)
6158

6259

@@ -512,20 +509,6 @@ async def test_unskip_updates_temporal_schedule(self, service, agent):
512509
temporal_id, scheduled_time=scheduled_time
513510
)
514511

515-
async def test_response_counts_tasks_created(self, service, agent):
516-
row = _persisted(agent.id, _request())
517-
service.schedule_repository.get_by_agent_id_and_id_or_raise.return_value = row
518-
service.agent_repository.get.return_value = agent
519-
service.task_repository.count_by_agent_id_and_task_metadata.return_value = 7
520-
521-
response = await service.get_schedule(agent.id, row.id)
522-
523-
assert response.num_tasks_created == 7
524-
service.task_repository.count_by_agent_id_and_task_metadata.assert_awaited_once_with(
525-
agent_id=agent.id,
526-
task_metadata={"schedule_id": row.id},
527-
)
528-
529512

530513
@pytest.mark.unit
531514
class TestCadenceValidation:

0 commit comments

Comments
 (0)