File size: 3,595 Bytes
5cb63c1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | /** Formats stable cron timeout and execution error messages. */
import { formatEmbeddedAgentExecutionPhase } from "../../agents/embedded-agent-runner/execution-phase.js";
import { extractErrorCode, formatErrorMessageWithCode } from "../../infra/errors.js";
import {
CRON_JOB_EXECUTION_TIMEOUT_ERROR,
CRON_PRE_EXECUTION_TIMEOUT_ERROR,
CRON_SETUP_TIMEOUT_ERROR,
isCronTimeoutErrorText,
} from "../execution-error-constants.js";
import type { CronAgentExecutionStarted } from "../types.js";
function formatCronAgentExecutionPhase(execution?: CronAgentExecutionStarted): string | undefined {
return formatEmbeddedAgentExecutionPhase(execution?.phase);
}
function hasCronTimeoutPrefix(error: string, prefix: string): boolean {
return error === prefix || error.startsWith(prefix + " ");
}
export function isCronTerminalAbortReasonText(error: string): boolean {
return isCronTimeoutErrorText(error);
}
/** Formats the generic cron execution timeout message with last-known phase context when available. */
export function timeoutErrorMessage(execution?: CronAgentExecutionStarted): string {
const phase = formatCronAgentExecutionPhase(execution);
if (!phase) {
return CRON_JOB_EXECUTION_TIMEOUT_ERROR;
}
return `${CRON_JOB_EXECUTION_TIMEOUT_ERROR} (last phase: ${phase})`;
}
/** Formats timeout text for runs that stalled before the isolated runner started. */
export function setupTimeoutErrorMessage(execution?: CronAgentExecutionStarted): string {
const phase = formatCronAgentExecutionPhase(execution);
if (!phase) {
return CRON_SETUP_TIMEOUT_ERROR;
}
return `${CRON_SETUP_TIMEOUT_ERROR} (last phase: ${phase})`;
}
/** Returns true for the setup-timeout class that fires before the isolated runner starts. */
export function isSetupTimeoutErrorText(error: string): boolean {
return hasCronTimeoutPrefix(error, CRON_SETUP_TIMEOUT_ERROR);
}
/** Formats timeout text for runs that stalled after setup but before execution start. */
export function preExecutionTimeoutErrorMessage(execution?: CronAgentExecutionStarted): string {
const phase = formatCronAgentExecutionPhase(execution);
if (!phase) {
return CRON_PRE_EXECUTION_TIMEOUT_ERROR;
}
return `${CRON_PRE_EXECUTION_TIMEOUT_ERROR} (last phase: ${phase})`;
}
/** Extracts a human timeout/abort reason, falling back to the canonical cron timeout text. */
export function resolveCronAbortReasonText(reason: unknown): string | undefined {
if (typeof reason === "string" && reason.trim()) {
return reason.trim();
}
if (reason instanceof Error) {
const message = reason.message.trim();
// Only an empty abort or one already carrying cron's canonical timeout text
// is unspecified. Coded aborts and other messages retain their exact reason.
if (extractErrorCode(reason) === undefined && (!message || message === timeoutErrorMessage())) {
return undefined;
}
return formatErrorMessageWithCode(reason);
}
return undefined;
}
/** Extracts a human timeout/abort reason, falling back to the canonical cron timeout text. */
export function abortErrorMessage(signal?: AbortSignal): string {
return resolveCronAbortReasonText(signal?.reason) ?? timeoutErrorMessage();
}
/** Normalizes thrown cron run failures into stable log/run-history text. */
export function normalizeCronRunErrorText(err: unknown): string {
if (
err instanceof Error &&
(err.name === "AbortError" || err.message.trim() === timeoutErrorMessage())
) {
return resolveCronAbortReasonText(err) ?? timeoutErrorMessage();
}
return formatErrorMessageWithCode(err);
}
|