Files
goodbuddy/src/main/desktop-notification.ts
T
mesalogo cbbe30896e feat: expand multi-runtime workflows for 0.10.0
GoodBuddy previously exposed Runtime capabilities, MCP assignments, plugin controls, context compaction, usage reporting, and task-completion behavior through incomplete or inconsistent paths. This release unifies managed OpenCode, Continue, and DeepSeek Harness controls; adds DSH plugin and image workflows; strengthens MCP and Runtime lifecycle bounds; fixes Windows notification activation; validates cross-architecture packages; and presents the approved bilingual four-section release notes.

The DSH plugin marketplace remains a default-off preview whose trusted third-party code runs with current-user permissions. Ask remains read-only, Execute keeps approval controls, and context compaction may use the selected model without deleting GoodBuddy chat history.

Release note: GoodBuddy 0.10.0 重点完善多 Runtime 工作流,统一 OpenCode、Continue 与 DeepSeek Harness 的能力、MCP、插件和上下文管理,并提升长对话、多会话与任务通知的连贯性。
2026-08-17 12:57:12 +08:00

112 lines
2.7 KiB
TypeScript

import {
Notification,
type BrowserWindow,
type NotificationConstructorOptions
} from 'electron'
import { showWindow } from './window'
const MAX_RETAINED_NOTIFICATIONS = 64
const NOTIFICATION_RETENTION_MS = 15 * 60_000
const activeNotifications = new Map<Notification, () => void>()
const pendingWindowActivations = new WeakSet<BrowserWindow>()
function activateWindow(window: BrowserWindow): void {
try {
if (window.isDestroyed()) {
return
}
const webContents = window.webContents
if (
webContents &&
(!webContents.getURL() ||
webContents.isLoadingMainFrame())
) {
if (!pendingWindowActivations.has(window)) {
pendingWindowActivations.add(window)
webContents.once('did-finish-load', () => {
pendingWindowActivations.delete(window)
activateWindow(window)
})
}
return
}
showWindow(window)
} catch {
// The window can be destroyed between checks while a native callback runs.
}
}
export function registerDesktopNotificationActivation(
window: BrowserWindow,
platform: NodeJS.Platform = process.platform
): void {
if (platform !== 'win32') {
return
}
Notification.handleActivation(() => {
activateWindow(window)
})
}
export function showDesktopNotificationWhenUnfocused(
window: BrowserWindow,
options: NotificationConstructorOptions
): boolean {
if (
window.isDestroyed() ||
window.isFocused() ||
!Notification.isSupported()
) {
return false
}
const notification = new Notification(options)
let retentionTimer: ReturnType<typeof setTimeout> | undefined
const release = (): void => {
if (retentionTimer) {
clearTimeout(retentionTimer)
retentionTimer = undefined
}
activeNotifications.delete(notification)
notification.removeListener('click', handleClick)
notification.removeListener('close', release)
notification.removeListener('failed', release)
}
const dismiss = (): void => {
try {
notification.close()
} catch {
// The native notification may already have been dismissed.
} finally {
release()
}
}
const handleClick = (): void => {
try {
activateWindow(window)
} finally {
release()
}
}
if (activeNotifications.size >= MAX_RETAINED_NOTIFICATIONS) {
activeNotifications.values().next().value?.()
}
activeNotifications.set(notification, dismiss)
retentionTimer = setTimeout(dismiss, NOTIFICATION_RETENTION_MS)
retentionTimer.unref?.()
notification.once('click', handleClick)
notification.once('close', release)
notification.once('failed', release)
try {
notification.show()
} catch (error) {
release()
throw error
}
return true
}