UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,616 lines • 82.4 kB
import { o as normalizeLowercaseStringOrEmpty, t as hasNonEmptyString } from "./string-coerce-1VuiLaaR.mjs";
import { R as timestampMsToIsoString, t as MAX_DATE_TIMESTAMP_MS } from "./number-coercion-CuVhdCBs.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { i as truncateWithMarker } from "./utf16-slice-D_ngcYKd.mjs";
import "./utils-DgZn2dLu.mjs";
import { M as parseAgentSessionKey } from "./session-key-C09ow4h0.mjs";
import { g as resolveSessionAgentId } from "./agent-scope-CG17uXFn.mjs";
import { u as CRON_TOOL_DISPLAY_SUMMARY } from "./tool-catalog-DyHnjiiS.mjs";
import { t as AUTOMATIONS_TOOL_NAME } from "./automations-tool-name-DBMZPbPL.mjs";
import { l as normalizeToolPolicyName, o as expandToolGroups } from "./tool-policy-shared-DWHChhWM.mjs";
import { r as createToolPolicyMatcher } from "./tool-policy-match-Dn8gpf-E.mjs";
import { o as expandPolicyWithPluginGroups, r as buildPluginToolGroups } from "./tool-policy-BKvvmcQR.mjs";
import { t as isStringOption } from "./string-readers-V5CfgPTP.mjs";
import { v as recordCronNextCheckProposal } from "./agent-run-registry-BaxMDLZF.mjs";
import { t as parseDurationMs } from "./parse-duration-C4JaJvt9.mjs";
import { r as getRuntimeConfig } from "./io.runtime-BsUXSsD4.mjs";
import "./config-DLycWFvB.mjs";
import { r as isInternalNonDeliveryChannel } from "./message-channel-constants-B3vfipkP.mjs";
import { Ou as CronRunLogEntrySchema, Tu as CronJobSchema, xu as CronDeliveryPreviewSchema, yu as CronAddResultSchema } from "./src-l2dWvEsN.mjs";
import { s as normalizeDeliveryContext } from "./delivery-context.shared-PF5njaoI.mjs";
import { n as resolveCronListPageNextOffset, t as readCanonicalCronListPage } from "./list-page-validation-gRErNGAY.mjs";
import { r as normalizeCronJobPatch, t as normalizeCronJobCreate } from "./normalize-Lu86QQtC.mjs";
import { t as parseCronPacingBounds } from "./pacing-BQDaWZD6.mjs";
import "./client-Bd9s4LXT.mjs";
import { t as GatewayClientRequestError } from "./request-error-Clu0Uyey.mjs";
import { H as extractTextFromChatContent } from "./user-facing-text-DC1sCo4O.mjs";
import { f as readCronScheduledToolProjection, l as setToolTerminalPresentation } from "./agent-tool-metadata-BiQWQ4ti.mjs";
import { t as jsonResult } from "./tool-results-BCM3fdVS.mjs";
import { c as readNonNegativeIntegerParam, m as readToolStringParam, u as readPositiveIntegerParam } from "./common-Dyxcwsy8.mjs";
import { a as getGatewayToolCallerIdentity, d as withGatewayToolCallerIdentity } from "./gateway-caller-context-DWr-fdGt.mjs";
import { t as extractDeliveryInfo } from "./delivery-info-Cbanwqua.mjs";
import { t as CRON_MANAGEMENT_METHODS } from "./cron-creator-authority-grant-B3kDRd63.mjs";
import { n as readGatewayCallOptions, t as callGatewayTool } from "./gateway-0EbWS5cg.mjs";
import { a as optionalNonNegativeIntegerSchema, c as optionalStringEnum, l as stringEnum, o as optionalPositiveIntegerSchema } from "./typebox-X-jU33BZ.mjs";
import { n as defineToolOutputSchema } from "./tool-output-schema-Bc4BKbzN.mjs";
import { a as bindCronRequesterGrant, i as bindCronManagementGrant } from "./cron-creator-authority-context-aezDQHzt.mjs";
import { t as gatewayCallOptionSchemaProperties } from "./gateway-schema-CKX1iL47.mjs";
import { a as resolveInternalSessionKey, o as resolveMainSessionAlias } from "./sessions-resolution-BvwJsG2r.mjs";
import "./sessions-helpers-VHCxnSFz.mjs";
import { t as assertCronDeliveryInputNonBlankFields } from "./delivery-target-validation-D5dmr1ev.mjs";
import { t as normalizeHttpWebhookUrl } from "./webhook-url-BqkZLKq3.mjs";
import { Type } from "typebox";
//#region src/cron/delivery-context.ts
/** Converts live or stored session routing into cron delivery config. */
function isInternalDeliveryContext(context) {
	const channel = context?.channel?.trim().toLowerCase();
	return Boolean(channel && (channel === "webchat" || isInternalNonDeliveryChannel(channel)));
}
/** Converts an active delivery context into cron announce delivery config. */
function cronDeliveryFromContext(context) {
	const normalized = normalizeDeliveryContext(context);
	if (!normalized?.to) return null;
	if (isInternalDeliveryContext(normalized)) return null;
	const delivery = {
		mode: "announce",
		to: normalized.to
	};
	if (normalized.channel) delivery.channel = normalized.channel;
	if (normalized.accountId) delivery.accountId = normalized.accountId;
	if (normalized.threadId != null) delivery.threadId = normalized.threadId;
	return delivery;
}
/** Recovers delivery context from a stored session key captured when the cron job was created. */
function resolveCronStoredDeliveryContext(params) {
	const sessionKey = params.sessionKey?.trim();
	if (!sessionKey) return;
	const { deliveryContext, threadId } = extractDeliveryInfo(sessionKey, { cfg: params.cfg });
	if (deliveryContext && threadId) return {
		...deliveryContext,
		threadId
	};
	return deliveryContext;
}
/** Resolves initial cron delivery, preferring the live context before falling back to session storage. */
function resolveCronCreationDelivery(params) {
	if (isInternalDeliveryContext(params.currentDeliveryContext)) return null;
	return cronDeliveryFromContext(params.currentDeliveryContext) ?? cronDeliveryFromContext(resolveCronStoredDeliveryContext({
		cfg: params.cfg,
		sessionKey: params.agentSessionKey
	}));
}
//#endregion
//#region src/agents/tools/cron-tool-canonicalize.ts
const CRON_SCHEDULE_KINDS$1 = [
	"at",
	"every",
	"cron",
	"on-exit",
	"stream"
];
const CRON_PAYLOAD_KINDS$1 = [
	"systemEvent",
	"agentTurn",
	"script",
	"command"
];
const CRON_FLAT_PAYLOAD_KEYS = [
	"message",
	"text",
	"script",
	"model",
	"fallbacks",
	"toolsAllow",
	"thinking",
	"timeoutSeconds",
	"toolBudget",
	"lightContext",
	"allowUnsafeExternalContent"
];
const CRON_FLAT_SCHEDULE_KEYS = [
	"kind",
	"at",
	"atMs",
	"every",
	"everyMs",
	"anchorMs",
	"cron",
	"expr",
	"tz",
	"stagger",
	"staggerMs",
	"exact",
	"command",
	"cwd",
	"mode",
	"match",
	"batchMs",
	"maxBatchBytes"
];
const CRON_RECOVERABLE_OBJECT_KEYS = /* @__PURE__ */ new Set([
	"name",
	"declarationKey",
	"displayName",
	"owner",
	"schedule",
	"pacing",
	"trigger",
	"sessionTarget",
	"wakeMode",
	"payload",
	"delivery",
	"enabled",
	"description",
	"deleteAfterRun",
	"agentId",
	"sessionKey",
	"failureAlert",
	"namePayload",
	"scheduleKind",
	"sessionTargetName",
	...CRON_FLAT_PAYLOAD_KEYS,
	...CRON_FLAT_SCHEDULE_KEYS
]);
const CRON_NESTABLE_OBJECT_KEYS = /* @__PURE__ */ new Set([
	"delivery",
	"failureAlert",
	"owner",
	"pacing",
	"payload",
	"schedule",
	"trigger"
]);
/** Path segments that would reach Object.prototype when assigned while nesting. */
const CRON_UNSAFE_KEY_SEGMENTS = /* @__PURE__ */ new Set([
	"__proto__",
	"constructor",
	"prototype"
]);
function isCronScheduleKind(value) {
	return isStringOption(value, CRON_SCHEDULE_KINDS$1);
}
function isCronPayloadKind(value) {
	return isStringOption(value, CRON_PAYLOAD_KINDS$1);
}
function isStringArrayOrNull(value) {
	return value === null || Array.isArray(value) && value.every((entry) => typeof entry === "string");
}
function moveDefinedField(source, target, from, to = from) {
	if (source[from] === void 0) return false;
	target[to] = source[from];
	delete source[from];
	return true;
}
function repairConcatenatedCronToolKeys(value) {
	if (!isRecord(value.payload) && isRecord(value.namePayload)) value.payload = { ...value.namePayload };
	const rawScheduleKind = value.scheduleKind;
	if (!isRecord(value.schedule)) {
		if (isRecord(rawScheduleKind)) value.schedule = { ...rawScheduleKind };
		else if (isCronScheduleKind(rawScheduleKind)) value.schedule = { kind: rawScheduleKind };
	} else if (isCronScheduleKind(rawScheduleKind) && !isCronScheduleKind(value.schedule.kind)) value.schedule = {
		...value.schedule,
		kind: rawScheduleKind
	};
	if (!hasNonEmptyString(value.name) && hasNonEmptyString(value.sessionTargetName)) value.name = value.sessionTargetName;
	delete value.namePayload;
	delete value.scheduleKind;
	delete value.sessionTargetName;
}
function setScheduleAtMs(schedule, value) {
	const atMs = typeof value === "number" ? value : Number(value);
	schedule.at = Number.isFinite(atMs) ? timestampMsToIsoString(Math.floor(atMs)) ?? value : value;
	if (!isCronScheduleKind(schedule.kind)) schedule.kind = "at";
}
function canonicalizeCronToolSchedule(value) {
	const schedule = isRecord(value.schedule) ? { ...value.schedule } : {};
	if (schedule.atMs !== void 0) {
		setScheduleAtMs(schedule, schedule.atMs);
		delete schedule.atMs;
	}
	for (const [from, to] of [
		["every", "everyMs"],
		["cron", "expr"],
		["stagger", "staggerMs"]
	]) if (schedule[to] === void 0) moveDefinedField(schedule, schedule, from, to);
	if (schedule.exact === true && schedule.staggerMs === void 0) schedule.staggerMs = 0;
	delete schedule.exact;
	if (isCronScheduleKind(value.kind) && !isCronScheduleKind(schedule.kind)) {
		schedule.kind = value.kind;
		delete value.kind;
	}
	if (moveDefinedField(value, schedule, "at") && !isCronScheduleKind(schedule.kind)) schedule.kind = "at";
	if (value.atMs !== void 0) {
		setScheduleAtMs(schedule, value.atMs);
		delete value.atMs;
	}
	for (const [kind, target, sources] of [
		[
			"every",
			"everyMs",
			["everyMs", "every"]
		],
		[
			"cron",
			"expr",
			["cron", "expr"]
		],
		[
			"on-exit",
			"command",
			["command"]
		]
	]) if (sources.some((source) => moveDefinedField(value, schedule, source, target)) && !isCronScheduleKind(schedule.kind)) schedule.kind = kind;
	for (const key of [
		"anchorMs",
		"tz",
		"staggerMs",
		"cwd",
		"mode",
		"match",
		"batchMs",
		"maxBatchBytes"
	]) moveDefinedField(value, schedule, key);
	moveDefinedField(value, schedule, "stagger", "staggerMs");
	if (value.exact === true && schedule.staggerMs === void 0) schedule.staggerMs = 0;
	delete value.exact;
	if (!isCronScheduleKind(schedule.kind)) {
		if (schedule.at !== void 0) schedule.kind = "at";
		else if (schedule.everyMs !== void 0) schedule.kind = "every";
		else if (schedule.expr !== void 0) schedule.kind = "cron";
		else if (schedule.command !== void 0) schedule.kind = "on-exit";
	}
	if (isRecord(value.schedule) || Object.keys(schedule).length > 0) value.schedule = schedule;
}
function canonicalizeCronToolPayload(value) {
	const payload = isRecord(value.payload) ? { ...value.payload } : {};
	for (const key of CRON_FLAT_PAYLOAD_KEYS) moveDefinedField(value, payload, key);
	if (isCronPayloadKind(value.kind) && !isCronPayloadKind(payload.kind)) {
		payload.kind = value.kind;
		delete value.kind;
	}
	if (!isCronPayloadKind(payload.kind)) {
		if (hasNonEmptyString(payload.script)) payload.kind = "script";
		else if (hasNonEmptyString(payload.message) || hasNonEmptyString(payload.model) || payload.model === null || hasNonEmptyString(payload.thinking) || typeof payload.timeoutSeconds === "number" && hasNonEmptyString(payload.text) || typeof payload.lightContext === "boolean" || typeof payload.allowUnsafeExternalContent === "boolean" || payload.fallbacks !== void 0 && isStringArrayOrNull(payload.fallbacks)) payload.kind = "agentTurn";
		else if (hasNonEmptyString(payload.text)) payload.kind = "systemEvent";
	}
	if (isRecord(value.payload) || Object.keys(payload).length > 0) value.payload = payload;
}
function repairPaddedCronKeys(value) {
	for (const key of Object.keys(value)) {
		const trimmed = key.trim();
		if (trimmed !== key && CRON_RECOVERABLE_OBJECT_KEYS.has(trimmed) && !(trimmed in value)) {
			value[trimmed] = value[key];
			delete value[key];
		}
	}
}
function stripCronKeyQuotes(segment) {
	if (segment.length >= 2) {
		const first = segment[0];
		if ((first === "\"" || first === "'") && segment.endsWith(first)) return segment.slice(1, -1);
	}
	return segment;
}
function nestDottedCronKey(value, key, entry, canonicalRoots) {
	const segments = stripCronKeyQuotes(key.trim()).split(".").map((segment) => segment.trim());
	if (segments[0] === "job") segments.shift();
	const [root, ...rest] = segments;
	if (rest.length === 0 || !root || !CRON_NESTABLE_OBJECT_KEYS.has(root)) return "ignored";
	if (segments.some((segment) => !segment || CRON_UNSAFE_KEY_SEGMENTS.has(segment))) return "ignored";
	let cursor = value;
	for (const [index, segment] of segments.entries()) {
		const last = index === segments.length - 1;
		if (segment in cursor) {
			if (last) return "conflict";
			const child = cursor[segment];
			if (!isRecord(child)) return "conflict";
			if (canonicalRoots.has(root)) return "recovered";
			cursor = child;
			continue;
		}
		if (last) {
			cursor[segment] = entry;
			return "recovered";
		}
		const child = {};
		cursor[segment] = child;
		cursor = child;
	}
	return "recovered";
}
/** Converts model-friendly cron tool shorthands into the nested gateway job/patch shape. */
function canonicalizeCronToolObject(value) {
	const next = { ...isRecord(value.data) ? value.data : isRecord(value.job) ? value.job : value };
	repairPaddedCronKeys(next);
	repairConcatenatedCronToolKeys(next);
	canonicalizeCronToolSchedule(next);
	canonicalizeCronToolPayload(next);
	return next;
}
const CRON_CREATE_NULLABLE_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set(["agentId", "sessionKey"]);
function deleteNullFields(record, keep) {
	for (const [key, entry] of Object.entries(record)) if (entry === null && !keep?.has(key)) delete record[key];
	else if (isRecord(entry)) deleteNullFields(entry);
}
/**
* Drops null-valued fields from a create job in place. The model-facing job
* schema is shared with update, where null means "clear this field"; on create
* there is nothing to clear, and the strict gateway cron.add contract rejects
* the nulls its update patch accepts.
*/
function stripCronCreateNullClears(value) {
	deleteNullFields(value, CRON_CREATE_NULLABLE_TOP_LEVEL_KEYS);
	return value;
}
/** Detects recovered update patches that contain no meaningful cron fields after normalization. */
function isEmptyRecoveredCronPatch(value) {
	if (!isRecord(value)) return true;
	const keys = Object.keys(value);
	return keys.length === 0 || keys.length === 1 && keys[0] === "payload" && isRecord(value.payload) && Object.keys(value.payload).length === 0;
}
/** Recovers cron job or patch fields that a model flattened beside the action arguments. */
function recoverCronObjectFromFlatParams(params) {
	const value = {};
	let found = false;
	for (const key of Object.keys(params)) if (CRON_RECOVERABLE_OBJECT_KEYS.has(key) && params[key] !== void 0) {
		value[key] = params[key];
		found = true;
	}
	const canonicalRoots = new Set(Object.keys(value));
	for (const key of Object.keys(params)) {
		if (CRON_RECOVERABLE_OBJECT_KEYS.has(key) || params[key] === void 0) continue;
		const outcome = nestDottedCronKey(value, key, params[key], canonicalRoots);
		if (outcome === "recovered") found = true;
		else if (outcome === "conflict") {
			value[key] = params[key];
			found = true;
		}
	}
	return {
		found,
		value: canonicalizeCronToolObject(value)
	};
}
/** Checks whether a recovered flat object has enough schedule/payload signal to create a job. */
function hasCronCreateSignal(value) {
	return value.schedule !== void 0 || value.at !== void 0 || value.atMs !== void 0 || value.everyMs !== void 0 || value.cron !== void 0 || value.expr !== void 0 || value.payload !== void 0 || value.message !== void 0 || value.text !== void 0;
}
const CRON_ACTIONS = [
	"status",
	"list",
	"get",
	"add",
	"update",
	"remove",
	"run",
	"runs",
	"next_check",
	"wake"
];
const CRON_SELF_ACTIONS = [
	"status",
	"list",
	"get",
	"remove",
	"runs",
	"next_check"
];
const CRON_SCHEDULE_KINDS = [
	"at",
	"every",
	"cron",
	"stream"
];
const CRON_SCHEDULE_KINDS_TRIGGERS_DISABLED = [
	"at",
	"every",
	"cron"
];
const CRON_WAKE_MODES = ["now", "next-heartbeat"];
const CRON_PAYLOAD_KINDS = [
	"systemEvent",
	"agentTurn",
	"script"
];
const CRON_PAYLOAD_KINDS_TRIGGERS_DISABLED = ["systemEvent", "agentTurn"];
const CRON_DELIVERY_MODES = [
	"none",
	"announce",
	"webhook"
];
const CRON_RUN_MODES = ["due", "force"];
function nullableStringSchema(description) {
	return Type.Optional(Type.Union([Type.String(), Type.Null()], { description }));
}
function nullableStringArraySchema(description) {
	return Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()], { description }));
}
function deliveryStringSchema(description) {
	return nullableStringSchema(`${description}, or null to clear`);
}
function createCronScheduleSchema(params) {
	return Type.Optional(Type.Object({
		kind: optionalStringEnum([...params.triggersEnabled ? CRON_SCHEDULE_KINDS : CRON_SCHEDULE_KINDS_TRIGGERS_DISABLED, ...params.management ? ["on-exit"] : []], { description: "Schedule kind" }),
		at: Type.Optional(Type.String({ description: "ISO-8601 time (kind=at)" })),
		everyMs: optionalPositiveIntegerSchema({
			description: "Interval ms (kind=every)",
			maximum: MAX_DATE_TIMESTAMP_MS
		}),
		anchorMs: optionalNonNegativeIntegerSchema({
			description: "Start anchor ms (kind=every)",
			maximum: MAX_DATE_TIMESTAMP_MS
		}),
		expr: Type.Optional(Type.String({ description: "Cron wall-time expr; never UTC-convert. Missing tz=Gateway local. Example \"0 18 * * *\", \"Asia/Shanghai\"." })),
		tz: Type.Optional(Type.String({ description: "IANA timezone for wall-clock fields; missing=Gateway host local timezone. Example \"Asia/Shanghai\"." })),
		staggerMs: optionalNonNegativeIntegerSchema({
			description: "Jitter ms (kind=cron)",
			maximum: MAX_DATE_TIMESTAMP_MS
		}),
		...params.triggersEnabled || params.management ? {
			command: Type.Optional(Type.Union([Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }), ...params.management ? [Type.String()] : []], { description: "Supervised source argv (kind=stream; disabled when cron.triggers.enabled=false)" })),
			cwd: Type.Optional(Type.String({ description: "Working directory (kind=stream)" })),
			mode: optionalStringEnum(["line", "match"]),
			match: Type.Optional(Type.String({ description: "Regex source (stream match mode)" })),
			batchMs: optionalNonNegativeIntegerSchema(),
			maxBatchBytes: optionalNonNegativeIntegerSchema()
		} : {}
	}, { additionalProperties: true }));
}
function createCronPacingSchema() {
	const pacing = Type.Object({
		min: Type.Optional(Type.String({ description: "Minimum dynamic delay" })),
		max: Type.Optional(Type.String({ description: "Maximum dynamic delay" }))
	}, {
		additionalProperties: false,
		description: "Dynamic-cadence bounds; at least one of min or max is required"
	});
	return Type.Optional(Type.Union([pacing, Type.Null()]));
}
function assertCronPacingInput(value) {
	if (value === void 0 || value === null) return;
	if (!isRecord(value)) throw new Error("cron pacing must be an object");
	parseCronPacingBounds(value);
}
function createCronPayloadSchema(params) {
	return Type.Optional(Type.Object({
		kind: optionalStringEnum([...params.triggersEnabled ? CRON_PAYLOAD_KINDS : CRON_PAYLOAD_KINDS_TRIGGERS_DISABLED, ...params.management ? ["command"] : []], { description: "Payload kind" }),
		text: Type.Optional(Type.String({ description: "systemEvent text" })),
		message: Type.Optional(Type.String({ description: "agentTurn prompt" })),
		...params.management ? { argv: Type.Optional(Type.Array(Type.String(), { description: "Existing command job argv" })) } : {},
		...params.triggersEnabled ? { script: Type.Optional(Type.String({ description: "Headless code-mode script" })) } : {},
		model: nullableStringSchema("Model override, or null to clear"),
		thinking: Type.Optional(Type.String({ description: "Thinking override" })),
		timeoutSeconds: Type.Optional(Type.Union([Type.Number({ minimum: 0 }), Type.Null()], { description: "Timeout seconds; null restores the default on update (omission preserves it)" })),
		...params.triggersEnabled ? { toolBudget: optionalPositiveIntegerSchema({ description: "Maximum script tool calls" }) } : {},
		lightContext: Type.Optional(Type.Boolean({ description: "Lightweight bootstrap context (skip full workspace context)" })),
		allowUnsafeExternalContent: Type.Optional(Type.Boolean({ description: "Allow untrusted external content in prompt" })),
		fallbacks: nullableStringArraySchema("Fallback models, or null to clear"),
		toolsAllow: nullableStringArraySchema("Allowed tool ids, or null to clear")
	}, { additionalProperties: true }));
}
function createCronTriggerSchema() {
	const trigger = Type.Object({
		script: Type.String({
			minLength: 1,
			maxLength: 65536
		}),
		once: Type.Optional(Type.Boolean())
	}, { additionalProperties: false });
	return Type.Optional(Type.Union([trigger, Type.Null()]));
}
function createCronDeliverySchema() {
	const failureDestinationObject = Type.Object({
		channel: deliveryStringSchema("Failure delivery channel"),
		to: deliveryStringSchema("Failure delivery target"),
		accountId: deliveryStringSchema("Failure delivery account"),
		mode: Type.Optional(Type.Union([
			Type.Literal("announce"),
			Type.Literal("webhook"),
			Type.Null()
		]))
	}, { additionalProperties: true });
	const completionDestinationObject = Type.Object({
		mode: Type.Literal("webhook"),
		to: Type.String({
			minLength: 1,
			description: "Completion webhook target; only valid with delivery.mode=announce"
		})
	}, { additionalProperties: true });
	return Type.Optional(Type.Object({
		mode: optionalStringEnum(CRON_DELIVERY_MODES, { description: "Delivery mode" }),
		channel: deliveryStringSchema("Delivery channel"),
		to: deliveryStringSchema("Delivery target"),
		threadId: Type.Optional(Type.Union([
			Type.String(),
			Type.Number(),
			Type.Null()
		], { description: "Thread/topic id" })),
		bestEffort: Type.Optional(Type.Boolean({ description: "Omitted/false requires requested delivery for successful completion; true lets successful execution complete and delete a one-shot despite failed/unknown delivery. Intentional silence succeeds in either mode." })),
		accountId: deliveryStringSchema("Delivery account"),
		failureDestination: Type.Optional(Type.Union([failureDestinationObject, Type.Null()], { description: "Failure-alert route override; required-delivery failures bypass after but share the execution-alert cooldown; null clears." })),
		completionDestination: Type.Optional(Type.Union([completionDestinationObject, Type.Null()], { description: "Completion webhook; requires delivery.mode=announce; null clears." }))
	}, { additionalProperties: true }));
}
function createCronFailureAlertSchema() {
	return Type.Optional(Type.Union([
		Type.Object({
			after: optionalPositiveIntegerSchema({ description: "Consecutive execution failures before alert; delivery failures bypass this threshold" }),
			channel: Type.Optional(Type.String({ description: "Alert channel" })),
			to: Type.Optional(Type.String({ description: "Alert target" })),
			cooldownMs: optionalNonNegativeIntegerSchema({ description: "Alert cooldown ms" }),
			includeSkipped: Type.Optional(Type.Boolean({ description: "Count skipped runs." })),
			mode: optionalStringEnum(["announce", "webhook"]),
			accountId: Type.Optional(Type.String())
		}, { additionalProperties: true }),
		Type.Literal(false),
		Type.Null()
	], { description: "Failure alert policy/route override; null clears. Route-backed jobs default to after=2 for execution failures and cooldownMs=3600000 for all failure alerts; false disables execution/delivery alerts but not the auto-disable safety notice." }));
}
function createCronToolSchema(options) {
	const triggersEnabled = options?.triggersEnabled !== false;
	const management = Boolean(options?.management);
	const managementOnly = options?.management === "only";
	const actions = managementOnly ? CRON_MANAGEMENT_METHODS.map((method) => method.slice(5)) : CRON_ACTIONS;
	const job = Type.Optional(Type.Object({
		name: Type.Optional(Type.String({ description: "Job name" })),
		declarationKey: Type.Optional(Type.String({
			description: "Idempotent declaration key (add only).",
			minLength: 1,
			maxLength: 200
		})),
		displayName: Type.Optional(Type.Union([Type.String({ maxLength: 200 }), Type.Null()], { description: "Human-readable label; null clears it" })),
		owner: Type.Optional(Type.Object({
			agentId: Type.Optional(Type.String()),
			sessionKey: Type.Optional(Type.String())
		}, { additionalProperties: false })),
		schedule: createCronScheduleSchema({
			triggersEnabled,
			management
		}),
		pacing: createCronPacingSchema(),
		...triggersEnabled ? { trigger: createCronTriggerSchema() } : {},
		sessionTarget: Type.Optional(Type.String({ description: "main | isolated | current (agentTurn default) | session:<id>" })),
		wakeMode: optionalStringEnum(CRON_WAKE_MODES, { description: "Wake timing" }),
		payload: createCronPayloadSchema({
			triggersEnabled,
			management
		}),
		delivery: createCronDeliverySchema(),
		...management || !options?.agentSessionKey?.trim() ? { agentId: nullableStringSchema("Agent id, or null to clear it") } : {},
		description: Type.Optional(Type.String({ description: "Human description" })),
		enabled: Type.Optional(Type.Boolean()),
		deleteAfterRun: Type.Optional(Type.Boolean({ description: "Delete one-shot after successful completion: delivery confirmed, not requested, intentionally silent, or explicitly bestEffort. Failed/unknown required delivery retains it disabled." })),
		sessionKey: nullableStringSchema("Explicit session key, or null to clear it"),
		failureAlert: createCronFailureAlertSchema()
	}, {
		additionalProperties: true,
		description: managementOnly ? "Partial update: only supplied fields change; null clears." : "Job fields. action=\"add\": full job. action=\"update\": partial patch — only supplied fields change; null clears."
	}));
	const schema = Type.Object({
		action: stringEnum(options?.selfRemoveOnly ? actions.filter((action) => CRON_SELF_ACTIONS.some((allowed) => allowed === action)) : actions),
		...gatewayCallOptionSchemaProperties(),
		includeDisabled: Type.Optional(Type.Boolean()),
		limit: optionalPositiveIntegerSchema({
			maximum: 200,
			description: "Maximum jobs returned by action=\"list\""
		}),
		offset: optionalNonNegativeIntegerSchema({ description: "Job offset for action=\"list\"; use nextOffset to load the next page" }),
		job: managementOnly ? Type.Optional(Type.Omit(job, ["declarationKey", "owner"])) : job,
		jobId: Type.Optional(Type.String()),
		id: Type.Optional(Type.String()),
		in: Type.Optional(Type.String({ description: "Relative duration for action=\"next_check\" (for example, \"15m\")" })),
		text: Type.Optional(Type.String({ description: "systemEvent text for action=\"wake\"" })),
		mode: optionalStringEnum(CRON_WAKE_MODES, { description: "Wake mode for action=\"wake\" (default next-heartbeat)" }),
		runMode: optionalStringEnum(CRON_RUN_MODES, { description: "Run mode for action=\"run\": omitted defaults to \"due\"; use \"force\" to trigger now." }),
		runId: Type.Optional(Type.String({ description: "Run id from action=\"run\" to read with action=\"runs\"" })),
		contextMessages: Type.Optional(Type.Integer({
			minimum: 0,
			maximum: 10
		})),
		agentId: Type.Optional(Type.String({ description: managementOnly ? "Agent filter for action=\"list\"." : "List filter for `action: \"list\"`; wake target override for `action: \"wake\"` (defaults to the calling agent when omitted on wake)" })),
		sessionKey: Type.Optional(Type.String({ description: "Wake target override for `action: \"wake\"`: route the event to another session owned by the calling agent. Defaults to the resolved calling-session key when omitted." }))
	}, { additionalProperties: true });
	if (options?.selfRemoveOnly) return Type.Pick(schema, [
		"action",
		"gatewayUrl",
		"gatewayToken",
		"timeoutMs",
		"includeDisabled",
		"jobId",
		"id",
		...managementOnly ? [] : ["in", "runId"]
	]);
	return managementOnly ? Type.Omit(schema, [
		"in",
		"text",
		"mode",
		"contextMessages",
		"sessionKey",
		"runId"
	]) : schema;
}
//#endregion
//#region src/agents/tools/cron-tool-context.ts
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
const REMINDER_CONTEXT_TOTAL_MAX = 700;
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
function stripExistingContext(text) {
	const index = text.indexOf(REMINDER_CONTEXT_MARKER);
	if (index === -1) return text;
	return text.slice(0, index).trim();
}
function truncateText(input, maxLen) {
	return truncateWithMarker(input, maxLen, {
		marker: "...",
		reserve: 3,
		trimEnd: true
	});
}
function extractMessageText(message) {
	const role = message.role;
	if (role !== "user" && role !== "assistant") return null;
	const text = extractTextFromChatContent(message.content);
	return text ? {
		role,
		text
	} : null;
}
async function buildReminderContextLines(params) {
	const maxMessages = Math.min(10, Math.max(0, Math.floor(params.contextMessages)));
	if (maxMessages <= 0) return [];
	const sessionKey = params.agentSessionKey?.trim();
	if (!sessionKey) return [];
	const cfg = getRuntimeConfig();
	const { alias } = resolveMainSessionAlias(cfg);
	const resolvedKey = resolveInternalSessionKey({
		key: sessionKey,
		alias
	});
	try {
		const res = await params.callGatewayTool("chat.history", params.gatewayOpts, {
			sessionKey: resolvedKey,
			agentId: params.agentId,
			limit: maxMessages
		});
		const recent = (Array.isArray(res?.messages) ? res.messages : []).map((msg) => extractMessageText(msg)).filter((msg) => Boolean(msg)).slice(-maxMessages);
		const lines = [];
		let total = 0;
		for (const entry of recent) {
			const line = `- ${entry.role === "user" ? "User" : "Assistant"}: ${truncateText(entry.text, REMINDER_CONTEXT_PER_MESSAGE_MAX)}`;
			total += line.length;
			if (total > REMINDER_CONTEXT_TOTAL_MAX) break;
			lines.push(line);
		}
		return lines;
	} catch {
		return [];
	}
}
//#endregion
//#region src/agents/tools/cron-tool-creator-cap.ts
/**
* Closed core-owned vocabulary a CLI backend may project its native tools into.
* Anything else is a backend contract bug and fails closed at capture time so a
* raw harness tool name can never become a persisted cron capability.
*/
const NATIVE_CRON_CREATOR_CAPABILITIES = /* @__PURE__ */ new Set([
	"read",
	"write",
	"edit",
	"apply_patch",
	"exec",
	"process",
	"web_search",
	"web_fetch"
]);
/** Fails closed on any backend-projected name outside the exact canonical vocabulary. */
function assertNativeCronCreatorCapabilities(names) {
	for (const name of names) if (!NATIVE_CRON_CREATOR_CAPABILITIES.has(name)) throw new Error(`cron creator authority rejected non-canonical native capability ${JSON.stringify(name)}`);
}
const CRON_CREATOR_AUTHORITY_RECOVERY_MESSAGE = "Retry from a fresh authenticated direct-local operator turn, or create/edit via the CLI or Gateway with an explicit finite toolsAllow list containing only currently visible tools; no automation changes were saved.";
const INCOMPLETE_CRON_CREATOR_AUTHORITY_MESSAGE = `Configured MCP authority is unavailable because this turn did not capture the complete model-callable tool surface. ${CRON_CREATOR_AUTHORITY_RECOVERY_MESSAGE}`;
/** No capture marker means this runtime has no deferred configured-MCP surface. */
function isCronCreatorToolCaptureComplete(captureRef) {
	return captureRef === void 0 || captureRef.value?.source === "final-executable-surface";
}
function assertInheritedCronToolCaptureReady(value, captureRef) {
	if ((isRecord(value) && isRecord(value.payload) ? value.payload : void 0)?.toolsAllowIsDefault !== true || isCronCreatorToolCaptureComplete(captureRef)) return;
	throw new Error(INCOMPLETE_CRON_CREATOR_AUTHORITY_MESSAGE);
}
function replaceWithEffectiveCronCreatorToolAllowlist(target, tools, toolMeta, options = {}) {
	target.length = 0;
	const captured = /* @__PURE__ */ new Map();
	for (const tool of tools) {
		const projection = readCronScheduledToolProjection(tool);
		const name = normalizeToolPolicyName(projection ? projection.targetTool : tool.name);
		if (!name) continue;
		const aliasName = projection ? normalizeToolPolicyName(tool.name) : void 0;
		const existing = captured.get(name);
		if (existing !== void 0) {
			if (aliasName && !existing.aliasName) existing.aliasName = aliasName;
			if (existing.execTarget && !projection?.execTarget) delete existing.execTarget;
			else if (existing.execTarget?.ask === "always" && projection?.execTarget?.ask !== "always") delete existing.execTarget.ask;
			continue;
		}
		const meta = toolMeta?.(tool);
		const pluginId = typeof meta?.pluginId === "string" ? normalizeToolPolicyName(meta.pluginId) : void 0;
		captured.set(name, {
			name,
			...pluginId ? { pluginId } : {},
			...aliasName && aliasName !== name ? { aliasName } : {},
			...projection?.execTarget ? { execTarget: { ...projection.execTarget } } : {}
		});
	}
	assertNativeCronCreatorCapabilities(options.canonicalToolNames ?? []);
	for (const name of options.canonicalToolNames ?? []) {
		if (captured.has(name)) continue;
		captured.set(name, name === "exec" && options.nativeExecTarget ? {
			name,
			execTarget: { ...options.nativeExecTarget }
		} : { name });
	}
	target.push(...captured.values());
}
/** Records the creator cap only after every runtime policy and schema quarantine has run. */
function captureFinalEffectiveCronCreatorToolAllowlist(target, captureRef, tools, toolMeta, options = {}) {
	replaceWithEffectiveCronCreatorToolAllowlist(target, tools, toolMeta, options);
	captureRef.value = {
		version: 1,
		source: "final-executable-surface"
	};
}
function normalizeCronCreatorToolsAllow(values) {
	const normalized = [];
	const seen = /* @__PURE__ */ new Set();
	for (const entry of values) {
		const tool = typeof entry === "string" ? { name: entry } : entry;
		const name = normalizeToolPolicyName(tool.name);
		if (!name || seen.has(name)) continue;
		seen.add(name);
		const pluginId = typeof tool.pluginId === "string" ? normalizeToolPolicyName(tool.pluginId) : void 0;
		const aliasName = typeof tool.aliasName === "string" ? normalizeToolPolicyName(tool.aliasName) : void 0;
		const execTarget = tool.execTarget?.host === "gateway" ? {
			host: "gateway",
			...tool.execTarget.ask === "always" ? { ask: "always" } : {}
		} : void 0;
		normalized.push({
			name,
			...pluginId ? { pluginId } : {},
			...aliasName && aliasName !== name ? { aliasName } : {},
			...execTarget ? { execTarget } : {}
		});
	}
	return normalized;
}
/** Restrict-only exec target present only when the creator's exec grant is host-pinned. */
function resolveCronCreatorExecToolTarget(entries) {
	const execEntry = normalizeCronCreatorToolsAllow(entries ?? []).find((tool) => tool.name === "exec");
	return execEntry?.execTarget ? { ...execEntry.execTarget } : void 0;
}
function hasCronTriggerScript(value) {
	return isRecord(value) && typeof value.script === "string" && value.script.trim().length > 0;
}
function classifyExplicitToolsAllow(payload) {
	if (!payload || !Object.hasOwn(payload, "toolsAllow")) return "absent";
	if (!Array.isArray(payload.toolsAllow)) return "resolved";
	const values = payload.toolsAllow.filter((entry) => typeof entry === "string");
	if (values.length === 0) return "empty";
	return values.some((entry) => {
		const normalized = normalizeToolPolicyName(entry);
		return normalized === "*" || normalized.startsWith("group:");
	}) ? "resolved" : "finite";
}
function explicitFiniteToolsNeedResolution(payload, creatorToolAllowlist) {
	if (classifyExplicitToolsAllow(payload) !== "finite") return false;
	const toolsAllow = payload?.toolsAllow;
	if (!Array.isArray(toolsAllow)) return false;
	const creatorNames = new Set(normalizeCronCreatorToolsAllow(creatorToolAllowlist ?? []).flatMap((tool) => tool.aliasName ? [tool.name, tool.aliasName] : [tool.name]));
	return expandToolGroups(toolsAllow.filter((entry) => typeof entry === "string")).some((name) => !creatorNames.has(name));
}
/** Whether an add needs the creator's complete authority rather than an explicit empty cap. */
function cronCreateRequiresCreatorAuthority(value, creatorToolAllowlist) {
	if (!isRecord(value)) return false;
	const payload = isRecord(value.payload) ? value.payload : void 0;
	const explicitToolsAllow = classifyExplicitToolsAllow(payload);
	if (explicitToolsAllow === "empty") return false;
	if (explicitToolsAllow === "finite") return explicitFiniteToolsNeedResolution(payload, creatorToolAllowlist);
	return hasCronTriggerScript(value.trigger) || payload?.kind === "agentTurn" || payload?.kind === "script" || explicitToolsAllow === "resolved";
}
function capCronJobToolsAllow(params) {
	const writesToolsAllow = Object.hasOwn(params.payload, "toolsAllow");
	if (params.payload.kind !== "agentTurn" && params.payload.kind !== "script" && !hasCronTriggerScript(params.trigger) && !writesToolsAllow) return;
	const creatorToolsAllow = normalizeCronCreatorToolsAllow(params.creatorToolAllowlist);
	const requestedRaw = writesToolsAllow ? params.payload.toolsAllow : params.defaultToolsAllow;
	const requestedToolsAllow = Array.isArray(requestedRaw) ? expandToolGroups(requestedRaw.filter((entry) => typeof entry === "string")) : ["*"];
	if (requestedToolsAllow.includes("*")) {
		if (params.payload.kind === "agentTurn" && !hasCronTriggerScript(params.trigger) && !params.creatorHoldsRuntimeAuthority) {
			params.payload.toolsAllow = ["*"];
			delete params.payload.toolsAllowIsDefault;
		} else {
			params.payload.toolsAllow = creatorToolsAllow.map((tool) => tool.name);
			params.payload.toolsAllowIsDefault = true;
		}
		return;
	}
	if (requestedToolsAllow.length === 0 || creatorToolsAllow.length === 0) {
		params.payload.toolsAllow = [];
		delete params.payload.toolsAllowIsDefault;
		return;
	}
	const pluginGroups = buildPluginToolGroups({
		tools: creatorToolsAllow,
		toolMeta: (tool) => tool.pluginId ? { pluginId: tool.pluginId } : void 0
	});
	const requestedPolicy = expandPolicyWithPluginGroups({ allow: requestedToolsAllow }, pluginGroups);
	const matches = createToolPolicyMatcher(requestedPolicy);
	params.payload.toolsAllow = creatorToolsAllow.filter((tool) => matches(tool.name) || tool.aliasName !== void 0 && matches(tool.aliasName)).map((tool) => tool.name);
	delete params.payload.toolsAllowIsDefault;
}
function capCronJobToolsAllowOnCreate(value, creatorToolAllowlist, creatorHoldsRuntimeAuthority) {
	if (!isRecord(value) || !isRecord(value.payload) || !creatorToolAllowlist) return;
	capCronJobToolsAllow({
		payload: value.payload,
		trigger: value.trigger,
		creatorToolAllowlist,
		creatorHoldsRuntimeAuthority
	});
}
function readCronPayloadKind(value) {
	return isRecord(value) && typeof value.kind === "string" ? value.kind : void 0;
}
/** Purely derives the agent-tool patch; current job state is requested only when required. */
function planCronJobUpdatePatch(params) {
	const patch = structuredClone(params.patch);
	const payload = isRecord(patch.payload) ? patch.payload : void 0;
	const explicitPayloadKind = readCronPayloadKind(payload);
	const explicitToolsAllow = classifyExplicitToolsAllow(payload);
	if (payload === void 0 && !Object.hasOwn(patch, "trigger")) return {
		kind: "ready",
		patch
	};
	if (explicitPayloadKind !== void 0 && explicitToolsAllow === "absent" && params.creatorAuthorityComplete !== false && !params.creatorToolAllowlist && !Object.hasOwn(patch, "trigger")) return {
		kind: "ready",
		patch
	};
	if (params.creatorAuthorityComplete === false && explicitFiniteToolsNeedResolution(payload, params.creatorToolAllowlist)) return { kind: "needs-creator-authority" };
	if (params.creatorToolAllowlist && (explicitToolsAllow === "empty" || explicitToolsAllow === "finite") && explicitPayloadKind !== void 0) {
		capCronJobToolsAllow({
			payload,
			trigger: patch.trigger,
			creatorToolAllowlist: params.creatorToolAllowlist
		});
		return {
			kind: "ready",
			patch
		};
	}
	if (!params.currentJob) return { kind: "needs-current-job" };
	const existingPayload = params.currentJob.payload;
	const existingPayloadRecord = isRecord(existingPayload) ? existingPayload : void 0;
	const existingPayloadKind = readCronPayloadKind(existingPayload);
	const payloadKind = explicitPayloadKind ?? existingPayloadKind;
	if (payload && payloadKind !== void 0) {
		payload.kind = payloadKind;
		patch.payload = payload;
	}
	const trigger = Object.hasOwn(patch, "trigger") ? patch.trigger : params.currentJob.trigger;
	const startsToolPayload = explicitPayloadKind !== void 0 && explicitPayloadKind !== existingPayloadKind && (payloadKind === "agentTurn" || payloadKind === "script");
	const startsToolTrigger = Object.hasOwn(patch, "trigger") && hasCronTriggerScript(trigger) && !hasCronTriggerScript(params.currentJob.trigger);
	const reusesDefaultAuthority = explicitToolsAllow === "absent" && (startsToolPayload || startsToolTrigger) && (existingPayloadRecord?.toolsAllowIsDefault === true || !Array.isArray(existingPayloadRecord?.toolsAllow) || existingPayloadRecord.toolsAllow.includes("*") && (payloadKind === "script" || startsToolTrigger));
	const needsResolvedAuthority = explicitToolsAllow === "resolved" || reusesDefaultAuthority || explicitFiniteToolsNeedResolution(payload, params.creatorToolAllowlist);
	if (needsResolvedAuthority && params.creatorAuthorityComplete === false) return { kind: "needs-creator-authority" };
	if (!needsResolvedAuthority && (explicitToolsAllow === "empty" || explicitToolsAllow === "finite") && params.creatorToolAllowlist) {
		capCronJobToolsAllow({
			payload,
			trigger,
			creatorToolAllowlist: params.creatorToolAllowlist
		});
		return {
			kind: "ready",
			patch
		};
	}
	if (!needsResolvedAuthority || !params.creatorToolAllowlist) return {
		kind: "ready",
		patch
	};
	const nextPayload = payload ?? {};
	if (payloadKind !== void 0) nextPayload.kind = payloadKind;
	patch.payload = nextPayload;
	capCronJobToolsAllow({
		payload: nextPayload,
		trigger,
		creatorToolAllowlist: params.creatorToolAllowlist,
		defaultToolsAllow: existingPayloadRecord && existingPayloadRecord.toolsAllowIsDefault !== true ? existingPayloadRecord.toolsAllow : void 0,
		creatorHoldsRuntimeAuthority: params.creatorHoldsRuntimeAuthority
	});
	return {
		kind: "ready",
		patch
	};
}
//#endregion
//#region src/agents/tools/cron-tool-output-schema.ts
const nullableNumber = Type.Union([Type.Number(), Type.Null()]);
const nullableString = Type.Union([Type.String(), Type.Null()]);
const job = CronJobSchema.properties;
const CompactCronJobSchema = Type.Object({
	id: job.id,
	name: job.name,
	agentId: job.agentId,
	updatedAtMs: Type.Optional(job.updatedAtMs),
	declarationKey: job.declarationKey,
	displayName: job.displayName,
	owner: job.owner,
	enabled: job.enabled,
	effectiveAgentId: Type.Optional(nullableString),
	nextRunAt: nullableString,
	nextRunAtMs: nullableNumber,
	scheduleKind: Type.Union([
		Type.Literal("at"),
		Type.Literal("every"),
		Type.Literal("cron"),
		Type.Literal("on-exit"),
		Type.Literal("stream")
	]),
	schedule: Type.Optional(Type.Union(job.schedule.anyOf.filter((schema) => [
		"at",
		"every",
		"cron"
	].includes(schema.properties.kind.const)))),
	trigger: Type.Optional(Type.Literal(true)),
	lastRunAt: nullableString,
	lastRunAtMs: nullableNumber,
	lastRunStatus: Type.Union([...job.lastRunStatus.anyOf, Type.Null()]),
	lastRunError: nullableString,
	runningAtMs: job.state.properties.runningAtMs,
	autoDisabled: job.state.properties.autoDisabled,
	lastDelivered: job.lastDelivered,
	lastDeliveryStatus: job.lastDeliveryStatus,
	lastDeliveryError: job.lastDeliveryError,
	deliverySuppressionReason: job.deliverySuppressionReason,
	lastFailureNotificationDelivered: job.lastFailureNotificationDelivered,
	lastFailureNotificationDeliveryStatus: job.lastFailureNotificationDeliveryStatus,
	lastFailureNotificationDeliveryError: job.lastFailureNotificationDeliveryError
}, { additionalProperties: false });
const FullCronListJobSchema = Type.Object({
	...job,
	effectiveAgentId: Type.Optional(nullableString)
}, { additionalProperties: false });
const page = {
	total: Type.Integer({ minimum: 0 }),
	offset: Type.Integer({ minimum: 0 }),
	limit: Type.Integer({ minimum: 0 }),
	hasMore: Type.Boolean(),
	nextOffset: Type.Union([Type.Integer({ minimum: 0 }), Type.Null()])
};
const CronListOutputSchema = Type.Object({
	jobs: Type.Array(Type.Union([CompactCronJobSchema, FullCronListJobSchema])),
	...page,
	snapshotRevision: Type.Optional(Type.String()),
	scope: Type.Optional(Type.Union([Type.Literal("caller"), Type.Literal("gateway")])),
	scopeHint: Type.Optional(Type.String()),
	deliveryPreviews: Type.Optional(Type.Object({}, { additionalProperties: CronDeliveryPreviewSchema }))
}, { additionalProperties: false });
const CronStatusOutputSchema = Type.Object({
	enabled: Type.Boolean(),
	triggersEnabled: Type.Optional(Type.Boolean()),
	storePath: Type.Optional(Type.String()),
	storage: Type.Optional(Type.Literal("sqlite")),
	sqlitePath: Type.Optional(Type.String()),
	jobs: Type.Optional(Type.Integer({ minimum: 0 })),
	nextWakeAtMs: Type.Optional(nullableNumber)
}, { additionalProperties: false });
const runEntry = CronRunLogEntrySchema.properties;
const processInstanceId = Type.Optional(Type.String());
const CronRunOutputSchema = Type.Union([
	Type.Object({
		ok: Type.Literal(false),
		processInstanceId
	}, { additionalProperties: false }),
	Type.Object({
		ok: Type.Literal(true),
		ran: Type.Literal(true),
		processInstanceId
	}, { additionalProperties: false }),
	Type.Object({
		ok: Type.Literal(true),
		enqueued: Type.Literal(true),
		runId: Type.String(),
		processInstanceId,
		run: Type.Optional(Type.Object({
			runId: runEntry.runId,
			status: runEntry.status,
			completionStatus: runEntry.completionStatus,
			error: runEntry.error,
			summary: runEntry.summary,
			deliveryStatus: runEntry.deliveryStatus,
			deliveryError: runEntry.deliveryError,
			durationMs: runEntry.durationMs
		}, { additionalProperties: true })),
		finished: Type.Optional(Type.Literal(true)),
		note: Type.Optional(Type.String())
	}, { additionalProperties: false }),
	Type.Object({
		ok: Type.Literal(true),
		ran: Type.Literal(false),
		reason: Type.Union([
			Type.Literal("disabled"),
			Type.Literal("not-due"),
			Type.Literal("already-running"),
			Type.Literal("invalid-spec"),
			Type.Literal("stopped"),
			Type.Literal("ownerless")
		]),
		processInstanceId
	}, { additionalProperties: false })
]);
/** Every non-throwing automations result, reusing the canonical job/history contracts. */
const CronToolOutputSchema = defineToolOutputSchema({
	inputProperty: "action",
	variants: {
		status: CronStatusOutputSchema,
		list: CronListOutputSchema,
		get: CronJobSchema,
		add: CronAddResultSchema,
		update: CronJobSchema,
		remove: Type.Union([Type.Object({
			ok: Type.Literal(true),
			removed: Type.Boolean(),
			activeRunCancellationRequested: Type.Optional(Type.Literal(true)),
			sessionCleanup: Type.Optional(Type.Literal("pending"))
		}, { additionalProperties: false }), Type.Object({
			ok: Type.Literal(false),
			removed: Type.Literal(false)
		}, { additionalProperties: false })]),
		run: CronRunOutputSchema,
		runs: Type.Object({
			entries: Type.Array(CronRunLogEntrySchema),
			...page
		}, { additionalProperties: false }),
		next_check: Type.Object({
			ok: Type.Literal(true),
			delayMs: Type.Number({ exclusiveMinimum: 0 })
		}, { additionalProperties: false }),
		wake: Type.Union([Type.Object({ ok: Type.Literal(true) }, { additionalProperties: false }), Type.Object({
			ok: Type.Literal(false),
			reason: Type.Optional(Type.Literal("unwakeable-session-key"))
		}, { additionalProperties: false })])
	}
});
//#endregion
//#region src/agents/tools/cron-tool-self-list.ts
const CRON_SELF_LIST_MAX_PAGES = 50;
const CRON_SELF_LIST_MAX_SNAPSHOT_RESTARTS = 3;
function filterDeliveryPreviewsByJobId(previews, jobId) {
	if (!isRecord(previews)) return previews;
	return Object.hasOwn(previews, jobId) ? { [jobId]: previews[jobId] } : {};
}
function filterCronListResultToJobId(result, jobId) {
	const jobs = result.jobs.filter((job) => isRecord(job) && job.id === jobId);
	const filteredResult = {
		...result,
		jobs,
		total: jobs.length,
		offset: 0,
		limit: jobs.length,
		hasMore: false,
		nextOffset: null,
		...Object.hasOwn(result, "deliveryPreviews") ? { deliveryPreviews: filterDeliveryPreviewsByJobId(result.deliveryPreviews, jobId) } : {}
	};
	delete filteredResult.snapshotRevision;
	return filteredResult;
}
async function listCronSelfJob(params) {
	for (let restart = 0; restart <= CRON_SELF_LIST_MAX_SNAPSHOT_RESTARTS; restart += 1) {
		let offset = 0;
		let snapshotRevision;
		let total;
		let snapshotChanged = false;
		for (let pageNumber = 0; pageNumber < CRON_SELF_LIST_MAX_PAGES; pageNumber += 1) {
			const page = readCanonicalCronListPage(await params.requestPage({
				limit: params.pageSize,
				offset
			}), params.pageSize);
			if (snapshotRevision !== void 0 && page.snapshotRevision !== snapshotRevision || total !== void 0 && page.total !== total) {
				snapshotChanged = true;
				break;
			}
			snapshotRevision ??= page.snapshotRevision;
			total ??= page.total;
			const nextOffset = resolveCronListPageNextOffset(page, offset);
			if (page.jobs.some((job) => isRecord(job) && job.id === params.jobId) || nextOffset === null) return filterCronListResultToJobId(page, params.jobId);
			offset = nextOffset;
		}
		if (!snapshotChanged) throw new Error("cron.list pagination exceeded maximum pages while reading current automation");
	}
	throw new Error("cron.list inventory changed repeatedly while reading current automation");
}
//#endregion
//#region src/agents/tools/cron-tool-write.ts
function assertNoCronShellExecution(value) {
	if (!isRecord(value)) return;
	const payload = isRecord(value.payload) ? value.payload : void 0;
	if (normalizeLowercaseStringOrEmpty(payload?.kind) === "command") throw new Error("automation command payloads cannot be created or edited through the agent automations tool; use the CLI or Gateway API.");
	if ((isRecord(value.schedule) ? value.schedule : void 0)?.kind === "on-exit") throw new Error("automation on-exit schedules cannot be created or edited through the agent automations tool; use the CLI or Gateway API.");
}
function assertCronCreatorAuthorityResolutionAvailable(params) {
	if (!params.required || params.resolveCreatorToolAuthority) return;
	if (params.unavailableReason === "queued-local-operator-configured-mcp" || !isCronCreatorToolCaptureComplete(params.creatorToolAllowlistCaptureRef)) throw new Error(params.unavailableReason === "queued-local-operator-configured-mcp" ? `Configured MCP authority is unavailable because this local operator turn was queued. ${CRON_CREATOR_AUTHORITY_RECOVERY_MESSAGE}` : INCOMPLETE_CRON_CREATOR_AUTHORITY_MESSAGE);
}
async function prepareCronJobUpdateForGateway(params) {
	params.operationSignal?.throwIfAborted();
	const initialPlan = planCronJobUpdatePatch({
		patch: params.patch,
		creatorToolAllowlist: params.creatorToolAllowlist,
		creatorAuthorityComplete: params.creatorAuthorityComplete
	});
	if (initialPlan.kind === "ready") return { patch: initialPlan.patch };
	const existing = await params.callGateway("cron.get", params.gatewayOpts, { id: params.id });
	params.operationSignal?.throwIfAborted();
	const existingRecord = isRecord(existing) ? existing : void 0;
	const expectedConfigRevision = existingRecord?.configRevision;
	if (typeof expectedConfigRevision !== "string" || expectedConfigRevision.length === 0) throw new Error("cron.get response is missing configRevision; restart the Gateway before retrying this update");
	let resolvedAuthority;
	let finalPlan = planCronJobUpdatePatch({
		patch: params.patch,
		creatorToolAllowlist: params.creatorToolAllowlist,
		currentJob: existingRecord,
		creatorAuthorityComplete: params.creatorAuthorityComplete
	});
	if (finalPlan.kind === "needs-creator-authority") {
		assertCronCreatorAuthorityResolutionAvailable({
			required: true,
			resolveCreatorToolAuthority: params.resolveCreatorToolAuthority,
			creatorToolAllowlistCaptureRef: params.creatorToolAllowlistCaptureRef,
			unavailableReason: params.creatorAuthorityUnavailableReason
		});
		if (!params.resolveCreatorToolAuthority) throw new Error("cron update requires complete creator tool authority");
		resolvedAuthority = await params.resolveCreatorToolAuthority({ signal: params.operationSignal });
		params.operationSignal?.throwIfAborted();
		finalPlan = planCronJobUpdatePatch({
			patch: params.patch,
			creatorToolAllowlist: resolvedAuthority.tools,
			currentJob: existingRecord,
			creatorAuthorityComplete: true,
			creatorHoldsRuntimeAuthority: resolvedAuthority.holdsRuntimeAuthority
		});
	}
	if (finalPlan.kind !== "ready") throw new Error("cron update patch planning did not use the loaded job");
	return {
		patch: finalPlan.patch,
		expectedConfigRevision,
		resolvedAuthority
	};
}
function isCronJobConfigRevisionConflict(error) {
	if (!(error instanceof Error) || error.name !== "GatewayClientRequestError") return false;
	return (isRecord(error.details) ? error.details : void 0)?.code === "CRON_JOB_CHANGED";
}
async function updateCronJobFromAgentTool(params) {
	const callerIncludedPayloadPatch = isRecord(params.patch.payload);
	let creatorAuthorityPromise;
	const resolveCreatorToolAuthority = params.resolveCreatorToolAuthority ? (options) => creatorAuthorityPromise ??= params.resolveCreatorToolAuthority(options) : void 0;
	for (let attempt = 0; attempt < 2; attempt += 1) {
		params.operationSignal?.throwIfAborted();
		const prepared = await prepareCronJobUpdateForGateway({
			...params,
			creatorAuthorityComplete: isCronCreatorToolCaptureComplete(params.creatorToolAllowlistCaptureRef) && resolveCreatorToolAuthority === void 0 && params.creatorAuthorityUnavailableReason === void 0,
			resolveCreatorToolAuthority
		});
		if (callerIncludedPayloadPatch && !params.adminManagement) assertNoCronShellExecution(prepared.patch);
		assertInheritedCronToolCaptureReady(prepared.patch, prepared.resolvedAuthority ? { value: prepared.resolvedAuthority.provenance } : params.creatorToolAllowlistCaptureRef);
		if (prepared.resolvedAuthority && !params.withCreatorAuthorityProvenance) throw new Error("fresh configured MCP cron authority requires an authenticated local agent run");
		try {
			const write = async () => {
				params.operationSignal?.throwIfAborted();
				return await params.callGateway("cron.update", params.gatewayOpts, {
					id: params.id,
					patch: prepared.patch,
					...prepared.expectedConfigRevision ? { expectedConfigRevision: prepared.expectedConfigRevision } : {}
				});
			};
			return prepared.resolvedAuthority && params.withCreatorAuthorityProvenance ? await params.withCreatorAuthorityProvenance(prepared.resolvedAuthority, write) : await write();
		} catch (error) {
			if (attempt === 0 && isCronJobConfigRevisionConflict(error)) continue;
			throw error;
		}
	}
	throw new Error("cron update retry exhausted");
}
//#endregion
//#region src/agents/tools/cron-tool.ts
function readCronToolJob(params, action) {
	let recovered = false;
	if (!params.job || isRecord(params.job) && Object.keys(params.job).length === 0) {
		const synthetic = recoverCronObjectFromFlatParams(params);
		if (synthetic.found && (action === "update" || hasCronCreateSignal(synthetic.value))) {
			params.job = synthetic.value;
			recovered = true;
		}
	}
	if (!params.job || typeof params.job !== "object") throw new Error("job required");
	return {
		job: canonicalizeCronToolObject(params.job),
		recovered
	};
}
function readCronJobIdParam(params) {
	return readToolStringParam(params, "jobId") ?? readToolStringParam(params, "id");
}
function requireCronJobIdParam(params) {
	const id = readCronJobIdParam(params);
	if (!id) throw new Error("jobId required (id accepted for backward compatibility)");
	return id;
}
const CRON_SELF_REMOVE_SCOPE_ERROR = "Automations tool is restricted to the current automation.";
const CRON_RUN_MAX_WAIT_MS = 6e5;
const CRON_RUN_ENQUEUE_TIMEOUT_MS = 6e4;
function readCronSelfRemoveOnlyJobId(opts) {
	return opts?.selfRemoveOnlyJobId?.trim() || void 0;
}
function assertCronSelfRemoveScope(opts, action, params) {
	const selfRemoveOnlyJobId = readCronSelfRemoveOnlyJobId(opts);
	if (!selfRemoveOnlyJobId || action === "status" || action === "list") return;
	if ([
		"next_check",
		"get",
		"remove",
		"runs"
	].includes(action)) {
		const id = readCronJobIdParam(params);
		if (id === selfRemoveOnlyJobId || action === "next_check" && !id) return;
	}
	throw new Error(CRON_SELF_REMOVE_SCOPE_ERROR);
}
function formatCronTerminalPresentation(params, result) {
	if (!isRecord(params) || !isRecord(result) || !isRecord(result.details)) return;
	switch (params.action) {
		case "status": return { text: `Automations scheduler status.\nEnabled: ${result.details.enabled === true ? "yes" : "no"}` };
		case "list": {
			const count = (typeof result.details.total === "number" && Number.isFinite(result.details.total) && result.details.total >= 0 ? Math.floor(result.details.total) : void 0) ?? (Array.isArray(result.details.jobs) ? result.details.jobs.length : void 0);
			return count === void 0 ? { text: "Automations listed." } : { text: `Automations listed.\nCount: ${count}` };
		}
		case "get": return { text: "Automation loaded." };
		case "runs": {
			const entries = Array.isArray(result.details.entries) ? result.details.entries.length : void 0;
			return entries === void 0 ? { text: "Automation run history loaded." } : { text: `Automation run history loaded.\nCount: ${entries}` };
		}
		default: return;
	}
}
function isOlderGatewayRejectingParam(error, method, param) {
	return error instanceof GatewayClientRequestError && error.gatewayCode === "INVALID_REQUEST" && error.message.includes(`invalid ${method} params`) && error.message.includes(`unexpected property '${param}'`);
}
function buildCronToolDescription(params) {
	const streamScheduleLine = params.triggersEnabled ? "\n- {kind:\"stream\",command:[argv]}: fires on supervised process output; disabled only when cron.triggers.enabled=false." : "";
	const scriptPayloadLine = params.triggersEnabled ? "\n- {kind:\"script\",script}: main|isolated only; disabled only when cron.triggers.enabled=false." : "";
	const triggerSection = params.triggersEnabled ? `TRIGGER (condition watcher on every/cron): {script}; available unless cron.triggers.enabled=false — if off, say so; never model-poll instead. Quiet headless check, no model; 30s/5 tool calls/16KB state. Read frozen trigger.state, return json({fire,message?,state?}) with NEW state; dedupe via state, never memory. fire:false saves state only. fire:true runs payload; message is that run's entire context — self-contained. Fire on failures/timeouts too; success-only watchers look healthy when broken. Script stays read-only; actions belong in payload. once:true disables after first fire. Code Mode: await exec({command:"..."}).` : `TRIGGERS DISABLED (cron.triggers.enabled=false): condition triggers, script payloads, and stream schedules are unavailable here. Omit trigger; use plain time-based schedules. If the user asks for a conditional watcher, say it is unsupported — never model-poll instead, and never silently create an unconditional job in its place.`;
	const silentWatcherCue = params.triggersEnabled ? " Silent watcher=>mode:\"none\"." : "";
	const scriptCue = params.triggersEnabled ? " When a script can decide there is nothing to do, use a trigger or script payload so quiet fires skip the model; scripts reach MCP only for servers named in toolsAllow (<server>__tool or <server>__*). When a run fails, throw from the script so the run records the failure (returning {error} still succeeds); only the failure alert waits for consecutive failures, run output still follows the job's delivery." : "";
	return `Gateway scheduler: reminders, delayed self-wakeups, loops, recurring work${params.triggersEnabled ? ", event watchers" : ""}. Never exec sleep/poll as timer.

ACTIONS: status | list [includeDisabled,limit?,offset?] (compact summaries with timing; use nextOffset for the next page) | get jobId (full schedule, payload, and delivery details) | add job | update jobId job (partial: only supplied fields change; null clears) | remove jobId (operator removal requests cancellation of an active run; the result reports activeRunCancellationRequested:true) | run jobId (runMode "force"=now; waits up to timeoutMs, default 60s, and returns the finished run: status, error, deliveryStatus, summary; a longer run, or a main-session job that starts after this turn, returns runId — check it later with runs jobId runId, never with a scheduled verify job) | runs jobId runId? = history | next_check in:"30m" (own paced run only) | wake text mode?:"now"|"next-heartbeat"(default) nudges a caller-owned lane (sessionKey/agentId to pick another).

SCOPE: Authenticated configured channel owner and Control UI administrator turns can list/get/update/run/remove any Gateway automation. Other turns see only caller-visible jobs; totals/counts and hasMore describe that scoped view, not global inventory. In that restricted view, an empty list or failed list/get/update/remove (including not-found) does not establish global absence, whatever the source of a known job id (including your own history). Never recreate or replace a known automation to satisfy an update/remove or reconciliation request solely because of these results. Report that you cannot establish global absence and ask an authorized administrator to check through a fresh authenticated configured channel owner or Control UI administrator turn or the Automations page. Genuinely new, requested automations can still be created.

ADD: job requires schedule+payload.

SCHEDULE:
- {kind:"at",at:"ISO-8601"} one-shot; no tz=UTC; auto-deletes after successful completion: delivery confirmed, not requested, intentionally silent, or explicitly bestEffort. Failed/unknown required delivery retains it disabled.
- {kind:"every",everyMs}.
- {kind:"cron",expr,tz?:"IANA"}: expr is wall time in tz; never pre-convert to UTC; no tz=gateway host local. 18:00 Shanghai => {expr:"0 18 * * *",tz:"Asia/Shanghai"}.${streamScheduleLine}

TARGET+PAYLOAD:
- "current" (agentTurn default) = this conversation: the run stays detached, reads bounded chat context, then commits its final visible assistant result to this conversation's durable history. Delayed work/loop = at|every + agentTurn + current. This is not a resumed parent turn: it uses the scheduled agent workspace with its own session identity, not the conversation worktree or cloud worker placement. In-flight turns sent through the job's stable cron key are canceled if that key is reassigned. Verify required checkout/tool access before delegating repository work; result delivery alone does not resume the original agent.
- "isolated" = fresh detached session; standalone background work recorded in cron run history.
- "main" = heartbeat lane; payload {kind:"systemEvent",text} (systemEvent default target).
- "session:<key>" = named session.
- {kind:"agentTurn",message}; timeoutSeconds 0=none.
- Inherited configured MCP authority includes only model-callable tools; interactive app-view-only capabilities are excluded from headless jobs.${scriptPayloadLine}

PACED LOOP: recurring job + pacing{min?,max?} durations ("15m","4h"; at least one). Inside its run, job calls next_check in:"<dur>" to set the next delay (clamped to bounds, measured from run end; failed runs keep normal backoff). Adaptive polling: tighten when active, back off when quiet.

AUTHORING (recurring): every fire re-runs the same instructions; keep the model for judgment only. Put repeatable logic (listing/diffing, dedupe, checkpoints/watermarks) in a workspace script the payload runs in one exec; keep detailed instructions in a workspace file beside it that the message references ("Follow scripts/<job>.md"), so fixes need only file edits. Message names exact tool ids + argument shapes; cap toolsAllow to what the run needs.${scriptCue}

${triggerSection}

DELIVERY: where detached run output goes. Omitted=announce (current=>canonical session commit, plus one normal channel send for external chats; isolated=>last route; set channel/to for a specific chat — no messaging tool inside the run). A current announce succeeds only after its history commit; WebChat observes that commit live and after reconnect without another user message.${silentWatcherCue} webhook posts finished-run event (successful empty summary is intentional silence, no POST) to URL in \`to\`. To keep announce delivery and also POST completion, use mode:"announce" with completionDestination:{mode:"webhook",to:"https://..."}.

FAILURE ALERTS: jobs with a failure route default to alerting after 2 consecutive execution failures with a 1h cooldown. Route order: job failureAlert fields, delivery.failureDestination over global cron.failureAlert destination fields, then primary announce. failureAlert:false disables execution/delivery alerts, not the auto-disable safety notice; a failureAlert object activates/tunes. bestEffort suppresses inherited execution alerts. Required completion-delivery failure uses only an alternate route, bypasses after, and shares the execution-alert cooldown from the first failure; it does not increment the execution streak.

Job wakeMode (main jobs): "now"(default)|"next-heartbeat". Restricted automation-run sessions: self status/list/get/runs/remove + own next_check only. jobId canonical (id=compat). contextMessages 0-10 embeds recent chat lines into reminder text.`;
}
function createCronTool(opts, deps) {
	const gatewayCall = deps?.callGatewayTool ?? callGatewayTool;
	const managementAuthority = bindCronManagementGrant(opts?.runId);
	const requesterAuthority = bindCronRequesterGrant(opts?.runId);
	const triggersEnabled = opts?.config?.cron?.triggers?.enabled !== false;
	const selfRemoveOnly = Boolean(readCronSelfRemoveOnlyJobId(opts));
	const tool = {
		label: "Automations",
		name: AUTOMATIONS_TOOL_NAME,
		displaySummary: CRON_TOOL_DISPLAY_SUMMARY,
		description: selfRemoveOnly ? managementAuthority?.managementOnly ? "Inspect or remove only the current automation. Actions: list [includeDisabled], get jobId, remove jobId. Use the current job ID; other jobs and management actions are unavailable." : "Inspect or remove only the current automation. Actions: status; list [includeDisabled]; get/runs/remove jobId; next_check in:\"15m\" for this paced run. Use the current job ID. To stop a finished job, remove it; creating/updating/running jobs and waking sessions are unavailable. Return the task result; the scheduler owns delivery." : managementAuthority?.managementOnly ? "Manage any existing automation on this Gateway with the admitted automation management authority. Actions: list [includeDisabled,limit,offset] (compact summaries with timing; follow nextOffset); get jobId (full schedule, payload, and delivery details); update jobId job (partial patch, null clears); run jobId (runMode:\"force\" runs now; waits up to timeoutMs, default 60s, and returns the finished run, or its runId if still running); remove jobId (operator removal requests cancellation of an active run; the result reports activeRunCancellationRequested:true). Creator attribution and scheduled execution policy stay intact. Use the Automations page for other actions." : buildCronToolDescription({ triggersEnabled }),
		outputSchema: CronToolOutputSchema,
		parameters: createCronToolSchema({
			agentSessionKey: opts?.agentSessionKey,
			triggersEnabled,
			selfRemoveOnly,
			management: managementAuthority ? managementAuthority.managementOnly ? "only" : "also" : void 0
		}),
		execute: async (_toolCallId, args, operationSignal) => {
			operationSignal?.throwIfAborted();
			const callGateway = async (...request) => {
				const identity = getGatewayToolCallerIdentity();
				const grant = managementAuthority?.mint(request[0], operationSignal);
				const requesterGrant = !grant && !identity?.cronCreatorAuthorityGrant && (request[0] === "cron.add" || request[0] === "cron.update") ? (requesterAuthority ?? identity?.mintCronRequesterGrant)?.(operationSignal) : void 0;
				if ((grant || requesterGrant) && !identity) throw new Error("Automation management requires the active configured channel owner or Control UI administrator turn.");
				return (grant || requesterGrant) && identity ? await withGatewayToolCallerIdentity({
					...identity,
					...grant ? { cronManagementGrant: grant } : {},
					...requesterGrant ? { cronCreatorAuthorityGrant: requesterGrant } : {}
				}, () => gatewayCall(...request)) : await gatewayCall(...request);
			};
			const params = args;
			const action = readToolStringParam(params, "action", { required: true });
			if (managementAuthority?.managementOnly && !CRON_MANAGEMENT_METHODS.some((method) => method === `cron.${action}`)) throw new Error("This turn can only list, get, update, run, or remove automations. Use the Automations page for other actions.");
			assertCronSelfRemoveScope(opts, action, params);
			const parsedGatewayOpts = readGatewayCallOptions(params);
			const gatewayOpts = {
				...parsedGatewayOpts,
				timeoutMs: parsedGatewayOpts.timeoutMs ?? 6e4
			};
			const runtimeConfig = getRuntimeConfig();
			const callerAgentId = opts?.agentSessionKey?.trim() ? resolveSessionAgentId({
				sessionKey: opts.agentSessionKey,
				config: runtimeConfig,
				agentId: opts.agentId
			}) : void 0;
			const creatorExecToolTarget = resolveCronCreatorExecToolTarget(opts?.creatorToolAllowlist);
			const callerIdentity = callerAgentId && opts?.agentSessionKey?.trim() ? {
				agentId: callerAgentId,
				sessionKey: opts.agentSessionKey.trim(),
				turnSourceAccountId: opts.agentAccountId,
				...readCronSelfRemoveOnlyJobId(opts) ? { cronSelfManagementJobId: readCronSelfRemoveOnlyJobId(opts) } : {},
				...opts?.creatorToolAllowlistCaptureRef?.value?.version === 1 && opts.creatorToolAllowlistCaptureRef.value.source === "final-executable-surface" ? {
					cronToolsAllowCapture: "final-executable-surface",
					...creatorExecToolTarget ? { cronExecToolTarget: creatorExecToolTarget } : {}
				} : {}
			} : void 0;
			const withCreatorAuthorityProvenance = async (authority, run) => {
				if (!authority) return await run();
				if (!callerIdentity) throw new Error("fresh configured MCP cron authority requires an authenticated local agent run");
				const cronExecToolTarget = resolveCronCreatorExecToolTarget(authority.tools);
				return await withGatewayToolCallerIdentity({
					...callerIdentity,
					cronToolsAllowCapture: "final-executable-surface",
					...cronExecToolTarget ? { cronExecToolTarget } : {},
					cronCreatorAuthorityGrant: authority.grant
				}, run);
			};
			return await withGatewayToolCallerIdentity(callerIdentity, async () => {
				switch (action) {
					case "status": {
						const result = await callGateway("cron.status", gatewayOpts, {});
						return jsonResult(readCronSelfRemoveOnlyJobId(opts) ? { enabled: isRecord(result) && result.enabled === true } : result);
					}
					case "list": {
						const selfRemoveOnlyJobId = readCronSelfRemoveOnlyJobId(opts);
						const listAgentId = readToolStringParam(params, "agentId");
						const includeDisabled = Boolean(params.includeDisabled);
						const requestedLimit = selfRemoveOnlyJobId ? void 0 : readPositiveIntegerParam(params, "limit", {
							max: 200,
							message: `limit must be a positive integer no greater than 200`
						});
						const requestedOffset = selfRemoveOnlyJobId ? void 0 : readNonNegativeIntegerParam(params, "offset");
						let useCompactList = true;
						const requestListPage = async (pageParams) => {
							for (;;) try {
								return await callGateway("cron.list", gatewayOpts, {
									includeDisabled,
									...useCompactList ? { compact: true } : {},
									...listAgentId ? { agentId: listAgentId } : {},
									...pageParams
								});
							} catch (error) {
								if (!useCompactList || !isOlderGatewayRejectingParam(error, "cron.list", "compact")) throw error;
								useCompactList = false;
							}
						};
						if (!selfRemoveOnlyJobId) {
							const result = await requestListPage({
								...requestedLimit !== void 0 ? { limit: requestedLimit } : {},
								...requestedOffset !== void 0 ? { offset: requestedOffset } : {}
							});
							return jsonResult({
								...result,
								scope: managementAuthority ? "gateway" : "caller",
								...!managementAuthority ? { scopeHint: "Restricted automation inventory. For Gateway-wide management, use a fresh authenticated configured channel owner or Control UI administrator turn or the Automations page." } : {}
							});
						}
						return jsonResult(await listCronSelfJob({
							jobId: selfRemoveOnlyJobId,
							pageSize: 200,
							requestPage: requestListPage
						}));
					}
					case "get":
					case "remove":
					case "runs": {
						const id = requireCronJobIdParam(params);
						const runId = action === "runs" ? readToolStringParam(params, "runId") : void 0;
						return jsonResult(await callGateway(`cron.${action}`, gatewayOpts, {
							id,
							...runId ? { runId } : {}
						}));
					}
					case "add": {
						const canonicalJob = stripCronCreateNullClears(readCronToolJob(params, "add").job);
						assertNoCronShellExecution(canonicalJob);
						assertCronDeliveryInputNonBlankFields(canonicalJob.delivery);
						assertCronPacingInput(canonicalJob.pacing);
						if (typeof canonicalJob.declarationKey === "string" && canonicalJob.declarationKey.trim().length === 0) throw new Error("declarationKey must be a non-empty string");
						if (typeof canonicalJob.displayName === "string" && canonicalJob.displayName.trim().length === 0) throw new Error("displayName must be a non-empty string");
						const enabledExplicit = typeof canonicalJob.enabled === "boolean";
						const job = normalizeCronJobCreate(canonicalJob, { sessionContext: { sessionKey: opts?.agentSessionKey } }) ?? canonicalJob;
						if (typeof job.declarationKey === "string" && job.declarationKey.length > 0 && !enabledExplicit) delete job.enabled;
						const requiresCreatorAuthority = cronCreateRequiresCreatorAuthority(job, opts?.creatorToolAllowlist);
						assertCronCreatorAuthorityResolutionAvailable({
							required: requiresCreatorAuthority,
							resolveCreatorToolAuthority: opts?.resolveCreatorToolAuthority,
							creatorToolAllowlistCaptureRef: opts?.creatorToolAllowlistCaptureRef,
							unavailableReason: opts?.creatorAuthorityUnavailableReason
						});
						const resolvedAuthority = requiresCreatorAuthority && opts?.resolveCreatorToolAuthority ? await opts.resolveCreatorToolAuthority({ signal: operationSignal }) : void 0;
						operationSignal?.throwIfAborted();
						const creatorToolAllowlist = resolvedAuthority?.tools ?? opts?.creatorToolAllowlist;
						const creatorToolAllowlistCaptureRef = resolvedAuthority ? { value: resolvedAuthority.provenance } : opts?.creatorToolAllowlistCaptureRef;
						capCronJobToolsAllowOnCreate(job, creatorToolAllowlist, resolvedAuthority?.holdsRuntimeAuthority);
						assertInheritedCronToolCaptureReady(job, creatorToolAllowlistCaptureRef);
						const { alias } = resolveMainSessionAlias(runtimeConfig);
						const resolvedSessionKey = opts?.agentSessionKey ? resolveInternalSessionKey({
							key: opts.agentSessionKey,
							alias
						}) : void 0;
						const sessionTarget = normalizeLowercaseStringOrEmpty(job.sessionTarget);
						if (!("sessionKey" in job) && resolvedSessionKey && sessionTarget !== "isolated") job.sessionKey = resolvedSessionKey;
						if ((opts?.agentSessionKey || opts?.currentDeliveryContext) && "payload" in job && job.payload?.kind === "agentTurn") {
							const deliveryValue = job.delivery;
							const delivery = isRecord(deliveryValue) ? deliveryValue : void 0;
							const modeRaw = typeof delivery?.mode === "string" ? delivery.mode : "";
							const mode = normalizeLowercaseStringOrEmpty(modeRaw);
							if (mode === "webhook") {
								const webhookUrl = normalizeHttpWebhookUrl(delivery?.to);
								if (!webhookUrl) throw new Error("delivery.mode=\"webhook\" requires delivery.to to be a valid http(s) URL");
								if (delivery) delivery.to = webhookUrl;
							}
							const hasTarget = typeof delivery?.channel === "string" && delivery.channel.trim() || typeof delivery?.to === "string" && delivery.to.trim();
							if ((deliveryValue == null || delivery) && (mode === "" || mode === "announce") && !hasTarget) {
								const inferred = resolveCronCreationDelivery({
									cfg: runtimeConfig,
									currentDeliveryContext: opts.currentDeliveryContext,
									agentSessionKey: opts.agentSessionKey
								});
								if (inferred) job.delivery = {
									...inferred,
									...delivery
								};
							}
						}
						const contextMessages = readNonNegativeIntegerParam(params, "contextMessages") ?? 0;
						if ("payload" in job && job.payload?.kind === "systemEvent") {
							const payload = job.payload;
							if (typeof payload.text === "string" && payload.text.trim()) {
								const contextLines = await buildReminderContextLines({
									agentSessionKey: opts?.agentSessionKey,
									agentId: callerAgentId,
									gatewayOpts,
									contextMessages,
									callGatewayTool: callGateway
								});
								if (contextLines.length > 0) payload.text = `${stripExistingContext(payload.text)}${REMINDER_CONTEXT_MARKER}${contextLines.join("\n")}`;
							}
						}
						return jsonResult(await withCreatorAuthorityProvenance(resolvedAuthority, () => callGateway("cron.add", gatewayOpts, job)));
					}
					case "update": {
						const id = requireCronJobIdParam(params);
						const { job: canonicalPatch, recovered: recoveredFlatPatch } = readCronToolJob(params, "update");
						if (!managementAuthority) assertNoCronShellExecution(canonicalPatch);
						assertCronDeliveryInputNonBlankFields(canonicalPatch.delivery);
						assertCronPacingInput(canonicalPatch.pacing);
						if (typeof canonicalPatch.displayName === "string" && canonicalPatch.displayName.trim().length === 0) throw new Error("displayName must be a non-empty string or null");
						const patch = normalizeCronJobPatch(canonicalPatch) ?? canonicalPatch;
						if (recoveredFlatPatch && isEmptyRecoveredCronPatch(patch)) throw new Error("job required");
						const creatorOptions = managementAuthority ? void 0 : opts;
						return jsonResult(await updateCronJobFromAgentTool({
							id,
							patch,
							adminManagement: Boolean(managementAuthority),
							creatorToolAllowlist: creatorOptions?.creatorToolAllowlist,
							creatorToolAllowlistCaptureRef: creatorOptions?.creatorToolAllowlistCaptureRef,
							resolveCreatorToolAuthority: creatorOptions?.resolveCreatorToolAuthority,
							withCreatorAuthorityProvenance: !managementAuthority && callerIdentity ? withCreatorAuthorityProvenance : void 0,
							gatewayOpts,
							callGateway,
							operationSignal,
							creatorAuthorityUnavailableReason: creatorOptions?.creatorAuthorityUnavailableReason
						}));
					}
					case "run": {
						const id = requireCronJobIdParam(params);
						const runMode = params.runMode === "due" || params.runMode === "force" ? params.runMode : "due";
						const waitTimeoutMs = Math.min(parsedGatewayOpts.timeoutMs ?? 6e4, CRON_RUN_MAX_WAIT_MS);
						let result;
						try {
							result = await callGateway("cron.run", {
								...gatewayOpts,
								timeoutMs: waitTimeoutMs + CRON_RUN_ENQUEUE_TIMEOUT_MS
							}, {
								id,
								mode: runMode,
								waitTimeoutMs
							});
						} catch (error) {
							if (!isOlderGatewayRejectingParam(error, "cron.run", "waitTimeoutMs")) throw error;
							result = await callGateway("cron.run", gatewayOpts, {
								id,
								mode: runMode
							});
						}
						if (result.enqueued !== true || result.run) return jsonResult(result);
						const followUp = managementAuthority?.managementOnly ? "check it later on the Automations page" : "check it later with runs jobId runId";
						return jsonResult({
							...result,
							note: result.finished ? `Finished, but its run history is not visible to this turn (a one-shot may have deleted itself after succeeding); ${followUp} if needed.` : `Not finished yet: it is still running, or it runs in this session and starts after this turn. Its delivery follows the job settings; ${followUp}. Do not schedule a verification job.`
						});
					}
					case "next_check": {
						const jobId = readCronSelfRemoveOnlyJobId(opts);
						const runId = opts?.runId?.trim();
						if (!jobId || !runId) throw new Error("cron next_check is only available to the currently running job");
						const rawDuration = readToolStringParam(params, "in", { required: true });
						let delayMs;
						try {
							delayMs = parseDurationMs(rawDuration);
						} catch {
							throw new Error("cron next_check in must be a positive duration");
						}
						if (delayMs <= 0) throw new Error("cron next_check in must be a positive duration");
						recordCronNextCheckProposal(runId, jobId, delayMs);
						return jsonResult({
							ok: true,
							delayMs
						});
					}
					case "wake": {
						const text = readToolStringParam(params, "text", { required: true });
						const mode = params.mode === "now" || params.mode === "next-heartbeat" ? params.mode : "next-heartbeat";
						const { alias } = resolveMainSessionAlias(runtimeConfig);
						const explicitSessionKey = readToolStringParam(params, "sessionKey");
						const explicitAgentId = readToolStringParam(params, "agentId");
						const inferredSessionKey = opts?.agentSessionKey ? resolveInternalSessionKey({
							key: opts.agentSessionKey,
							alias
						}) : void 0;
						const sessionKey = explicitSessionKey ?? inferredSessionKey;
						const agentIdFromExplicitSessionKey = explicitSessionKey ? parseAgentSessionKey(explicitSessionKey)?.agentId : void 0;
						const agentId = explicitAgentId ?? (explicitSessionKey ? agentIdFromExplicitSessionKey : callerAgentId);
						return jsonResult(await callGateway("wake", gatewayOpts, {
							mode,
							text,
							...sessionKey ? { sessionKey } : {},
							...agentId ? { agentId } : {}
						}, { expectFinal: false }));
					}
					default: throw new Error(`Unknown action: ${action}`);
				}
			});
		}
	};
	return setToolTerminalPresentation(tool, formatCronTerminalPresentation);
}
//#endregion
export { resolveCronStoredDeliveryContext as a, replaceWithEffectiveCronCreatorToolAllowlist as i, assertNativeCronCreatorCapabilities as n, captureFinalEffectiveCronCreatorToolAllowlist as r, createCronTool as t };