+ {/* Header */}
+
+
+ {jobs.filter((j) => j.enabled).length} of {jobs.length} jobs enabled
+
+
+
+
+ {/* Error */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Empty state */}
+ {jobs.length === 0 && !loading && (
+
+
+
No scheduled tasks
+
+ Use the cron tool in Chat to create one.
+
+
+ )}
+
+ {/* Job list */}
+ {jobs.length > 0 && (
+
+ {jobs.map((job) => {
+ const isToggling = togglingJobs.has(job.id)
+ const isRemoving = removingJobs.has(job.id)
+
+ return (
+
+ {/* Info */}
+
+
+ {job.name}
+
+
+
+ {job.schedule}
+ {job.nextRunAt && job.enabled && (
+ next: {formatRelativeTime(job.nextRunAt)}
+ )}
+ {job.lastRunAt && (
+ last: {formatRelativeTime(job.lastRunAt)}
+ )}
+
+ {job.lastError && (
+
{job.lastError}
+ )}
+
+
+ {/* Actions */}
+
+
+ {isToggling && (
+
+ )}
+ handleToggle(job.id)}
+ disabled={isToggling}
+ />
+
+
+ )
+ })}
+
+ )}
+
+ )
+}
+
+export default CronJobList
diff --git a/apps/desktop/src/hooks/use-cron-jobs.ts b/apps/desktop/src/hooks/use-cron-jobs.ts
new file mode 100644
index 00000000..e01c81e3
--- /dev/null
+++ b/apps/desktop/src/hooks/use-cron-jobs.ts
@@ -0,0 +1,107 @@
+import { useState, useEffect, useCallback } from 'react'
+
+export interface CronJobInfo {
+ id: string
+ name: string
+ description?: string
+ enabled: boolean
+ schedule: string
+ sessionTarget: string
+ nextRunAt: string | null
+ lastStatus: 'ok' | 'error' | 'skipped' | null
+ lastRunAt: string | null
+ lastDurationMs: number | null
+ lastError: string | null
+}
+
+export interface UseCronJobsReturn {
+ jobs: CronJobInfo[]
+ loading: boolean
+ error: string | null
+ toggleJob: (jobId: string) => Promise