From 7fb5ad0f48b4ef48bdad9dd87fc20ac115a44fc7 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Wed, 13 May 2026 17:43:08 +0300 Subject: [PATCH] fix: check window focus before summarization to avoid wasted Zen API calls When notificationMode is 'hidden-only', the isWindowFocused check now happens before summarization and template resolution, not after. This prevents costly Zen API calls for notifications that would be skipped anyway, and eliminates stale notifications arriving after the user has already read the response and switched away. --- packages/electron/main.mjs | 1 + packages/web/server/index.js | 3 +++ .../web/server/lib/notifications/runtime.js | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index ca94f381..3d7b5d92 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -785,6 +785,7 @@ const spawnLocalServer = async () => { attachSignals: false, exitOnShutdown: false, onDesktopNotification: (payload) => maybeShowNativeNotification(payload), + getIsWindowFocused: isAnyWindowFocused, }); const port = handle.getPort(); diff --git a/packages/web/server/index.js b/packages/web/server/index.js index fef16793..19e5c33e 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -1073,6 +1073,9 @@ async function main(options = {}) { if (typeof options.onDesktopNotification === 'function') { notificationEmitterRuntime.setOnDesktopNotification(options.onDesktopNotification); } + if (typeof options.getIsWindowFocused === 'function') { + notificationTriggerRuntime.setGetIsWindowFocused(options.getIsWindowFocused); + } console.log(`Starting OpenChamber on port ${port === 0 ? 'auto' : port}`); diff --git a/packages/web/server/lib/notifications/runtime.js b/packages/web/server/lib/notifications/runtime.js index 276c57f2..c7b4357d 100644 --- a/packages/web/server/lib/notifications/runtime.js +++ b/packages/web/server/lib/notifications/runtime.js @@ -16,6 +16,14 @@ export const createNotificationTriggerRuntime = (deps) => { getOpenCodeAuthHeaders, } = deps; + let getIsWindowFocused = typeof deps.getIsWindowFocused === 'function' + ? deps.getIsWindowFocused + : null; + + const setGetIsWindowFocused = (cb) => { + getIsWindowFocused = typeof cb === 'function' ? cb : null; + }; + const PUSH_READY_COOLDOWN_MS = 5000; const PUSH_QUESTION_DEBOUNCE_MS = 500; const PUSH_PERMISSION_DEBOUNCE_MS = 500; @@ -211,6 +219,10 @@ export const createNotificationTriggerRuntime = (deps) => { return; } + if (settings.notificationMode !== 'always' && getIsWindowFocused?.()) { + return; + } + const now = Date.now(); const lastAt = lastReadyNotificationAt.get(sessionId) ?? 0; if (now - lastAt < PUSH_READY_COOLDOWN_MS) { @@ -283,6 +295,10 @@ export const createNotificationTriggerRuntime = (deps) => { const settings = await readSettingsFromDisk(); if (settings.notifyOnError === false) return; + if (settings.notificationMode !== 'always' && getIsWindowFocused?.()) { + return; + } + let title = 'Tool error'; let body = 'An error occurred'; @@ -355,6 +371,10 @@ export const createNotificationTriggerRuntime = (deps) => { return; } + if (settings.notificationMode !== 'always' && getIsWindowFocused?.()) { + return; + } + const firstQuestion = payload.properties?.questions?.[0]; const header = typeof firstQuestion?.header === 'string' ? firstQuestion.header.trim() : ''; const questionText = typeof firstQuestion?.question === 'string' ? firstQuestion.question.trim() : ''; @@ -473,6 +493,10 @@ export const createNotificationTriggerRuntime = (deps) => { return; } + if (settings.notificationMode !== 'always' && getIsWindowFocused?.()) { + return; + } + const sessionTitle = payload.properties?.sessionTitle; const permissionText = typeof permission === 'string' && permission.length > 0 ? permission : ''; const fallbackMessage = typeof sessionTitle === 'string' && sessionTitle.trim().length > 0 @@ -543,5 +567,6 @@ export const createNotificationTriggerRuntime = (deps) => { return { maybeSendPushForTrigger, setAutoAcceptSession, + setGetIsWindowFocused, }; };