diff --git a/src/cron/service.ts b/src/cron/service.ts index 87046c62e..620ebd16f 100644 --- a/src/cron/service.ts +++ b/src/cron/service.ts @@ -37,6 +37,11 @@ export type CronServiceDeps = { cronEnabled: boolean; enqueueSystemEvent: (text: string) => void; requestHeartbeatNow: (opts?: { reason?: string }) => void; + runHeartbeatOnce?: (opts?: { reason?: string }) => Promise<{ + status: "ran" | "skipped" | "failed"; + durationMs: number; + reason?: string; + }>; runIsolatedAgentJob: (params: { job: CronJob; message: string }) => Promise<{ status: "ok" | "error" | "skipped"; summary?: string; @@ -514,10 +519,31 @@ export class CronService { return; } this.deps.enqueueSystemEvent(text); - if (job.wakeMode === "now") { + if (job.wakeMode === "now" && this.deps.runHeartbeatOnce) { + const heartbeatResult = await this.deps.runHeartbeatOnce({ + reason: `cron:${job.id}`, + }); + // Map heartbeat status to cron status + if (heartbeatResult.status === "ran") { + await finish("ok", undefined, text); + } else if (heartbeatResult.status === "skipped") { + await finish( + "skipped", + heartbeatResult.reason ?? "heartbeat skipped", + text, + ); + } else if (heartbeatResult.status === "failed") { + await finish( + "error", + heartbeatResult.reason ?? "heartbeat failed", + text, + ); + } + } else { + // wakeMode is "next-heartbeat" or runHeartbeatOnce not available this.deps.requestHeartbeatNow({ reason: `cron:${job.id}` }); + await finish("ok", undefined, text); } - await finish("ok", undefined, text); return; } diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 805ae7ffe..caabc1517 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -61,7 +61,10 @@ import { startNodeBridgeServer } from "../infra/bridge/server.js"; import { resolveCanvasHostUrl } from "../infra/canvas-host-url.js"; import { GatewayLockError } from "../infra/gateway-lock.js"; import { onHeartbeatEvent } from "../infra/heartbeat-events.js"; -import { startHeartbeatRunner } from "../infra/heartbeat-runner.js"; +import { + runHeartbeatOnce, + startHeartbeatRunner, +} from "../infra/heartbeat-runner.js"; import { requestHeartbeatNow } from "../infra/heartbeat-wake.js"; import { getMachineDisplayName } from "../infra/machine-name.js"; import { resolveOutboundTarget } from "../infra/outbound/targets.js"; @@ -715,6 +718,14 @@ export async function startGatewayServer( enqueueSystemEvent(text, { sessionKey: resolveMainSessionKey(cfg) }); }, requestHeartbeatNow, + runHeartbeatOnce: async (opts) => { + const runtimeConfig = loadConfig(); + return await runHeartbeatOnce({ + cfg: runtimeConfig, + reason: opts?.reason, + deps: { runtime }, + }); + }, runIsolatedAgentJob: async ({ job, message }) => { const runtimeConfig = loadConfig(); return await runCronIsolatedAgentTurn({