import * as z from "zod"; import { Id } from "./ids.js"; /** Continuations without a checkpoint, or failed runs in a row, before the autopilot pauses. */ export const AUTOPILOT_IDLE_LIMIT = 3; export const AUTOPILOT_FAIL_LIMIT = 3; export const AUTOPILOT_LIMITS = { goal: 500, focus: 200, checkpoint: 300, result: 2000 } as const; /** Checkpoints kept on the start line; older ones only count toward the step total. */ export const AUTOPILOT_CHECKPOINT_LOG = 50; export const AutopilotStatusSchema = z.enum(["active", "paused", "finished", "stopped"]); export type AutopilotStatus = z.infer; /** * A quiet timeline line for an autopilot: when it started (with its goal, end time, focus and * the approvals granted only for it), paused, stopped or finished. `ended` is set on the start * line once the autopilot is over, so its Stop control goes away. */ export const AutopilotBlockSchema = z.object({ kind: z.literal("autopilot"), event: z.enum([ "autopilot.started", "autopilot.paused", "autopilot.stopped", "autopilot.finished", ]), autopilotId: Id, goal: z.string(), focus: z.string().optional(), until: z.string().optional(), /** Tools or apps the bot may use without asking while this autopilot runs. */ allowed: z.array(z.string()).optional(), steps: z.number().int().nonnegative().optional(), /** The latest checkpoints, newest last, on the start line. */ checkpoints: z .array(z.object({ text: z.string(), at: z.string() })) .max(AUTOPILOT_CHECKPOINT_LOG) .optional(), durationMs: z.number().int().nonnegative().optional(), /** Why it paused, or what it achieved. */ text: z.string().optional(), ended: z.boolean().optional(), }); export type AutopilotBlock = z.infer; export const AutopilotSchema = z.object({ id: Id, botId: Id, threadId: Id, goal: z.string(), focus: z.string().nullable(), until: z.string().nullable(), status: AutopilotStatusSchema, pauseReason: z.string().nullable(), lastCheckpoint: z.string().nullable(), nextStep: z.string().nullable(), result: z.string().nullable(), stepCount: z.number().int(), startedAt: z.string(), endedAt: z.string().nullable(), }); export type Autopilot = z.infer; /** How long a finished or stopped autopilot stays in Today, so the night's work is there at breakfast. */ export const AUTOPILOT_RECENT_MS = 24 * 60 * 60 * 1000; /** An autopilot in Today: open ones, and those that ended within AUTOPILOT_RECENT_MS. */ export const AutopilotRecentSchema = AutopilotSchema.extend({ spaceId: Id, groupId: Id.nullable(), }); export type AutopilotRecent = z.infer; export const AutopilotStartInputSchema = z.object({ goal: z.string().trim().min(1).max(AUTOPILOT_LIMITS.goal), focus: z.string().trim().max(AUTOPILOT_LIMITS.focus).optional(), /** ISO time to stop at; without it the autopilot runs until the goal is done. */ until: z.string().datetime({ offset: true }).optional(), });