UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,407 lines 83.8 kB
import { C as parseStrictNonNegativeInteger, D as resolveExpiresAtMsFromDurationMs, F as resolveTimerTimeoutMs, P as resolvePositiveTimerTimeoutMs, R as timestampMsToIsoString, w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { G as CRON_GATEWAY_OPTION_NAMES, K as createCronOutputCommand, q as isCronMachineOutput } from "./argv-DNjh_yaF.js";
import { o as truncateToVisibleWidth, s as visibleWidth } from "./ansi-DrXAcdMD.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as defaultRuntime, t as ExitError } from "./runtime-CF2WjnNZ.js";
import { t as exitCliAfterOutput } from "./one-shot-exit-BkFPnjbM.js";
import { t as sleep } from "./sleep-D7nua6TP.js";
import { g as sanitizeAgentId } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { n as formatTimestamp } from "./timestamps-CD_Oo5x3.js";
import { t as parseDurationMs } from "./parse-duration-CuuCHKpt.js";
import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js";
import { Vt as resolveCronCompletionStatus } from "./openclaw-state-db-BRTnL-D8.js";
import { n as THINKING_LEVELS_HELP } from "./thinking.shared-bHYuuc1L.js";
import { t as formatDocsLink } from "./links-ClIwBcy4.js";
import { n as isRich, r as theme, t as colorize } from "./theme-vjDs9tao.js";
import { n as inheritOptionFromParent } from "./command-options-BDuSHeWG.js";
import { n as callGatewayFromCli, t as addGatewayClientOptions } from "./gateway-rpc-C9fDQ0vT.js";
import { t as danger } from "./globals-CTaGxEqj.js";
import { i as formatLookupMiss } from "./error-format-Cae7EwnT.js";
import { a as readCronJobNotFoundError } from "./gateway-error-details-w0nAGBBp.js";
import { c as rethrowExpectedCliError } from "./failure-output-Dc5ce9_F.js";
import { t as parseTimeoutMs } from "./parse-timeout-BhPKqfrV.js";
import { i as listChannelPlugins } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { n as formatDurationHuman } from "./format-duration-CeDWULoS.js";
import { r as isOffsetlessIsoDateTime } from "./iso-time-C2akJNwm.js";
import { t as parseAbsoluteTimeMs } from "./parse-tlCSjrTr.js";
import { r as resolveCronStaggerMs, t as isSystemOwnedCronPayloadKind } from "./types-DQrueG6d.js";
import { t as CRON_JOB_SCRATCH_MAX_BYTES } from "./scratch-contract-B-Laspel.js";
import { t as normalizeHttpWebhookUrl } from "./webhook-url-AutetAqs.js";
import { t as readByteStreamWithLimit } from "./read-byte-stream-with-limit-CNew-qG0.js";
import { r as isJsonOutputModeActive } from "./json-output-mode-D0F07HLf.js";
import { t as parseOffsetlessIsoDateTimeInTimeZone } from "./parse-offsetless-zoned-datetime-DGKymK8c.js";
import { t as applyParentDefaultHelpAction } from "./parent-default-help-DQUF3qKA.js";
import { n as isUnknownCronGetMethodError, r as listCronJobsFromGateway, t as findCronJobByIdOrName } from "./list-jobs-C_GC9a0W.js";
import { n as setCommandJsonMode } from "./json-mode-jHy4Wzfh.js";
import { t as formatExactDuration } from "./format-duration-exact-hT1gVF5x.js";
import { createReadStream } from "node:fs";
//#region src/cli/cron-cli/shared.ts
function parseCronArgv(value, flag) {
	if (typeof value !== "string") return;
	let parsed;
	try {
		parsed = JSON.parse(value);
	} catch {
		throw new Error(`${flag} must be a JSON array of strings`);
	}
	if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((entry) => typeof entry !== "string" || entry.length === 0)) throw new Error(`${flag} must be a non-empty JSON array of non-empty strings`);
	return parsed;
}
function parseCronCommandArgv(value) {
	return parseCronArgv(value, "--command-argv");
}
function parseCronStreamCommandArgv(value) {
	return parseCronArgv(value, "--stream-command");
}
function parseCronCommandEnv(values) {
	const rawValues = Array.isArray(values) ? values : typeof values === "string" ? [values] : [];
	if (rawValues.length === 0) return;
	const env = {};
	for (const raw of rawValues) {
		if (typeof raw !== "string") throw new Error("--command-env must be KEY=VALUE");
		const idx = raw.indexOf("=");
		const key = idx > 0 ? raw.slice(0, idx).trim() : "";
		if (!key) throw new Error("--command-env must be KEY=VALUE");
		env[key] = raw.slice(idx + 1);
	}
	return env;
}
const getCronChannelOptions = () => {
	const pluginIds = listChannelPlugins().map((plugin) => plugin.id).filter(Boolean);
	return pluginIds.length > 0 ? ["last", ...pluginIds].join("|") : "last|<channel-plugin-id>";
};
function toLocalIsoTime(value) {
	return typeof value === "number" && Number.isFinite(value) ? formatTimestamp(new Date(value), { style: "long" }) : void 0;
}
/**
* CLI-only display enrichment for `cron runs` history entries: adds a short
* `cause` alias for `errorReason` plus readable local-offset ISO mirrors of the
* numeric timestamps (matching the diagnostic log `time` format). Stored data
* and the gateway protocol stay unchanged; raw numeric fields are preserved.
*/
function enrichCronRunEntriesForDisplay(value) {
	if (!value || typeof value !== "object") return value;
	const record = value;
	const entries = record.entries;
	if (!Array.isArray(entries)) return value;
	const nextEntries = entries.map((entry) => {
		if (!entry || typeof entry !== "object") return entry;
		const item = entry;
		if (item.action !== "finished") return item;
		const extra = {};
		const cause = typeof item.errorReason === "string" ? item.errorReason.trim() : "";
		if (cause) extra.cause = cause;
		const tsIso = toLocalIsoTime(item.ts);
		if (tsIso) extra.tsIso = tsIso;
		const runAtIso = toLocalIsoTime(item.runAtMs);
		if (runAtIso) extra.runAtIso = runAtIso;
		const nextRunAtIso = toLocalIsoTime(item.nextRunAtMs);
		if (nextRunAtIso) extra.nextRunAtIso = nextRunAtIso;
		return Object.keys(extra).length > 0 ? Object.assign({}, item, extra) : item;
	});
	return {
		...record,
		entries: nextEntries
	};
}
function printCronJson(value) {
	defaultRuntime.writeJson(enrichCronRunEntriesForDisplay(value));
}
/**
* Enrich a CronJob (or list response) with a computed `status` field
* derived from enabled + state.runningAtMs + state.lastRunStatus.
* This mirrors the human-readable status shown by `cron list` / `cron show`.
*/
function enrichCronJsonWithStatus(value) {
	if (!value || typeof value !== "object") return value;
	const obj = value;
	if ("state" in obj && "enabled" in obj) return {
		...obj,
		status: computeStatus(obj)
	};
	if ("jobs" in obj && Array.isArray(obj.jobs)) {
		const enrichedJobs = obj.jobs.map((job) => {
			const status = computeStatus(job);
			return Object.assign({}, job, { status });
		});
		return {
			...obj,
			jobs: enrichedJobs
		};
	}
	return value;
}
function computeStatus(job) {
	const state = asOptionalRecord(job.state) ?? {};
	if (state.runningAtMs) return "running";
	if (!job.enabled) return "disabled";
	return typeof state.lastRunStatus === "string" ? state.lastRunStatus : typeof state.lastStatus === "string" ? state.lastStatus : "idle";
}
function decorateStatusWithFailures(status, consecutiveErrors) {
	const failures = consecutiveErrors ?? 0;
	if (status !== "error" || failures <= 1) return status;
	return failures > 99 ? `${status} (99+x)` : `${status} (${failures}x)`;
}
function formatCronStatusForDisplay(job) {
	const state = job.state ?? {};
	const status = computeStatus(job);
	const streamDisabled = job.enabled && job.schedule?.kind === "stream" && state.streamStatus === "disabled";
	const undelivered = status === "ok" && state.lastDeliveryStatus === "not-delivered";
	const suppressed = undelivered && !streamDisabled && state.deliverySuppressionReason !== void 0;
	const color = status === "error" ? theme.error : status === "running" || undelivered && !suppressed ? theme.warn : status === "ok" ? theme.success : theme.muted;
	let label = decorateStatusWithFailures(status, state.consecutiveErrors);
	if (streamDisabled) label = "disabled";
	else if (status === "disabled" && state.autoDisabled) label = state.autoDisabled.reason === "schedule-errors" ? "disabled (schedule)" : `disabled (${state.autoDisabled.consecutiveErrors}x)`;
	else if (undelivered) label = suppressed ? "ok (suppressed)" : "ok (not delivered)";
	return {
		label,
		color
	};
}
function handleCronCliError(err) {
	if (err instanceof ExitError) throw err;
	rethrowExpectedCliError(err);
	const missingJob = readCronJobNotFoundError(err);
	const message = missingJob ? formatCronLookupMiss(missingJob.jobId) : formatErrorMessage(err);
	if (isJsonOutputModeActive(process.argv)) throw missingJob ? new Error(message) : err;
	defaultRuntime.error(danger(message));
	exitCliAfterOutput(defaultRuntime, 1);
}
const formatCronLookupMiss = (jobId) => formatLookupMiss({
	noun: "Automation",
	value: sanitizeTerminalText(jobId),
	listCommand: "openclaw cron list",
	valueLabel: "automation id"
});
async function warnIfCronSchedulerDisabled(opts) {
	try {
		const res = await callGatewayFromCli("cron.status", opts, {});
		if (res?.enabled !== false) return;
		const store = typeof res?.sqlitePath === "string" ? res.sqlitePath : typeof res?.storePath === "string" ? res.storePath : "";
		defaultRuntime.error([
			"warning: the automations scheduler is disabled in the Gateway; jobs are saved but will not run automatically.",
			"Re-enable with `cron.enabled: true` (or remove `cron.enabled: false`) and restart the Gateway.",
			store ? `store: ${store}` : ""
		].filter(Boolean).join("\n"));
	} catch {}
}
function parsePositiveCronDurationMs(input) {
	try {
		const result = parseDurationMs(input);
		return result > 0 && result <= 864e13 ? result : null;
	} catch {
		return null;
	}
}
function parseCronStaggerMs(params) {
	if (params.useExact) return 0;
	if (!params.staggerRaw) return;
	const parsed = parsePositiveCronDurationMs(params.staggerRaw);
	if (!parsed) throw new Error("Invalid --stagger; use e.g. 30s, 1m, 5m");
	return parsed;
}
function parseCronToolsAllow(input) {
	const tools = (Array.isArray(input) ? input.map((value) => String(value)).join(" ") : typeof input === "string" ? input : "").split(/[,\s]+/u).map((tool) => normalizeOptionalString(tool)).filter((tool) => Boolean(tool));
	return tools.length > 0 ? tools : void 0;
}
function parseCronFallbacks(input) {
	if (input === void 0) return;
	return (Array.isArray(input) ? input.map((value) => String(value)).join(" ") : typeof input === "string" ? input : "").split(/[,\s]+/u).map((fallback) => normalizeOptionalString(fallback)).filter((fallback) => Boolean(fallback));
}
/**
* Parse a one-shot `--at` value into an ISO string (UTC).
*
* When `tz` is provided and the input is an offset-less datetime
* (e.g. `2026-03-23T23:00:00`), the datetime is interpreted in
* that IANA timezone instead of UTC.
*/
function parseAt(input, tz) {
	const raw = input.trim();
	if (!raw) return null;
	if (tz && isOffsetlessIsoDateTime(raw)) return parseOffsetlessIsoDateTimeInTimeZone(raw, tz);
	const absolute = parseAbsoluteTimeMs(raw);
	if (absolute !== null) return timestampMsToIsoString(absolute) ?? null;
	const dur = parsePositiveCronDurationMs(raw.startsWith("+") ? raw.slice(1) : raw);
	if (dur !== null) {
		const expiresAt = resolveExpiresAtMsFromDurationMs(dur);
		return timestampMsToIsoString(expiresAt) ?? null;
	}
	return null;
}
const CRON_ID_PAD = 36;
const CRON_DECLARATION_PAD = 24;
const CRON_NAME_PAD = 24;
const CRON_SCHEDULE_PAD = 32;
const CRON_NEXT_PAD = 10;
const CRON_LAST_PAD = 10;
const CRON_STATUS_PAD = 19;
const CRON_TARGET_PAD = 9;
const CRON_DELIVERY_PAD = 64;
const CRON_AGENT_PAD = 10;
const CRON_OWNER_PAD = 24;
const CRON_MODEL_PAD = 20;
const TRUNCATED_SUFFIX = "...";
const stringifyCell = (value, fallback = "-") => {
	if (typeof value === "string") return value;
	if (typeof value === "number" || typeof value === "boolean") return String(value);
	return fallback;
};
const formatCell = (value, width) => {
	const text = sanitizeTerminalText(stringifyCell(value));
	const truncated = visibleWidth(text) <= width ? text : width <= 3 ? truncateToVisibleWidth(text, width) : `${truncateToVisibleWidth(text, width - 3)}${TRUNCATED_SUFFIX}`;
	const remaining = width - visibleWidth(truncated);
	return remaining > 0 ? `${truncated}${" ".repeat(remaining)}` : truncated;
};
const formatIsoMinute = (iso) => {
	const isoStr = timestampMsToIsoString(parseAbsoluteTimeMs(iso));
	return isoStr ? `${isoStr.slice(0, -8).replace("T", " ")}Z` : "-";
};
const formatSpan = (ms) => ms < 6e4 ? "<1m" : formatDurationHuman(ms);
const formatRelative = (ms, nowMs) => {
	if (!ms) return "-";
	const delta = ms - nowMs;
	const label = formatSpan(Math.abs(delta));
	return delta >= 0 ? `in ${label}` : `${label} ago`;
};
const formatSchedule = (schedule, hasTrigger = false) => {
	const suffix = hasTrigger ? "+trigger" : "";
	if (schedule?.kind === "at") return `at ${formatIsoMinute(schedule.at)}${suffix}`;
	if (schedule?.kind === "every") return `every ${formatExactDuration(schedule.everyMs)}${suffix}`;
	if (schedule?.kind === "on-exit") {
		const cwd = schedule.cwd ? ` @ ${schedule.cwd}` : "";
		return `on-exit ${schedule.command}${cwd}`;
	}
	if (schedule?.kind === "stream") {
		const cwd = schedule.cwd ? ` @ ${schedule.cwd}` : "";
		return `stream ${schedule.command.join(" ")}${cwd}${suffix}`;
	}
	if (schedule?.kind !== "cron") return "-";
	const base = schedule.tz ? `cron ${schedule.expr} @ ${schedule.tz}${suffix}` : `cron ${schedule.expr}${suffix}`;
	const staggerMs = resolveCronStaggerMs(schedule);
	if (staggerMs <= 0) return `${base} (exact)`;
	return `${base} (stagger ${formatExactDuration(staggerMs)})`;
};
function coerceCronDeliveryPreviews(value) {
	const previews = value && typeof value === "object" ? value.deliveryPreviews : void 0;
	if (!previews || typeof previews !== "object") return /* @__PURE__ */ new Map();
	return new Map(Object.entries(previews).flatMap(([jobId, preview]) => {
		if (!preview || typeof preview !== "object") return [];
		const record = preview;
		if (typeof record.label !== "string" || typeof record.detail !== "string") return [];
		return [[jobId, {
			label: record.label,
			detail: record.detail
		}]];
	}));
}
function printCronList(jobs, runtime = defaultRuntime, opts) {
	if (jobs.length === 0) {
		runtime.log("No automations.");
		return;
	}
	const rich = isRich();
	const header = [
		formatCell("ID", CRON_ID_PAD),
		formatCell("Declaration", CRON_DECLARATION_PAD),
		formatCell("Name", CRON_NAME_PAD),
		formatCell("Schedule", CRON_SCHEDULE_PAD),
		formatCell("Next", CRON_NEXT_PAD),
		formatCell("Last", CRON_LAST_PAD),
		formatCell("Status", CRON_STATUS_PAD),
		formatCell("Target", CRON_TARGET_PAD),
		formatCell("Delivery", CRON_DELIVERY_PAD),
		formatCell("Agent ID", CRON_AGENT_PAD),
		formatCell("Owner", CRON_OWNER_PAD),
		formatCell("Model", CRON_MODEL_PAD)
	].join(" ");
	const lines = [rich ? theme.heading(header) : header];
	const now = Date.now();
	for (const job of jobs) {
		const state = job.state ?? {};
		const idLabel = formatCell(job.id, CRON_ID_PAD);
		const declarationLabel = formatCell(job.declarationKey, CRON_DECLARATION_PAD);
		const nameLabel = formatCell(job.displayName ?? job.name, CRON_NAME_PAD);
		const scheduleLabel = formatCell(formatSchedule(job.schedule, job.trigger !== void 0), CRON_SCHEDULE_PAD);
		const nextLabel = formatCell(job.enabled ? formatRelative(state.nextRunAtMs, now) : "-", CRON_NEXT_PAD);
		const lastLabel = formatCell(formatRelative(state.lastRunAtMs, now), CRON_LAST_PAD);
		const status = formatCronStatusForDisplay(job);
		const statusLabel = formatCell(status.label, CRON_STATUS_PAD);
		const targetLabel = formatCell(job.sessionTarget, CRON_TARGET_PAD);
		const deliveryPreview = opts?.deliveryPreviews?.get(job.id);
		const deliveryText = deliveryPreview ? `${deliveryPreview.label} (${deliveryPreview.detail})` : "-";
		const deliveryLabel = formatCell(deliveryText, CRON_DELIVERY_PAD);
		const agentLabel = formatCell(job.agentId, CRON_AGENT_PAD);
		const ownerLabel = formatCell(job.owner?.sessionKey ?? job.owner?.agentId, CRON_OWNER_PAD);
		const modelLabel = formatCell(job.payload?.kind === "agentTurn" ? job.payload.model : void 0, CRON_MODEL_PAD);
		const coloredTarget = job.sessionTarget === "main" ? colorize(rich, theme.accent, targetLabel) : colorize(rich, theme.accentBright, targetLabel);
		const coloredAgent = job.agentId ? colorize(rich, theme.info, agentLabel) : colorize(rich, theme.muted, agentLabel);
		const line = [
			colorize(rich, theme.accent, idLabel),
			colorize(rich, theme.muted, declarationLabel),
			colorize(rich, theme.info, nameLabel),
			colorize(rich, theme.info, scheduleLabel),
			colorize(rich, theme.muted, nextLabel),
			colorize(rich, theme.muted, lastLabel),
			colorize(rich, status.color, statusLabel),
			coloredTarget,
			deliveryPreview ? colorize(rich, theme.info, deliveryLabel) : colorize(rich, theme.muted, deliveryLabel),
			coloredAgent,
			colorize(rich, job.owner ? theme.info : theme.muted, ownerLabel),
			job.payload?.kind === "agentTurn" && job.payload.model ? colorize(rich, theme.info, modelLabel) : colorize(rich, theme.muted, modelLabel)
		].join(" ");
		lines.push(line.trimEnd());
	}
	runtime.log(lines.join("\n"));
}
function printCronShow(job, runtime = defaultRuntime, opts) {
	const preview = opts?.deliveryPreview ?? {
		label: "-",
		detail: "unavailable"
	};
	const showValue = (value) => sanitizeTerminalText(stringifyCell(value));
	runtime.log(`id: ${showValue(job.id)}`);
	runtime.log(`declaration: ${showValue(job.declarationKey)}`);
	runtime.log(`name: ${showValue(job.name)}`);
	runtime.log(`display name: ${showValue(job.displayName)}`);
	runtime.log(`owner agent: ${showValue(job.owner?.agentId)}`);
	runtime.log(`owner session: ${showValue(job.owner?.sessionKey)}`);
	runtime.log(`enabled: ${job.enabled ? "yes" : "no"}`);
	runtime.log(`schedule: ${showValue(formatSchedule(job.schedule, job.trigger !== void 0))}`);
	if (job.schedule?.kind === "stream") {
		runtime.log(`stream status: ${showValue(job.state.streamStatus)}`);
		runtime.log(`stream error: ${showValue(job.state.streamError)}`);
	}
	runtime.log(`trigger: ${job.trigger ? `once=${job.trigger.once === true ? "yes" : "no"}; evals=${job.state.triggerEvalCount ?? 0}; last eval=${formatRelative(job.state.lastTriggerEvalAtMs, Date.now())}; last fire=${formatRelative(job.state.lastTriggerFireAtMs, Date.now())}` : "-"}`);
	runtime.log(`session: ${showValue(job.sessionTarget)}`);
	runtime.log(`agent: ${showValue(job.agentId)}`);
	runtime.log(`model: ${showValue(job.payload.kind === "agentTurn" ? job.payload.model : void 0)}`);
	runtime.log(`delivery: ${showValue(preview.label)} (${showValue(preview.detail)})`);
	runtime.log(`next: ${formatRelative(job.state.nextRunAtMs, Date.now())}`);
	runtime.log(`last: ${formatRelative(job.state.lastRunAtMs, Date.now())}`);
	runtime.log(`status: ${showValue(formatCronStatusForDisplay(job).label)}`);
	runtime.log(`last error: ${showValue(job.state.lastError)}`);
	runtime.log(`last delivery: ${showValue(job.state.lastDeliveryStatus)}`);
	runtime.log(`last delivery suppression: ${showValue(job.state.deliverySuppressionReason)}`);
	runtime.log(`last delivery error: ${showValue(job.state.lastDeliveryError)}`);
	runtime.log(`diagnostic: ${showValue(job.state.lastDiagnosticSummary)}`);
}
//#endregion
//#region src/cli/cron-cli/register.cron-options.ts
function registerCronMutationOptions(command, mode) {
	const create = mode === "add";
	return command.option("--name <name>", "Job name").option("--display-name <name>", "Human-readable job label").option("--description <text>", "Job description").option("--delete-after-run", "Delete one-shot after successful completion (confirmed/no delivery, intentional silence, or best effort); failed/unknown required delivery retains it disabled", false).option("--keep-after-run", "Keep one-shot job after it succeeds", false).option("--agent <id>", "Agent id for this job").option("--session <target>", "Session target (main|isolated|current|session:<id>)").option("--session-key <key>", "Session key for job routing").option("--wake <mode>", "Wake mode (now|next-heartbeat)", create ? "now" : void 0).option("--at <when>", "One-shot time (ISO, offset-less uses --tz) or duration like 20m").option("--every <duration>", "Interval duration (e.g. 10m, 1h)").option("--pacing-min <duration>", "Minimum delay for a dynamic next check").option("--pacing-max <duration>", "Maximum delay for a dynamic next check").option("--cron <expr>", "Cron expression (5-field or 6-field with seconds)").option("--on-exit <shell>", "Fire once when the watched command exits").option("--on-exit-cwd <path>", "Working directory for the --on-exit watched command").option("--stream-command <json>", "Stream source argv as a JSON array of strings").option("--stream-cwd <path>", "Working directory for the stream source").option("--stream-mode <mode>", "Stream line selection mode (line|match)").option("--stream-match <regex>", "Regex source required for stream match mode").option("--stream-batch-ms <n>", "Quiet-window batch delay in milliseconds").option("--stream-max-batch-bytes <n>", "Maximum UTF-8 bytes per stream batch").option("--tz <iana>", "Timezone for cron expressions (IANA; cron default: Gateway host local timezone)", create ? "" : void 0).option("--stagger <duration>", "Cron stagger window (e.g. 30s, 5m)").option("--exact", "Disable cron staggering (set stagger to 0)", create ? false : void 0).option("--trigger-script <path|->", "Condition script file, or - for stdin").option("--trigger-once", "Disable after the first successful triggered run", false).option("--system-event <text>", "System event payload (main session)").option("--message <text>", "Agent message payload").option("--script <file|->", "Headless script payload file, or - for stdin").option("--script-timeout-seconds <n>", "Script wall-clock timeout seconds").option("--script-tool-budget <n>", "Maximum script tool calls").option("--command <shell>", "Command payload run as sh -lc <shell> on the Gateway").option("--command-argv <json>", "Command payload argv as JSON array of strings").option("--command-cwd <path>", "Working directory for command payloads").option("--command-env <KEY=VALUE>", "Environment override for command payloads (repeatable)", (value, previous) => [...previous ?? [], value]).option("--command-input <text>", "stdin for command payloads").option("--thinking <level>", `Thinking level for agent jobs (${THINKING_LEVELS_HELP})`).option("--model <model>", "Model override for agent jobs (provider/model or alias)").option("--fallbacks <list>", "Fallback model list for agent jobs").option("--timeout-seconds <n>", "Timeout seconds for agent or command jobs").option("--no-output-timeout-seconds <n>", "No-output timeout seconds for command jobs").option("--output-max-bytes <n>", "Maximum captured stdout/stderr bytes for command jobs").option("--light-context", "Use lightweight bootstrap context for agent jobs", create ? false : void 0).option("--tools <list>", "Tool allow-list (e.g. exec,read,write or exec read write)").option("--announce", "Fallback-deliver final text to a chat", create ? false : void 0).option("--deliver", "Deprecated (use --announce). Fallback-delivers final text to a chat.").option("--no-deliver", "Disable runner fallback delivery").option("--webhook <url>", "POST the finished payload to a webhook URL").option("--channel <channel>", `Delivery channel (${getCronChannelOptions()})`, create ? "last" : void 0).option("--to <dest>", "Delivery destination (E.164, Telegram chatId, or Discord channel/user)").option("--thread-id <id>", "Telegram forum topic thread id").option("--account <id>", "Channel account id for delivery (multi-account setups)").option("--best-effort-deliver", create ? "Do not fail the job if delivery fails" : "Do not fail job if delivery fails (also implies --announce when used alone)", create ? false : void 0);
}
//#endregion
//#region src/cli/cron-cli/schedule-options.ts
/** Resolve explicit `--at`, `--every`, or `--cron` options for cron creation. */
function resolveCronCreateSchedule(options) {
	const normalized = normalizeScheduleOptions(options);
	if (normalized.onExitCwd && !normalized.onExitCommand) throw new Error("--on-exit-cwd requires --on-exit.");
	if (countChosenSchedules(normalized) !== 1) throw new Error("Choose exactly one schedule: --at, --every, --cron, --on-exit, or --stream-command");
	const schedule = resolveDirectSchedule(normalized);
	if (!schedule) throw new Error("Choose exactly one schedule: --at, --every, --cron, --on-exit, or --stream-command");
	return schedule;
}
/** Resolve cron creation schedule from either a positional shorthand or explicit flags. */
function resolveCronCreateScheduleFromArgs(options) {
	const positionalSchedule = normalizeOptionalString(options.positionalSchedule);
	if (!positionalSchedule) return resolveCronCreateSchedule(options);
	if (countChosenSchedules(normalizeScheduleOptions(options)) > 0) throw new Error("Choose a positional schedule or one of --at, --every, --cron, --on-exit, or --stream-command.");
	const every = parseEverySchedule(positionalSchedule);
	return resolveCronCreateSchedule({
		...options,
		at: every ? void 0 : looksLikeCronExpression(positionalSchedule) ? void 0 : positionalSchedule,
		cron: looksLikeCronExpression(positionalSchedule) ? positionalSchedule : void 0,
		every
	});
}
/** Resolve a cron edit request, allowing at most one direct schedule replacement. */
function resolveCronEditScheduleRequest(options) {
	const normalized = normalizeScheduleOptions(options);
	const chosen = countChosenSchedules(normalized);
	if (hasStreamSchedulePatch(normalized) && !normalized.streamCommand) {
		if (normalized.tz !== void 0 || normalized.requestedStaggerMs !== void 0) throw new Error("--tz/--stagger/--exact are not valid with stream schedule edits");
		if (chosen > 0) throw new Error("Choose at most one schedule change");
		return {
			kind: "patch-existing-stream",
			cwd: normalized.streamCwdSupplied ? normalized.streamCwd ?? null : void 0,
			mode: normalized.streamModeSupplied ? normalized.streamMode : void 0,
			match: normalized.streamMatchSupplied ? normalized.streamMatch ?? null : void 0,
			batchMs: normalized.streamBatchMs,
			maxBatchBytes: normalized.streamMaxBatchBytes
		};
	}
	if (chosen > 1) throw new Error("Choose at most one schedule change");
	const schedule = resolveDirectSchedule(normalized, { deferStreamMetadataValidation: true });
	if (schedule) return {
		kind: "direct",
		schedule
	};
	if (normalized.requestedStaggerMs !== void 0 || normalized.tz !== void 0) return {
		kind: "patch-existing-cron",
		tz: normalized.tz,
		staggerMs: normalized.requestedStaggerMs
	};
	return { kind: "none" };
}
/** Apply stream metadata edits without requiring callers to restate the source argv. */
function applyExistingStreamSchedulePatch(existingSchedule, request) {
	if (existingSchedule.kind !== "stream") throw new Error("Current job is not a stream schedule; use --stream-command to convert first");
	const mode = request.mode ?? existingSchedule.mode ?? "line";
	const requestedMatch = request.match === void 0 ? existingSchedule.match : request.match ?? void 0;
	if (mode === "match" && !requestedMatch) throw new Error("--stream-match is required when --stream-mode=match");
	if (mode === "line" && request.match) throw new Error("--stream-match requires --stream-mode=match");
	return {
		...existingSchedule,
		cwd: request.cwd === void 0 ? existingSchedule.cwd : request.cwd ?? void 0,
		mode,
		match: mode === "match" ? requestedMatch : void 0,
		batchMs: request.batchMs ?? existingSchedule.batchMs,
		maxBatchBytes: request.maxBatchBytes ?? existingSchedule.maxBatchBytes
	};
}
/** Validate a newly-created stream schedule after edit metadata has been merged. */
function validateStreamScheduleMetadata(schedule) {
	const mode = schedule.mode ?? "line";
	if (mode === "match" && !schedule.match) throw new Error("--stream-match is required when --stream-mode=match");
	if (mode === "line" && schedule.match) throw new Error("--stream-match requires --stream-mode=match");
}
/** Apply `--tz`, `--stagger`, or `--exact` metadata changes to an existing cron schedule. */
function applyExistingCronSchedulePatch(existingSchedule, request) {
	if (existingSchedule.kind !== "cron") throw new Error("Current job is not a cron schedule; use --cron to convert first");
	return {
		kind: "cron",
		expr: existingSchedule.expr,
		tz: request.tz ?? existingSchedule.tz,
		staggerMs: request.staggerMs !== void 0 ? request.staggerMs : existingSchedule.staggerMs
	};
}
function normalizeScheduleOptions(options) {
	const staggerRaw = normalizeOptionalString(options.stagger) ?? "";
	const useExact = Boolean(options.exact);
	if (staggerRaw && useExact) throw new Error("Choose either --stagger or --exact, not both");
	const streamModeSupplied = options.streamMode !== void 0;
	const suppliedStreamMode = normalizeOptionalString(options.streamMode);
	if (streamModeSupplied && !suppliedStreamMode) throw new Error("--stream-mode must be line or match");
	const streamModeRaw = suppliedStreamMode ?? "line";
	if (streamModeRaw !== "line" && streamModeRaw !== "match") throw new Error("--stream-mode must be line or match");
	const parsePositiveInteger = (value, flag) => {
		if (value === void 0) return;
		if (typeof value !== "string" && typeof value !== "number") throw new Error(`${flag} must be a positive integer`);
		const text = String(value).trim();
		if (!/^\d+$/u.test(text)) throw new Error(`${flag} must be a positive integer`);
		const parsed = Number(text);
		if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error(`${flag} must be a positive integer`);
		return parsed;
	};
	return {
		at: normalizeOptionalString(options.at) ?? "",
		every: normalizeOptionalString(options.every) ?? "",
		cronExpr: normalizeOptionalString(options.cron) ?? "",
		onExitCommand: normalizeOptionalString(options.onExit) ?? "",
		onExitCwd: normalizeOptionalString(options.onExitCwd),
		streamCommand: parseCronStreamCommandArgv(options.streamCommand),
		streamCwd: normalizeOptionalString(options.streamCwd),
		streamCwdSupplied: options.streamCwd !== void 0,
		streamMode: streamModeRaw,
		streamModeSupplied,
		streamMatch: normalizeOptionalString(options.streamMatch),
		streamMatchSupplied: options.streamMatch !== void 0,
		streamBatchMs: parsePositiveInteger(options.streamBatchMs, "--stream-batch-ms"),
		streamMaxBatchBytes: parsePositiveInteger(options.streamMaxBatchBytes, "--stream-max-batch-bytes"),
		tz: normalizeOptionalString(options.tz),
		requestedStaggerMs: parseCronStaggerMs({
			staggerRaw,
			useExact
		})
	};
}
function hasStreamSchedulePatch(options) {
	return options.streamCwdSupplied || options.streamModeSupplied || options.streamMatchSupplied || options.streamBatchMs !== void 0 || options.streamMaxBatchBytes !== void 0;
}
function countChosenSchedules(options) {
	return [
		Boolean(options.at),
		Boolean(options.every),
		Boolean(options.cronExpr),
		Boolean(options.onExitCommand),
		Boolean(options.streamCommand)
	].filter(Boolean).length;
}
function parseEverySchedule(value) {
	return /^every\s+(.+)$/iu.exec(value.trim())?.[1]?.trim() || void 0;
}
function looksLikeCronExpression(value) {
	const parts = value.trim().split(/\s+/u);
	return parts.length === 5 || parts.length === 6;
}
function resolveDirectSchedule(options, behavior = {}) {
	if (options.onExitCwd && !options.onExitCommand) throw new Error("--on-exit-cwd requires --on-exit.");
	if (hasStreamSchedulePatch(options) && !options.streamCommand) throw new Error("Stream options require --stream-command.");
	if (options.tz && options.every) throw new Error("--tz is only valid with --cron or offset-less --at");
	if (options.requestedStaggerMs !== void 0 && (options.at || options.every)) throw new Error("--stagger/--exact are only valid for cron schedules");
	if (options.at) {
		const atIso = parseAt(options.at, options.tz);
		if (!atIso) throw new Error("Invalid --at. Use an ISO timestamp or a duration like 20m.");
		return {
			kind: "at",
			at: atIso
		};
	}
	if (options.every) {
		const everyMs = parsePositiveCronDurationMs(options.every);
		if (!everyMs) throw new Error("Invalid --every. Use a duration like 10m, 1h, or 1d.");
		return {
			kind: "every",
			everyMs
		};
	}
	if (options.cronExpr) return {
		kind: "cron",
		expr: options.cronExpr,
		tz: options.tz,
		staggerMs: options.requestedStaggerMs
	};
	if (options.onExitCommand) {
		if (options.tz || options.requestedStaggerMs !== void 0) throw new Error("--tz/--stagger/--exact are not valid with --on-exit");
		return {
			kind: "on-exit",
			command: options.onExitCommand,
			...options.onExitCwd ? { cwd: options.onExitCwd } : {}
		};
	}
	if (options.streamCommand) {
		if (options.tz || options.requestedStaggerMs !== void 0) throw new Error("--tz/--stagger/--exact are not valid with --stream-command");
		const schedule = {
			kind: "stream",
			command: options.streamCommand,
			...options.streamCwd ? { cwd: options.streamCwd } : {},
			mode: options.streamMode,
			...options.streamMatch ? { match: options.streamMatch } : {},
			...options.streamBatchMs !== void 0 ? { batchMs: options.streamBatchMs } : {},
			...options.streamMaxBatchBytes !== void 0 ? { maxBatchBytes: options.streamMaxBatchBytes } : {}
		};
		if (!behavior.deferStreamMetadataValidation) validateStreamScheduleMetadata(schedule);
		return schedule;
	}
}
//#endregion
//#region src/cli/cron-cli/thread-id-shared.ts
function parseCronThreadIdOption(value) {
	if (typeof value !== "string") return;
	const raw = normalizeOptionalString(value);
	if (!raw || !/^\d+$/.test(raw)) throw new Error("--thread-id must be a positive integer Telegram topic thread id");
	const parsed = Number.parseInt(raw, 10);
	if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new Error("--thread-id must be a safe positive integer Telegram topic thread id");
	return parsed;
}
function normalizeCronSessionTargetOption(value) {
	const raw = normalizeOptionalString(value);
	if (!raw) return;
	const lower = normalizeLowercaseStringOrEmpty(raw);
	if (lower === "main" || lower === "isolated" || lower === "current") return lower;
	if (lower.startsWith("session:")) {
		const id = normalizeOptionalString(raw.slice(8));
		return id ? `session:${id}` : void 0;
	}
}
//#endregion
//#region src/cli/cron-cli/trigger-options.ts
const MAX_CRON_TRIGGER_SCRIPT_BYTES = 65536;
async function readScriptStream(stream, label) {
	return (await readByteStreamWithLimit(stream, {
		maxBytes: MAX_CRON_TRIGGER_SCRIPT_BYTES,
		onOverflow: () => /* @__PURE__ */ new Error(`${label} exceeds ${MAX_CRON_TRIGGER_SCRIPT_BYTES} bytes`)
	})).toString("utf8");
}
/** Reads a trigger script locally before sending the cron RPC. */
async function readCronTriggerScript(source, deps) {
	const script = (await readScriptStream(source === "-" ? deps?.stdin ?? process.stdin : createReadStream(source), "Trigger script")).trim();
	if (!script) throw new Error("Trigger script must not be empty");
	return script;
}
/** Reads a script payload locally before sending the cron RPC. */
async function readCronPayloadScript(source, deps) {
	const script = (await readScriptStream(source === "-" ? deps?.stdin ?? process.stdin : createReadStream(source), "Script payload")).trim();
	if (!script) throw new Error("Script payload must not be empty");
	return script;
}
/** Reads exact scratch content locally; empty content is a meaningful value. */
async function readCronScratchContent(source, deps) {
	const stream = source === "-" ? deps?.stdin ?? process.stdin : createReadStream(source);
	return (await readByteStreamWithLimit(stream, {
		maxBytes: CRON_JOB_SCRATCH_MAX_BYTES,
		onOverflow: () => /* @__PURE__ */ new Error(`Cron scratch exceeds ${CRON_JOB_SCRATCH_MAX_BYTES} bytes`)
	})).toString("utf8");
}
//#endregion
//#region src/cli/cron-cli/register.cron-add.ts
function registerCronStatusCommand(cron) {
	addGatewayClientOptions(createCronOutputCommand(cron, "status").description("Show automations scheduler status").action(async (opts) => {
		try {
			printCronJson(await callGatewayFromCli("cron.status", opts, {}));
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
function registerCronListCommand(cron) {
	addGatewayClientOptions(cron.command("list").description("List automations").option("--all", "Include disabled jobs", false).option("--agent <id>", "Filter by agent id").option("--json", "Output JSON", false).action(async (opts) => {
		try {
			const listParams = { includeDisabled: Boolean(opts.all) };
			const agentId = normalizeOptionalString(opts.agent);
			if (typeof opts.agent === "string" && !agentId) throw new Error("--agent must not be blank");
			if (agentId) listParams.agentId = sanitizeAgentId(agentId);
			const res = await listCronJobsFromGateway(opts, listParams);
			if (opts.json) {
				printCronJson(enrichCronJsonWithStatus(res));
				return;
			}
			const jobs = res?.jobs ?? [];
			const deliveryPreviews = coerceCronDeliveryPreviews(res);
			printCronList(jobs, defaultRuntime, { deliveryPreviews });
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
function registerCronAddCommand(cron) {
	addGatewayClientOptions(registerCronMutationOptions(createCronOutputCommand(cron, "add").description("Add an automation").argument("[scheduleOrName]", "Schedule string, or job name when using --at/--every/--cron").argument("[message]", "Agent message when using a positional schedule"), "add").option("--declaration-key <key>", "Idempotent declaration identity key").option("--disabled", "Create job disabled", false).action(async (nameArg, messageArg, opts, cmd) => {
		try {
			const hasScheduleFlag = typeof opts.at === "string" || typeof opts.cron === "string" || typeof opts.every === "string" || typeof opts.onExit === "string" || typeof opts.streamCommand === "string";
			const positionalSchedule = hasScheduleFlag ? void 0 : nameArg;
			const schedule = resolveCronCreateScheduleFromArgs({
				...opts,
				positionalSchedule
			});
			const wakeMode = normalizeOptionalString(opts.wake) ?? "now";
			if (wakeMode !== "now" && wakeMode !== "next-heartbeat") throw new Error("--wake must be now or next-heartbeat");
			const rawAgentId = normalizeOptionalString(opts.agent);
			const agentId = rawAgentId ? sanitizeAgentId(rawAgentId) : void 0;
			const hasAnnounce = Boolean(opts.announce) || opts.deliver === true;
			const hasNoDeliver = opts.deliver === false;
			const webhookUrl = typeof opts.webhook === "string" ? normalizeHttpWebhookUrl(opts.webhook) : null;
			if (typeof opts.webhook === "string" && !webhookUrl) throw new Error("--webhook must be a valid http(s) URL");
			const hasWebhook = Boolean(webhookUrl);
			if ([
				hasAnnounce,
				hasNoDeliver,
				hasWebhook
			].filter(Boolean).length > 1) throw new Error("Choose at most one of --announce, --no-deliver, or --webhook");
			const payload = (() => {
				const systemEvent = normalizeOptionalString(opts.systemEvent) ?? "";
				const optionMessage = normalizeOptionalString(opts.message);
				const positionalMessage = normalizeOptionalString(messageArg);
				const commandShell = normalizeOptionalString(opts.command);
				const commandArgv = parseCronCommandArgv(opts.commandArgv);
				const scriptPath = normalizeOptionalString(opts.script);
				const toolsAllow = parseCronToolsAllow(opts.tools);
				if (optionMessage && positionalMessage && optionMessage !== positionalMessage) throw new Error("Pass the automation message either positionally or with --message, not both.");
				const message = optionMessage ?? positionalMessage ?? "";
				if (commandShell && commandArgv) throw new Error("Pass command payload either with --command or --command-argv, not both.");
				if ([
					Boolean(systemEvent),
					Boolean(message),
					Boolean(commandShell) || Boolean(commandArgv),
					Boolean(scriptPath)
				].filter(Boolean).length !== 1) throw new Error("Choose exactly one payload: --system-event, --message, --command, or --script");
				if (systemEvent) return {
					kind: "systemEvent",
					text: systemEvent,
					...toolsAllow ? { toolsAllow } : {}
				};
				if (scriptPath) {
					if (opts.timeoutSeconds !== void 0) throw new Error("Use --script-timeout-seconds for script jobs, not --timeout-seconds.");
					const scriptTimeoutSeconds = parseStrictPositiveInteger(opts.scriptTimeoutSeconds);
					if (opts.scriptTimeoutSeconds !== void 0 && scriptTimeoutSeconds === void 0) throw new Error("Invalid --script-timeout-seconds (must be a positive integer).");
					const scriptToolBudget = parseStrictPositiveInteger(opts.scriptToolBudget);
					if (opts.scriptToolBudget !== void 0 && scriptToolBudget === void 0) throw new Error("Invalid --script-tool-budget (must be a positive integer).");
					return {
						kind: "script",
						scriptPath,
						timeoutSeconds: scriptTimeoutSeconds,
						toolBudget: scriptToolBudget,
						toolsAllow
					};
				}
				const timeoutSeconds = parseStrictPositiveInteger(opts.timeoutSeconds);
				if (opts.timeoutSeconds !== void 0 && timeoutSeconds === void 0) throw new Error("Invalid --timeout-seconds (must be a positive integer).");
				if (commandShell || commandArgv) {
					const rawNoOutputTimeoutSeconds = opts.noOutputTimeoutSeconds ?? (typeof opts.outputTimeoutSeconds === "string" || typeof opts.outputTimeoutSeconds === "number" ? opts.outputTimeoutSeconds : void 0);
					const noOutputTimeoutSeconds = parseStrictPositiveInteger(rawNoOutputTimeoutSeconds);
					if (rawNoOutputTimeoutSeconds !== void 0 && noOutputTimeoutSeconds === void 0) throw new Error("Invalid --no-output-timeout-seconds (must be a positive integer).");
					const outputMaxBytes = parseStrictPositiveInteger(opts.outputMaxBytes);
					if (opts.outputMaxBytes !== void 0 && outputMaxBytes === void 0) throw new Error("Invalid --output-max-bytes (must be a positive integer).");
					return {
						kind: "command",
						argv: commandArgv ?? [
							"sh",
							"-lc",
							commandShell ?? ""
						],
						cwd: normalizeOptionalString(opts.commandCwd),
						env: parseCronCommandEnv(opts.commandEnv),
						input: typeof opts.commandInput === "string" ? opts.commandInput : void 0,
						timeoutSeconds,
						noOutputTimeoutSeconds,
						outputMaxBytes,
						...toolsAllow ? { toolsAllow } : {}
					};
				}
				return {
					kind: "agentTurn",
					message,
					model: normalizeOptionalString(opts.model),
					fallbacks: parseCronFallbacks(opts.fallbacks),
					thinking: normalizeOptionalString(opts.thinking),
					timeoutSeconds,
					lightContext: opts.lightContext === true ? true : void 0,
					toolsAllow
				};
			})();
			const resolvedPayload = await (async () => {
				if (payload.kind !== "script") return payload;
				const { scriptPath, ...scriptPayload } = payload;
				return {
					...scriptPayload,
					script: await readCronPayloadScript(scriptPath)
				};
			})();
			const sessionSource = cmd.getOptionValueSource("session");
			const sessionTargetRaw = normalizeOptionalString(opts.session) ?? "";
			const inferredSessionTarget = resolvedPayload.kind === "agentTurn" || resolvedPayload.kind === "command" || resolvedPayload.kind === "script" ? "isolated" : "main";
			const sessionTarget = sessionSource === "cli" ? normalizeCronSessionTargetOption(sessionTargetRaw) || "" : inferredSessionTarget;
			const isCustomSessionTarget = normalizeLowercaseStringOrEmpty(sessionTarget).startsWith("session:") && Boolean(normalizeOptionalString(sessionTarget.slice(8)));
			const isIsolatedLikeSessionTarget = sessionTarget === "isolated" || sessionTarget === "current" || isCustomSessionTarget;
			if (sessionTarget !== "main" && !isIsolatedLikeSessionTarget) throw new Error("--session must be main, isolated, current, or session:<id>");
			if (opts.deleteAfterRun && opts.keepAfterRun) throw new Error("Choose --delete-after-run or --keep-after-run, not both");
			if (sessionTarget === "main" && resolvedPayload.kind !== "systemEvent" && resolvedPayload.kind !== "script") throw new Error("Main jobs require --system-event or --script.");
			if (resolvedPayload.kind === "script" && sessionTarget !== "main" && sessionTarget !== "isolated") throw new Error("Script jobs require --session main or --session isolated.");
			if (isIsolatedLikeSessionTarget && resolvedPayload.kind !== "agentTurn" && resolvedPayload.kind !== "command" && resolvedPayload.kind !== "script") throw new Error("Isolated jobs require --message, --command, or --script.");
			if ((opts.announce || typeof opts.deliver === "boolean") && (!isIsolatedLikeSessionTarget || resolvedPayload.kind !== "agentTurn" && resolvedPayload.kind !== "command" && resolvedPayload.kind !== "script")) throw new Error("--announce/--no-deliver require a non-main agentTurn, command, or script session target.");
			const accountId = normalizeOptionalString(opts.account);
			const threadId = parseCronThreadIdOption(opts.threadId);
			const hasThreadId = typeof threadId === "number";
			const hasChatDeliveryTarget = cmd.getOptionValueSource("channel") === "cli" || typeof opts.to === "string" || Boolean(accountId) || hasThreadId;
			if (hasChatDeliveryTarget && (!isIsolatedLikeSessionTarget || resolvedPayload.kind !== "agentTurn" && resolvedPayload.kind !== "command" && resolvedPayload.kind !== "script")) throw new Error("--channel, --to, --account, and --thread-id require a non-main agentTurn, command, or script job with delivery.");
			if (hasWebhook && hasChatDeliveryTarget) throw new Error("--webhook cannot be combined with chat delivery options.");
			const deliveryMode = hasWebhook ? "webhook" : isIsolatedLikeSessionTarget && (resolvedPayload.kind === "agentTurn" || resolvedPayload.kind === "command" || resolvedPayload.kind === "script") ? hasAnnounce ? "announce" : hasNoDeliver ? "none" : "announce" : void 0;
			const optionName = normalizeOptionalString(opts.name);
			const positionalName = hasScheduleFlag ? normalizeOptionalString(nameArg) : void 0;
			if (optionName && positionalName && optionName !== positionalName) throw new Error("Pass the automation name either positionally or with --name, not both.");
			const name = optionName ?? positionalName ?? "";
			if (!name) throw new Error("Cron job name is required. Pass a name or --name <name>.");
			const description = normalizeOptionalString(opts.description);
			const declarationKey = normalizeOptionalString(opts.declarationKey);
			if (typeof opts.declarationKey === "string" && !declarationKey) throw new Error("--declaration-key must not be blank");
			const displayName = normalizeOptionalString(opts.displayName);
			if (typeof opts.displayName === "string" && !displayName) throw new Error("--display-name must not be blank");
			const pacingMin = normalizeOptionalString(opts.pacingMin);
			const pacingMax = normalizeOptionalString(opts.pacingMax);
			if (typeof opts.pacingMin === "string" && !pacingMin) throw new Error("--pacing-min must not be blank");
			if (typeof opts.pacingMax === "string" && !pacingMax) throw new Error("--pacing-max must not be blank");
			const sessionKey = normalizeOptionalString(opts.sessionKey);
			const triggerScriptPath = normalizeOptionalString(opts.triggerScript);
			if ((opts.triggerOnce || opts.triggerScript !== void 0) && !triggerScriptPath) throw new Error(`--trigger-${opts.triggerOnce ? "once requires --trigger-script" : "script must not be blank"}`);
			const trigger = triggerScriptPath && {
				script: await readCronTriggerScript(triggerScriptPath),
				...opts.triggerOnce ? { once: true } : {}
			};
			if ((resolvedPayload.kind === "agentTurn" || resolvedPayload.kind === "script") && !agentId) defaultRuntime.error(theme.warn("No --agent specified; the job will run with the configured default agent. Specify --agent to choose a specific agent, or set agents.defaults.systemAgent.agentId."));
			const params = {
				name,
				declarationKey,
				displayName,
				description,
				...declarationKey && cmd.getOptionValueSource("disabled") !== "cli" ? {} : { enabled: !opts.disabled },
				deleteAfterRun: opts.deleteAfterRun ? true : opts.keepAfterRun ? false : void 0,
				agentId,
				sessionKey,
				schedule,
				...pacingMin || pacingMax ? { pacing: {
					...pacingMin ? { min: pacingMin } : {},
					...pacingMax ? { max: pacingMax } : {}
				} } : {},
				trigger,
				sessionTarget,
				wakeMode,
				payload: resolvedPayload,
				delivery: deliveryMode ? {
					mode: deliveryMode,
					channel: hasWebhook ? void 0 : normalizeOptionalString(opts.channel),
					to: hasWebhook ? webhookUrl : normalizeOptionalString(opts.to),
					threadId: hasWebhook ? void 0 : threadId,
					accountId: hasWebhook ? void 0 : accountId,
					bestEffort: opts.bestEffortDeliver ? true : void 0
				} : void 0
			};
			printCronJson(await callGatewayFromCli("cron.add", opts, params));
			await warnIfCronSchedulerDisabled(opts);
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
//#endregion
//#region src/cli/cron-cli/register.cron-edit-options.ts
const assignIf = (target, key, value, shouldAssign) => {
	if (shouldAssign) target[key] = value;
};
async function resolveCronEditPayloadDeliveryPatch(opts, loadExistingJob, webhookUrl, commandCwd) {
	const patch = {};
	const hasSystemEventPatch = typeof opts.systemEvent === "string";
	const scriptPath = normalizeOptionalString(opts.script);
	const commandShell = normalizeOptionalString(opts.command);
	const commandArgv = parseCronCommandArgv(opts.commandArgv);
	if (commandShell && commandArgv) throw new Error("Pass command payload either with --command or --command-argv, not both.");
	const hasModel = typeof opts.model === "string";
	const model = normalizeOptionalString(opts.model);
	if (hasModel && opts.clearModel) throw new Error("Use --model or --clear-model, not both");
	const hasThinking = typeof opts.thinking === "string";
	const thinking = normalizeOptionalString(opts.thinking);
	if (hasThinking && opts.clearThinking) throw new Error("Use --thinking or --clear-thinking, not both");
	const fallbacks = parseCronFallbacks(opts.fallbacks);
	if (typeof opts.fallbacks === "string" && opts.clearFallbacks) throw new Error("Use --fallbacks or --clear-fallbacks, not both");
	const toolsAllow = parseCronToolsAllow(opts.tools);
	const timeoutSecondsValue = opts.timeoutSeconds;
	const rawTimeoutSeconds = timeoutSecondsValue === void 0 ? void 0 : typeof timeoutSecondsValue === "string" || typeof timeoutSecondsValue === "number" ? String(timeoutSecondsValue).trim() : "";
	if (rawTimeoutSeconds !== void 0 && !/^\d+$/u.test(rawTimeoutSeconds)) throw new Error("Invalid --timeout-seconds (must be a positive integer).");
	const timeoutSeconds = rawTimeoutSeconds === void 0 ? void 0 : Number(rawTimeoutSeconds);
	const hasTimeoutSeconds = typeof timeoutSeconds === "number" && Number.isSafeInteger(timeoutSeconds) && timeoutSeconds > 0;
	if (rawTimeoutSeconds !== void 0 && !hasTimeoutSeconds) throw new Error("Invalid --timeout-seconds (must be a positive integer).");
	const rawNoOutputTimeoutSeconds = opts.noOutputTimeoutSeconds ?? (typeof opts.outputTimeoutSeconds === "string" || typeof opts.outputTimeoutSeconds === "number" ? opts.outputTimeoutSeconds : void 0);
	const noOutputTimeoutSeconds = parseStrictPositiveInteger(rawNoOutputTimeoutSeconds);
	if (rawNoOutputTimeoutSeconds !== void 0 && noOutputTimeoutSeconds === void 0) throw new Error("Invalid --no-output-timeout-seconds (must be a positive integer).");
	const outputMaxBytes = parseStrictPositiveInteger(opts.outputMaxBytes);
	if (opts.outputMaxBytes !== void 0 && outputMaxBytes === void 0) throw new Error("Invalid --output-max-bytes (must be a positive integer).");
	const scriptTimeoutSeconds = parseStrictPositiveInteger(opts.scriptTimeoutSeconds);
	if (opts.scriptTimeoutSeconds !== void 0 && scriptTimeoutSeconds === void 0) throw new Error("Invalid --script-timeout-seconds (must be a positive integer).");
	const scriptToolBudget = parseStrictPositiveInteger(opts.scriptToolBudget);
	if (opts.scriptToolBudget !== void 0 && scriptToolBudget === void 0) throw new Error("Invalid --script-tool-budget (must be a positive integer).");
	const hasWebhookDelivery = Boolean(webhookUrl);
	const hasDeliveryModeFlag = opts.announce || typeof opts.deliver === "boolean" || hasWebhookDelivery;
	const threadId = parseCronThreadIdOption(opts.threadId);
	const hasDeliveryThreadId = typeof threadId === "number";
	const hasDeliveryTarget = typeof opts.channel === "string" || typeof opts.to === "string" || hasDeliveryThreadId || Boolean(opts.clearChannel) || Boolean(opts.clearTo) || Boolean(opts.clearThreadId);
	const hasDeliveryAccount = typeof opts.account === "string" || Boolean(opts.clearAccount);
	const hasBestEffort = typeof opts.bestEffortDeliver === "boolean";
	if (hasWebhookDelivery && (hasDeliveryTarget || hasDeliveryAccount)) throw new Error("--webhook cannot be combined with chat delivery options.");
	if (typeof opts.channel === "string" && opts.clearChannel) throw new Error("Use --channel or --clear-channel, not both");
	if (typeof opts.to === "string" && opts.clearTo) throw new Error("Use --to or --clear-to, not both");
	if (hasDeliveryThreadId && opts.clearThreadId) throw new Error("Use --thread-id or --clear-thread-id, not both");
	if (typeof opts.account === "string" && opts.clearAccount) throw new Error("Use --account or --clear-account, not both");
	const hasCommandInput = typeof opts.commandInput === "string";
	const hasCommandSpecificPayloadField = Boolean(commandShell) || Boolean(commandArgv) || Boolean(commandCwd) || hasCommandInput || opts.commandEnv !== void 0 || noOutputTimeoutSeconds !== void 0 || outputMaxBytes !== void 0;
	const hasToolsAllowPatch = typeof opts.tools === "string" || Array.isArray(opts.tools) || Boolean(opts.clearTools);
	const hasAgentTurnSpecificPayloadField = typeof opts.message === "string" || Boolean(model) || Boolean(opts.clearModel) || typeof opts.fallbacks === "string" || Boolean(opts.clearFallbacks) || Boolean(thinking) || Boolean(opts.clearThinking) || typeof opts.lightContext === "boolean";
	const hasScriptSpecificPayloadField = Boolean(scriptPath) || scriptTimeoutSeconds !== void 0 || scriptToolBudget !== void 0;
	if (hasTimeoutSeconds && hasScriptSpecificPayloadField) throw new Error("Use --script-timeout-seconds for script jobs, not --timeout-seconds.");
	if (hasTimeoutSeconds && hasSystemEventPatch) throw new Error("--timeout-seconds is not supported for systemEvent jobs.");
	let timeoutOnlyPayloadKind;
	if (hasTimeoutSeconds && !hasCommandSpecificPayloadField && !hasAgentTurnSpecificPayloadField) {
		const existingKind = (await loadExistingJob()).payload.kind;
		if (existingKind === "script") throw new Error("Use --script-timeout-seconds for script jobs, not --timeout-seconds.");
		if (existingKind === "systemEvent" || isSystemOwnedCronPayloadKind(existingKind)) throw new Error(`--timeout-seconds is not supported for ${existingKind} jobs.`);
		timeoutOnlyPayloadKind = existingKind;
	}
	let toolsOnlyPayloadKind;
	if (hasToolsAllowPatch && !hasSystemEventPatch && !hasAgentTurnSpecificPayloadField && !hasCommandSpecificPayloadField && !hasScriptSpecificPayloadField && !hasTimeoutSeconds) toolsOnlyPayloadKind = (await loadExistingJob()).payload.kind;
	const hasAgentTurnPayloadField = hasAgentTurnSpecificPayloadField || timeoutOnlyPayloadKind === "agentTurn" || hasToolsAllowPatch && toolsOnlyPayloadKind === "agentTurn";
	const hasCommandPayloadField = hasCommandSpecificPayloadField || timeoutOnlyPayloadKind === "command" || toolsOnlyPayloadKind === "command";
	const hasAgentTurnPatch = hasAgentTurnPayloadField;
	const hasCommandPatch = hasCommandPayloadField;
	const hasScriptPatch = hasScriptSpecificPayloadField || toolsOnlyPayloadKind === "script";
	const hasSystemEventOrToolsPatch = hasSystemEventPatch || toolsOnlyPayloadKind === "systemEvent";
	if ([
		hasSystemEventOrToolsPatch,
		hasAgentTurnPatch,
		hasCommandPatch,
		hasScriptPatch
	].filter(Boolean).length > 1) throw new Error("Choose at most one payload change");
	const assignToolsAllowPatch = (payload) => {
		if (opts.clearTools) payload.toolsAllow = ["*"];
		else if (toolsAllow) payload.toolsAllow = toolsAllow;
	};
	if (hasSystemEventOrToolsPatch) {
		const payload = { kind: "systemEvent" };
		assignIf(payload, "text", String(opts.systemEvent), hasSystemEventPatch);
		assignToolsAllowPatch(payload);
		patch.payload = payload;
	} else if (hasAgentTurnPatch) {
		const payload = { kind: "agentTurn" };
		assignIf(payload, "message", String(opts.message), typeof opts.message === "string");
		if (opts.clearModel) payload.model = null;
		else assignIf(payload, "model", model, Boolean(model));
		assignIf(payload, "fallbacks", fallbacks, typeof opts.fallbacks === "string");
		assignIf(payload, "fallbacks", null, Boolean(opts.clearFallbacks));
		if (opts.clearThinking) payload.thinking = null;
		else assignIf(payload, "thinking", thinking, Boolean(thinking));
		assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
		assignIf(payload, "lightContext", opts.lightContext, typeof opts.lightContext === "boolean");
		assignToolsAllowPatch(payload);
		patch.payload = payload;
	} else if (hasCommandPatch) {
		const payload = { kind: "command" };
		assignIf(payload, "argv", commandArgv, Boolean(commandArgv));
		assignIf(payload, "argv", [
			"sh",
			"-lc",
			commandShell
		], Boolean(commandShell));
		assignIf(payload, "cwd", commandCwd, Boolean(commandCwd));
		assignIf(payload, "env", parseCronCommandEnv(opts.commandEnv), opts.commandEnv !== void 0);
		assignIf(payload, "input", opts.commandInput, hasCommandInput);
		assignIf(payload, "timeoutSeconds", timeoutSeconds, hasTimeoutSeconds);
		assignIf(payload, "noOutputTimeoutSeconds", noOutputTimeoutSeconds, noOutputTimeoutSeconds !== void 0);
		assignIf(payload, "outputMaxBytes", outputMaxBytes, outputMaxBytes !== void 0);
		assignToolsAllowPatch(payload);
		patch.payload = payload;
	} else if (hasScriptPatch) {
		const payload = { kind: "script" };
		if (scriptPath) payload.script = await readCronPayloadScript(scriptPath);
		assignIf(payload, "timeoutSeconds", scriptTimeoutSeconds, scriptTimeoutSeconds !== void 0);
		assignIf(payload, "toolBudget", scriptToolBudget, scriptToolBudget !== void 0);
		assignToolsAllowPatch(payload);
		patch.payload = payload;
	}
	if (hasDeliveryModeFlag || hasDeliveryTarget || hasDeliveryAccount || hasBestEffort) {
		const delivery = {};
		if (hasDeliveryModeFlag) delivery.mode = hasWebhookDelivery ? "webhook" : opts.announce || opts.deliver === true ? "announce" : "none";
		else if (opts.bestEffortDeliver === true) delivery.mode = "announce";
		if (opts.clearChannel) delivery.channel = null;
		else if (typeof opts.channel === "string") {
			const channel = opts.channel.trim();
			delivery.channel = channel ? channel : void 0;
		}
		if (hasWebhookDelivery) delivery.to = webhookUrl;
		else if (opts.clearTo) delivery.to = null;
		else if (typeof opts.to === "string") {
			const to = opts.to.trim();
			delivery.to = to ? to : void 0;
		}
		if (opts.clearThreadId) delivery.threadId = null;
		else if (hasDeliveryThreadId) delivery.threadId = threadId;
		if (opts.clearAccount) delivery.accountId = null;
		else if (typeof opts.account === "string") {
			const account = opts.account.trim();
			delivery.accountId = account ? account : void 0;
		}
		if (typeof opts.bestEffortDeliver === "boolean") delivery.bestEffort = opts.bestEffortDeliver;
		patch.delivery = delivery;
	}
	return patch;
}
//#endregion
//#region src/cli/cron-cli/register.cron-edit.ts
async function readCronJobForEdit(opts, id) {
	try {
		return await callGatewayFromCli("cron.get", opts, { id });
	} catch (error) {
		if (!isUnknownCronGetMethodError(error)) throw error;
		const existing = (await listCronJobsFromGateway(opts, { includeDisabled: true }, { allowLegacyUnversionedPagination: true })).jobs.find((job) => job.id === id);
		if (!existing) throw new Error(`unknown automation id: ${id}`, { cause: error });
		return existing;
	}
}
function registerCronEditCommand(cron) {
	addGatewayClientOptions(registerCronMutationOptions(createCronOutputCommand(cron, "edit").description("Edit an automation (patch fields)").argument("<id>", "Job id"), "edit").option("--clear-display-name", "Restore the stable name in list and detail views", false).option("--enable", "Enable job", false).option("--disable", "Disable job", false).option("--clear-agent", "Unset agent and use default", false).option("--clear-session-key", "Unset session key", false).option("--clear-pacing", "Remove dynamic-cadence bounds", false).option("--clear-trigger", "Remove the condition trigger", false).option("--clear-thinking", "Remove the per-job thinking override (restore normal cron thinking precedence)", false).option("--clear-fallbacks", "Remove per-job fallback override", false).option("--clear-model", "Remove the per-job model override (restore normal cron model precedence)", false).option("--no-light-context", "Disable lightweight bootstrap context for agent jobs").option("--clear-tools", "Remove tool allow-list (use all tools)", false).option("--clear-channel", "Unset the delivery channel", false).option("--clear-to", "Unset the delivery destination", false).option("--clear-thread-id", "Unset the Telegram forum topic thread id", false).option("--clear-account", "Unset the per-job delivery account override", false).option("--no-best-effort-deliver", "Fail job when delivery fails").option("--failure-alert", "Enable failure alerts for this job").option("--no-failure-alert", "Disable failure alerts for this job").option("--failure-alert-after <n>", "Alert after N consecutive job errors").option("--failure-alert-channel <channel>", `Failure alert channel (${getCronChannelOptions()})`).option("--failure-alert-to <dest>", "Failure alert destination").option("--failure-alert-cooldown <duration>", "Minimum time between alerts (e.g. 1h, 30m)").option("--failure-alert-include-skipped", "Count consecutive skipped runs toward alerts").option("--failure-alert-exclude-skipped", "Alert only on execution errors").option("--failure-alert-mode <mode>", "Failure alert delivery mode (announce or webhook)").option("--failure-alert-account-id <id>", "Account ID for failure alert channel (multi-account setups)").action(async (id, opts) => {
		try {
			if (opts.clearTools && opts.tools !== void 0) throw new Error("Use --tools or --clear-tools, not both");
			const commandCwd = normalizeOptionalString(opts.commandCwd);
			if (typeof opts.commandCwd === "string" && !commandCwd) throw new Error("--command-cwd must not be blank");
			let existingJobPromise;
			let expectedConfigRevision;
			const readExistingCronJob = async () => {
				const existing = await (existingJobPromise ??= readCronJobForEdit(opts, String(id)));
				if (typeof existing.configRevision === "string") expectedConfigRevision = existing.configRevision;
				return existing;
			};
			const sessionTarget = typeof opts.session === "string" ? normalizeCronSessionTargetOption(opts.session) : void 0;
			if (typeof opts.session === "string" && !sessionTarget) throw new Error("--session must be main, isolated, current, or session:<id>");
			if (sessionTarget === "main" && (opts.message || opts.command || opts.commandArgv)) throw new Error("Main jobs cannot use --message or --command; use --system-event or --session isolated.");
			if ((sessionTarget === "current" || sessionTarget?.startsWith("session:")) && typeof opts.script === "string") throw new Error("Script jobs require --session main or --session isolated.");
			if ((sessionTarget === "isolated" || sessionTarget === "current" || sessionTarget?.startsWith("session:")) && opts.systemEvent) throw new Error("Isolated jobs cannot use --system-event; use --message, --command, or --session main.");
			const hasExplicitChatDelivery = parseCronThreadIdOption(opts.threadId) !== void 0 || typeof opts.channel === "string" || typeof opts.to === "string" || typeof opts.account === "string";
			if (sessionTarget === "main" && typeof opts.systemEvent === "string" && hasExplicitChatDelivery) throw new Error("--channel, --to, --account, and --thread-id require a non-main agentTurn or command job with delivery.");
			const webhookUrl = typeof opts.webhook === "string" ? normalizeHttpWebhookUrl(opts.webhook) ?? void 0 : void 0;
			if (typeof opts.webhook === "string" && !webhookUrl) throw new Error("--webhook must be a valid http(s) URL");
			const hasWebhookDelivery = Boolean(webhookUrl);
			if ([
				Boolean(opts.announce),
				typeof opts.deliver === "boolean",
				hasWebhookDelivery
			].filter(Boolean).length > 1) throw new Error("Choose at most one of --announce, --no-deliver, or --webhook.");
			const triggerScriptPath = normalizeOptionalString(opts.triggerScript);
			if (typeof opts.triggerScript === "string" && !triggerScriptPath) throw new Error("--trigger-script must not be blank");
			if (opts.clearTrigger && (triggerScriptPath || opts.triggerOnce)) throw new Error("Use --clear-trigger or trigger options, not both");
			const triggerScript = triggerScriptPath ? await readCronTriggerScript(triggerScriptPath) : void 0;
			const patch = {};
			if (typeof opts.name === "string") patch.name = opts.name;
			const displayName = normalizeOptionalString(opts.displayName);
			if (typeof opts.displayName === "string" && !displayName) throw new Error("--display-name must not be blank");
			if (displayName && opts.clearDisplayName) throw new Error("Use --display-name or --clear-display-name, not both");
			if (displayName) patch.displayName = displayName;
			if (opts.clearDisplayName) patch.displayName = null;
			if (typeof opts.description === "string") patch.description = opts.description;
			if (opts.enable && opts.disable) throw new Error("Choose --enable or --disable, not both");
			if (opts.enable) patch.enabled = true;
			if (opts.disable) patch.enabled = false;
			if (opts.deleteAfterRun && opts.keepAfterRun) throw new Error("Choose --delete-after-run or --keep-after-run, not both");
			if (opts.deleteAfterRun) patch.deleteAfterRun = true;
			if (opts.keepAfterRun) patch.deleteAfterRun = false;
			if (typeof opts.session === "string") patch.sessionTarget = sessionTarget;
			if (typeof opts.wake === "string") {
				const wakeMode = opts.wake.trim();
				if (wakeMode !== "now" && wakeMode !== "next-heartbeat") throw new Error("--wake must be now or next-heartbeat");
				patch.wakeMode = wakeMode;
			}
			const agentId = normalizeOptionalString(opts.agent);
			if (typeof opts.agent === "string" && !agentId) throw new Error("--agent must not be blank");
			if (agentId && opts.clearAgent) throw new Error("Use --agent or --clear-agent, not both");
			if (agentId) patch.agentId = sanitizeAgentId(agentId);
			if (opts.clearAgent) patch.agentId = null;
			const sessionKey = normalizeOptionalString(opts.sessionKey);
			if (typeof opts.sessionKey === "string" && !sessionKey) throw new Error("--session-key must not be blank");
			if (sessionKey && opts.clearSessionKey) throw new Error("Use --session-key or --clear-session-key, not both");
			if (sessionKey) patch.sessionKey = sessionKey;
			if (opts.clearSessionKey) patch.sessionKey = null;
			const pacingMin = normalizeOptionalString(opts.pacingMin);
			const pacingMax = normalizeOptionalString(opts.pacingMax);
			const hasPacingMin = typeof opts.pacingMin === "string";
			const hasPacingMax = typeof opts.pacingMax === "string";
			if (hasPacingMin && !pacingMin) throw new Error("--pacing-min must not be blank");
			if (hasPacingMax && !pacingMax) throw new Error("--pacing-max must not be blank");
			if (opts.clearPacing && (hasPacingMin || hasPacingMax)) throw new Error("Use --clear-pacing or pacing bounds, not both");
			if (opts.clearPacing) patch.pacing = null;
			else if (hasPacingMin || hasPacingMax) patch.pacing = {
				...(await readExistingCronJob()).pacing,
				...pacingMin ? { min: pacingMin } : {},
				...pacingMax ? { max: pacingMax } : {}
			};
			if (opts.clearTrigger) patch.trigger = null;
			else if (triggerScript !== void 0) patch.trigger = {
				...(await readExistingCronJob()).trigger,
				script: triggerScript,
				...opts.triggerOnce ? { once: true } : {}
			};
			else if (opts.triggerOnce) {
				const existing = await readExistingCronJob();
				if (!existing.trigger) throw new Error("--trigger-once requires an existing trigger or --trigger-script");
				patch.trigger = {
					...existing.trigger,
					once: true
				};
			}
			const scheduleRequest = resolveCronEditScheduleRequest(opts);
			if (scheduleRequest.kind === "direct") {
				if (scheduleRequest.schedule.kind === "stream") {
					const existing = await readExistingCronJob();
					if (existing.schedule.kind === "stream") {
						const metadataRequest = resolveCronEditScheduleRequest({
							streamCwd: opts.streamCwd,
							streamMode: opts.streamMode,
							streamMatch: opts.streamMatch,
							streamBatchMs: opts.streamBatchMs,
							streamMaxBatchBytes: opts.streamMaxBatchBytes
						});
						patch.schedule = {
							...metadataRequest.kind === "patch-existing-stream" ? applyExistingStreamSchedulePatch(existing.schedule, metadataRequest) : existing.schedule,
							command: scheduleRequest.schedule.command
						};
					} else {
						validateStreamScheduleMetadata(scheduleRequest.schedule);
						patch.schedule = scheduleRequest.schedule;
					}
				} else if (scheduleRequest.schedule.kind === "cron" && scheduleRequest.schedule.tz === void 0) {
					const existing = await readExistingCronJob();
					patch.schedule = existing.schedule.kind === "cron" && existing.schedule.tz !== void 0 ? {
						...scheduleRequest.schedule,
						tz: existing.schedule.tz
					} : scheduleRequest.schedule;
				} else patch.schedule = scheduleRequest.schedule;
			} else if (scheduleRequest.kind === "patch-existing-cron") patch.schedule = applyExistingCronSchedulePatch((await readExistingCronJob()).schedule, scheduleRequest);
			else if (scheduleRequest.kind === "patch-existing-stream") patch.schedule = applyExistingStreamSchedulePatch((await readExistingCronJob()).schedule, scheduleRequest);
			Object.assign(patch, await resolveCronEditPayloadDeliveryPatch(opts, readExistingCronJob, webhookUrl, commandCwd));
			const hasFailureAlertAfter = typeof opts.failureAlertAfter === "string";
			const hasFailureAlertChannel = typeof opts.failureAlertChannel === "string";
			const hasFailureAlertTo = typeof opts.failureAlertTo === "string";
			const hasFailureAlertCooldown = typeof opts.failureAlertCooldown === "string";
			const hasFailureAlertIncludeSkipped = typeof opts.failureAlertIncludeSkipped === "boolean";
			const hasFailureAlertExcludeSkipped = typeof opts.failureAlertExcludeSkipped === "boolean";
			const hasFailureAlertMode = typeof opts.failureAlertMode === "string";
			const hasFailureAlertAccountId = typeof opts.failureAlertAccountId === "string";
			if (hasFailureAlertIncludeSkipped && hasFailureAlertExcludeSkipped) throw new Error("Use either --failure-alert-include-skipped or --failure-alert-exclude-skipped.");
			const hasFailureAlertFields = hasFailureAlertAfter || hasFailureAlertChannel || hasFailureAlertTo || hasFailureAlertCooldown || hasFailureAlertIncludeSkipped || hasFailureAlertExcludeSkipped || hasFailureAlertMode || hasFailureAlertAccountId;
			const failureAlertFlag = typeof opts.failureAlert === "boolean" ? opts.failureAlert : void 0;
			if (failureAlertFlag === false && hasFailureAlertFields) throw new Error("Use --no-failure-alert alone (without failure-alert-* options).");
			if (failureAlertFlag === false) patch.failureAlert = false;
			else if (failureAlertFlag === true || hasFailureAlertFields) {
				const failureAlert = {};
				if (hasFailureAlertAfter) {
					const after = parseStrictPositiveInteger(opts.failureAlertAfter);
					if (after === void 0) throw new Error("Invalid --failure-alert-after (must be a positive integer).");
					failureAlert.after = after;
				}
				if (hasFailureAlertChannel) failureAlert.channel = normalizeOptionalLowercaseString(opts.failureAlertChannel);
				if (hasFailureAlertTo) {
					const to = normalizeOptionalString(opts.failureAlertTo) ?? "";
					failureAlert.to = to ? to : void 0;
				}
				if (hasFailureAlertCooldown) {
					let cooldownMs;
					try {
						cooldownMs = parseDurationMs(String(opts.failureAlertCooldown));
					} catch {
						throw new Error("Invalid --failure-alert-cooldown.");
					}
					failureAlert.cooldownMs = cooldownMs;
				}
				if (hasFailureAlertIncludeSkipped || hasFailureAlertExcludeSkipped) failureAlert.includeSkipped = hasFailureAlertIncludeSkipped;
				if (hasFailureAlertMode) {
					const mode = normalizeOptionalLowercaseString(opts.failureAlertMode);
					if (mode !== "announce" && mode !== "webhook") throw new Error("Invalid --failure-alert-mode (must be 'announce' or 'webhook').");
					failureAlert.mode = mode;
				}
				if (hasFailureAlertAccountId) {
					const accountId = normalizeOptionalString(opts.failureAlertAccountId) ?? "";
					failureAlert.accountId = accountId ? accountId : void 0;
				}
				patch.failureAlert = failureAlert;
			}
			const res = await callGatewayFromCli("cron.update", opts, {
				id,
				patch,
				...expectedConfigRevision !== void 0 ? { expectedConfigRevision } : {}
			});
			defaultRuntime.writeJson(res);
			await warnIfCronSchedulerDisabled(opts);
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
//#endregion
//#region src/cli/cron-cli/register.cron-scratch.ts
function parseExpectedRevision(value) {
	if (value === void 0) return;
	const revision = parseStrictNonNegativeInteger(value);
	if (revision === void 0) throw new Error("--expected-revision must be a non-negative integer");
	return revision;
}
function registerCronScratchCommand(cron) {
	addGatewayClientOptions(createCronOutputCommand(cron, "scratch").description("Read or replace an automation's private scratch").argument("<id>", "Job id").option("--set <text>", "Replace scratch with exact text").option("--file <path>", "Replace scratch from a file, or - for stdin").option("--unset", "Remove the scratch row", false).option("--expected-revision <n>", "Require the current scratch revision").action(async (id, opts) => {
		try {
			const mutations = [
				opts.set !== void 0,
				opts.file !== void 0,
				opts.unset === true
			].filter(Boolean).length;
			if (mutations > 1) throw new Error("choose only one of --set, --file, or --unset");
			const current = await callGatewayFromCli("cron.scratch.get", opts, { id: String(id) });
			if (mutations === 0) {
				if (opts.json) printCronJson(current);
				else if (current.scratch) process.stdout.write(current.scratch.content);
				return;
			}
			const expectedRevision = parseExpectedRevision(opts.expectedRevision) ?? current.currentRevision;
			const content = opts.unset ? null : opts.file !== void 0 ? await readCronScratchContent(String(opts.file)) : String(opts.set ?? "");
			const result = await callGatewayFromCli("cron.scratch.set", opts, {
				id: String(id),
				content,
				expectedRevision
			});
			if (!result.ok) throw new Error(`cron scratch changed concurrently (current revision ${result.currentRevision})`);
			printCronJson(result);
		} catch (error) {
			handleCronCliError(error);
		}
	}));
}
//#endregion
//#region src/cli/cron-cli/register.cron-simple.ts
const CRON_RUN_WAIT_TIMEOUT_DEFAULT = "10m";
const CRON_RUN_WAIT_POLL_INTERVAL_DEFAULT = "2s";
function parseCronRunWaitDuration(raw, label) {
	const durationMs = parseDurationMs(typeof raw === "string" || typeof raw === "number" || typeof raw === "bigint" ? String(raw) : "", { defaultUnit: "ms" });
	if (!Number.isFinite(durationMs) || durationMs < 0) throw new Error(`invalid ${label}`);
	return resolveTimerTimeoutMs(durationMs, 0, 0);
}
function parseCronRunPollInterval(raw) {
	const durationMs = parseCronRunWaitDuration(raw, "--poll-interval");
	if (durationMs <= 0) throw new Error("invalid --poll-interval");
	return resolvePositiveTimerTimeoutMs(durationMs, 2e3);
}
async function waitForCronRunCompletion(params) {
	const startedAt = performance.now();
	let hasPolled = false;
	for (;;) {
		const elapsedBeforePollMs = Math.floor(performance.now() - startedAt);
		if (hasPolled && elapsedBeforePollMs >= params.timeoutMs) throw new Error(`timed out waiting for cron run ${params.runId}`);
		const remainingMs = Math.max(1, params.timeoutMs - elapsedBeforePollMs);
		const configuredTimeoutMs = parseTimeoutMs(params.opts.timeout);
		const pollTimeoutMs = configuredTimeoutMs === void 0 ? remainingMs : Math.min(configuredTimeoutMs, remainingMs);
		hasPolled = true;
		const pollOpts = {
			...params.opts,
			timeout: String(pollTimeoutMs)
		};
		const entry = (await callGatewayFromCli("cron.runs", pollOpts, {
			id: params.jobId,
			runId: params.runId,
			limit: 1
		})).entries?.[0];
		if (entry?.status === "ok" || entry?.status === "error" || entry?.status === "skipped") return entry;
		const elapsedMs = Math.floor(performance.now() - startedAt);
		if (elapsedMs >= params.timeoutMs) throw new Error(`timed out waiting for cron run ${params.runId}`);
		await sleep(Math.min(params.pollIntervalMs, params.timeoutMs - elapsedMs));
	}
}
function registerCronToggleCommand(params) {
	addGatewayClientOptions(createCronOutputCommand(params.cron, params.name).description(params.description).argument("<id>", "Job id").action(async (id, opts) => {
		try {
			printCronJson(await callGatewayFromCli("cron.update", opts, {
				id,
				patch: { enabled: params.enabled }
			}));
			if (!params.enabled && process.stderr.isTTY) process.stderr.write(`Note: 'openclaw cron list' hides disabled jobs by default. Use 'openclaw cron list --all' to see this job, or 'openclaw cron enable <id>' to re-enable it.\n`);
			await warnIfCronSchedulerDisabled(opts);
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
function registerCronSimpleCommands(cron) {
	addGatewayClientOptions(createCronOutputCommand(cron, "rm").description("Remove an automation").argument("<id>", "Job id").action(async (id, opts) => {
		try {
			printCronJson(await callGatewayFromCli("cron.remove", opts, { id }));
		} catch (err) {
			handleCronCliError(err);
		}
	}));
	registerCronToggleCommand({
		cron,
		name: "enable",
		description: "Enable an automation",
		enabled: true
	});
	registerCronToggleCommand({
		cron,
		name: "disable",
		description: "Disable an automation",
		enabled: false
	});
	addGatewayClientOptions(createCronOutputCommand(cron, "get").description("Get an automation as JSON").argument("<id>", "Job id").action(async (id, opts) => {
		try {
			printCronJson(await callGatewayFromCli("cron.get", opts, { id: String(id) }));
		} catch (err) {
			handleCronCliError(err);
		}
	}));
	addGatewayClientOptions(cron.command("show").description("Show an automation").argument("<id>", "Job id or exact name").option("--json", "Output JSON", false).action(async (id, opts) => {
		try {
			const { job, deliveryPreview } = await findCronJobByIdOrName(opts, String(id), { includeDeliveryPreview: !opts.json });
			if (!job) throw new Error(formatCronLookupMiss(String(id)));
			if (opts.json) {
				printCronJson(enrichCronJsonWithStatus(job));
				return;
			}
			printCronShow(job, defaultRuntime, { deliveryPreview });
		} catch (err) {
			handleCronCliError(err);
		}
	}));
	addGatewayClientOptions(createCronOutputCommand(cron, "runs").description("Show automation run history").argument("[id]", "Job id").option("--id <id>", "Job id (alternative to positional argument)").option("--run-id <runId>", "Filter by cron run id").option("--limit <n>", "Max entries (default 50)", "50").action(async (idArg, opts) => {
		try {
			const argId = normalizeOptionalString(idArg);
			const flagId = normalizeOptionalString(opts.id);
			if (argId && flagId && argId !== flagId) throw new Error(`Conflicting job ids: positional "${argId}" and --id "${flagId}".`);
			const id = argId ?? flagId;
			if (!id) throw new Error("Missing job id. Pass it positionally or with --id.");
			const limit = parseStrictPositiveInteger(opts.limit ?? "50");
			if (limit === void 0) throw new Error("Invalid --limit (must be a positive integer).");
			if (typeof opts.runId === "string" && !opts.runId.trim()) throw new Error("--run-id must not be blank");
			printCronJson(await callGatewayFromCli("cron.runs", opts, {
				id,
				...typeof opts.runId === "string" && opts.runId.trim() ? { runId: opts.runId } : {},
				limit
			}));
		} catch (err) {
			handleCronCliError(err);
		}
	}));
	addGatewayClientOptions(createCronOutputCommand(cron, "run").description("Run an automation now (debug)").argument("<id>", "Job id").option("--due", "Run only when due (default behavior in older versions)", false).option("--wait", "Wait for the queued run to finish", false).option("--wait-timeout <duration>", "Maximum time to wait for --wait", CRON_RUN_WAIT_TIMEOUT_DEFAULT).option("--poll-interval <duration>", "Polling interval for --wait", CRON_RUN_WAIT_POLL_INTERVAL_DEFAULT).action(async (id, opts, command) => {
		try {
			let waitTimeoutMs = 0;
			let pollIntervalMs = 0;
			if (opts.wait) {
				waitTimeoutMs = parseCronRunWaitDuration(opts.waitTimeout, "--wait-timeout");
				pollIntervalMs = parseCronRunPollInterval(opts.pollInterval);
			}
			if (command.getOptionValueSource("timeout") === "default") opts.timeout = "600000";
			const res = await callGatewayFromCli("cron.run", opts, {
				id,
				mode: opts.due ? "due" : "force"
			});
			const result = res;
			if (opts.wait && result?.ok && result.enqueued) {
				if (!result.runId) throw new Error("cron run did not return a runId to wait for");
				const run = await waitForCronRunCompletion({
					opts,
					jobId: String(id),
					runId: result.runId,
					timeoutMs: waitTimeoutMs,
					pollIntervalMs
				});
				const completionStatus = run.completionStatus ?? resolveCronCompletionStatus({
					status: run.status,
					delivered: run.delivered,
					deliveryStatus: run.deliveryStatus
				});
				const completedRun = {
					...run,
					completionStatus
				};
				printCronJson({
					...res,
					completed: true,
					status: run.status,
					completionStatus,
					run: completedRun
				});
				exitCliAfterOutput(defaultRuntime, completionStatus === "succeeded" ? 0 : 1);
			}
			printCronJson(res);
			exitCliAfterOutput(defaultRuntime, result?.ok && (result.ran || result.enqueued) ? 0 : 1);
		} catch (err) {
			handleCronCliError(err);
		}
	}));
}
//#endregion
//#region src/cli/cron-cli/register.ts
function inheritCronGatewayOptions(command) {
	for (const name of CRON_GATEWAY_OPTION_NAMES) {
		const inherited = inheritOptionFromParent(command, name);
		const source = command.parent?.getOptionValueSource(name);
		if (inherited !== void 0 && source && source !== "default") command.setOptionValueWithSource(name, inherited, source);
	}
}
function registerCronCli(program) {
	const cron = program.command("cron").alias("automations").description("Manage automations (via Gateway)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/cron", "docs.openclaw.ai/cli/cron")}\n${theme.muted("Upgrade tip:")} run \`openclaw doctor --fix\` to normalize legacy automation storage.\n`);
	addGatewayClientOptions(cron);
	cron.hook("preAction", (_parent, command) => inheritCronGatewayOptions(command));
	registerCronStatusCommand(cron);
	registerCronListCommand(cron);
	registerCronAddCommand(cron);
	registerCronSimpleCommands(cron);
	registerCronScratchCommand(cron);
	registerCronEditCommand(cron);
	setCommandJsonMode(cron, "output", ({ argv }) => isCronMachineOutput(argv));
	applyParentDefaultHelpAction(cron);
}
//#endregion
export { registerCronCli };