diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 514b15d783..9db45aa80d 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -69,6 +69,13 @@ export const globalSettingsSchema = z.object({ soundEnabled: z.boolean().optional(), soundVolume: z.number().optional(), + // Desktop notification settings + desktopNotificationsEnabled: z.boolean().optional(), + desktopNotificationApprovalRequests: z.boolean().optional(), + desktopNotificationErrors: z.boolean().optional(), + desktopNotificationTaskCompletion: z.boolean().optional(), + desktopNotificationTimeout: z.number().optional(), + maxOpenTabsContext: z.number().optional(), maxWorkspaceFiles: z.number().optional(), showRooIgnoredFiles: z.boolean().optional(), diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8fa9ceccfa..5c5871414d 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1407,6 +1407,7 @@ export class ClineProvider listApiConfigMeta, pinnedApiConfigs, mode, + modeApiConfigs, customModePrompts, customSupportPrompts, enhancementApiConfigId, @@ -1434,6 +1435,11 @@ export class ClineProvider profileThresholds, alwaysAllowFollowupQuestions, followupAutoApproveTimeoutMs, + desktopNotificationsEnabled, + desktopNotificationApprovalRequests, + desktopNotificationErrors, + desktopNotificationTaskCompletion, + desktopNotificationTimeout, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1506,6 +1512,7 @@ export class ClineProvider listApiConfigMeta: listApiConfigMeta ?? [], pinnedApiConfigs: pinnedApiConfigs ?? {}, mode: mode ?? defaultModeSlug, + modeApiConfigs: modeApiConfigs ?? {}, customModePrompts: customModePrompts ?? {}, customSupportPrompts: customSupportPrompts ?? {}, enhancementApiConfigId, @@ -1553,6 +1560,12 @@ export class ClineProvider hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false, alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false, followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000, + // Desktop notification settings + desktopNotificationsEnabled: desktopNotificationsEnabled ?? false, + desktopNotificationApprovalRequests: desktopNotificationApprovalRequests ?? true, + desktopNotificationErrors: desktopNotificationErrors ?? true, + desktopNotificationTaskCompletion: desktopNotificationTaskCompletion ?? true, + desktopNotificationTimeout: desktopNotificationTimeout ?? 10000, } } @@ -1715,6 +1728,12 @@ export class ClineProvider codebaseIndexSearchMinScore: stateValues.codebaseIndexConfig?.codebaseIndexSearchMinScore, }, profileThresholds: stateValues.profileThresholds ?? {}, + // Desktop notification settings + desktopNotificationsEnabled: stateValues.desktopNotificationsEnabled ?? false, + desktopNotificationApprovalRequests: stateValues.desktopNotificationApprovalRequests ?? true, + desktopNotificationErrors: stateValues.desktopNotificationErrors ?? true, + desktopNotificationTaskCompletion: stateValues.desktopNotificationTaskCompletion ?? true, + desktopNotificationTimeout: stateValues.desktopNotificationTimeout ?? 10000, } } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index e70b39df8f..8d0377357e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -964,6 +964,26 @@ export const webviewMessageHandler = async ( case "stopTts": stopTts() break + case "desktopNotificationsEnabled": + await updateGlobalState("desktopNotificationsEnabled", message.bool ?? true) + await provider.postStateToWebview() + break + case "desktopNotificationApprovalRequests": + await updateGlobalState("desktopNotificationApprovalRequests", message.bool ?? true) + await provider.postStateToWebview() + break + case "desktopNotificationErrors": + await updateGlobalState("desktopNotificationErrors", message.bool ?? true) + await provider.postStateToWebview() + break + case "desktopNotificationTaskCompletion": + await updateGlobalState("desktopNotificationTaskCompletion", message.bool ?? true) + await provider.postStateToWebview() + break + case "desktopNotificationTimeout": + await updateGlobalState("desktopNotificationTimeout", message.value ?? 10000) + await provider.postStateToWebview() + break case "diffEnabled": const diffEnabled = message.bool ?? true await updateGlobalState("diffEnabled", diffEnabled) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 833c51336b..7772b32449 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -195,6 +195,11 @@ export type ExtensionState = Pick< | "ttsSpeed" | "soundEnabled" | "soundVolume" + | "desktopNotificationsEnabled" + | "desktopNotificationApprovalRequests" + | "desktopNotificationErrors" + | "desktopNotificationTaskCompletion" + | "desktopNotificationTimeout" // | "maxOpenTabsContext" // Optional in GlobalSettings, required here. // | "maxWorkspaceFiles" // Optional in GlobalSettings, required here. // | "showRooIgnoredFiles" // Optional in GlobalSettings, required here. diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d5dc3f8c28..712fc77eef 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -92,6 +92,14 @@ export interface WebviewMessage { | "ttsEnabled" | "ttsSpeed" | "soundVolume" + | "desktopNotificationsEnabled" + | "desktopNotificationApprovalRequests" + | "desktopNotificationErrors" + | "desktopNotificationTaskCompletion" + | "desktopNotificationUserInputRequired" + | "desktopNotificationSessionTimeouts" + | "desktopNotificationTimeout" + | "desktopNotificationSound" | "diffEnabled" | "enableCheckpoints" | "browserViewportSize" diff --git a/webview-ui/src/components/settings/NotificationSettings.tsx b/webview-ui/src/components/settings/NotificationSettings.tsx index 9610cabad8..78ab5d2a8f 100644 --- a/webview-ui/src/components/settings/NotificationSettings.tsx +++ b/webview-ui/src/components/settings/NotificationSettings.tsx @@ -13,7 +13,23 @@ type NotificationSettingsProps = HTMLAttributes & { ttsSpeed?: number soundEnabled?: boolean soundVolume?: number - setCachedStateField: SetCachedStateField<"ttsEnabled" | "ttsSpeed" | "soundEnabled" | "soundVolume"> + // Desktop notification settings + desktopNotificationsEnabled?: boolean + desktopNotificationApprovalRequests?: boolean + desktopNotificationErrors?: boolean + desktopNotificationTaskCompletion?: boolean + desktopNotificationTimeout?: number + setCachedStateField: SetCachedStateField< + | "ttsEnabled" + | "ttsSpeed" + | "soundEnabled" + | "soundVolume" + | "desktopNotificationsEnabled" + | "desktopNotificationApprovalRequests" + | "desktopNotificationErrors" + | "desktopNotificationTaskCompletion" + | "desktopNotificationTimeout" + > } export const NotificationSettings = ({ @@ -21,6 +37,11 @@ export const NotificationSettings = ({ ttsSpeed, soundEnabled, soundVolume, + desktopNotificationsEnabled, + desktopNotificationApprovalRequests, + desktopNotificationErrors, + desktopNotificationTaskCompletion, + desktopNotificationTimeout, setCachedStateField, ...props }: NotificationSettingsProps) => { @@ -100,6 +121,78 @@ export const NotificationSettings = ({ )} + +
+ setCachedStateField("desktopNotificationsEnabled", e.target.checked)} + data-testid="desktop-notifications-enabled-checkbox"> + {t("settings:notifications.desktop.label")} + +
+ {t("settings:notifications.desktop.description")} +
+
+ + {desktopNotificationsEnabled && ( +
+
+ setCachedStateField("desktopNotificationApprovalRequests", e.target.checked)} + data-testid="desktop-notification-approval-requests-checkbox"> + {t("settings:notifications.desktop.approvalRequests.label")} + +
+ {t("settings:notifications.desktop.approvalRequests.description")} +
+
+ +
+ setCachedStateField("desktopNotificationErrors", e.target.checked)} + data-testid="desktop-notification-errors-checkbox"> + {t("settings:notifications.desktop.errors.label")} + +
+ {t("settings:notifications.desktop.errors.description")} +
+
+ +
+ setCachedStateField("desktopNotificationTaskCompletion", e.target.checked)} + data-testid="desktop-notification-task-completion-checkbox"> + {t("settings:notifications.desktop.taskCompletion.label")} + +
+ {t("settings:notifications.desktop.taskCompletion.description")} +
+
+ +
+ +
+ setCachedStateField("desktopNotificationTimeout", value * 1000)} + data-testid="desktop-notification-timeout-slider" + /> + {Math.round((desktopNotificationTimeout ?? 10000) / 1000)}s +
+
+ {t("settings:notifications.desktop.timeout.description")} +
+
+
+ )} ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index cf9e779cbd..aee2a827e0 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -176,6 +176,11 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowFollowupQuestions, alwaysAllowUpdateTodoList, followupAutoApproveTimeoutMs, + desktopNotificationsEnabled, + desktopNotificationApprovalRequests, + desktopNotificationErrors, + desktopNotificationTaskCompletion, + desktopNotificationTimeout, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -323,6 +328,11 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) + vscode.postMessage({ type: "desktopNotificationsEnabled", bool: desktopNotificationsEnabled }) + vscode.postMessage({ type: "desktopNotificationApprovalRequests", bool: desktopNotificationApprovalRequests }) + vscode.postMessage({ type: "desktopNotificationErrors", bool: desktopNotificationErrors }) + vscode.postMessage({ type: "desktopNotificationTaskCompletion", bool: desktopNotificationTaskCompletion }) + vscode.postMessage({ type: "desktopNotificationTimeout", value: desktopNotificationTimeout }) setChangeDetected(false) } } @@ -640,6 +650,11 @@ const SettingsView = forwardRef(({ onDone, t ttsSpeed={ttsSpeed} soundEnabled={soundEnabled} soundVolume={soundVolume} + desktopNotificationsEnabled={desktopNotificationsEnabled} + desktopNotificationApprovalRequests={desktopNotificationApprovalRequests} + desktopNotificationErrors={desktopNotificationErrors} + desktopNotificationTaskCompletion={desktopNotificationTaskCompletion} + desktopNotificationTimeout={desktopNotificationTimeout} setCachedStateField={setCachedStateField} /> )} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 25428cfb16..ff0f7bb952 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -426,6 +426,26 @@ "label": "Enable text-to-speech", "description": "When enabled, Roo will read aloud its responses using text-to-speech.", "speedLabel": "Speed" + }, + "desktop": { + "label": "Enable desktop notifications", + "description": "When enabled, Roo will show OS-level desktop notifications for important events.", + "approvalRequests": { + "label": "Show approval request notifications", + "description": "Get notified when Roo needs approval to perform actions." + }, + "errors": { + "label": "Show error notifications", + "description": "Get notified when errors occur during task execution." + }, + "taskCompletion": { + "label": "Show task completion notifications", + "description": "Get notified when tasks are completed successfully." + }, + "timeout": { + "label": "Notification timeout", + "description": "How long notifications stay visible (in seconds). Set to 0 for no timeout." + } } }, "contextManagement": {