import { useEffect, useState } from "react"; import { del, get, patch, post } from "../api"; import { fmtRelative, useT } from "../i18n"; import { navigate } from "../router"; import { confirmDialog, toast } from "../dialogs"; import { Icon } from "./Icon"; import { Spinner } from "./Activity"; import { NewJobDialog } from "./NewJobDialog"; export interface JobInfo { id: string; path: string; enabled: boolean; last_run?: number | null; last_session_id?: string | null; last_status?: string | null; next_run?: number | null; error?: string; webhook?: boolean; spec?: { name: string; schedule: string; agent: string; model?: string | null; permission_mode: string; timeout_minutes: number; prompt: string; assistant_session?: string | null }; } const fmtDate = (ms?: number | null) => (ms ? new Date(ms).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) : "–"); /** Automations: agents started on a schedule with the instructions from a Markdown file. */ export function Jobs({ onMenu }: { onMenu: () => void }) { const t = useT(); const [jobs, setJobs] = useState(); const [path, setPath] = useState(""); const [busy, setBusy] = useState(); const [hook, setHook] = useState<{ id: string; url: string; secret: string }>(); const [templates, setTemplates] = useState(false); const reload = () => get("/jobs") .then(setJobs) .catch((e) => toast(e.message)); useEffect(() => { void reload(); const h = setInterval(reload, 15000); return () => clearInterval(h); }, []); const act = async (id: string, f: () => Promise) => { setBusy(id); try { await f(); await reload(); } catch (e: any) { toast(e.message); } finally { setBusy(undefined); } }; return ( <>

{t("nav.jobs")}

{t("jobs.intro")}
{templates && setTemplates(false)} />} {!jobs &&
} {jobs?.length === 0 &&
{t("jobs.none")}
} {jobs?.map((j) => (

{j.path}
{j.error ? (
{j.error}
) : (
{t("jobs.schedule")} {j.spec?.schedule} {t("jobs.next")} {j.enabled ? fmtDate(j.next_run) : "–"} {t("jobs.last")} {j.last_run ? `${fmtRelative(j.last_run)} · ` : "–"} {j.last_status && {t(`jobs.status.${j.last_status.startsWith("error") ? "error" : j.last_status}` as any)}} {j.last_session_id && ( )} {t("jobs.mode")} {j.spec?.agent} · {j.spec?.model || t("agents.sessionModel")} · {t(`permissionMode.${j.spec?.permission_mode ?? "auto"}` as any)} · max {j.spec?.timeout_minutes} min
)} {j.last_status?.startsWith("error") &&
{j.last_status}
} {j.spec && (
{t("jobs.prompt")}
{j.spec.prompt}
)}
{t("jobs.webhook")}: {j.webhook ? t("jobs.on") : t("jobs.off")} {j.webhook && ( )}
{hook?.id === j.id && (
{t("jobs.webhookShown")}
URL: {hook.url}
Secret: {hook.secret}
{t("jobs.webhookHelp")}
{`curl -X POST ${hook.url} \\\n  -H "X-CodeCraft-Secret: ${hook.secret}" \\\n  -H "Content-Type: application/json" -d '{"event":"test"}'`}
)}
))}

{t("jobs.register")}

{t("jobs.registerHint")}
setPath(e.target.value)} />
); }