feat: consolidate notifications onto hook events with expanded settings#1145
feat: consolidate notifications onto hook events with expanded settings#1145arnestrickmann merged 7 commits intomainfrom
Conversation
Adds an HTTP relay server (AgentEventService) that receives structured events from Claude Code's Notification and Stop hooks via curl. Events are broadcast over IPC to the renderer, which plays synthesized tones (needs_attention, task_complete) using the Web Audio API. - Write .claude/settings.local.json with hook config before Claude PTY start - Pass EMDASH_HOOK_PORT, EMDASH_PTY_ID, EMDASH_HOOK_TOKEN env vars to PTYs - Normalize snake_case payload fields from Claude Code to camelCase - Update activityStore to mark tasks idle on permission/stop events - Sounds play for all tasks regardless of focus - Existing regex-based status detection kept as fallback for non-hook providers
… focus mode Consolidate notifications onto hook-driven pipeline. Add separate toggles for sound and OS banner notifications, plus a sound timing mode (always vs only when unfocused). Remove unreliable PTY exit-0 notification path.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR successfully consolidates notification delivery onto a hook-based event pipeline, replacing the unreliable PTY exit-0 notification path with a more robust HTTP callback system. The implementation adds a new Key improvements:
Architecture: The implementation is clean, well-structured, and follows the codebase patterns. Settings normalization handles edge cases properly, and error handling is appropriate throughout. Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| src/main/services/AgentEventService.ts | New HTTP service for receiving hook callbacks from agents, with token auth, payload normalization, and OS notification delivery |
| src/main/services/ClaudeHookService.ts | Writes Claude Code hook configuration to worktree .claude/settings.local.json with curl commands for event callbacks |
| src/main/settings.ts | Added osNotifications and soundFocusMode fields to notification settings with proper defaults and normalization |
| src/main/services/ptyIpc.ts | Removed old PTY exit-based notification code, added Claude hook config write on PTY start |
| src/renderer/lib/soundPlayer.ts | New Web Audio API-based sound player with focus mode gating and two sound types (needs_attention, task_complete) |
| src/renderer/components/NotificationSettingsCard.tsx | Expanded from simple toggle to full settings card with master toggle, sound, sound timing, and OS notifications |
Sequence Diagram
sequenceDiagram
participant Claude as Claude Code Agent
participant Hook as Claude Hook (bash/curl)
participant AES as AgentEventService (HTTP)
participant Main as Main Process
participant IPC as IPC Channel
participant Renderer as Renderer Process
participant SP as soundPlayer
participant OS as OS Notifications
Claude->>Hook: Trigger event (stop/notification)
Hook->>AES: POST /hook with token + event data
AES->>AES: Validate token & ptyId
AES->>AES: Normalize snake_case → camelCase
AES->>Main: Compute appFocused via BrowserWindow
alt App is unfocused & OS notifications enabled
AES->>OS: Show notification banner (silent)
end
AES->>IPC: Broadcast agent:event + {appFocused}
IPC->>Renderer: Deliver event to all windows
Renderer->>SP: Map event to sound (stop→task_complete, notification→needs_attention)
alt Sound enabled & focus mode satisfied
SP->>SP: Play Web Audio API tones
end
Renderer->>Renderer: Update activityStore (task busy/idle status)
Last reviewed commit: 336e062
- Use curl -d @- to pipe stdin directly as POST body instead of embedding payload in a shell string, preventing corruption from $, backticks, or backslashes in AI-generated text - Send ptyId and event type as HTTP headers instead of in the JSON body - Add isDestroyed guards on BrowserWindow and webContents before IPC send, with per-window try/catch so one closing window doesn't abort delivery to the rest - Remove EMDASH_HOOK_* from AGENT_ENV_VARS passthrough list since they are already set explicitly in startDirectPty/startPty
Filter out existing Emdash entries by EMDASH_HOOK_PORT marker and append a fresh one, instead of replacing the entire Notification and Stop hook arrays. User-defined hooks for those event types are now preserved across task starts.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| useEnv['EMDASH_HOOK_PORT'] = String(hookPort); | ||
| useEnv['EMDASH_PTY_ID'] = id; | ||
| useEnv['EMDASH_HOOK_TOKEN'] = agentEventService.getToken(); | ||
| } |
There was a problem hiding this comment.
Duplicated hook environment variable setup logic
Low Severity
The hook env var block (EMDASH_HOOK_PORT, EMDASH_PTY_ID, EMDASH_HOOK_TOKEN) is identically duplicated in both startDirectPty and startPty. If the env var names, token retrieval, or guard logic change, both call sites need consistent updates — a likely source of future drift. This could be a small shared helper.


Summary
stopandnotificationevents (permission prompts, idle prompts, elicitation dialogs) when the app is unfocusedBrowserWindow.getAllWindows().some(w => w.isFocused())and passed as IPC metadataChanged files
src/main/settings.ts— AddedosNotificationsandsoundFocusModeto settings schema and normalizationsrc/main/services/AgentEventService.ts— Added OS banner notifications, app focus detection, metadata broadcastingsrc/main/services/ptyIpc.ts— Removed oldshowCompletionNotification()and its call sitesrc/main/preload.ts— UpdatedonAgentEventto pass{ appFocused }metadatasrc/renderer/lib/soundPlayer.ts— Added focus mode gatingsrc/renderer/hooks/useAgentEvents.ts— WiredappFocusedinto sound playersrc/renderer/components/NotificationSettingsCard.tsx— Expanded from single toggle to full settings cardsrc/renderer/App.tsx— Derives sound enabled state from settings on loadsrc/renderer/types/electron-api.d.ts— Updated notification types andonAgentEventsignaturesrc/test/main/ptyIpc.test.ts— Updated test expectations for removed notification pathTest plan
pnpm run type-check && pnpm run lint && pnpm exec vitest runpassesNote
Medium Risk
Adds a new localhost HTTP hook server and propagates its token/port into spawned PTYs, plus expands persisted notification settings; failures here could break notifications or introduce local event spoofing if the hook env vars leak.
Overview
Consolidates notifications around hook-driven
AgentEvents instead of PTY exit. The main process now starts/stops a localAgentEventServiceHTTP server that authenticates hook callbacks via a per-run token, normalizes payload fields, computes focus state, and broadcastsagent:event(with{ appFocused }) to all renderer windows.Enables provider hooks and new notification controls. PTY spawns now receive
EMDASH_HOOK_PORT/EMDASH_HOOK_TOKEN/EMDASH_PTY_IDenv vars, and Claude worktrees get an idempotent.claude/settings.local.jsonhook config soNotification/Stopevents POST back to Emdash. Notification settings expand to includeosNotificationsandsoundFocusMode, the UI is updated accordingly, and the renderer addssoundPlayer/useAgentEventsto play sounds and update task activity; OS banners are now shown fromAgentEventServicewhen unfocused and enabled.Written by Cursor Bugbot for commit f14872b. This will update automatically on new commits. Configure here.