Files
adolf/apps/macos/Sources/OpenClaw/CronModels.swift
alvis bedb527145
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Vendor OpenClaw source as Adolf fork baseline
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
2026-07-05 09:36:54 +00:00

430 lines
13 KiB
Swift

import Foundation
enum CronSessionTarget: String, CaseIterable, Identifiable, Codable {
case main
case isolated
case current
var id: String {
self.rawValue
}
}
enum CronCustomSessionTarget: Codable, Equatable {
case predefined(CronSessionTarget)
case session(id: String)
var rawValue: String {
switch self {
case let .predefined(target):
target.rawValue
case let .session(id):
"session:\(id)"
}
}
static func from(_ value: String) -> CronCustomSessionTarget {
if let predefined = CronSessionTarget(rawValue: value) {
return .predefined(predefined)
}
if value.hasPrefix("session:") {
let sessionId = String(value.dropFirst(8))
return .session(id: sessionId)
}
// Fallback to isolated for unknown values
return .predefined(.isolated)
}
}
enum CronWakeMode: String, CaseIterable, Identifiable, Codable {
case now
case nextHeartbeat = "next-heartbeat"
var id: String {
self.rawValue
}
}
enum CronDeliveryMode: String, CaseIterable, Identifiable, Codable {
case none
case announce
case webhook
var id: String {
self.rawValue
}
}
struct CronDelivery: Codable, Equatable {
var mode: CronDeliveryMode
var channel: String?
var to: String?
var bestEffort: Bool?
}
enum CronSchedule: Codable, Equatable {
case at(at: String)
case every(everyMs: Int, anchorMs: Int?)
case cron(expr: String, tz: String?)
case onExit(command: String, cwd: String?)
enum CodingKeys: String, CodingKey { case kind, at, atMs, everyMs, anchorMs, expr, tz, command, cwd }
var kind: String {
switch self {
case .at: "at"
case .every: "every"
case .cron: "cron"
case .onExit: "on-exit"
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(String.self, forKey: .kind)
switch kind {
case "at":
if let at = try container.decodeIfPresent(String.self, forKey: .at),
!at.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
self = .at(at: at)
return
}
if let atMs = try container.decodeIfPresent(Int.self, forKey: .atMs) {
let date = Date(timeIntervalSince1970: TimeInterval(atMs) / 1000)
self = .at(at: Self.formatIsoDate(date))
return
}
throw DecodingError.dataCorruptedError(
forKey: .at,
in: container,
debugDescription: "Missing schedule.at")
case "every":
self = try .every(
everyMs: container.decode(Int.self, forKey: .everyMs),
anchorMs: container.decodeIfPresent(Int.self, forKey: .anchorMs))
case "cron":
self = try .cron(
expr: container.decode(String.self, forKey: .expr),
tz: container.decodeIfPresent(String.self, forKey: .tz))
case "on-exit":
self = try .onExit(
command: container.decode(String.self, forKey: .command),
cwd: container.decodeIfPresent(String.self, forKey: .cwd))
default:
throw DecodingError.dataCorruptedError(
forKey: .kind,
in: container,
debugDescription: "Unknown schedule kind: \(kind)")
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.kind, forKey: .kind)
switch self {
case let .at(at):
try container.encode(at, forKey: .at)
case let .every(everyMs, anchorMs):
try container.encode(everyMs, forKey: .everyMs)
try container.encodeIfPresent(anchorMs, forKey: .anchorMs)
case let .cron(expr, tz):
try container.encode(expr, forKey: .expr)
try container.encodeIfPresent(tz, forKey: .tz)
case let .onExit(command, cwd):
try container.encode(command, forKey: .command)
try container.encodeIfPresent(cwd, forKey: .cwd)
}
}
static func parseAtDate(_ value: String) -> Date? {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { return nil }
if let date = makeIsoFormatter(withFractional: true).date(from: trimmed) { return date }
return self.makeIsoFormatter(withFractional: false).date(from: trimmed)
}
static func formatIsoDate(_ date: Date) -> String {
self.makeIsoFormatter(withFractional: false).string(from: date)
}
private static func makeIsoFormatter(withFractional: Bool) -> ISO8601DateFormatter {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = withFractional
? [.withInternetDateTime, .withFractionalSeconds]
: [.withInternetDateTime]
return formatter
}
}
enum CronPayload: Codable, Equatable {
case systemEvent(text: String)
case agentTurn(
message: String,
thinking: String?,
timeoutSeconds: Int?,
deliver: Bool?,
channel: String?,
to: String?,
bestEffortDeliver: Bool?)
enum CodingKeys: String, CodingKey {
case kind, text, message, thinking, timeoutSeconds, deliver, channel, provider, to, bestEffortDeliver
}
var kind: String {
switch self {
case .systemEvent: "systemEvent"
case .agentTurn: "agentTurn"
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(String.self, forKey: .kind)
switch kind {
case "systemEvent":
self = try .systemEvent(text: container.decode(String.self, forKey: .text))
case "agentTurn":
self = try .agentTurn(
message: container.decode(String.self, forKey: .message),
thinking: container.decodeIfPresent(String.self, forKey: .thinking),
timeoutSeconds: container.decodeIfPresent(Int.self, forKey: .timeoutSeconds),
deliver: container.decodeIfPresent(Bool.self, forKey: .deliver),
channel: container.decodeIfPresent(String.self, forKey: .channel)
?? container.decodeIfPresent(String.self, forKey: .provider),
to: container.decodeIfPresent(String.self, forKey: .to),
bestEffortDeliver: container.decodeIfPresent(Bool.self, forKey: .bestEffortDeliver))
default:
throw DecodingError.dataCorruptedError(
forKey: .kind,
in: container,
debugDescription: "Unknown payload kind: \(kind)")
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.kind, forKey: .kind)
switch self {
case let .systemEvent(text):
try container.encode(text, forKey: .text)
case let .agentTurn(message, thinking, timeoutSeconds, deliver, channel, to, bestEffortDeliver):
try container.encode(message, forKey: .message)
try container.encodeIfPresent(thinking, forKey: .thinking)
try container.encodeIfPresent(timeoutSeconds, forKey: .timeoutSeconds)
try container.encodeIfPresent(deliver, forKey: .deliver)
try container.encodeIfPresent(channel, forKey: .channel)
try container.encodeIfPresent(to, forKey: .to)
try container.encodeIfPresent(bestEffortDeliver, forKey: .bestEffortDeliver)
}
}
}
struct CronJobState: Codable, Equatable {
var nextRunAtMs: Int?
var runningAtMs: Int?
var lastRunAtMs: Int?
var lastStatus: String?
var lastError: String?
var lastDurationMs: Int?
}
struct CronJob: Identifiable, Codable, Equatable {
let id: String
let agentId: String?
var name: String
var description: String?
var enabled: Bool
var deleteAfterRun: Bool?
let createdAtMs: Int
let updatedAtMs: Int
let schedule: CronSchedule
private let sessionTargetRaw: String
let wakeMode: CronWakeMode
let payload: CronPayload
let delivery: CronDelivery?
let state: CronJobState
enum CodingKeys: String, CodingKey {
case id
case agentId
case name
case description
case enabled
case deleteAfterRun
case createdAtMs
case updatedAtMs
case schedule
case sessionTargetRaw = "sessionTarget"
case wakeMode
case payload
case delivery
case state
}
init(
id: String,
agentId: String?,
name: String,
description: String?,
enabled: Bool,
deleteAfterRun: Bool?,
createdAtMs: Int,
updatedAtMs: Int,
schedule: CronSchedule,
sessionTarget: CronSessionTarget,
wakeMode: CronWakeMode,
payload: CronPayload,
delivery: CronDelivery?,
state: CronJobState)
{
self.init(
id: id,
agentId: agentId,
name: name,
description: description,
enabled: enabled,
deleteAfterRun: deleteAfterRun,
createdAtMs: createdAtMs,
updatedAtMs: updatedAtMs,
schedule: schedule,
sessionTarget: .predefined(sessionTarget),
wakeMode: wakeMode,
payload: payload,
delivery: delivery,
state: state)
}
init(
id: String,
agentId: String?,
name: String,
description: String?,
enabled: Bool,
deleteAfterRun: Bool?,
createdAtMs: Int,
updatedAtMs: Int,
schedule: CronSchedule,
sessionTarget: CronCustomSessionTarget,
wakeMode: CronWakeMode,
payload: CronPayload,
delivery: CronDelivery?,
state: CronJobState)
{
self.id = id
self.agentId = agentId
self.name = name
self.description = description
self.enabled = enabled
self.deleteAfterRun = deleteAfterRun
self.createdAtMs = createdAtMs
self.updatedAtMs = updatedAtMs
self.schedule = schedule
self.sessionTargetRaw = sessionTarget.rawValue
self.wakeMode = wakeMode
self.payload = payload
self.delivery = delivery
self.state = state
}
/// Parsed session target (predefined or custom session ID)
var parsedSessionTarget: CronCustomSessionTarget {
CronCustomSessionTarget.from(self.sessionTargetRaw)
}
/// Compatibility shim for existing editor/UI code paths that still use the
/// predefined enum.
var sessionTarget: CronSessionTarget {
switch self.parsedSessionTarget {
case let .predefined(target):
target
case .session:
.isolated
}
}
var sessionTargetDisplayValue: String {
self.parsedSessionTarget.rawValue
}
var transcriptSessionKey: String? {
switch self.parsedSessionTarget {
case .predefined(.main):
nil
case .predefined(.isolated), .predefined(.current):
"cron:\(self.id)"
case let .session(id):
id
}
}
var supportsAnnounceDelivery: Bool {
switch self.parsedSessionTarget {
case .predefined(.main):
false
case .predefined(.isolated), .predefined(.current), .session:
true
}
}
var displayName: String {
let trimmed = self.name.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? "Untitled job" : trimmed
}
var nextRunDate: Date? {
guard let ms = self.state.nextRunAtMs else { return nil }
return Date(timeIntervalSince1970: TimeInterval(ms) / 1000)
}
var lastRunDate: Date? {
guard let ms = self.state.lastRunAtMs else { return nil }
return Date(timeIntervalSince1970: TimeInterval(ms) / 1000)
}
}
struct CronEvent: Codable {
let jobId: String
let action: String
let runAtMs: Int?
let durationMs: Int?
let status: String?
let error: String?
let summary: String?
let nextRunAtMs: Int?
}
struct CronRunLogEntry: Codable, Identifiable {
var id: String {
"\(self.jobId)-\(self.ts)"
}
let ts: Int
let jobId: String
let action: String
let status: String?
let error: String?
let summary: String?
let runAtMs: Int?
let durationMs: Int?
let nextRunAtMs: Int?
var date: Date {
Date(timeIntervalSince1970: TimeInterval(self.ts) / 1000)
}
var runDate: Date? {
guard let runAtMs else { return nil }
return Date(timeIntervalSince1970: TimeInterval(runAtMs) / 1000)
}
}
struct CronListResponse: Codable {
let jobs: [CronJob]
}
struct CronRunsResponse: Codable {
let entries: [CronRunLogEntry]
}