mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 07:24:32 +00:00
* feat(cron): add failure destination support with webhook mode and bestEffort handling Extends PR #24789 failure alerts with features from PR #29145: - Add webhook delivery mode for failure alerts (mode: 'webhook') - Add accountId support for multi-account channel configurations - Add bestEffort handling to skip alerts when job has bestEffort=true - Add separate failureDestination config (global + per-job in delivery) - Add duplicate prevention (prevents sending to same as primary delivery) - Add CLI flags: --failure-alert-mode, --failure-alert-account-id - Add UI fields for new options in web cron editor * fix(cron): merge failureAlert mode/accountId and preserve failureDestination on updates - Fix mergeCronFailureAlert to merge mode and accountId fields - Fix mergeCronDelivery to preserve failureDestination on updates - Fix isSameDeliveryTarget to use 'announce' as default instead of 'none' to properly detect duplicates when delivery.mode is undefined * fix(cron): validate webhook mode requires URL in resolveFailureDestination When mode is 'webhook' but no 'to' URL is provided, return null instead of creating an invalid plan that silently fails later. * fix(cron): fail closed on webhook mode without URL and make failureDestination fields clearable - sendCronFailureAlert: fail closed when mode is webhook but URL is missing - mergeCronDelivery: use per-key presence checks so callers can clear nested failureDestination fields via cron.update Note: protocol:check shows missing internalEvents in Swift models - this is a pre-existing issue unrelated to these changes (upstream sync needed). * fix(cron): use separate schema for failureDestination and fix type cast - Create CronFailureDestinationSchema excluding after/cooldownMs fields - Fix type cast in sendFailureNotificationAnnounce to use CronMessageChannel * fix(cron): merge global failureDestination with partial job overrides When job has partial failureDestination config, fall back to global config for unset fields instead of treating it as a full override. * fix(cron): avoid forcing announce mode and clear inherited to on mode change - UI: only include mode in patch if explicitly set to non-default - delivery.ts: clear inherited 'to' when job overrides mode, since URL semantics differ between announce and webhook modes * fix(cron): preserve explicit to on mode override and always include mode in UI patches - delivery.ts: preserve job-level explicit 'to' when overriding mode - UI: always include mode in failureAlert patch so users can switch between announce/webhook * fix(cron): allow clearing accountId and treat undefined global mode as announce - UI: always include accountId in patch so users can clear it - delivery.ts: treat undefined global mode as announce when comparing for clearing inherited 'to' * Cron: harden failure destination routing and add regression coverage * Cron: resolve failure destination review feedback * Cron: drop unrelated timeout assertions from conflict resolution * Cron: format cron CLI regression test * Cron: align gateway cron test mock types --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
175 lines
5.0 KiB
TypeScript
175 lines
5.0 KiB
TypeScript
import type { ChannelId } from "../channels/plugins/types.js";
|
|
|
|
export type CronSchedule =
|
|
| { kind: "at"; at: string }
|
|
| { kind: "every"; everyMs: number; anchorMs?: number }
|
|
| {
|
|
kind: "cron";
|
|
expr: string;
|
|
tz?: string;
|
|
/** Optional deterministic stagger window in milliseconds (0 keeps exact schedule). */
|
|
staggerMs?: number;
|
|
};
|
|
|
|
export type CronSessionTarget = "main" | "isolated";
|
|
export type CronWakeMode = "next-heartbeat" | "now";
|
|
|
|
export type CronMessageChannel = ChannelId | "last";
|
|
|
|
export type CronDeliveryMode = "none" | "announce" | "webhook";
|
|
|
|
export type CronDelivery = {
|
|
mode: CronDeliveryMode;
|
|
channel?: CronMessageChannel;
|
|
to?: string;
|
|
/** Explicit channel account id for multi-account setups (e.g. multiple Telegram bots). */
|
|
accountId?: string;
|
|
bestEffort?: boolean;
|
|
/** Separate destination for failure notifications. */
|
|
failureDestination?: CronFailureDestination;
|
|
};
|
|
|
|
export type CronFailureDestination = {
|
|
channel?: CronMessageChannel;
|
|
to?: string;
|
|
accountId?: string;
|
|
mode?: "announce" | "webhook";
|
|
};
|
|
|
|
export type CronDeliveryPatch = Partial<CronDelivery>;
|
|
|
|
export type CronRunStatus = "ok" | "error" | "skipped";
|
|
export type CronDeliveryStatus = "delivered" | "not-delivered" | "unknown" | "not-requested";
|
|
|
|
export type CronUsageSummary = {
|
|
input_tokens?: number;
|
|
output_tokens?: number;
|
|
total_tokens?: number;
|
|
cache_read_tokens?: number;
|
|
cache_write_tokens?: number;
|
|
};
|
|
|
|
export type CronRunTelemetry = {
|
|
model?: string;
|
|
provider?: string;
|
|
usage?: CronUsageSummary;
|
|
};
|
|
|
|
export type CronRunOutcome = {
|
|
status: CronRunStatus;
|
|
error?: string;
|
|
/** Optional classifier for execution errors to guide fallback behavior. */
|
|
errorKind?: "delivery-target";
|
|
summary?: string;
|
|
sessionId?: string;
|
|
sessionKey?: string;
|
|
};
|
|
|
|
export type CronFailureAlert = {
|
|
after?: number;
|
|
channel?: CronMessageChannel;
|
|
to?: string;
|
|
cooldownMs?: number;
|
|
/** Delivery mode: announce (via messaging channels) or webhook (HTTP POST). */
|
|
mode?: "announce" | "webhook";
|
|
/** Account ID for multi-account channel configurations. */
|
|
accountId?: string;
|
|
};
|
|
|
|
export type CronPayload =
|
|
| { kind: "systemEvent"; text: string }
|
|
| {
|
|
kind: "agentTurn";
|
|
message: string;
|
|
/** Optional model override (provider/model or alias). */
|
|
model?: string;
|
|
/** Optional per-job fallback models; overrides agent/global fallbacks when defined. */
|
|
fallbacks?: string[];
|
|
thinking?: string;
|
|
timeoutSeconds?: number;
|
|
allowUnsafeExternalContent?: boolean;
|
|
/** If true, run with lightweight bootstrap context. */
|
|
lightContext?: boolean;
|
|
deliver?: boolean;
|
|
channel?: CronMessageChannel;
|
|
to?: string;
|
|
bestEffortDeliver?: boolean;
|
|
};
|
|
|
|
export type CronPayloadPatch =
|
|
| { kind: "systemEvent"; text?: string }
|
|
| {
|
|
kind: "agentTurn";
|
|
message?: string;
|
|
model?: string;
|
|
fallbacks?: string[];
|
|
thinking?: string;
|
|
timeoutSeconds?: number;
|
|
allowUnsafeExternalContent?: boolean;
|
|
/** If true, run with lightweight bootstrap context. */
|
|
lightContext?: boolean;
|
|
deliver?: boolean;
|
|
channel?: CronMessageChannel;
|
|
to?: string;
|
|
bestEffortDeliver?: boolean;
|
|
};
|
|
|
|
export type CronJobState = {
|
|
nextRunAtMs?: number;
|
|
runningAtMs?: number;
|
|
lastRunAtMs?: number;
|
|
/** Preferred execution outcome field. */
|
|
lastRunStatus?: CronRunStatus;
|
|
/** Back-compat alias for lastRunStatus. */
|
|
lastStatus?: "ok" | "error" | "skipped";
|
|
lastError?: string;
|
|
lastDurationMs?: number;
|
|
/** Number of consecutive execution errors (reset on success). Used for backoff. */
|
|
consecutiveErrors?: number;
|
|
/** Last failure alert timestamp (ms since epoch) for cooldown gating. */
|
|
lastFailureAlertAtMs?: number;
|
|
/** Number of consecutive schedule computation errors. Auto-disables job after threshold. */
|
|
scheduleErrorCount?: number;
|
|
/** Explicit delivery outcome, separate from execution outcome. */
|
|
lastDeliveryStatus?: CronDeliveryStatus;
|
|
/** Delivery-specific error text when available. */
|
|
lastDeliveryError?: string;
|
|
/** Whether the last run's output was delivered to the target channel. */
|
|
lastDelivered?: boolean;
|
|
};
|
|
|
|
export type CronJob = {
|
|
id: string;
|
|
agentId?: string;
|
|
/** Origin session namespace for reminder delivery and wake routing. */
|
|
sessionKey?: string;
|
|
name: string;
|
|
description?: string;
|
|
enabled: boolean;
|
|
deleteAfterRun?: boolean;
|
|
createdAtMs: number;
|
|
updatedAtMs: number;
|
|
schedule: CronSchedule;
|
|
sessionTarget: CronSessionTarget;
|
|
wakeMode: CronWakeMode;
|
|
payload: CronPayload;
|
|
delivery?: CronDelivery;
|
|
failureAlert?: CronFailureAlert | false;
|
|
state: CronJobState;
|
|
};
|
|
|
|
export type CronStoreFile = {
|
|
version: 1;
|
|
jobs: CronJob[];
|
|
};
|
|
|
|
export type CronJobCreate = Omit<CronJob, "id" | "createdAtMs" | "updatedAtMs" | "state"> & {
|
|
state?: Partial<CronJobState>;
|
|
};
|
|
|
|
export type CronJobPatch = Partial<Omit<CronJob, "id" | "createdAtMs" | "state" | "payload">> & {
|
|
payload?: CronPayloadPatch;
|
|
delivery?: CronDeliveryPatch;
|
|
state?: Partial<CronJobState>;
|
|
};
|