mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
feat: add desktop notification system for user interactions
- Add node-notifier dependency for cross-platform desktop notifications - Create NotificationService with support for Windows, macOS, and Linux - Add comprehensive VSCode settings for notification preferences - Integrate notifications with askApproval flow in presentAssistantMessage - Add error notifications to handleError function - Include localization support for all notification settings - Provide graceful fallback to VSCode notifications on failure Addresses #5015
This commit is contained in:
parent
0c014f0028
commit
c2fe775757
7 changed files with 516 additions and 2 deletions
52
pnpm-lock.yaml
generated
52
pnpm-lock.yaml
generated
|
|
@ -678,6 +678,9 @@ importers:
|
|||
node-ipc:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
node-notifier:
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1
|
||||
openai:
|
||||
specifier: ^5.0.0
|
||||
version: 5.5.1(ws@8.18.2)(zod@3.25.61)
|
||||
|
|
@ -808,6 +811,9 @@ importers:
|
|||
'@types/node-ipc':
|
||||
specifier: ^9.2.3
|
||||
version: 9.2.3
|
||||
'@types/node-notifier':
|
||||
specifier: ^8.0.5
|
||||
version: 8.0.5
|
||||
'@types/proper-lockfile':
|
||||
specifier: ^4.1.4
|
||||
version: 4.1.4
|
||||
|
|
@ -3841,6 +3847,9 @@ packages:
|
|||
'@types/node-ipc@9.2.3':
|
||||
resolution: {integrity: sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==}
|
||||
|
||||
'@types/node-notifier@8.0.5':
|
||||
resolution: {integrity: sha512-LX7+8MtTsv6szumAp6WOy87nqMEdGhhry/Qfprjm1Ma6REjVzeF7SCyvPtp5RaF6IkXCS9V4ra8g5fwvf2ZAYg==}
|
||||
|
||||
'@types/node@12.20.55':
|
||||
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
|
||||
|
||||
|
|
@ -6000,6 +6009,9 @@ packages:
|
|||
graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
|
||||
growly@1.3.0:
|
||||
resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==}
|
||||
|
||||
gtoken@7.1.0:
|
||||
resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -6309,6 +6321,11 @@ packages:
|
|||
is-decimal@2.0.1:
|
||||
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
|
||||
|
||||
is-docker@2.2.1:
|
||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
|
@ -6462,6 +6479,10 @@ packages:
|
|||
resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-wsl@3.1.0:
|
||||
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
|
||||
engines: {node: '>=16'}
|
||||
|
|
@ -7447,6 +7468,9 @@ packages:
|
|||
resolution: {integrity: sha512-QHJ2gAJiqA3cM7cQiRjLsfCOBRB0TwQ6axYD4FSllQWipEbP6i7Se1dP8EzPKk5J1nCe27W69eqPmCoKyQ61Vg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
node-notifier@10.0.1:
|
||||
resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==}
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
|
|
@ -8486,6 +8510,9 @@ packages:
|
|||
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
shellwords@0.1.1:
|
||||
resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==}
|
||||
|
||||
shiki@3.4.1:
|
||||
resolution: {integrity: sha512-PSnoczt+iWIOB4iRQ+XVPFtTuN1FcmuYzPgUBZTSv5pC6CozssIx2M4O5n4S9gJlUu9A3FxMU0ZPaHflky/6LA==}
|
||||
|
||||
|
|
@ -13043,6 +13070,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/node': 20.17.57
|
||||
|
||||
'@types/node-notifier@8.0.5':
|
||||
dependencies:
|
||||
'@types/node': 20.19.1
|
||||
|
||||
'@types/node@12.20.55': {}
|
||||
|
||||
'@types/node@14.18.63': {}
|
||||
|
|
@ -13133,7 +13164,7 @@ snapshots:
|
|||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 20.17.57
|
||||
'@types/node': 20.19.1
|
||||
optional: true
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||
|
|
@ -15532,6 +15563,8 @@ snapshots:
|
|||
|
||||
graphemer@1.4.0: {}
|
||||
|
||||
growly@1.3.0: {}
|
||||
|
||||
gtoken@7.1.0:
|
||||
dependencies:
|
||||
gaxios: 6.7.1
|
||||
|
|
@ -15892,6 +15925,8 @@ snapshots:
|
|||
|
||||
is-decimal@2.0.1: {}
|
||||
|
||||
is-docker@2.2.1: {}
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
|
@ -16014,6 +16049,10 @@ snapshots:
|
|||
|
||||
is-windows@1.0.2: {}
|
||||
|
||||
is-wsl@2.2.0:
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
|
||||
is-wsl@3.1.0:
|
||||
dependencies:
|
||||
is-inside-container: 1.0.0
|
||||
|
|
@ -17272,6 +17311,15 @@ snapshots:
|
|||
js-queue: 2.0.2
|
||||
strong-type: 1.1.0
|
||||
|
||||
node-notifier@10.0.1:
|
||||
dependencies:
|
||||
growly: 1.3.0
|
||||
is-wsl: 2.2.0
|
||||
semver: 7.7.2
|
||||
shellwords: 0.1.1
|
||||
uuid: 8.3.2
|
||||
which: 2.0.2
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
noms@0.0.0:
|
||||
|
|
@ -18527,6 +18575,8 @@ snapshots:
|
|||
shell-quote@1.8.3:
|
||||
optional: true
|
||||
|
||||
shellwords@0.1.1: {}
|
||||
|
||||
shiki@3.4.1:
|
||||
dependencies:
|
||||
'@shikijs/core': 3.4.1
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { TelemetryService } from "@roo-code/telemetry"
|
|||
|
||||
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
|
||||
import type { ToolParamName, ToolResponse } from "../../shared/tools"
|
||||
import { NotificationService, NotificationType } from "../../services/notification"
|
||||
|
||||
import { fetchInstructionsTool } from "../tools/fetchInstructionsTool"
|
||||
import { listFilesTool } from "../tools/listFilesTool"
|
||||
|
|
@ -266,6 +267,47 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
progressStatus?: ToolProgressStatus,
|
||||
isProtected?: boolean,
|
||||
) => {
|
||||
// Initialize notification service and send desktop notification for approval requests
|
||||
const provider = cline.providerRef.deref()
|
||||
if (provider) {
|
||||
try {
|
||||
const notificationService = new NotificationService(provider.context)
|
||||
|
||||
// Determine the tool name from the current block for better notification context
|
||||
let toolName = block.name
|
||||
let notificationMessage = partialMessage || `Approval required for ${toolName}`
|
||||
|
||||
// Customize notification message based on tool type
|
||||
switch (toolName) {
|
||||
case "execute_command":
|
||||
notificationMessage = `Execute command: ${block.params.command}`
|
||||
break
|
||||
case "write_to_file":
|
||||
notificationMessage = `Write to file: ${block.params.path}`
|
||||
break
|
||||
case "read_file":
|
||||
// Handle both old and new read_file parameter formats
|
||||
const filePath =
|
||||
typeof block.params.args === "string"
|
||||
? "multiple files"
|
||||
: block.params.path || "unknown"
|
||||
notificationMessage = `Read file: ${filePath}`
|
||||
break
|
||||
case "browser_action":
|
||||
notificationMessage = `Browser action: ${block.params.action}`
|
||||
break
|
||||
default:
|
||||
notificationMessage = `${toolName} - Approval required`
|
||||
break
|
||||
}
|
||||
|
||||
await notificationService.sendApprovalRequest(notificationMessage, toolName)
|
||||
} catch (error) {
|
||||
// Don't let notification errors break the approval flow
|
||||
console.error("Failed to send desktop notification:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const { response, text, images } = await cline.ask(
|
||||
type,
|
||||
partialMessage,
|
||||
|
|
@ -307,6 +349,18 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
const handleError = async (action: string, error: Error) => {
|
||||
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
|
||||
|
||||
// Send desktop notification for errors
|
||||
const provider = cline.providerRef.deref()
|
||||
if (provider) {
|
||||
try {
|
||||
const notificationService = new NotificationService(provider.context)
|
||||
await notificationService.sendError(`Error ${action}`, error)
|
||||
} catch (notificationError) {
|
||||
// Don't let notification errors break the error handling flow
|
||||
console.error("Failed to send error notification:", notificationError)
|
||||
}
|
||||
}
|
||||
|
||||
await cline.say(
|
||||
"error",
|
||||
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
|
||||
|
|
|
|||
|
|
@ -373,6 +373,48 @@
|
|||
"type": "string",
|
||||
"default": "",
|
||||
"description": "%settings.autoImportSettingsPath.description%"
|
||||
},
|
||||
"roo-cline.notifications.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.enabled.description%"
|
||||
},
|
||||
"roo-cline.notifications.showApprovalRequests": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.showApprovalRequests.description%"
|
||||
},
|
||||
"roo-cline.notifications.showErrors": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.showErrors.description%"
|
||||
},
|
||||
"roo-cline.notifications.showTaskCompletion": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.showTaskCompletion.description%"
|
||||
},
|
||||
"roo-cline.notifications.showUserInputRequired": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.showUserInputRequired.description%"
|
||||
},
|
||||
"roo-cline.notifications.showSessionTimeouts": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.showSessionTimeouts.description%"
|
||||
},
|
||||
"roo-cline.notifications.timeout": {
|
||||
"type": "number",
|
||||
"default": 10000,
|
||||
"minimum": 0,
|
||||
"maximum": 60000,
|
||||
"description": "%settings.notifications.timeout.description%"
|
||||
},
|
||||
"roo-cline.notifications.sound": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.notifications.sound.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -432,6 +474,7 @@
|
|||
"monaco-vscode-textmate-theme-converter": "^0.1.7",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-ipc": "^12.0.0",
|
||||
"node-notifier": "^10.0.1",
|
||||
"openai": "^5.0.0",
|
||||
"os-name": "^6.0.0",
|
||||
"p-limit": "^6.2.0",
|
||||
|
|
@ -476,6 +519,7 @@
|
|||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/node-cache": "^4.1.3",
|
||||
"@types/node-notifier": "^8.0.5",
|
||||
"@types/node-ipc": "^9.2.3",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/ps-tree": "^1.1.6",
|
||||
|
|
|
|||
|
|
@ -34,5 +34,13 @@
|
|||
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
|
||||
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
|
||||
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
|
||||
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import."
|
||||
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
|
||||
"settings.notifications.enabled.description": "Enable desktop notifications for Roo Code events",
|
||||
"settings.notifications.showApprovalRequests.description": "Show desktop notifications when approval is required for tool operations",
|
||||
"settings.notifications.showErrors.description": "Show desktop notifications for errors and failures",
|
||||
"settings.notifications.showTaskCompletion.description": "Show desktop notifications when tasks are completed",
|
||||
"settings.notifications.showUserInputRequired.description": "Show desktop notifications when user input is required",
|
||||
"settings.notifications.showSessionTimeouts.description": "Show desktop notifications for session timeouts",
|
||||
"settings.notifications.timeout.description": "Notification timeout in milliseconds (0 = no timeout, max 60 seconds)",
|
||||
"settings.notifications.sound.description": "Play sound with desktop notifications"
|
||||
}
|
||||
|
|
|
|||
262
src/services/notification/NotificationService.ts
Normal file
262
src/services/notification/NotificationService.ts
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
import * as vscode from "vscode"
|
||||
import * as notifier from "node-notifier"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import {
|
||||
NotificationConfig,
|
||||
NotificationData,
|
||||
NotificationType,
|
||||
PlatformNotificationOptions,
|
||||
DEFAULT_NOTIFICATION_CONFIG,
|
||||
} from "./types"
|
||||
|
||||
/**
|
||||
* Cross-platform desktop notification service for Roo Code
|
||||
* Provides OS-level notifications when the AI agent requires user interaction
|
||||
*/
|
||||
export class NotificationService {
|
||||
private config: NotificationConfig
|
||||
private readonly iconPath: string
|
||||
|
||||
constructor(private readonly context: vscode.ExtensionContext) {
|
||||
this.config = this.loadConfig()
|
||||
this.iconPath = path.join(context.extensionPath, "assets", "icons", "icon.png")
|
||||
}
|
||||
|
||||
/**
|
||||
* Load notification configuration from VSCode settings
|
||||
*/
|
||||
private loadConfig(): NotificationConfig {
|
||||
const config = vscode.workspace.getConfiguration("roo-cline.notifications")
|
||||
return {
|
||||
enabled: config.get("enabled", DEFAULT_NOTIFICATION_CONFIG.enabled),
|
||||
showApprovalRequests: config.get("showApprovalRequests", DEFAULT_NOTIFICATION_CONFIG.showApprovalRequests),
|
||||
showErrors: config.get("showErrors", DEFAULT_NOTIFICATION_CONFIG.showErrors),
|
||||
showTaskCompletion: config.get("showTaskCompletion", DEFAULT_NOTIFICATION_CONFIG.showTaskCompletion),
|
||||
showUserInputRequired: config.get(
|
||||
"showUserInputRequired",
|
||||
DEFAULT_NOTIFICATION_CONFIG.showUserInputRequired,
|
||||
),
|
||||
showSessionTimeouts: config.get("showSessionTimeouts", DEFAULT_NOTIFICATION_CONFIG.showSessionTimeouts),
|
||||
timeout: config.get("timeout", DEFAULT_NOTIFICATION_CONFIG.timeout),
|
||||
sound: config.get("sound", DEFAULT_NOTIFICATION_CONFIG.sound),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update configuration when settings change
|
||||
*/
|
||||
public updateConfig(): void {
|
||||
this.config = this.loadConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if notifications should be shown for a specific type
|
||||
*/
|
||||
private shouldShowNotification(type: NotificationType): boolean {
|
||||
if (!this.config.enabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NotificationType.APPROVAL_REQUEST:
|
||||
return this.config.showApprovalRequests
|
||||
case NotificationType.ERROR:
|
||||
return this.config.showErrors
|
||||
case NotificationType.TASK_COMPLETION:
|
||||
return this.config.showTaskCompletion
|
||||
case NotificationType.USER_INPUT_REQUIRED:
|
||||
return this.config.showUserInputRequired
|
||||
case NotificationType.SESSION_TIMEOUT:
|
||||
return this.config.showSessionTimeouts
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform-specific notification options
|
||||
*/
|
||||
private getPlatformOptions(): PlatformNotificationOptions {
|
||||
const platform = os.platform()
|
||||
|
||||
switch (platform) {
|
||||
case "win32":
|
||||
return {
|
||||
windows: {
|
||||
appID: "RooCode.VSCodeExtension",
|
||||
remove: this.config.timeout > 0,
|
||||
},
|
||||
}
|
||||
case "darwin":
|
||||
return {
|
||||
macos: {
|
||||
bundleId: "com.roocode.vscode-extension",
|
||||
critical: false,
|
||||
},
|
||||
}
|
||||
case "linux":
|
||||
return {
|
||||
linux: {
|
||||
desktopEntry: "roo-code",
|
||||
urgency: "normal",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a desktop notification
|
||||
*/
|
||||
public async sendNotification(data: NotificationData): Promise<void> {
|
||||
if (!this.shouldShowNotification(data.type)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const platformOptions = this.getPlatformOptions()
|
||||
const timeout = data.timeout ?? this.config.timeout
|
||||
const sound = data.sound ?? this.config.sound
|
||||
|
||||
// Prepare notification options based on platform
|
||||
const notificationOptions: any = {
|
||||
title: data.title,
|
||||
message: data.message,
|
||||
icon: data.icon || this.iconPath,
|
||||
sound: sound,
|
||||
wait: false, // Don't wait for user interaction
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if (timeout > 0) {
|
||||
notificationOptions.timeout = timeout / 1000 // node-notifier expects seconds
|
||||
}
|
||||
|
||||
// Add platform-specific options
|
||||
const platform = os.platform()
|
||||
if (platform === "win32" && platformOptions.windows) {
|
||||
notificationOptions.appID = platformOptions.windows.appID
|
||||
notificationOptions.remove = platformOptions.windows.remove
|
||||
} else if (platform === "darwin" && platformOptions.macos) {
|
||||
notificationOptions.bundleId = platformOptions.macos.bundleId
|
||||
notificationOptions.critical = platformOptions.macos.critical
|
||||
} else if (platform === "linux" && platformOptions.linux) {
|
||||
notificationOptions.hint = `string:desktop-entry:${platformOptions.linux.desktopEntry}`
|
||||
notificationOptions.urgency = platformOptions.linux.urgency
|
||||
}
|
||||
|
||||
// Send the notification
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
notifier.notify(notificationOptions, (err: Error | null, response: any) => {
|
||||
if (err) {
|
||||
console.error("Failed to send notification:", err)
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error sending desktop notification:", error)
|
||||
// Fallback to VSCode notification if desktop notification fails
|
||||
this.sendVSCodeFallbackNotification(data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback to VSCode notification if desktop notification fails
|
||||
*/
|
||||
private sendVSCodeFallbackNotification(data: NotificationData): void {
|
||||
const message = `${data.title}: ${data.message}`
|
||||
|
||||
switch (data.type) {
|
||||
case NotificationType.ERROR:
|
||||
vscode.window.showErrorMessage(message)
|
||||
break
|
||||
case NotificationType.APPROVAL_REQUEST:
|
||||
case NotificationType.USER_INPUT_REQUIRED:
|
||||
vscode.window.showWarningMessage(message)
|
||||
break
|
||||
default:
|
||||
vscode.window.showInformationMessage(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an approval request notification
|
||||
*/
|
||||
public async sendApprovalRequest(message: string, toolName?: string): Promise<void> {
|
||||
const title = toolName ? `${toolName} - Approval Required` : "Approval Required"
|
||||
await this.sendNotification({
|
||||
type: NotificationType.APPROVAL_REQUEST,
|
||||
title,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error notification
|
||||
*/
|
||||
public async sendError(message: string, error?: Error): Promise<void> {
|
||||
const errorMessage = error ? `${message}: ${error.message}` : message
|
||||
await this.sendNotification({
|
||||
type: NotificationType.ERROR,
|
||||
title: "Roo Code Error",
|
||||
message: errorMessage,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a task completion notification
|
||||
*/
|
||||
public async sendTaskCompletion(message: string): Promise<void> {
|
||||
await this.sendNotification({
|
||||
type: NotificationType.TASK_COMPLETION,
|
||||
title: "Task Completed",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a user input required notification
|
||||
*/
|
||||
public async sendUserInputRequired(message: string): Promise<void> {
|
||||
await this.sendNotification({
|
||||
type: NotificationType.USER_INPUT_REQUIRED,
|
||||
title: "Input Required",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a session timeout notification
|
||||
*/
|
||||
public async sendSessionTimeout(message: string): Promise<void> {
|
||||
await this.sendNotification({
|
||||
type: NotificationType.SESSION_TIMEOUT,
|
||||
title: "Session Timeout",
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notification functionality
|
||||
*/
|
||||
public async testNotification(): Promise<void> {
|
||||
await this.sendNotification({
|
||||
type: NotificationType.APPROVAL_REQUEST,
|
||||
title: "Roo Code Notification Test",
|
||||
message: "Desktop notifications are working correctly!",
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the service
|
||||
*/
|
||||
public dispose(): void {
|
||||
// Clean up any resources if needed
|
||||
}
|
||||
}
|
||||
3
src/services/notification/index.ts
Normal file
3
src/services/notification/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { NotificationService } from "./NotificationService"
|
||||
export type { NotificationConfig, NotificationData, PlatformNotificationOptions } from "./types"
|
||||
export { NotificationType, DEFAULT_NOTIFICATION_CONFIG } from "./types"
|
||||
93
src/services/notification/types.ts
Normal file
93
src/services/notification/types.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Configuration interface for desktop notifications
|
||||
*/
|
||||
export interface NotificationConfig {
|
||||
/** Whether desktop notifications are enabled */
|
||||
enabled: boolean
|
||||
/** Whether to show notifications for approval requests */
|
||||
showApprovalRequests: boolean
|
||||
/** Whether to show notifications for errors */
|
||||
showErrors: boolean
|
||||
/** Whether to show notifications for task completion */
|
||||
showTaskCompletion: boolean
|
||||
/** Whether to show notifications when user input is required */
|
||||
showUserInputRequired: boolean
|
||||
/** Whether to show notifications for session timeouts */
|
||||
showSessionTimeouts: boolean
|
||||
/** Timeout in milliseconds for notifications (0 = no timeout) */
|
||||
timeout: number
|
||||
/** Whether to play sound with notifications */
|
||||
sound: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Default notification configuration
|
||||
*/
|
||||
export const DEFAULT_NOTIFICATION_CONFIG: NotificationConfig = {
|
||||
enabled: true,
|
||||
showApprovalRequests: true,
|
||||
showErrors: true,
|
||||
showTaskCompletion: true,
|
||||
showUserInputRequired: true,
|
||||
showSessionTimeouts: true,
|
||||
timeout: 10000, // 10 seconds
|
||||
sound: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of notifications that can be sent
|
||||
*/
|
||||
export enum NotificationType {
|
||||
APPROVAL_REQUEST = "approval_request",
|
||||
ERROR = "error",
|
||||
TASK_COMPLETION = "task_completion",
|
||||
USER_INPUT_REQUIRED = "user_input_required",
|
||||
SESSION_TIMEOUT = "session_timeout",
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification data structure
|
||||
*/
|
||||
export interface NotificationData {
|
||||
/** Type of notification */
|
||||
type: NotificationType
|
||||
/** Notification title */
|
||||
title: string
|
||||
/** Notification message */
|
||||
message: string
|
||||
/** Optional icon path */
|
||||
icon?: string
|
||||
/** Whether to play sound */
|
||||
sound?: boolean
|
||||
/** Timeout in milliseconds (0 = no timeout) */
|
||||
timeout?: number
|
||||
/** Optional actions for the notification */
|
||||
actions?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-specific notification options
|
||||
*/
|
||||
export interface PlatformNotificationOptions {
|
||||
/** Windows-specific options */
|
||||
windows?: {
|
||||
/** App ID for Windows notifications */
|
||||
appID?: string
|
||||
/** Whether to remove notification after timeout */
|
||||
remove?: boolean
|
||||
}
|
||||
/** macOS-specific options */
|
||||
macos?: {
|
||||
/** Bundle ID for macOS notifications */
|
||||
bundleId?: string
|
||||
/** Whether notification should be critical */
|
||||
critical?: boolean
|
||||
}
|
||||
/** Linux-specific options */
|
||||
linux?: {
|
||||
/** Desktop entry name */
|
||||
desktopEntry?: string
|
||||
/** Urgency level (low, normal, critical) */
|
||||
urgency?: "low" | "normal" | "critical"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue