From b26ed1eb539416f50ba5bf364fbd332e556e5f6f Mon Sep 17 00:00:00 2001 From: Peter Foster Date: Mon, 16 Feb 2026 11:27:27 +0000 Subject: [PATCH] feat: add business hours scheduling to heartbeat - Configurable start/end hours (0-23) - Timezone support (default: UTC) - Silent skip outside business hours (no log spam) - Backward compatible (no schedule = always run) --- lib/services/heartbeat.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/services/heartbeat.ts b/lib/services/heartbeat.ts index 307df3f..7ef83d8 100644 --- a/lib/services/heartbeat.ts +++ b/lib/services/heartbeat.ts @@ -31,6 +31,13 @@ export type HeartbeatConfig = { enabled: boolean; intervalSeconds: number; maxPickupsPerTick: number; + schedule?: { + enabled_hours?: { + start: number; // 0-23 + end: number; // 0-23 + timezone?: string; // IANA timezone (default: UTC) + }; + }; }; type Agent = { @@ -165,6 +172,26 @@ function hasProjects(workspace: string): boolean { ); } +/** + * Check if current time is within enabled hours. + */ +function isWithinBusinessHours(config: HeartbeatConfig): boolean { + if (!config.schedule?.enabled_hours) return true; + + const { start, end, timezone = "UTC" } = config.schedule.enabled_hours; + + // Get current hour in the configured timezone + const now = new Date(); + const formatter = new Intl.DateTimeFormat("en-US", { + timeZone: timezone, + hour: "numeric", + hour12: false, + }); + const currentHour = parseInt(formatter.format(now), 10); + + return currentHour >= start && currentHour < end; +} + /** * Run one heartbeat tick for all agents. */ @@ -174,6 +201,11 @@ async function runHeartbeatTick( pluginConfig: Record | undefined, logger: ServiceContext["logger"], ): Promise { + // Skip tick if outside business hours + if (!isWithinBusinessHours(config)) { + return; // Silent skip - no logging spam + } + try { const result = await processAllAgents(agents, config, pluginConfig, logger); logTickResult(result, logger);