import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import manageDbClient_default from "../../../data/db/manageDbClient.js";
import "../../../data/db/index.js";
import { buildTimezoneHeaders, executeAgentAsync } from "../../run/services/TriggerService.js";
import { requireProjectPermission } from "../../../middleware/projectAccess.js";
import { speakeasyOffsetLimitPagination } from "../../../utils/speakeasy.js";
import { onTriggerUpdated } from "../../run/services/ScheduledTriggerService.js";
import { assertCanMutateTrigger, validateRunAsUserId } from "./triggerHelpers.js";
import { OpenAPIHono, z } from "@hono/zod-openapi";
import { DateTimeFilterQueryParamsSchema, OrgRoles, PaginationQueryParamsSchema, ScheduledTriggerApiInsertSchema, ScheduledTriggerApiUpdateSchema, ScheduledTriggerInvocationListResponse, ScheduledTriggerInvocationResponse, ScheduledTriggerInvocationStatusEnum, ScheduledTriggerResponse, ScheduledTriggerWithRunInfoListResponse, TenantProjectAgentParamsSchema, addConversationIdToInvocation, canUseProjectStrict, cancelPendingInvocationsForTrigger, commonGetErrorResponses, computeNextRunAt, createApiError, createScheduledTrigger, createScheduledTriggerInvocation, deleteScheduledTrigger, generateId, getProjectScopedRef, getScheduledTriggerById, getScheduledTriggerInvocationById, getScheduledTriggerRunInfoBatch, getWaitUntil, interpolateTemplate, listScheduledTriggerInvocationsPaginated, listScheduledTriggersPaginated, listUpcomingInvocationsForAgentPaginated, markScheduledTriggerInvocationCancelled, markScheduledTriggerInvocationCompleted, markScheduledTriggerInvocationFailed, markScheduledTriggerInvocationRunning, resolveRef, updateScheduledTrigger, updateScheduledTriggerInvocationStatus } from "@inkeep/agents-core";
import { createProtectedRoute } from "@inkeep/agents-core/middleware";
import { CronExpressionParser } from "cron-parser";

//#region src/domains/manage/routes/scheduledTriggers.ts
const logger = getLogger$1("scheduled-triggers");
function validateRunNowDelegation(params) {
	const { runAsUserId, callerId, tenantRole } = params;
	if (!runAsUserId) return;
	if (runAsUserId === callerId) return;
	if (!(tenantRole === OrgRoles.OWNER || tenantRole === OrgRoles.ADMIN)) throw createApiError({
		code: "forbidden",
		message: "Only org admins or owners can run triggers configured to run as a different user."
	});
}
const app = new OpenAPIHono();
const ScheduledTriggerIdParamsSchema = TenantProjectAgentParamsSchema.extend({ id: z.string().describe("Scheduled Trigger ID") });
/**
* List Scheduled Triggers for an Agent
*/
app.openapi(createProtectedRoute({
	method: "get",
	path: "/",
	summary: "List Scheduled Triggers",
	operationId: "list-scheduled-triggers",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("view"),
	request: {
		params: TenantProjectAgentParamsSchema,
		query: PaginationQueryParamsSchema
	},
	responses: {
		200: {
			description: "List of scheduled triggers retrieved successfully",
			content: { "application/json": { schema: ScheduledTriggerWithRunInfoListResponse } }
		},
		...commonGetErrorResponses
	},
	...speakeasyOffsetLimitPagination
}), async (c) => {
	const { tenantId, projectId, agentId } = c.req.valid("param");
	const { page, limit } = c.req.valid("query");
	const { data, pagination } = await listScheduledTriggersPaginated(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		pagination: {
			page,
			limit
		}
	});
	const triggerIds = data.map((trigger) => ({
		agentId,
		triggerId: trigger.id
	}));
	const runInfoMap = await getScheduledTriggerRunInfoBatch(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		triggerIds
	});
	const dataWithRunInfo = data.map((trigger) => {
		const { tenantId: _tid, projectId: _pid, agentId: _aid, ...rest } = trigger;
		const runInfo = runInfoMap.get(trigger.id) || {
			lastRunAt: null,
			lastRunStatus: null,
			lastRunConversationIds: [],
			nextRunAt: null
		};
		if (!runInfo.nextRunAt && trigger.enabled) {
			if (trigger.runAt) {
				if (new Date(trigger.runAt) > /* @__PURE__ */ new Date()) runInfo.nextRunAt = trigger.runAt;
			} else if (trigger.cronExpression) try {
				const baseDate = /* @__PURE__ */ new Date();
				runInfo.nextRunAt = CronExpressionParser.parse(trigger.cronExpression, {
					currentDate: baseDate,
					tz: trigger.cronTimezone || "UTC"
				}).next().toISOString();
			} catch (error) {
				logger.warn({
					triggerId: trigger.id,
					cronExpression: trigger.cronExpression,
					error
				}, "Failed to calculate nextRunAt from cron expression");
			}
		}
		return {
			...rest,
			...runInfo
		};
	});
	return c.json({
		data: dataWithRunInfo,
		pagination
	});
});
const UpcomingRunsQueryParamsSchema = PaginationQueryParamsSchema.extend({ includeRunning: z.enum(["true", "false"]).optional().transform((val) => val === "true").describe("Include currently running invocations in results") }).openapi("UpcomingRunsQueryParams");
/**
* List Upcoming Runs Across All Scheduled Triggers
* Dashboard endpoint to view all pending/running invocations for an agent
*/
app.openapi(createProtectedRoute({
	method: "get",
	path: "/upcoming-runs",
	summary: "List Upcoming Runs",
	operationId: "list-upcoming-scheduled-runs",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("view"),
	request: {
		params: TenantProjectAgentParamsSchema,
		query: UpcomingRunsQueryParamsSchema
	},
	responses: {
		200: {
			description: "List of upcoming scheduled runs retrieved successfully",
			content: { "application/json": { schema: ScheduledTriggerInvocationListResponse } }
		},
		...commonGetErrorResponses
	},
	...speakeasyOffsetLimitPagination
}), async (c) => {
	const { tenantId, projectId, agentId } = c.req.valid("param");
	const { page, limit, includeRunning } = c.req.valid("query");
	logger.info({
		tenantId,
		projectId,
		agentId,
		includeRunning,
		page,
		limit
	}, "Listing upcoming scheduled runs across all triggers");
	const result = await listUpcomingInvocationsForAgentPaginated(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		pagination: {
			page,
			limit
		},
		includeRunning: includeRunning ?? true
	});
	logger.info({
		count: result.data.length,
		total: result.pagination.total,
		tenantId,
		projectId,
		agentId
	}, "Upcoming runs query result");
	const dataWithoutScopes = result.data.map((invocation) => {
		const { tenantId: _tid, projectId: _pid, agentId: _aid, ...rest } = invocation;
		return rest;
	});
	return c.json({
		data: dataWithoutScopes,
		pagination: result.pagination
	});
});
/**
* Get Scheduled Trigger by ID
*/
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{id}",
	summary: "Get Scheduled Trigger",
	operationId: "get-scheduled-trigger-by-id",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("view"),
	request: { params: ScheduledTriggerIdParamsSchema },
	responses: {
		200: {
			description: "Scheduled trigger found",
			content: { "application/json": { schema: ScheduledTriggerResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id } = c.req.valid("param");
	const trigger = await getScheduledTriggerById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id
	});
	if (!trigger) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger not found"
	});
	const { tenantId: _tid, projectId: _pid, agentId: _aid, ...triggerWithoutScopes } = trigger;
	return c.json({ data: triggerWithoutScopes });
});
/**
* Create Scheduled Trigger
*/
app.openapi(createProtectedRoute({
	method: "post",
	path: "/",
	summary: "Create Scheduled Trigger",
	operationId: "create-scheduled-trigger",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: {
		params: TenantProjectAgentParamsSchema,
		body: { content: { "application/json": { schema: ScheduledTriggerApiInsertSchema } } }
	},
	responses: {
		201: {
			description: "Scheduled trigger created successfully",
			content: { "application/json": { schema: ScheduledTriggerResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId } = c.req.valid("param");
	const body = c.req.valid("json");
	const callerId = c.get("userId") ?? "";
	const tenantRole = c.get("tenantRole");
	if (!tenantRole) throw createApiError({
		code: "unauthorized",
		message: "Missing tenant role"
	});
	const id = body.id || generateId();
	const runAsUserId = body.runAsUserId || null;
	if (runAsUserId) {
		if (!callerId) throw createApiError({
			code: "bad_request",
			message: "Authenticated user ID is required when setting runAsUserId"
		});
		await validateRunAsUserId({
			runAsUserId,
			callerId,
			tenantId,
			projectId,
			tenantRole
		});
	}
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId: id,
		runAsUserId
	}, "Creating scheduled trigger");
	const enabled = body.enabled ?? true;
	const nextRunAt = enabled ? computeNextRunAt({
		cronExpression: body.cronExpression,
		cronTimezone: body.cronTimezone,
		runAt: body.runAt
	}) : null;
	const { tenantId: _tid, projectId: _pid, agentId: _aid, ...triggerWithoutScopes } = await createScheduledTrigger(runDbClient_default)({
		...body,
		id,
		tenantId,
		projectId,
		agentId,
		description: body.description ?? null,
		enabled,
		cronExpression: body.cronExpression ?? null,
		cronTimezone: body.cronTimezone ?? "UTC",
		runAt: body.runAt ?? null,
		payload: body.payload ?? null,
		messageTemplate: body.messageTemplate ?? null,
		runAsUserId,
		createdBy: callerId || null,
		nextRunAt
	});
	return c.json({ data: triggerWithoutScopes }, 201);
});
/**
* Update Scheduled Trigger
*/
app.openapi(createProtectedRoute({
	method: "patch",
	path: "/{id}",
	summary: "Update Scheduled Trigger",
	operationId: "update-scheduled-trigger",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: {
		params: ScheduledTriggerIdParamsSchema,
		body: { content: { "application/json": { schema: ScheduledTriggerApiUpdateSchema } } }
	},
	responses: {
		200: {
			description: "Scheduled trigger updated successfully",
			content: { "application/json": { schema: ScheduledTriggerResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id } = c.req.valid("param");
	const body = c.req.valid("json");
	const callerId = c.get("userId") ?? "";
	const tenantRole = c.get("tenantRole");
	if (!tenantRole) throw createApiError({
		code: "unauthorized",
		message: "Missing tenant role"
	});
	if (!(body.name !== void 0 || body.description !== void 0 || body.enabled !== void 0 || body.cronExpression !== void 0 || body.cronTimezone !== void 0 || body.runAt !== void 0 || body.ref !== void 0 || body.payload !== void 0 || body.messageTemplate !== void 0 || body.maxRetries !== void 0 || body.retryDelaySeconds !== void 0 || body.timeoutSeconds !== void 0 || body.runAsUserId !== void 0)) throw createApiError({
		code: "bad_request",
		message: "No fields to update"
	});
	const runAsUserId = body.runAsUserId !== void 0 ? body.runAsUserId || null : void 0;
	if (runAsUserId) {
		if (!callerId) throw createApiError({
			code: "bad_request",
			message: "Authenticated user ID is required when setting runAsUserId"
		});
		await validateRunAsUserId({
			runAsUserId,
			callerId,
			tenantId,
			projectId,
			tenantRole
		});
	}
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId: id,
		runAsUserId
	}, "Updating scheduled trigger");
	const existing = await getScheduledTriggerById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id
	});
	if (!existing) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger not found"
	});
	assertCanMutateTrigger({
		trigger: existing,
		callerId,
		tenantRole
	});
	const merged = {
		cronExpression: body.cronExpression !== void 0 ? body.cronExpression : existing.cronExpression,
		runAt: body.runAt !== void 0 ? body.runAt : existing.runAt
	};
	if (merged.cronExpression && merged.runAt) throw createApiError({
		code: "bad_request",
		message: "Cannot have both cronExpression and runAt. Please provide only one."
	});
	if (!merged.cronExpression && !merged.runAt) throw createApiError({
		code: "bad_request",
		message: "Either cronExpression or runAt must be provided."
	});
	const scheduleChanged = body.cronExpression !== void 0 || body.cronTimezone !== void 0 || body.runAt !== void 0;
	const previousEnabled = existing.enabled;
	const resolveRetryValue = (bodyValue, existingValue, defaultValue) => {
		if (typeof bodyValue === "number" && !Number.isNaN(bodyValue)) return bodyValue;
		if (bodyValue === void 0) return existingValue ?? defaultValue;
		return defaultValue;
	};
	const mergedEnabled = body.enabled !== void 0 ? body.enabled : existing.enabled;
	const enabledChanged = body.enabled !== void 0 && body.enabled !== existing.enabled;
	let nextRunAt;
	if (!mergedEnabled) nextRunAt = null;
	else if (scheduleChanged || enabledChanged) nextRunAt = computeNextRunAt({
		cronExpression: body.cronExpression !== void 0 ? body.cronExpression : existing.cronExpression,
		cronTimezone: body.cronTimezone !== void 0 ? body.cronTimezone : existing.cronTimezone,
		runAt: body.runAt !== void 0 ? body.runAt : existing.runAt
	});
	const updatedTrigger = await updateScheduledTrigger(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id,
		data: {
			...body,
			maxRetries: resolveRetryValue(body.maxRetries, existing.maxRetries, 3),
			retryDelaySeconds: resolveRetryValue(body.retryDelaySeconds, existing.retryDelaySeconds, 60),
			timeoutSeconds: resolveRetryValue(body.timeoutSeconds, existing.timeoutSeconds, 300),
			runAsUserId,
			...nextRunAt !== void 0 ? { nextRunAt } : {}
		}
	});
	try {
		await onTriggerUpdated({
			trigger: updatedTrigger,
			previousEnabled,
			scheduleChanged
		});
	} catch (err) {
		logger.error({
			err,
			tenantId,
			projectId,
			agentId,
			scheduledTriggerId: id
		}, "Failed to update workflow for scheduled trigger");
	}
	const { tenantId: _tid, projectId: _pid, agentId: _aid, ...triggerWithoutScopes } = updatedTrigger;
	return c.json({ data: triggerWithoutScopes });
});
/**
* Delete Scheduled Trigger
*/
app.openapi(createProtectedRoute({
	method: "delete",
	path: "/{id}",
	summary: "Delete Scheduled Trigger",
	operationId: "delete-scheduled-trigger",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: { params: ScheduledTriggerIdParamsSchema },
	responses: {
		204: { description: "Scheduled trigger deleted successfully" },
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id } = c.req.valid("param");
	const callerId = c.get("userId") ?? "";
	const tenantRole = c.get("tenantRole");
	if (!tenantRole) throw createApiError({
		code: "unauthorized",
		message: "Missing tenant role"
	});
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId: id
	}, "Deleting scheduled trigger");
	const existing = await getScheduledTriggerById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id
	});
	if (!existing) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger not found"
	});
	assertCanMutateTrigger({
		trigger: existing,
		callerId,
		tenantRole
	});
	const cancelledCount = await cancelPendingInvocationsForTrigger(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id
	});
	if (cancelledCount > 0) logger.info({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId: id,
		cancelledCount
	}, "Cancelled pending invocations before deleting scheduled trigger");
	await deleteScheduledTrigger(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId: id
	});
	return c.body(null, 204);
});
const ScheduledTriggerInvocationQueryParamsSchema = PaginationQueryParamsSchema.merge(DateTimeFilterQueryParamsSchema).extend({ status: ScheduledTriggerInvocationStatusEnum.optional().describe("Filter by invocation status") }).openapi("ScheduledTriggerInvocationQueryParams");
/**
* List Scheduled Trigger Invocations
*/
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{id}/invocations",
	summary: "List Scheduled Trigger Invocations",
	operationId: "list-scheduled-trigger-invocations",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("view"),
	request: {
		params: ScheduledTriggerIdParamsSchema,
		query: ScheduledTriggerInvocationQueryParamsSchema
	},
	responses: {
		200: {
			description: "List of scheduled trigger invocations retrieved successfully",
			content: { "application/json": { schema: ScheduledTriggerInvocationListResponse } }
		},
		...commonGetErrorResponses
	},
	...speakeasyOffsetLimitPagination
}), async (c) => {
	const { tenantId, projectId, agentId, id: scheduledTriggerId } = c.req.valid("param");
	const { page, limit, status, from, to } = c.req.valid("query");
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		status,
		from,
		to
	}, "Listing scheduled trigger invocations");
	const { data, pagination } = await listScheduledTriggerInvocationsPaginated(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId,
		pagination: {
			page,
			limit
		},
		filters: {
			status,
			from,
			to
		}
	});
	const dataWithoutScopes = data.map((invocation) => {
		const { tenantId: _tid, projectId: _pid, agentId: _aid, ...rest } = invocation;
		return rest;
	});
	return c.json({
		data: dataWithoutScopes,
		pagination
	});
});
/**
* Get Scheduled Trigger Invocation by ID
*/
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{id}/invocations/{invocationId}",
	summary: "Get Scheduled Trigger Invocation",
	operationId: "get-scheduled-trigger-invocation-by-id",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("view"),
	request: { params: ScheduledTriggerIdParamsSchema.extend({ invocationId: z.string().describe("Scheduled Trigger Invocation ID") }) },
	responses: {
		200: {
			description: "Scheduled trigger invocation found",
			content: { "application/json": { schema: ScheduledTriggerInvocationResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id: scheduledTriggerId, invocationId } = c.req.valid("param");
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		invocationId
	}, "Getting scheduled trigger invocation");
	const invocation = await getScheduledTriggerInvocationById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId,
		invocationId
	});
	if (!invocation) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger invocation not found"
	});
	const { tenantId: _tid, projectId: _pid, agentId: _aid, ...invocationWithoutScopes } = invocation;
	return c.json({ data: invocationWithoutScopes });
});
/**
* Cancel Scheduled Trigger Invocation
*/
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{id}/invocations/{invocationId}/cancel",
	summary: "Cancel Scheduled Trigger Invocation",
	operationId: "cancel-scheduled-trigger-invocation",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: { params: ScheduledTriggerIdParamsSchema.extend({ invocationId: z.string().describe("Scheduled Trigger Invocation ID") }) },
	responses: {
		200: {
			description: "Scheduled trigger invocation cancelled successfully",
			content: { "application/json": { schema: z.object({
				success: z.boolean(),
				invocationId: z.string(),
				previousStatus: z.string().optional()
			}) } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id: scheduledTriggerId, invocationId } = c.req.valid("param");
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		invocationId
	}, "Cancelling scheduled trigger invocation");
	const invocation = await getScheduledTriggerInvocationById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId,
		invocationId
	});
	if (!invocation) throw createApiError({
		code: "not_found",
		message: "Invocation not found"
	});
	if (invocation.status === "completed" || invocation.status === "failed") throw createApiError({
		code: "bad_request",
		message: `Cannot cancel invocation with status: ${invocation.status}`
	});
	if (invocation.status === "cancelled") return c.json({
		success: true,
		invocationId,
		previousStatus: "cancelled"
	});
	const previousStatus = invocation.status;
	await markScheduledTriggerInvocationCancelled(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId,
		invocationId
	});
	logger.info({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		invocationId,
		previousStatus
	}, "Scheduled trigger invocation cancelled");
	return c.json({
		success: true,
		invocationId,
		previousStatus
	});
});
/**
* Rerun Scheduled Trigger Invocation
* Creates a new invocation and executes it immediately (manual rerun of a past run)
*/
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{id}/invocations/{invocationId}/rerun",
	summary: "Rerun Scheduled Trigger Invocation",
	operationId: "rerun-scheduled-trigger-invocation",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: { params: ScheduledTriggerIdParamsSchema.extend({ invocationId: z.string().describe("Scheduled Trigger Invocation ID to rerun") }) },
	responses: {
		200: {
			description: "Scheduled trigger invocation rerun initiated successfully",
			content: { "application/json": { schema: z.object({
				success: z.boolean(),
				newInvocationId: z.string(),
				originalInvocationId: z.string()
			}) } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id: scheduledTriggerId, invocationId } = c.req.valid("param");
	const callerId = c.get("userId") ?? "";
	const tenantRole = c.get("tenantRole");
	if (!tenantRole) throw createApiError({
		code: "unauthorized",
		message: "Missing tenant role"
	});
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		invocationId
	}, "Rerunning scheduled trigger invocation");
	const originalInvocation = await getScheduledTriggerInvocationById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId,
		invocationId
	});
	if (!originalInvocation) throw createApiError({
		code: "not_found",
		message: "Invocation not found"
	});
	if (originalInvocation.status === "pending" || originalInvocation.status === "running") throw createApiError({
		code: "bad_request",
		message: `Cannot rerun invocation with status: ${originalInvocation.status}. Wait for it to complete or cancel it first.`
	});
	const trigger = await getScheduledTriggerById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId
	});
	if (!trigger) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger not found"
	});
	validateRunNowDelegation({
		runAsUserId: trigger.runAsUserId,
		callerId,
		tenantRole
	});
	const { maxRetries, retryDelaySeconds, timeoutSeconds } = trigger;
	if (!trigger.ref) throw createApiError({
		code: "bad_request",
		message: "Scheduled trigger has no ref configured"
	});
	const rerunProjectScopedRef = getProjectScopedRef(tenantId, projectId, trigger.ref);
	const resolvedRef = await resolveRef(manageDbClient_default)(rerunProjectScopedRef);
	if (!resolvedRef) throw createApiError({
		code: "bad_request",
		message: `Failed to resolve ref '${trigger.ref}' for project ${projectId}. The branch may have been deleted.`
	});
	const newInvocationId = generateId();
	await createScheduledTriggerInvocation(runDbClient_default)({
		id: newInvocationId,
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		ref: resolvedRef,
		status: "pending",
		scheduledFor: (/* @__PURE__ */ new Date()).toISOString(),
		idempotencyKey: `manual-rerun-${invocationId}-${Date.now()}`,
		attemptNumber: 1
	});
	logger.info({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		originalInvocationId: invocationId,
		newInvocationId,
		maxRetries,
		retryDelaySeconds,
		timeoutSeconds
	}, "Created new invocation for manual rerun with retry support");
	const scopes = {
		tenantId,
		projectId,
		agentId
	};
	const rerunExecutionPromise = (async () => {
		try {
			await markScheduledTriggerInvocationRunning(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId: newInvocationId
			});
			const effectivePayload = trigger.payload ?? {};
			const userMessage = trigger.messageTemplate ? interpolateTemplate(trigger.messageTemplate, effectivePayload) : JSON.stringify(effectivePayload);
			const messageParts = [];
			if (trigger.messageTemplate) messageParts.push({
				kind: "text",
				text: userMessage
			});
			messageParts.push({
				kind: "data",
				data: effectivePayload,
				metadata: {
					source: "scheduled-trigger",
					triggerId: scheduledTriggerId
				}
			});
			if (trigger.runAsUserId) {
				if (!await canUseProjectStrict({
					userId: trigger.runAsUserId,
					tenantId,
					projectId
				})) throw new Error(`User ${trigger.runAsUserId} no longer has access to project ${projectId}`);
			}
			const maxAttempts = maxRetries + 1;
			let attemptNumber = 1;
			let lastError = null;
			const executeWithTimeout = async (conversationId) => {
				const timeoutMs = timeoutSeconds * 1e3;
				const timeoutPromise = new Promise((_, reject) => {
					setTimeout(() => reject(/* @__PURE__ */ new Error(`Execution timed out after ${timeoutSeconds}s`)), timeoutMs);
				});
				await Promise.race([executeAgentAsync({
					tenantId,
					projectId,
					agentId,
					triggerId: scheduledTriggerId,
					invocationId: newInvocationId,
					conversationId,
					userMessage,
					messageParts,
					resolvedRef,
					runAsUserId: trigger.runAsUserId ?? void 0,
					forwardedHeaders: buildTimezoneHeaders(trigger.cronTimezone)
				}), timeoutPromise]);
			};
			while (attemptNumber <= maxAttempts) {
				const conversationId = generateId();
				let success = false;
				try {
					await executeWithTimeout(conversationId);
					success = true;
				} catch (execErr) {
					lastError = execErr instanceof Error ? execErr.message : String(execErr);
					logger.error({
						invocationId: newInvocationId,
						attemptNumber,
						error: lastError
					}, "Manual rerun failed with error");
				}
				await addConversationIdToInvocation(runDbClient_default)({
					scopes,
					scheduledTriggerId,
					invocationId: newInvocationId,
					conversationId
				}).catch((err) => {
					logger.error({
						invocationId: newInvocationId,
						error: err instanceof Error ? err.message : String(err)
					}, "Failed to save conversation ID");
				});
				if (success) {
					await markScheduledTriggerInvocationCompleted(runDbClient_default)({
						scopes,
						scheduledTriggerId,
						invocationId: newInvocationId
					});
					logger.info({
						invocationId: newInvocationId,
						conversationId,
						attemptNumber
					}, "Manual rerun completed");
					lastError = null;
					break;
				}
				if (attemptNumber < maxAttempts) {
					await updateScheduledTriggerInvocationStatus(runDbClient_default)({
						scopes,
						scheduledTriggerId,
						invocationId: newInvocationId,
						data: {
							attemptNumber: attemptNumber + 1,
							status: "running"
						}
					}).catch((err) => {
						logger.error({
							invocationId: newInvocationId,
							error: err instanceof Error ? err.message : String(err)
						}, "Failed to update invocation attempt number");
					});
					attemptNumber++;
					const jitter = Math.random() * .3;
					await new Promise((resolve) => setTimeout(resolve, retryDelaySeconds * 1e3 * (1 + jitter)));
				} else break;
			}
			if (lastError) await markScheduledTriggerInvocationFailed(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId: newInvocationId
			}).catch((err) => {
				logger.error({
					invocationId: newInvocationId,
					error: err instanceof Error ? err.message : String(err)
				}, "Failed to mark invocation as failed after retries exhausted");
			});
		} catch (err) {
			const error = err instanceof Error ? err.message : String(err);
			logger.error({
				invocationId: newInvocationId,
				error
			}, "Manual rerun setup failed");
			await markScheduledTriggerInvocationFailed(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId: newInvocationId
			}).catch((err$1) => {
				logger.error({
					invocationId: newInvocationId,
					error: err$1 instanceof Error ? err$1.message : String(err$1)
				}, "Failed to mark invocation as failed in error handler");
			});
		}
	})();
	const waitUntil = await getWaitUntil();
	if (waitUntil) waitUntil(rerunExecutionPromise);
	else rerunExecutionPromise.catch((error) => {
		logger.error({
			invocationId: newInvocationId,
			error: error instanceof Error ? error.message : String(error)
		}, "Background rerun execution failed (no waitUntil available)");
	});
	return c.json({
		success: true,
		newInvocationId,
		originalInvocationId: invocationId
	});
});
/**
* Run Scheduled Trigger Now
* Creates a new invocation and executes it immediately (manual trigger)
*/
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{id}/run",
	summary: "Run Scheduled Trigger Now",
	operationId: "run-scheduled-trigger-now",
	tags: ["Scheduled Triggers"],
	permission: requireProjectPermission("edit"),
	request: { params: ScheduledTriggerIdParamsSchema },
	responses: {
		200: {
			description: "Scheduled trigger run initiated successfully",
			content: { "application/json": { schema: z.object({
				success: z.boolean(),
				invocationId: z.string()
			}) } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, agentId, id: scheduledTriggerId } = c.req.valid("param");
	const callerId = c.get("userId") ?? "";
	const tenantRole = c.get("tenantRole");
	if (!tenantRole) throw createApiError({
		code: "unauthorized",
		message: "Missing tenant role"
	});
	logger.debug({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId
	}, "Running scheduled trigger now");
	const trigger = await getScheduledTriggerById(runDbClient_default)({
		scopes: {
			tenantId,
			projectId,
			agentId
		},
		scheduledTriggerId
	});
	if (!trigger) throw createApiError({
		code: "not_found",
		message: "Scheduled trigger not found"
	});
	validateRunNowDelegation({
		runAsUserId: trigger.runAsUserId,
		callerId,
		tenantRole
	});
	const maxRetries = trigger.maxRetries ?? 1;
	const retryDelaySeconds = trigger.retryDelaySeconds ?? 60;
	const timeoutSeconds = trigger.timeoutSeconds ?? 780;
	if (!trigger.ref) throw createApiError({
		code: "bad_request",
		message: "Scheduled trigger has no ref configured"
	});
	const projectScopedRef = getProjectScopedRef(tenantId, projectId, trigger.ref);
	const resolvedRef = await resolveRef(manageDbClient_default)(projectScopedRef);
	if (!resolvedRef) throw createApiError({
		code: "bad_request",
		message: `Failed to resolve ref '${trigger.ref}' for project ${projectId}. The branch may have been deleted.`
	});
	const invocationId = generateId();
	await createScheduledTriggerInvocation(runDbClient_default)({
		id: invocationId,
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		ref: resolvedRef,
		status: "pending",
		scheduledFor: (/* @__PURE__ */ new Date()).toISOString(),
		idempotencyKey: `manual-run-${scheduledTriggerId}-${Date.now()}`,
		attemptNumber: 1
	});
	logger.info({
		tenantId,
		projectId,
		agentId,
		scheduledTriggerId,
		invocationId,
		maxRetries,
		retryDelaySeconds,
		runAsUserId: trigger.runAsUserId
	}, "Created new invocation for manual run");
	const scopes = {
		tenantId,
		projectId,
		agentId
	};
	const executionPromise = (async () => {
		try {
			await markScheduledTriggerInvocationRunning(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId
			});
			const effectivePayload = trigger.payload ?? {};
			const userMessage = trigger.messageTemplate ? interpolateTemplate(trigger.messageTemplate, effectivePayload) : JSON.stringify(effectivePayload);
			const messageParts = [];
			if (trigger.messageTemplate) messageParts.push({
				kind: "text",
				text: userMessage
			});
			messageParts.push({
				kind: "data",
				data: effectivePayload,
				metadata: {
					source: "scheduled-trigger",
					triggerId: scheduledTriggerId
				}
			});
			if (trigger.runAsUserId) {
				if (!await canUseProjectStrict({
					userId: trigger.runAsUserId,
					tenantId,
					projectId
				})) throw new Error(`User ${trigger.runAsUserId} no longer has access to project ${projectId}`);
			}
			const maxAttempts = maxRetries + 1;
			let attemptNumber = 1;
			let lastError = null;
			const executeWithTimeout = async (conversationId) => {
				const timeoutMs = timeoutSeconds * 1e3;
				const timeoutPromise = new Promise((_, reject) => {
					setTimeout(() => reject(/* @__PURE__ */ new Error(`Execution timed out after ${timeoutSeconds}s`)), timeoutMs);
				});
				await Promise.race([executeAgentAsync({
					tenantId,
					projectId,
					agentId,
					triggerId: scheduledTriggerId,
					invocationId,
					conversationId,
					userMessage,
					messageParts,
					resolvedRef,
					runAsUserId: trigger.runAsUserId ?? void 0,
					forwardedHeaders: buildTimezoneHeaders(trigger.cronTimezone)
				}), timeoutPromise]);
			};
			while (attemptNumber <= maxAttempts) {
				const conversationId = generateId();
				let success = false;
				try {
					await executeWithTimeout(conversationId);
					success = true;
				} catch (execErr) {
					lastError = execErr instanceof Error ? execErr.message : String(execErr);
					logger.error({
						invocationId,
						attemptNumber,
						error: lastError
					}, "Manual run failed with error");
				}
				await addConversationIdToInvocation(runDbClient_default)({
					scopes,
					scheduledTriggerId,
					invocationId,
					conversationId
				}).catch((err) => {
					logger.error({
						invocationId,
						error: err instanceof Error ? err.message : String(err)
					}, "Failed to save conversation ID");
				});
				if (success) {
					await markScheduledTriggerInvocationCompleted(runDbClient_default)({
						scopes,
						scheduledTriggerId,
						invocationId
					});
					logger.info({
						invocationId,
						conversationId,
						attemptNumber
					}, "Manual run completed");
					lastError = null;
					break;
				}
				if (attemptNumber < maxAttempts) {
					await updateScheduledTriggerInvocationStatus(runDbClient_default)({
						scopes,
						scheduledTriggerId,
						invocationId,
						data: {
							attemptNumber: attemptNumber + 1,
							status: "running"
						}
					}).catch((err) => {
						logger.error({
							invocationId,
							error: err instanceof Error ? err.message : String(err)
						}, "Failed to update invocation attempt number");
					});
					attemptNumber++;
					const jitter = Math.random() * .3;
					await new Promise((resolve) => setTimeout(resolve, retryDelaySeconds * 1e3 * (1 + jitter)));
				} else break;
			}
			if (lastError) await markScheduledTriggerInvocationFailed(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId
			}).catch((err) => {
				logger.error({
					invocationId,
					error: err instanceof Error ? err.message : String(err)
				}, "Failed to mark invocation as failed after retries exhausted");
			});
		} catch (err) {
			const error = err instanceof Error ? err.message : String(err);
			logger.error({
				invocationId,
				error
			}, "Manual run setup failed");
			await markScheduledTriggerInvocationFailed(runDbClient_default)({
				scopes,
				scheduledTriggerId,
				invocationId
			}).catch((err$1) => {
				logger.error({
					invocationId,
					error: err$1 instanceof Error ? err$1.message : String(err$1)
				}, "Failed to mark invocation as failed in error handler");
			});
		}
	})();
	const waitUntil = await getWaitUntil();
	if (waitUntil) waitUntil(executionPromise);
	else executionPromise.catch((error) => {
		logger.error({
			invocationId,
			error: error instanceof Error ? error.message : String(error)
		}, "Background execution failed (no waitUntil available)");
	});
	return c.json({
		success: true,
		invocationId
	});
});
var scheduledTriggers_default = app;

//#endregion
export { assertCanMutateTrigger, scheduledTriggers_default as default, validateRunAsUserId };