fix(background-agent): defer parent wakes during active turns

Record fresh parent session message activity before parent-wake flushing so stale idle status cannot dispatch a background completion into a live reasoning turn.

Add regression coverage for the Discord 4.2.3/OpenCode 1.15.5 duplicate-branch repro shape where a parent reasoning delta arrives before the background all-complete wake.

Refs #4212

Refs #4019

Refs #3774

Plan: plans/background-notification-active-turn-queue.md
This commit is contained in:
YeonGyu-Kim
2026-05-21 11:50:30 +09:00
parent fcb96841f8
commit a8201726ef
3 changed files with 184 additions and 0 deletions
+5
View File
@@ -142,6 +142,7 @@ const PARENT_WAKE_TOOL_CALL_DEFER_MAX_MS = 5_000
* env. See issue #4120.
*/
const PARENT_WAKE_USER_MESSAGE_IN_PROGRESS_WINDOW_MS = 2_000
const PARENT_WAKE_SESSION_ACTIVITY_IN_PROGRESS_WINDOW_MS = 2_000
interface MessagePartInfo {
id?: string
@@ -309,6 +310,7 @@ export class BackgroundManager {
toolCallDeferMaxMs: PARENT_WAKE_TOOL_CALL_DEFER_MAX_MS,
failureRequeueWindowMs: PARENT_WAKE_FAILURE_REQUEUE_WINDOW_MS,
userMessageInProgressWindowMs: PARENT_WAKE_USER_MESSAGE_IN_PROGRESS_WINDOW_MS,
parentSessionActivityInProgressWindowMs: PARENT_WAKE_SESSION_ACTIVITY_IN_PROGRESS_WINDOW_MS,
},
)
this.registerProcessCleanup()
@@ -1479,6 +1481,7 @@ The fallback retry session is now created and can be inspected directly.
const role = (info as Record<string, unknown>)["role"]
if (!sessionID) return
this.clearDispatchedParentWake(sessionID)
this.parentWakeNotifier.recordParentSessionActivity(sessionID)
if (role === "tool") {
this.markSessionOutputObserved(sessionID)
@@ -1513,6 +1516,7 @@ The fallback retry session is now created and can be inspected directly.
const sessionID = resolveMessageEventSessionID(props)
if (!sessionID) return
this.clearDispatchedParentWake(sessionID)
this.parentWakeNotifier.recordParentSessionActivity(sessionID)
const resolved = this.resolveTaskAttemptBySession(sessionID)
if (!resolved?.isCurrent) return
@@ -1621,6 +1625,7 @@ The fallback retry session is now created and can be inspected directly.
if (!props || typeof props !== "object") return
const sessionID = resolveSessionEventID(props)
if (sessionID) {
this.parentWakeNotifier.clearParentSessionActivity(sessionID)
void this.enqueueNotificationForParent(sessionID, () => this.flushPendingParentWake(sessionID)).catch((error) => {
log("[background-agent] Failed to flush pending parent wake:", { sessionID, error })
})