Repository Public
1import { z } from 'zod';23/** Terminal session lifecycle. WAITING_FOR_INPUT is only ever set from a Claude Code hook signal. */4export const SessionState = z.enum([5 'CREATED',6 'STARTING',7 'RUNNING',8 'DETACHED',9 'WAITING_FOR_INPUT',10 'STOPPING',11 'STOPPED',12 'FAILED',13 'RECOVERABLE',14]);15export type SessionState = z.infer<typeof SessionState>;1617/** Allowed transitions (from -> to[]). Enforced server-side; unit-tested. */18export const SESSION_TRANSITIONS: Record<SessionState, readonly SessionState[]> = {19 CREATED: ['STARTING', 'FAILED', 'STOPPED'],20 STARTING: ['RUNNING', 'DETACHED', 'FAILED', 'STOPPED'],21 RUNNING: ['DETACHED', 'WAITING_FOR_INPUT', 'STOPPING', 'STOPPED', 'FAILED', 'RECOVERABLE'],22 DETACHED: ['RUNNING', 'WAITING_FOR_INPUT', 'STOPPING', 'STOPPED', 'FAILED', 'RECOVERABLE'],23 WAITING_FOR_INPUT: ['RUNNING', 'DETACHED', 'STOPPING', 'STOPPED', 'FAILED', 'RECOVERABLE'],24 STOPPING: ['STOPPED', 'FAILED'],25 STOPPED: [],26 FAILED: [],27 // After a host reboot / lost tmux server: metadata survives, process does not.28 RECOVERABLE: ['STARTING', 'STOPPED'],29};30export function canTransition(from: SessionState, to: SessionState): boolean {31 return from === to || SESSION_TRANSITIONS[from].includes(to);32}33export const LIVE_SESSION_STATES: readonly SessionState[] = [34 'STARTING',35 'RUNNING',36 'DETACHED',37 'WAITING_FOR_INPUT',38 'STOPPING',39];4041export const TaskState = z.enum([42 'QUEUED',43 'RUNNING',44 'SUCCEEDED',45 'FAILED',46 'CANCELLING',47 'CANCELLED',48 'TIMED_OUT',49 'LOST', // host rebooted / runner vanished; never auto-retried50]);51export type TaskState = z.infer<typeof TaskState>;52export const TASK_TRANSITIONS: Record<TaskState, readonly TaskState[]> = {53 QUEUED: ['RUNNING', 'CANCELLED', 'FAILED'],54 RUNNING: ['SUCCEEDED', 'FAILED', 'CANCELLING', 'CANCELLED', 'TIMED_OUT', 'LOST'],55 CANCELLING: ['CANCELLED', 'FAILED', 'SUCCEEDED', 'LOST'],56 SUCCEEDED: [],57 FAILED: [],58 CANCELLED: [],59 TIMED_OUT: [],60 LOST: [],61};62export function canTaskTransition(from: TaskState, to: TaskState): boolean {63 return from === to || TASK_TRANSITIONS[from].includes(to);64}6566/** Health vocabulary used by every dashboard. Stale data must be UNKNOWN, never ONLINE. */67export const Health = z.enum(['ONLINE', 'OFFLINE', 'DEGRADED', 'UNKNOWN']);68export type Health = z.infer<typeof Health>;69