package store import ( "context" "errors" "fmt" "sort" "time" "github.com/adhocore/gronx" "github.com/google/uuid" ) var ( ErrCronJobNotFound = errors.New("cron job not found") ErrCronJobNoFutureRun = errors.New("cron job has no future run") ErrCronCredentialOwnerMismatch = errors.New("cron credential context belongs to a different user") ErrCronCommandInvalid = errors.New("invalid cron command payload") ) // Cron payload kinds. A job either runs an LLM/agent turn ("agent_turn") or a // deterministic shell command in the gateway process ("command", no model cost). const ( CronPayloadKindAgentTurn = "agent_turn" CronPayloadKindCommand = "command" ) // CronJob represents a scheduled job. type CronJob struct { ID string `json:"id" db:"id"` TenantID uuid.UUID `json:"tenantId,omitempty" db:"tenant_id"` Name string `json:"name" db:"name"` AgentID string `json:"agentId,omitempty" db:"agent_id"` UserID string `json:"userId,omitempty" db:"user_id"` Enabled bool `json:"enabled" db:"enabled"` Schedule CronSchedule `json:"schedule" db:"-"` Payload CronPayload `json:"payload" db:"-"` State CronJobState `json:"state" db:"-"` CreatedAtMS int64 `json:"createdAtMs" db:"-"` UpdatedAtMS int64 `json:"updatedAtMs" db:"-"` DeleteAfterRun bool `json:"deleteAfterRun,omitempty" db:"delete_after_run"` Stateless bool `json:"stateless" db:"stateless"` Deliver bool `json:"deliver" db:"deliver"` DeliverChannel string `json:"deliverChannel" db:"deliver_channel"` DeliverTo string `json:"deliverTo" db:"deliver_to"` WakeHeartbeat bool `json:"wakeHeartbeat" db:"wake_heartbeat"` ProviderID *uuid.UUID `json:"providerId,omitempty" db:"provider_id"` Model *string `json:"model,omitempty" db:"model"` } // CronSchedule defines when a job should run. type CronSchedule struct { Kind string `json:"kind" db:"-"` // "at", "every", "cron" AtMS *int64 `json:"atMs,omitempty" db:"-"` EveryMS *int64 `json:"everyMs,omitempty" db:"-"` Expr string `json:"expr,omitempty" db:"-"` TZ string `json:"tz,omitempty" db:"-"` } // CronCommandSpec is a deterministic shell command run by a cron job whose // payload Kind is "command". It executes inside the gateway process WITHOUT an // LLM/agent turn (zero model tokens). It is a trusted automation surface gated // by cron.command_enabled — argv is run with the gateway process's privileges. type CronCommandSpec struct { // Argv is the executable plus arguments. No shell parsing is done; wrap as // ["sh", "-c", "