Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked | 1 | /** |
| 2 | * When routines run, and what a routine must have: pure, so it is tested | |
| 3 | * on its own (routines.ts runs them). | |
| 4 | */ | |
| 5 | import type { NewRoutine, RoutineSchedule } from "@g1t/contracts"; | |
| 6 | ||
| 7 | const EVERY = ["hour", "day", "weekday", "week"] as const; | |
| 8 | ||
| 9 | /** A schedule as given, checked; a message when it isn't one. */ | |
| 10 | export function checkSchedule(input: unknown): { ok: true; value: RoutineSchedule } | { ok: false; message: string } { | |
| 11 | if (!input || typeof input !== "object") return { ok: false, message: "Say when it runs." }; | |
| 12 | const s = input as Record<string, unknown>; | |
| 13 | const every = EVERY.find((e) => e === s.every); | |
| 14 | if (!every) return { ok: false, message: "A routine runs every hour, day, weekday or week." }; | |
| 15 | const int = (value: unknown, min: number, max: number) => (typeof value === "number" && Number.isInteger(value) && value >= min && value <= max ? value : null); | |
| 16 | const minute = int(s.minute ?? 0, 0, 59); | |
| 17 | const hour = int(s.hour ?? 9, 0, 23); | |
| 18 | const weekday = int(s.weekday ?? 1, 0, 6); | |
| 19 | if (minute === null || hour === null || weekday === null) return { ok: false, message: "The minute is 0 to 59, the hour 0 to 23 and the day 0 (Sunday) to 6." }; | |
| 20 | return { ok: true, value: { every, minute, hour, weekday } }; | |
| 21 | } | |
| 22 | ||
| 23 | /** When a routine next runs after `after`, in UTC. */ | |
| 24 | export function nextRun(schedule: RoutineSchedule, after: Date): Date { | |
| 25 | const next = new Date(after.getTime()); | |
| 26 | next.setUTCSeconds(0, 0); | |
| 27 | if (schedule.every === "hour") { | |
| 28 | next.setUTCMinutes(schedule.minute); | |
| 29 | if (next <= after) next.setUTCHours(next.getUTCHours() + 1); | |
| 30 | return next; | |
| 31 | } | |
| 32 | next.setUTCHours(schedule.hour, schedule.minute); | |
| 33 | if (next <= after) next.setUTCDate(next.getUTCDate() + 1); | |
| 34 | for (let i = 0; i < 8; i++) { | |
| 35 | const day = next.getUTCDay(); | |
| 36 | const fits = schedule.every === "day" || (schedule.every === "weekday" ? day >= 1 && day <= 5 : day === schedule.weekday); | |
| 37 | if (fits) return next; | |
| 38 | next.setUTCDate(next.getUTCDate() + 1); | |
| 39 | } | |
| 40 | return next; | |
| 41 | } | |
| 42 | ||
| 43 | /** A schedule in words: "Every weekday at 09:00 UTC". */ | |
| 44 | export function describeSchedule(schedule: RoutineSchedule): string { | |
| 45 | const pad = (n: number) => String(n).padStart(2, "0"); | |
| 46 | const at = `${pad(schedule.hour)}:${pad(schedule.minute)} UTC`; | |
| 47 | const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | |
| 48 | switch (schedule.every) { | |
| 49 | case "hour": | |
| 50 | return `Every hour at :${pad(schedule.minute)}`; | |
| 51 | case "day": | |
| 52 | return `Every day at ${at}`; | |
| 53 | case "weekday": | |
| 54 | return `Every weekday at ${at}`; | |
| 55 | case "week": | |
| 56 | return `Every ${days[schedule.weekday]} at ${at}`; | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | /** A routine as given, checked: a name, instructions and a schedule. */ | |
| 61 | export function checkRoutine(input: NewRoutine): { ok: true; value: NewRoutine & { schedule: RoutineSchedule } } | { ok: false; message: string } { | |
| 62 | const name = typeof input?.name === "string" ? input.name.trim().slice(0, 80) : ""; | |
| 63 | const instructions = typeof input?.instructions === "string" ? input.instructions.trim().slice(0, 8000) : ""; | |
| 64 | if (!name) return { ok: false, message: "Give the routine a name." }; | |
| 65 | if (instructions.length < 10) return { ok: false, message: "Say what the routine does, in a sentence or more." }; | |
| 66 | if (typeof input.channel_id !== "string" || !input.channel_id) return { ok: false, message: "Choose the channel it posts in." }; | |
| 67 | const schedule = checkSchedule(input.schedule); | |
| 68 | if (!schedule.ok) return schedule; | |
| 69 | return { ok: true, value: { ...input, name, instructions, schedule: schedule.value, enabled: input.enabled !== false } }; | |
| 70 | } | |
| 71 |