diff --git a/src/hooks/session-notification-input-needed.test.ts b/src/hooks/session-notification-input-needed.test.ts index 5e8552907..ee1614b88 100644 --- a/src/hooks/session-notification-input-needed.test.ts +++ b/src/hooks/session-notification-input-needed.test.ts @@ -3,6 +3,7 @@ const { describe, expect, test, beforeEach, afterEach, spyOn } = require("bun:te const { createSessionNotification } = require("./session-notification") const { setMainSession, subagentSessions, _resetForTesting } = require("../features/claude-code-session-state") const utils = require("./session-notification-utils") +const sender = require("./session-notification-sender") describe("session-notification input-needed events", () => { let notificationCalls: string[] @@ -37,6 +38,10 @@ describe("session-notification input-needed events", () => { spyOn(utils, "getNotifySendPath").mockResolvedValue("/usr/bin/notify-send") spyOn(utils, "getPowershellPath").mockResolvedValue("powershell") spyOn(utils, "startBackgroundCheck").mockImplementation(() => {}) + spyOn(sender, "detectPlatform").mockReturnValue("darwin") + spyOn(sender, "sendSessionNotification").mockImplementation(async (_ctx: unknown, _platform: unknown, _title: unknown, message: string) => { + notificationCalls.push(message) + }) }) afterEach(() => { @@ -47,7 +52,7 @@ describe("session-notification input-needed events", () => { test("sends question notification when question tool asks for input", async () => { const sessionID = "main-question" setMainSession(sessionID) - const hook = createSessionNotification(createMockPluginInput()) + const hook = createSessionNotification(createMockPluginInput(), { enforceMainSessionFilter: false }) await hook({ event: { @@ -74,7 +79,7 @@ describe("session-notification input-needed events", () => { test("sends permission notification for permission events", async () => { const sessionID = "main-permission" setMainSession(sessionID) - const hook = createSessionNotification(createMockPluginInput()) + const hook = createSessionNotification(createMockPluginInput(), { enforceMainSessionFilter: false }) await hook({ event: { diff --git a/src/hooks/session-notification.test.ts b/src/hooks/session-notification.test.ts index 2f0377a4c..cf895ba98 100644 --- a/src/hooks/session-notification.test.ts +++ b/src/hooks/session-notification.test.ts @@ -1,8 +1,9 @@ -import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test" +const { describe, expect, test, beforeEach, afterEach, spyOn } = require("bun:test") import { createSessionNotification } from "./session-notification" import { setMainSession, subagentSessions, _resetForTesting } from "../features/claude-code-session-state" import * as utils from "./session-notification-utils" +import * as sender from "./session-notification-sender" describe("session-notification", () => { let notificationCalls: string[] @@ -40,6 +41,10 @@ describe("session-notification", () => { spyOn(utils, "getPaplayPath").mockResolvedValue("/usr/bin/paplay") spyOn(utils, "getAplayPath").mockResolvedValue("/usr/bin/aplay") spyOn(utils, "startBackgroundCheck").mockImplementation(() => {}) + spyOn(sender, "detectPlatform").mockReturnValue("darwin") + spyOn(sender, "sendSessionNotification").mockImplementation(async (_ctx, _platform, _title, message) => { + notificationCalls.push(message) + }) }) afterEach(() => { @@ -105,6 +110,7 @@ describe("session-notification", () => { const hook = createSessionNotification(createMockPluginInput(), { idleConfirmationDelay: 10, skipIfIncompleteTodos: false, + enforceMainSessionFilter: false, }) // when - main session goes idle @@ -332,6 +338,7 @@ describe("session-notification", () => { const hook = createSessionNotification(createMockPluginInput(), { idleConfirmationDelay: 10, skipIfIncompleteTodos: false, + enforceMainSessionFilter: false, }) // when - session goes idle twice diff --git a/src/hooks/session-notification.ts b/src/hooks/session-notification.ts index 48e0d288b..3b3dcc514 100644 --- a/src/hooks/session-notification.ts +++ b/src/hooks/session-notification.ts @@ -4,11 +4,9 @@ import { startBackgroundCheck, } from "./session-notification-utils" import { - detectPlatform, - getDefaultSoundPath, - playSessionNotificationSound, - sendSessionNotification, + type Platform, } from "./session-notification-sender" +import * as sessionNotificationSender from "./session-notification-sender" import { hasIncompleteTodos } from "./session-todo-status" import { createIdleNotificationScheduler } from "./session-notification-scheduler" @@ -25,13 +23,14 @@ interface SessionNotificationConfig { skipIfIncompleteTodos?: boolean /** Maximum number of sessions to track before cleanup (default: 100) */ maxTrackedSessions?: number + enforceMainSessionFilter?: boolean } export function createSessionNotification( ctx: PluginInput, config: SessionNotificationConfig = {} ) { - const currentPlatform = detectPlatform() - const defaultSoundPath = getDefaultSoundPath(currentPlatform) + const currentPlatform: Platform = sessionNotificationSender.detectPlatform() + const defaultSoundPath = sessionNotificationSender.getDefaultSoundPath(currentPlatform) startBackgroundCheck(currentPlatform) @@ -45,6 +44,7 @@ export function createSessionNotification( idleConfirmationDelay: 1500, skipIfIncompleteTodos: true, maxTrackedSessions: 100, + enforceMainSessionFilter: true, ...config, } @@ -53,8 +53,8 @@ export function createSessionNotification( platform: currentPlatform, config: mergedConfig, hasIncompleteTodos, - send: sendSessionNotification, - playSound: playSessionNotificationSound, + send: sessionNotificationSender.sendSessionNotification, + playSound: sessionNotificationSender.playSessionNotificationSound, }) const QUESTION_TOOLS = new Set(["question", "ask_user_question", "askuserquestion"]) @@ -81,8 +81,10 @@ export function createSessionNotification( const shouldNotifyForSession = (sessionID: string): boolean => { if (subagentSessions.has(sessionID)) return false - const mainSessionID = getMainSessionID() - if (mainSessionID && sessionID !== mainSessionID) return false + if (mergedConfig.enforceMainSessionFilter) { + const mainSessionID = getMainSessionID() + if (mainSessionID && sessionID !== mainSessionID) return false + } return true } @@ -146,9 +148,14 @@ export function createSessionNotification( if (!shouldNotifyForSession(sessionID)) return scheduler.markSessionActivity(sessionID) - await sendSessionNotification(ctx, currentPlatform, mergedConfig.title, mergedConfig.permissionMessage) + await sessionNotificationSender.sendSessionNotification( + ctx, + currentPlatform, + mergedConfig.title, + mergedConfig.permissionMessage, + ) if (mergedConfig.playSound && mergedConfig.soundPath) { - await playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) + await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) } return } @@ -168,9 +175,9 @@ export function createSessionNotification( ? mergedConfig.permissionMessage : mergedConfig.questionMessage - await sendSessionNotification(ctx, currentPlatform, mergedConfig.title, message) + await sessionNotificationSender.sendSessionNotification(ctx, currentPlatform, mergedConfig.title, message) if (mergedConfig.playSound && mergedConfig.soundPath) { - await playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) + await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) } } }