import { runtimeFetch } from './runtime-fetch'; export type ScheduledTaskStatus = 'idle' | 'running' | 'success' | 'error'; export type ScheduledTask = { id: string; name: string; enabled: boolean; /** Absolute path of the `.agents/loops/*.md` file driving this task, when * any. Present only for loop-sourced tasks; unknown to older clients. */ loopFile?: string; schedule: { kind: 'daily' | 'weekly' | 'once' | 'cron'; times?: string[]; time?: string; date?: string; weekdays?: number[]; cron?: string; timezone?: string; }; execution: { prompt: string; providerID: string; modelID: string; variant?: string; agent?: string; goalEnabled?: boolean; goalTokenBudget?: number; permissionAutoAccept?: boolean; }; state: { createdAt: number; updatedAt: number; lastRunAt?: number; lastStatus?: ScheduledTaskStatus; lastError?: string; lastDurationMs?: number; lastSessionId?: string; nextRunAt?: number; }; }; const parseErrorMessage = async (response: Response, fallback: string) => { try { const parsed = await response.json(); if (parsed && typeof parsed.error === 'string' && parsed.error.trim().length > 0) { return parsed.error; } } catch { return fallback; } return fallback; }; const ensureProjectID = (projectID: string): string => { const trimmed = typeof projectID === 'string' ? projectID.trim() : ''; if (!trimmed) { throw new Error('projectId is required'); } return trimmed; }; export const fetchScheduledTasks = async (projectID: string): Promise => { const safeProjectID = ensureProjectID(projectID); const response = await runtimeFetch(`/api/projects/${encodeURIComponent(safeProjectID)}/scheduled-tasks`); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to load scheduled tasks')); } const parsed = await response.json().catch(() => null); if (!parsed || !Array.isArray(parsed.tasks)) { return []; } return parsed.tasks as ScheduledTask[]; }; export const upsertScheduledTask = async (projectID: string, task: Partial): Promise => { const safeProjectID = ensureProjectID(projectID); const response = await runtimeFetch(`/api/projects/${encodeURIComponent(safeProjectID)}/scheduled-tasks`, { method: 'PUT', headers: { 'content-type': 'application/json', accept: 'application/json', }, body: JSON.stringify({ task }), }); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to save scheduled task')); } const parsed = await response.json().catch(() => null); if (!parsed || !Array.isArray(parsed.tasks)) { return []; } return parsed.tasks as ScheduledTask[]; }; export const deleteScheduledTask = async (projectID: string, taskID: string): Promise => { const safeProjectID = ensureProjectID(projectID); const safeTaskID = ensureProjectID(taskID); const response = await runtimeFetch(`/api/projects/${encodeURIComponent(safeProjectID)}/scheduled-tasks/${encodeURIComponent(safeTaskID)}`, { method: 'DELETE', headers: { accept: 'application/json', }, }); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to delete scheduled task')); } const parsed = await response.json().catch(() => null); if (!parsed || !Array.isArray(parsed.tasks)) { return []; } return parsed.tasks as ScheduledTask[]; }; const getLoopFileEndpoint = (projectID: string, taskID: string): string => { const safeProjectID = ensureProjectID(projectID); const safeTaskID = ensureProjectID(taskID); return `/api/projects/${encodeURIComponent(safeProjectID)}/scheduled-tasks/${encodeURIComponent(safeTaskID)}/loop-file`; }; export const setLoopScheduledTaskEnabled = async (projectID: string, taskID: string, enabled: boolean): Promise => { const response = await runtimeFetch(getLoopFileEndpoint(projectID, taskID), { method: 'PATCH', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ enabled }), }); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to update loop task')); } }; export const deleteScheduledTaskLoopFile = async (projectID: string, taskID: string): Promise => { const response = await runtimeFetch(getLoopFileEndpoint(projectID, taskID), { method: 'DELETE', headers: { accept: 'application/json' }, }); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to delete loop file')); } }; export const syncScheduledTaskLoops = async (projectID: string): Promise => { await fetchScheduledTasks(projectID); }; export const runScheduledTaskNow = async ( projectID: string, taskID: string, ): Promise<{ sessionId?: string; persistError?: string }> => { const safeProjectID = ensureProjectID(projectID); const safeTaskID = ensureProjectID(taskID); const response = await runtimeFetch(`/api/projects/${encodeURIComponent(safeProjectID)}/scheduled-tasks/${encodeURIComponent(safeTaskID)}/run`, { method: 'POST', headers: { accept: 'application/json', }, }); if (!response.ok) { throw new Error(await parseErrorMessage(response, 'Failed to run scheduled task')); } const parsed = await response.json().catch(() => null); return { sessionId: typeof parsed?.sessionId === 'string' && parsed.sessionId.length > 0 ? parsed.sessionId : undefined, persistError: typeof parsed?.persistError === 'string' && parsed.persistError.trim().length > 0 ? parsed.persistError.trim() : undefined, }; };