diff --git a/.env.example b/.env.example index b4f04090..cf22477b 100644 --- a/.env.example +++ b/.env.example @@ -80,6 +80,9 @@ OPENCODE_MODEL_ID=big-pickle # If exceeded, the bot stops waiting for the result and marks the run as failed. # SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES=120 +# Send scheduled task result/error messages without Telegram push notifications (default: false) +# SCHEDULED_TASK_DISABLE_NOTIFICATION=false + # Response streaming mode: "edit" (default) or "draft" (default: edit) # edit = uses editMessageText to update messages incrementally (may flicker) # draft = uses sendMessageDraft (Bot API 9.5+) for smooth animated draft previews, diff --git a/src/bot/messages/scheduled-task-delivery.ts b/src/bot/messages/scheduled-task-delivery.ts index a2a6f5d5..b437609f 100644 --- a/src/bot/messages/scheduled-task-delivery.ts +++ b/src/bot/messages/scheduled-task-delivery.ts @@ -18,6 +18,12 @@ function getScheduledTaskDeliveryFormat(): "raw" | "markdown_v2" { return config.bot.messageFormatMode === "markdown" ? "markdown_v2" : "raw"; } +function getSilentDeliveryOptions(): { options: { disable_notification: true } } | Record { + return config.bot.scheduledTaskNotificationsSilent + ? { options: { disable_notification: true } } + : {}; +} + function buildScheduledTaskSuccessMessageParts(delivery: QueuedScheduledTaskDelivery): string[] { if (!delivery.resultText) { return [delivery.notificationText]; @@ -56,6 +62,10 @@ export function createScheduledTaskDeliverySender( : [delivery.notificationText]; const format = delivery.status === "success" ? getScheduledTaskDeliveryFormat() : "raw"; const suppressResultNotification = delivery.status === "success" && Boolean(delivery.footerText); + const resultDeliveryOptions = + suppressResultNotification && !config.bot.scheduledTaskNotificationsSilent + ? { options: { disable_notification: true } } + : getSilentDeliveryOptions(); for (const part of messageParts) { await sendBotText({ @@ -63,7 +73,7 @@ export function createScheduledTaskDeliverySender( chatId, text: part, format, - ...(suppressResultNotification ? { options: { disable_notification: true } } : {}), + ...resultDeliveryOptions, }); } @@ -73,6 +83,7 @@ export function createScheduledTaskDeliverySender( chatId, text: delivery.footerText, format: "raw", + ...getSilentDeliveryOptions(), }); } diff --git a/src/config.ts b/src/config.ts index af07120d..da800c93 100644 --- a/src/config.ts +++ b/src/config.ts @@ -177,6 +177,10 @@ export const config = { "SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES", 120, ), + scheduledTaskNotificationsSilent: getOptionalBooleanEnvVar( + "SCHEDULED_TASK_DISABLE_NOTIFICATION", + false, + ), responseStreamThrottleMs: getOptionalPositiveIntEnvVar("RESPONSE_STREAM_THROTTLE_MS", 1000), responseStreamingMode: getOptionalStreamingModeEnvVar("RESPONSE_STREAMING_MODE", "edit"), bashToolDisplayMaxLength: getOptionalPositiveIntEnvVar("BASH_TOOL_DISPLAY_MAX_LENGTH", 128), diff --git a/tests/config-scheduled-task-notifications.test.ts b/tests/config-scheduled-task-notifications.test.ts new file mode 100644 index 00000000..97286999 --- /dev/null +++ b/tests/config-scheduled-task-notifications.test.ts @@ -0,0 +1,39 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +async function loadConfig() { + vi.resetModules(); + const module = await import("../src/config.js"); + return module.config; +} + +describe("config scheduled task notifications", () => { + beforeEach(() => { + vi.stubEnv("TELEGRAM_BOT_TOKEN", "test-telegram-token"); + vi.stubEnv("TELEGRAM_ALLOWED_USER_ID", "123456789"); + vi.stubEnv("OPENCODE_MODEL_PROVIDER", "test-provider"); + vi.stubEnv("OPENCODE_MODEL_ID", "test-model"); + vi.stubEnv("SCHEDULED_TASK_DISABLE_NOTIFICATION", ""); + }); + + it("keeps scheduled task notifications enabled by default", async () => { + const config = await loadConfig(); + + expect(config.bot.scheduledTaskNotificationsSilent).toBe(false); + }); + + it("parses SCHEDULED_TASK_DISABLE_NOTIFICATION as a boolean", async () => { + vi.stubEnv("SCHEDULED_TASK_DISABLE_NOTIFICATION", "true"); + + const config = await loadConfig(); + + expect(config.bot.scheduledTaskNotificationsSilent).toBe(true); + }); + + it("falls back to enabled notifications on invalid values", async () => { + vi.stubEnv("SCHEDULED_TASK_DISABLE_NOTIFICATION", "banana"); + + const config = await loadConfig(); + + expect(config.bot.scheduledTaskNotificationsSilent).toBe(false); + }); +});