import { m as extractTextFromChatContent } from "./pi-embedded-utils-BmQgIEul.js";
import { G as truncateUtf16Safe, j as isRecord } from "./subsystem-Ci61Iebe.js";
import "./paths-aMBPuCJR.js";
import "./auth-profiles-CfyCILIe.js";
import "./env-3oW17BWM.js";
import { b as parseAgentSessionKey, h as sanitizeAgentId } from "./session-key-j5Y_js6r.js";
import "./exec-B5OgjUV4.js";
import { u as resolveSessionAgentId } from "./agent-scope-CCFV2dzL.js";
import "./github-copilot-token-CgLcR-3z.js";
import "./reliability-B6TVU4OW.js";
import "./sqlite-CbyDDiup.js";
import "./accounts-TaYXhwSR.js";
import "./dock-BRgbOpqS.js";
import "./normalize-Dz9m5w2g.js";
import "./bindings-jC2ZutIF.js";
import "./plugins-6aUfx6Yt.js";
import "./message-channel-BCPYwYUJ.js";
import "./image-ops-DfgorOBP.js";
import "./pi-embedded-helpers-DmbNPuyh.js";
import { n as loadConfig } from "./config-CaBTSkpu.js";
import "./manifest-registry-BPLS_vpk.js";
import "./chrome-CDWGRT0_.js";
import "./server-context-CU0HZM8q.js";
import "./skills-CEb7zc7i.js";
import "./redact-BXcZFg1V.js";
import "./errors-CGLTQpIl.js";
import "./ssrf-Clw7QAxV.js";
import "./sessions-FK8zYE33.js";
import "./paths-a4Li8t1f.js";
import "./tool-images-D39TNWEi.js";
import "./thinking-B4KYQMkY.js";
import { a as jsonResult, u as readStringParam } from "./common-BkBiFnII.js";
import "./call-CHvC3jba.js";
import { f as resolveInternalSessionKey, p as resolveMainSessionAlias } from "./sessions-helpers-BkUTlqiP.js";
import { t as callGatewayTool } from "./gateway-B-qVaiZA.js";
import { i as stringEnum, r as optionalStringEnum } from "./typebox-BrLUZMNm.js";
import { Type } from "@sinclair/typebox";

//#region src/platform/cron/legacy-delivery.ts
function hasLegacyDeliveryHints(payload) {
	if (typeof payload.deliver === "boolean") return true;
	if (typeof payload.bestEffortDeliver === "boolean") return true;
	if (typeof payload.to === "string" && payload.to.trim()) return true;
	return false;
}
function buildDeliveryFromLegacyPayload(payload) {
	const mode = payload.deliver === false ? "none" : "announce";
	const channelRaw = typeof payload.channel === "string" ? payload.channel.trim().toLowerCase() : "";
	const toRaw = typeof payload.to === "string" ? payload.to.trim() : "";
	const next = { mode };
	if (channelRaw) next.channel = channelRaw;
	if (toRaw) next.to = toRaw;
	if (typeof payload.bestEffortDeliver === "boolean") next.bestEffort = payload.bestEffortDeliver;
	return next;
}
function stripLegacyDeliveryFields(payload) {
	if ("deliver" in payload) delete payload.deliver;
	if ("channel" in payload) delete payload.channel;
	if ("to" in payload) delete payload.to;
	if ("bestEffortDeliver" in payload) delete payload.bestEffortDeliver;
}

//#endregion
//#region src/platform/cron/parse.ts
const ISO_TZ_RE = /(Z|[+-]\d{2}:?\d{2})$/i;
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T/;
function normalizeUtcIso(raw) {
	if (ISO_TZ_RE.test(raw)) return raw;
	if (ISO_DATE_RE.test(raw)) return `${raw}T00:00:00Z`;
	if (ISO_DATE_TIME_RE.test(raw)) return `${raw}Z`;
	return raw;
}
function parseAbsoluteTimeMs(input) {
	const raw = input.trim();
	if (!raw) return null;
	if (/^\d+$/.test(raw)) {
		const n = Number(raw);
		if (Number.isFinite(n) && n > 0) return Math.floor(n);
	}
	const parsed = Date.parse(normalizeUtcIso(raw));
	return Number.isFinite(parsed) ? parsed : null;
}

//#endregion
//#region src/platform/cron/payload-migration.ts
function readString(value) {
	if (typeof value !== "string") return;
	return value;
}
function normalizeChannel(value) {
	return value.trim().toLowerCase();
}
function migrateLegacyCronPayload(payload) {
	let mutated = false;
	const channelValue = readString(payload.channel);
	const providerValue = readString(payload.provider);
	const nextChannel = typeof channelValue === "string" && channelValue.trim().length > 0 ? normalizeChannel(channelValue) : typeof providerValue === "string" && providerValue.trim().length > 0 ? normalizeChannel(providerValue) : "";
	if (nextChannel) {
		if (channelValue !== nextChannel) {
			payload.channel = nextChannel;
			mutated = true;
		}
	}
	if ("provider" in payload) {
		delete payload.provider;
		mutated = true;
	}
	return mutated;
}

//#endregion
//#region src/platform/cron/service/normalize.ts
function truncateText$1(input, maxLen) {
	if (input.length <= maxLen) return input;
	return `${truncateUtf16Safe(input, Math.max(0, maxLen - 1)).trimEnd()}…`;
}
function inferLegacyName(job) {
	const firstLine = (job?.payload?.kind === "systemEvent" && typeof job.payload.text === "string" ? job.payload.text : job?.payload?.kind === "agentTurn" && typeof job.payload.message === "string" ? job.payload.message : "").split("\n").map((l) => l.trim()).find(Boolean) ?? "";
	if (firstLine) return truncateText$1(firstLine, 60);
	const kind = typeof job?.schedule?.kind === "string" ? job.schedule.kind : "";
	if (kind === "cron" && typeof job?.schedule?.expr === "string") return `Cron: ${truncateText$1(job.schedule.expr, 52)}`;
	if (kind === "every" && typeof job?.schedule?.everyMs === "number") return `Every: ${job.schedule.everyMs}ms`;
	if (kind === "at") return "One-shot";
	return "Cron job";
}

//#endregion
//#region src/platform/cron/stagger.ts
const DEFAULT_TOP_OF_HOUR_STAGGER_MS = 300 * 1e3;
function parseCronFields(expr) {
	return expr.trim().split(/\s+/).filter(Boolean);
}
function isRecurringTopOfHourCronExpr(expr) {
	const fields = parseCronFields(expr);
	if (fields.length === 5) {
		const [minuteField, hourField] = fields;
		return minuteField === "0" && hourField.includes("*");
	}
	if (fields.length === 6) {
		const [secondField, minuteField, hourField] = fields;
		return secondField === "0" && minuteField === "0" && hourField.includes("*");
	}
	return false;
}
function normalizeCronStaggerMs(raw) {
	const numeric = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() ? Number(raw) : NaN;
	if (!Number.isFinite(numeric)) return;
	return Math.max(0, Math.floor(numeric));
}
function resolveDefaultCronStaggerMs(expr) {
	return isRecurringTopOfHourCronExpr(expr) ? DEFAULT_TOP_OF_HOUR_STAGGER_MS : void 0;
}

//#endregion
//#region src/platform/cron/normalize.ts
const DEFAULT_OPTIONS = { applyDefaults: false };
function coerceSchedule(schedule) {
	const next = { ...schedule };
	const rawKind = typeof schedule.kind === "string" ? schedule.kind.trim().toLowerCase() : "";
	const kind = rawKind === "at" || rawKind === "every" || rawKind === "cron" ? rawKind : void 0;
	const atMsRaw = schedule.atMs;
	const atRaw = schedule.at;
	const atString = typeof atRaw === "string" ? atRaw.trim() : "";
	const parsedAtMs = typeof atMsRaw === "number" ? atMsRaw : typeof atMsRaw === "string" ? parseAbsoluteTimeMs(atMsRaw) : atString ? parseAbsoluteTimeMs(atString) : null;
	if (kind) next.kind = kind;
	else if (typeof schedule.atMs === "number" || typeof schedule.at === "string" || typeof schedule.atMs === "string") next.kind = "at";
	else if (typeof schedule.everyMs === "number") next.kind = "every";
	else if (typeof schedule.expr === "string") next.kind = "cron";
	if (atString) next.at = parsedAtMs !== null ? new Date(parsedAtMs).toISOString() : atString;
	else if (parsedAtMs !== null) next.at = new Date(parsedAtMs).toISOString();
	if ("atMs" in next) delete next.atMs;
	const staggerMs = normalizeCronStaggerMs(schedule.staggerMs);
	if (staggerMs !== void 0) next.staggerMs = staggerMs;
	else if ("staggerMs" in next) delete next.staggerMs;
	return next;
}
function coercePayload(payload) {
	const next = { ...payload };
	migrateLegacyCronPayload(next);
	const kindRaw = typeof next.kind === "string" ? next.kind.trim().toLowerCase() : "";
	if (kindRaw === "agentturn") next.kind = "agentTurn";
	else if (kindRaw === "systemevent") next.kind = "systemEvent";
	else if (kindRaw) next.kind = kindRaw;
	if (!next.kind) {
		const hasMessage = typeof next.message === "string" && next.message.trim().length > 0;
		const hasText = typeof next.text === "string" && next.text.trim().length > 0;
		const hasAgentTurnHint = typeof next.model === "string" || typeof next.thinking === "string" || typeof next.timeoutSeconds === "number" || typeof next.allowUnsafeExternalContent === "boolean";
		if (hasMessage) next.kind = "agentTurn";
		else if (hasText) next.kind = "systemEvent";
		else if (hasAgentTurnHint) next.kind = "agentTurn";
	}
	if (typeof next.message === "string") {
		const trimmed = next.message.trim();
		if (trimmed) next.message = trimmed;
	}
	if (typeof next.text === "string") {
		const trimmed = next.text.trim();
		if (trimmed) next.text = trimmed;
	}
	if ("model" in next) if (typeof next.model === "string") {
		const trimmed = next.model.trim();
		if (trimmed) next.model = trimmed;
		else delete next.model;
	} else delete next.model;
	if ("thinking" in next) if (typeof next.thinking === "string") {
		const trimmed = next.thinking.trim();
		if (trimmed) next.thinking = trimmed;
		else delete next.thinking;
	} else delete next.thinking;
	if ("timeoutSeconds" in next) if (typeof next.timeoutSeconds === "number" && Number.isFinite(next.timeoutSeconds)) next.timeoutSeconds = Math.max(0, Math.floor(next.timeoutSeconds));
	else delete next.timeoutSeconds;
	if ("allowUnsafeExternalContent" in next && typeof next.allowUnsafeExternalContent !== "boolean") delete next.allowUnsafeExternalContent;
	return next;
}
function coerceDelivery(delivery) {
	const next = { ...delivery };
	if (typeof delivery.mode === "string") {
		const mode = delivery.mode.trim().toLowerCase();
		if (mode === "deliver") next.mode = "announce";
		else if (mode === "announce" || mode === "none" || mode === "webhook") next.mode = mode;
		else delete next.mode;
	} else if ("mode" in next) delete next.mode;
	if (typeof delivery.channel === "string") {
		const trimmed = delivery.channel.trim().toLowerCase();
		if (trimmed) next.channel = trimmed;
		else delete next.channel;
	}
	if (typeof delivery.to === "string") {
		const trimmed = delivery.to.trim();
		if (trimmed) next.to = trimmed;
		else delete next.to;
	}
	return next;
}
function unwrapJob(raw) {
	if (isRecord(raw.data)) return raw.data;
	if (isRecord(raw.job)) return raw.job;
	return raw;
}
function normalizeSessionTarget(raw) {
	if (typeof raw !== "string") return;
	const trimmed = raw.trim().toLowerCase();
	if (trimmed === "main" || trimmed === "isolated") return trimmed;
}
function normalizeWakeMode(raw) {
	if (typeof raw !== "string") return;
	const trimmed = raw.trim().toLowerCase();
	if (trimmed === "now" || trimmed === "next-heartbeat") return trimmed;
}
function normalizeScope(raw) {
	if (typeof raw !== "string") return;
	const trimmed = raw.trim().toLowerCase();
	if (trimmed === "universal" || trimmed === "session") return trimmed;
}
function copyTopLevelAgentTurnFields(next, payload) {
	const copyString = (field) => {
		if (typeof payload[field] === "string" && payload[field].trim()) return;
		const value = next[field];
		if (typeof value === "string" && value.trim()) payload[field] = value.trim();
	};
	copyString("model");
	copyString("thinking");
	if (typeof payload.timeoutSeconds !== "number" && typeof next.timeoutSeconds === "number") payload.timeoutSeconds = next.timeoutSeconds;
	if (typeof payload.allowUnsafeExternalContent !== "boolean" && typeof next.allowUnsafeExternalContent === "boolean") payload.allowUnsafeExternalContent = next.allowUnsafeExternalContent;
}
function copyTopLevelLegacyDeliveryFields(next, payload) {
	if (typeof payload.deliver !== "boolean" && typeof next.deliver === "boolean") payload.deliver = next.deliver;
	if (typeof payload.channel !== "string" && typeof next.channel === "string" && next.channel.trim()) payload.channel = next.channel.trim();
	if (typeof payload.to !== "string" && typeof next.to === "string" && next.to.trim()) payload.to = next.to.trim();
	if (typeof payload.bestEffortDeliver !== "boolean" && typeof next.bestEffortDeliver === "boolean") payload.bestEffortDeliver = next.bestEffortDeliver;
	if (typeof payload.provider !== "string" && typeof next.provider === "string" && next.provider.trim()) payload.provider = next.provider.trim();
}
function stripLegacyTopLevelFields(next) {
	delete next.model;
	delete next.thinking;
	delete next.timeoutSeconds;
	delete next.allowUnsafeExternalContent;
	delete next.message;
	delete next.text;
	delete next.deliver;
	delete next.channel;
	delete next.to;
	delete next.bestEffortDeliver;
	delete next.provider;
}
function normalizeCronJobInput(raw, options = DEFAULT_OPTIONS) {
	if (!isRecord(raw)) return null;
	const base = unwrapJob(raw);
	const next = { ...base };
	if ("agentId" in base) {
		const agentId = base.agentId;
		if (agentId === null) next.agentId = null;
		else if (typeof agentId === "string") {
			const trimmed = agentId.trim();
			if (trimmed) next.agentId = sanitizeAgentId(trimmed);
			else delete next.agentId;
		}
	}
	if ("sessionKey" in base) {
		const sessionKey = base.sessionKey;
		if (sessionKey === null) next.sessionKey = null;
		else if (typeof sessionKey === "string") {
			const trimmed = sessionKey.trim();
			if (trimmed) next.sessionKey = trimmed;
			else delete next.sessionKey;
		}
	}
	if ("enabled" in base) {
		const enabled = base.enabled;
		if (typeof enabled === "boolean") next.enabled = enabled;
		else if (typeof enabled === "string") {
			const trimmed = enabled.trim().toLowerCase();
			if (trimmed === "true") next.enabled = true;
			if (trimmed === "false") next.enabled = false;
		}
	}
	if ("sessionTarget" in base) {
		const normalized = normalizeSessionTarget(base.sessionTarget);
		if (normalized) next.sessionTarget = normalized;
		else delete next.sessionTarget;
	}
	if ("wakeMode" in base) {
		const normalized = normalizeWakeMode(base.wakeMode);
		if (normalized) next.wakeMode = normalized;
		else delete next.wakeMode;
	}
	if ("scope" in base) {
		const normalized = normalizeScope(base.scope);
		if (normalized) next.scope = normalized;
		else delete next.scope;
	}
	if (isRecord(base.schedule)) next.schedule = coerceSchedule(base.schedule);
	if (!("payload" in next) || !isRecord(next.payload)) {
		const message = typeof next.message === "string" ? next.message.trim() : "";
		const text = typeof next.text === "string" ? next.text.trim() : "";
		if (message) next.payload = {
			kind: "agentTurn",
			message
		};
		else if (text) next.payload = {
			kind: "systemEvent",
			text
		};
	}
	if (isRecord(base.payload)) next.payload = coercePayload(base.payload);
	if (isRecord(base.delivery)) next.delivery = coerceDelivery(base.delivery);
	if ("isolation" in next) delete next.isolation;
	const payload = isRecord(next.payload) ? next.payload : null;
	if (payload && payload.kind === "agentTurn") {
		copyTopLevelAgentTurnFields(next, payload);
		copyTopLevelLegacyDeliveryFields(next, payload);
	}
	stripLegacyTopLevelFields(next);
	if (options.applyDefaults) {
		if (!next.wakeMode) next.wakeMode = "now";
		if (typeof next.enabled !== "boolean") next.enabled = true;
		if ((typeof next.name !== "string" || !next.name.trim()) && isRecord(next.schedule) && isRecord(next.payload)) next.name = inferLegacyName({
			schedule: next.schedule,
			payload: next.payload
		});
		else if (typeof next.name === "string") {
			const trimmed = next.name.trim();
			if (trimmed) next.name = trimmed;
		}
		if (!next.sessionTarget && isRecord(next.payload)) {
			const kind = typeof next.payload.kind === "string" ? next.payload.kind : "";
			if (kind === "systemEvent") next.sessionTarget = "main";
			if (kind === "agentTurn") next.sessionTarget = "isolated";
		}
		if ("schedule" in next && isRecord(next.schedule) && next.schedule.kind === "at" && !("deleteAfterRun" in next)) next.deleteAfterRun = true;
		if ("schedule" in next && isRecord(next.schedule) && next.schedule.kind === "cron") {
			const schedule = next.schedule;
			const expr = typeof schedule.expr === "string" ? schedule.expr.trim() : "";
			if (expr) {
				const fields = expr.split(/\s+/);
				if (fields.length !== 5) throw new Error(`Invalid cron expression "${expr}": expected 5 fields (min hour dom month dow), got ${fields.length}. Examples: "30 9 * * 1-5" (weekdays 9:30 AM), "0 * * * *" (every hour), "*/5 * * * *" (every 5 min).`);
			}
			const explicit = normalizeCronStaggerMs(schedule.staggerMs);
			if (explicit !== void 0) schedule.staggerMs = explicit;
			else {
				const defaultStaggerMs = resolveDefaultCronStaggerMs(expr);
				if (defaultStaggerMs !== void 0) schedule.staggerMs = defaultStaggerMs;
			}
		}
		const payload = isRecord(next.payload) ? next.payload : null;
		const payloadKind = payload && typeof payload.kind === "string" ? payload.kind : "";
		const sessionTarget = typeof next.sessionTarget === "string" ? next.sessionTarget : "";
		const isIsolatedAgentTurn = sessionTarget === "isolated" || sessionTarget === "" && payloadKind === "agentTurn";
		const hasDelivery = "delivery" in next && next.delivery !== void 0;
		const hasLegacyDelivery = payload ? hasLegacyDeliveryHints(payload) : false;
		if (!hasDelivery && isIsolatedAgentTurn && payloadKind === "agentTurn") if (payload && hasLegacyDelivery) {
			next.delivery = buildDeliveryFromLegacyPayload(payload);
			stripLegacyDeliveryFields(payload);
		} else next.delivery = { mode: "announce" };
	}
	return next;
}
function normalizeCronJobCreate(raw, options) {
	return normalizeCronJobInput(raw, {
		applyDefaults: true,
		...options
	});
}
function normalizeCronJobPatch(raw, options) {
	return normalizeCronJobInput(raw, {
		applyDefaults: false,
		...options
	});
}

//#endregion
//#region src/platform/cron/webhook-url.ts
function isAllowedWebhookProtocol(protocol) {
	return protocol === "http:" || protocol === "https:";
}
function normalizeHttpWebhookUrl(value) {
	if (typeof value !== "string") return null;
	const trimmed = value.trim();
	if (!trimmed) return null;
	try {
		if (!isAllowedWebhookProtocol(new URL(trimmed).protocol)) return null;
		return trimmed;
	} catch {
		return null;
	}
}

//#endregion
//#region src/agents/tools/automation/cron-tool.ts
const CRON_ACTIONS = [
	"status",
	"list",
	"add",
	"update",
	"remove",
	"run",
	"runs",
	"wake"
];
const CRON_WAKE_MODES = ["now", "next-heartbeat"];
const CRON_RUN_MODES = ["due", "force"];
const REMINDER_CONTEXT_MESSAGES_MAX = 10;
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
const REMINDER_CONTEXT_TOTAL_MAX = 700;
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
const CronToolSchema = Type.Object({
	action: stringEnum(CRON_ACTIONS),
	gatewayUrl: Type.Optional(Type.String()),
	gatewayToken: Type.Optional(Type.String()),
	timeoutMs: Type.Optional(Type.Number()),
	includeDisabled: Type.Optional(Type.Boolean()),
	job: Type.Optional(Type.Object({}, { additionalProperties: true })),
	jobId: Type.Optional(Type.String()),
	id: Type.Optional(Type.String()),
	patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
	text: Type.Optional(Type.String()),
	mode: optionalStringEnum(CRON_WAKE_MODES),
	runMode: optionalStringEnum(CRON_RUN_MODES),
	contextMessages: Type.Optional(Type.Number({
		minimum: 0,
		maximum: REMINDER_CONTEXT_MESSAGES_MAX
	}))
});
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) {
	if (input.length <= maxLen) return input;
	return `${truncateUtf16Safe(input, Math.max(0, maxLen - 3)).trimEnd()}...`;
}
function extractMessageText(message) {
	const role = typeof message.role === "string" ? 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(REMINDER_CONTEXT_MESSAGES_MAX, Math.max(0, Math.floor(params.contextMessages)));
	if (maxMessages <= 0) return [];
	const sessionKey = params.agentSessionKey?.trim();
	if (!sessionKey) return [];
	const { mainKey, alias } = resolveMainSessionAlias(loadConfig());
	const resolvedKey = resolveInternalSessionKey({
		key: sessionKey,
		alias,
		mainKey
	});
	try {
		const res = await callGatewayTool("chat.history", params.gatewayOpts, {
			sessionKey: resolvedKey,
			limit: maxMessages
		});
		const recent = (Array.isArray(res?.messages) ? res.messages : []).map((msg) => extractMessageText(msg)).filter((msg) => Boolean(msg)).slice(-maxMessages);
		if (recent.length === 0) return [];
		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 [];
	}
}
function stripThreadSuffixFromSessionKey(sessionKey) {
	const idx = sessionKey.toLowerCase().lastIndexOf(":thread:");
	if (idx <= 0) return sessionKey;
	const parent = sessionKey.slice(0, idx).trim();
	return parent ? parent : sessionKey;
}
function inferDeliveryFromSessionKey(agentSessionKey) {
	const rawSessionKey = agentSessionKey?.trim();
	if (!rawSessionKey) return null;
	const parsed = parseAgentSessionKey(stripThreadSuffixFromSessionKey(rawSessionKey));
	if (!parsed || !parsed.rest) return null;
	const parts = parsed.rest.split(":").filter(Boolean);
	if (parts.length === 0) return null;
	const head = parts[0]?.trim().toLowerCase();
	if (!head || head === "main" || head === "subagent" || head === "acp") return null;
	const markerIndex = parts.findIndex((part) => part === "direct" || part === "dm" || part === "group" || part === "channel");
	if (markerIndex === -1) return null;
	const peerId = parts.slice(markerIndex + 1).join(":").trim();
	if (!peerId) return null;
	let channel;
	if (markerIndex >= 1) channel = parts[0]?.trim().toLowerCase();
	const delivery = {
		mode: "announce",
		to: peerId
	};
	if (channel) delivery.channel = channel;
	return delivery;
}
function createCronTool(opts) {
	return {
		label: "Cron",
		name: "cron",
		description: `Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events.

ACTIONS:
- status: Check cron scheduler status
- list: List jobs (use includeDisabled:true to include disabled)
- add: Create job (requires job object, see schema below)
- update: Modify job (requires jobId + patch object)
- remove: Delete job (requires jobId)
- run: Trigger job immediately (requires jobId)
- runs: Get job run history (requires jobId)
- wake: Send wake event (requires text, optional mode)

JOB SCHEMA (for add action):
{
  "name": "string (optional)",
  "schedule": { ... },      // Required: when to run
  "payload": { ... },       // Required: what to execute
  "delivery": { ... },      // Optional: announce summary or webhook POST
  "sessionTarget": "main" | "isolated",  // Required
  "enabled": true | false   // Optional, default true
}

SCHEDULE TYPES (schedule.kind):
- "at": One-shot at absolute time
  { "kind": "at", "at": "<ISO-8601 timestamp>" }
- "every": Recurring interval
  { "kind": "every", "everyMs": <interval-ms>, "anchorMs": <optional-start-ms> }
- "cron": Cron expression
  { "kind": "cron", "expr": "<cron-expression>", "tz": "<optional-timezone>" }

ISO timestamps without an explicit timezone are treated as UTC.

PAYLOAD TYPES (payload.kind):
- "systemEvent": Injects text as system event into session
  { "kind": "systemEvent", "text": "<message>" }
- "agentTurn": Runs agent with message (isolated sessions only)
  { "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional, 0 means no timeout> }

DELIVERY (top-level):
  { "mode": "none|announce|webhook", "channel": "<optional>", "to": "<optional>", "bestEffort": <optional-bool> }
  - Default for isolated agentTurn jobs (when delivery omitted): "announce"
  - announce: send to chat channel (optional channel/to target)
  - webhook: send finished-run event as HTTP POST to delivery.to (URL required)
  - If the task needs to send to a specific chat/recipient, set announce delivery.channel/to; do not call messaging tools inside the run.

SCOPE (top-level, optional):
- "session" (default): Job is tied to the current conversation/user session. Events route to that session only.
- "universal": Job runs independently of any user session. Output routes to agent's main session. Use for global reminders or background tasks not tied to a specific user.

CRITICAL CONSTRAINTS:
- sessionTarget="main" REQUIRES payload.kind="systemEvent"
- sessionTarget="isolated" REQUIRES payload.kind="agentTurn"
- For webhook callbacks, use delivery.mode="webhook" with delivery.to set to a URL.
Default: prefer isolated agentTurn jobs unless the user explicitly wants a main-session system event.

WAKE MODES (for wake action):
- "next-heartbeat" (default): Wake on next heartbeat
- "now": Wake immediately

Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.`,
		parameters: CronToolSchema,
		execute: async (_toolCallId, args) => {
			const params = args;
			const action = readStringParam(params, "action", { required: true });
			const gatewayOpts = {
				gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
				gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
				timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : 6e4
			};
			switch (action) {
				case "status": return jsonResult(await callGatewayTool("cron.status", gatewayOpts, {}));
				case "list": return jsonResult(await callGatewayTool("cron.list", gatewayOpts, { includeDisabled: Boolean(params.includeDisabled) }));
				case "add": {
					if (!params.job || typeof params.job === "object" && params.job !== null && Object.keys(params.job).length === 0) {
						const JOB_KEYS = new Set([
							"name",
							"schedule",
							"sessionTarget",
							"wakeMode",
							"payload",
							"delivery",
							"enabled",
							"description",
							"deleteAfterRun",
							"agentId",
							"sessionKey",
							"scope",
							"message",
							"text",
							"model",
							"thinking",
							"timeoutSeconds",
							"allowUnsafeExternalContent"
						]);
						const synthetic = {};
						let found = false;
						for (const key of Object.keys(params)) if (JOB_KEYS.has(key) && params[key] !== void 0) {
							synthetic[key] = params[key];
							found = true;
						}
						if (found && (synthetic.schedule !== void 0 || synthetic.payload !== void 0 || synthetic.message !== void 0 || synthetic.text !== void 0)) params.job = synthetic;
					}
					if (!params.job || typeof params.job !== "object") throw new Error("job required");
					const job = normalizeCronJobCreate(params.job) ?? params.job;
					if (job && typeof job === "object") {
						const cfg = loadConfig();
						const { mainKey, alias } = resolveMainSessionAlias(cfg);
						const resolvedSessionKey = opts?.agentSessionKey ? resolveInternalSessionKey({
							key: opts.agentSessionKey,
							alias,
							mainKey
						}) : void 0;
						if (!("agentId" in job)) {
							const agentId = opts?.agentSessionKey ? resolveSessionAgentId({
								sessionKey: opts.agentSessionKey,
								config: cfg
							}) : void 0;
							if (agentId) job.agentId = agentId;
						}
						if ((job.scope ?? "session") === "universal") delete job.sessionKey;
						else if (!("sessionKey" in job) && resolvedSessionKey) job.sessionKey = resolvedSessionKey;
					}
					if (opts?.agentSessionKey && job && typeof job === "object" && "payload" in job && job.payload?.kind === "agentTurn") {
						const deliveryValue = job.delivery;
						const delivery = isRecord(deliveryValue) ? deliveryValue : void 0;
						const mode = (typeof delivery?.mode === "string" ? delivery.mode : "").trim().toLowerCase();
						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 = inferDeliveryFromSessionKey(opts.agentSessionKey);
							if (inferred) job.delivery = {
								...delivery,
								...inferred
							};
						}
					}
					const contextMessages = typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages) ? params.contextMessages : 0;
					if (job && typeof job === "object" && "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,
								gatewayOpts,
								contextMessages
							});
							if (contextLines.length > 0) payload.text = `${stripExistingContext(payload.text)}${REMINDER_CONTEXT_MARKER}${contextLines.join("\n")}`;
						}
					}
					return jsonResult(await callGatewayTool("cron.add", gatewayOpts, job));
				}
				case "update": {
					const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");
					if (!id) throw new Error("jobId required (id accepted for backward compatibility)");
					if (!params.patch || typeof params.patch !== "object") throw new Error("patch required");
					return jsonResult(await callGatewayTool("cron.update", gatewayOpts, {
						id,
						patch: normalizeCronJobPatch(params.patch) ?? params.patch
					}));
				}
				case "remove": {
					const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");
					if (!id) throw new Error("jobId required (id accepted for backward compatibility)");
					return jsonResult(await callGatewayTool("cron.remove", gatewayOpts, { id }));
				}
				case "run": {
					const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");
					if (!id) throw new Error("jobId required (id accepted for backward compatibility)");
					return jsonResult(await callGatewayTool("cron.run", gatewayOpts, {
						id,
						mode: params.runMode === "due" || params.runMode === "force" ? params.runMode : "force"
					}));
				}
				case "runs": {
					const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");
					if (!id) throw new Error("jobId required (id accepted for backward compatibility)");
					return jsonResult(await callGatewayTool("cron.runs", gatewayOpts, { id }));
				}
				case "wake": {
					const text = readStringParam(params, "text", { required: true });
					return jsonResult(await callGatewayTool("wake", gatewayOpts, {
						mode: params.mode === "now" || params.mode === "next-heartbeat" ? params.mode : "next-heartbeat",
						text
					}, { expectFinal: false }));
				}
				default: throw new Error(`Unknown action: ${action}`);
			}
		}
	};
}

//#endregion
export { createCronTool };