Suppress OS notifications for subagent (child) sessions (#236)

This PR prevents OS notification spam from spawned subagent sessions by
skipping OS-level notifications for any session that is a child thread
(`parentId !== null`).

What changed
- `packages/ui/src/stores/session-events.ts`
- Added `isChildSession(...)` +
`shouldSendOsNotificationForSession(...)`
  - Applied the check to OS notifications emitted from:
    - `handleSessionIdle(...)`
    - `handlePermissionUpdated(...)`
    - `handleQuestionAsked(...)`
- If a session is not yet hydrated in the client store, we
conservatively *do not* emit an OS notification (avoids early subagent
spam).

Why
- Subagent sessions are represented as child sessions in the UI thread
model; OS notifications were previously emitted for all sessions
indiscriminately.

Testing
- Not run here: `bun run typecheck` fails in this environment due to
missing installed deps/types (e.g. `solid-js`).

Closes #228

--
Yours,
[CodeNomadBot](https://github.com/NeuralNomadsAI/CodeNomad)

Co-authored-by: Shantur Rathore <i@shantur.com>
This commit is contained in:
codenomadbot[bot]
2026-03-20 22:48:06 +00:00
committed by GitHub
parent 313e82880b
commit 823dd2d687

View File

@@ -77,6 +77,29 @@ function shouldSendOsNotification(kind: "needsInput" | "idle"): boolean {
return false
}
function isChildSession(instanceId: string, sessionId: string): boolean | null {
const session = sessions().get(instanceId)?.get(sessionId)
if (!session) return null
return session.parentId !== null && session.parentId !== undefined
}
function shouldSendOsNotificationForSession(
kind: "needsInput" | "idle",
instanceId: string,
sessionId: string | undefined | null,
): boolean {
if (!shouldSendOsNotification(kind)) return false
if (!sessionId) return true
const child = isChildSession(instanceId, sessionId)
// Avoid notification spam from spawned child/subagent sessions arriving before hydration.
if (child === null) return false
if (child) return false
return true
}
function getInstanceDisplayName(instanceId: string): string {
const instanceFolder = instances().get(instanceId)?.folder ?? instanceId
return instanceFolder.split(/[\\/]/).filter(Boolean).pop() ?? instanceFolder
@@ -492,7 +515,7 @@ function handleSessionIdle(instanceId: string, event: EventSessionIdle): void {
const sessionId = event.properties?.sessionID
if (!sessionId) return
if (shouldSendOsNotification("idle")) {
if (shouldSendOsNotificationForSession("idle", instanceId, sessionId)) {
const title = getInstanceDisplayName(instanceId)
const label = getSessionTitle(instanceId, sessionId)
const body = label ? `Session "${label}" is idle` : "Session is idle"
@@ -607,9 +630,10 @@ function handlePermissionUpdated(instanceId: string, event: { type: string; prop
addPermissionToQueue(instanceId, permission)
upsertPermissionV2(instanceId, permission)
if (shouldSendOsNotification("needsInput")) {
const sessionId = getPermissionSessionId(permission)
if (shouldSendOsNotificationForSession("needsInput", instanceId, sessionId)) {
const title = getInstanceDisplayName(instanceId)
const sessionId = getPermissionSessionId(permission)
const label = getSessionTitle(instanceId, sessionId)
const body = label ? `Session "${label}" needs permission` : "Session needs permission"
fireOsNotification({ title, body })
@@ -634,9 +658,10 @@ function handleQuestionAsked(instanceId: string, event: { type: string; properti
addQuestionToQueue(instanceId, request)
upsertQuestionV2(instanceId, request)
if (shouldSendOsNotification("needsInput")) {
const sessionId = getQuestionSessionId(request)
if (shouldSendOsNotificationForSession("needsInput", instanceId, sessionId)) {
const title = getInstanceDisplayName(instanceId)
const sessionId = getQuestionSessionId(request)
const label = getSessionTitle(instanceId, sessionId)
const body = label ? `Session "${label}" needs input` : "Session needs input"
fireOsNotification({ title, body })