/* Schedules — recurring agent spawns. Every interval the worker starts a fresh, * stateless agent named - with the stored prompt. The canonical * pattern: keep state in a file in the repo and have the prompt continue from it. */ "use client"; import { useMemo, useState } from "react"; import { useDashboard } from "@/components/store"; import { Badge, Button, Card, Input, Select, Textarea, Toggle } from "@/components/ui"; import { fmtFull } from "@/lib/format"; const ROLE_OPTS = [ { value: "", label: "Role — none" }, { value: "junior", label: "junior" }, { value: "senior", label: "senior" }, { value: "deploy", label: "deploy" }, ]; const INTERVAL_OPTS = [ { value: "900", label: "every 15 minutes" }, { value: "1800", label: "every 30 minutes" }, { value: "3600", label: "every hour" }, { value: "21600", label: "every 6 hours" }, { value: "86400", label: "every day" }, { value: "604800", label: "every week" }, ]; const TASK_PLACEHOLDER = "Read @notes.md and continue from where it left off. Before finishing, overwrite " + "@notes.md with the current state so the next run can pick up from there."; function intervalLabel(seconds: number): string { const opt = INTERVAL_OPTS.find((o) => Number(o.value) === seconds); if (opt) return opt.label; if (seconds % 3600 === 0) return `every ${seconds / 3600}h`; if (seconds % 60 === 0) return `every ${seconds / 60}m`; return `every ${seconds}s`; } const emptyForm = { name_prefix: "", task: "", interval: "3600", role: "", model_id: "" }; export function SchedulesSection() { const s = useDashboard(); const [form, setForm] = useState(emptyForm); const projectOpts = useMemo( () => s.projects.map((p) => ({ value: p.id, label: p.id })), [s.projects], ); /* Same choice the spawn form offers: Claude subscription by default, plus every * enabled backend from the Claude page's Models tab. Every fired run spawns on it. */ const modelOpts = useMemo( () => [ { value: "", label: "Claude (subscription)" }, ...s.claudeModels .filter((m) => m.enabled) .map((m) => ({ value: String(m.id), label: `${m.name} (${m.model})` })), ], [s.claudeModels], ); const modelName = useMemo(() => { const byId = new Map(s.claudeModels.map((m) => [m.id, m.name])); return (id: number | null | undefined) => (id == null ? null : byId.get(id) ?? `#${id}`); }, [s.claudeModels]); const create = async () => { const ok = await s.createSchedule(s.selectedProjectId, { name_prefix: form.name_prefix, task: form.task, interval_seconds: Number(form.interval), role: form.role, model_id: form.model_id, }); if (ok) setForm(emptyForm); }; return ( <>
Schedules
Spawn a fresh agent on an interval. Each run is stateless — keep continuity in a file the prompt reads and overwrites.
{s.projects.length === 0 ? (
Register a repository first.
) : ( <>
setForm({ ...form, name_prefix: v })} placeholder="nightly" /> setForm({ ...form, role: v })} options={ROLE_OPTS} />