fix(background-agent): defer parent wake during active turns
This commit is contained in:
@@ -108,7 +108,7 @@ async function flushPendingParentWakeForTest(manager: BackgroundManager, session
|
||||
}
|
||||
|
||||
describe("BackgroundManager parent wake active turn events", () => {
|
||||
test("#when background task completes during active parent turn #then parent gets same-turn no-reply reminder", async () => {
|
||||
test("#when background task completes during active parent turn #then parent wake stays deferred", async () => {
|
||||
// given
|
||||
const sessionStatuses: Record<string, { type: string }> = {
|
||||
"parent-1": { type: "busy" },
|
||||
@@ -129,9 +129,57 @@ describe("BackgroundManager parent wake active turn events", () => {
|
||||
await notifyParentSessionForTest(manager, task)
|
||||
await flushPendingParentWakeForTest(manager, "parent-1")
|
||||
|
||||
// then
|
||||
expect(promptAsyncCalls).toHaveLength(0)
|
||||
expect(getPendingParentWakes(manager).has("parent-1")).toBe(true)
|
||||
})
|
||||
|
||||
test("#when duplicate background completions overlap an active parent turn #then one coalesced wake dispatches after idle", async () => {
|
||||
// given
|
||||
const sessionStatuses: Record<string, { type: string }> = {
|
||||
"parent-1": { type: "busy" },
|
||||
}
|
||||
const { manager, promptAsyncCalls } = createManager(sessionStatuses)
|
||||
managerUnderTest = manager
|
||||
const taskA = createTask({
|
||||
id: "task-a",
|
||||
parentSessionId: "parent-1",
|
||||
description: "task A",
|
||||
status: "completed",
|
||||
completedAt: new Date("2026-05-20T14:19:14.625Z"),
|
||||
})
|
||||
const taskB = createTask({
|
||||
id: "task-b",
|
||||
parentSessionId: "parent-1",
|
||||
description: "task B",
|
||||
status: "completed",
|
||||
completedAt: new Date("2026-05-20T14:19:15.625Z"),
|
||||
})
|
||||
getTasks(manager).set(taskA.id, taskA)
|
||||
getTasks(manager).set(taskB.id, taskB)
|
||||
getPendingByParent(manager).set(taskA.parentSessionId, new Set([taskA.id, taskB.id]))
|
||||
|
||||
// when
|
||||
await notifyParentSessionForTest(manager, taskA)
|
||||
await notifyParentSessionForTest(manager, taskB)
|
||||
await Promise.all([
|
||||
flushPendingParentWakeForTest(manager, "parent-1"),
|
||||
flushPendingParentWakeForTest(manager, "parent-1"),
|
||||
])
|
||||
|
||||
// then
|
||||
expect(promptAsyncCalls).toHaveLength(0)
|
||||
expect(getPendingParentWakes(manager).has("parent-1")).toBe(true)
|
||||
|
||||
// when
|
||||
sessionStatuses["parent-1"] = { type: "idle" }
|
||||
await Promise.all([
|
||||
flushPendingParentWakeForTest(manager, "parent-1"),
|
||||
flushPendingParentWakeForTest(manager, "parent-1"),
|
||||
])
|
||||
|
||||
// then
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
expect(promptAsyncCalls[0]?.body.noReply).toBe(true)
|
||||
expect(JSON.stringify(promptAsyncCalls[0]?.body.parts)).toContain("ALL BACKGROUND TASKS COMPLETE")
|
||||
expect(getPendingParentWakes(manager).has("parent-1")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -77,19 +77,6 @@ type ToolWaitDeferralDecision = {
|
||||
|
||||
type Unrefable = ReturnType<typeof setTimeout> & { unref?: () => unknown }
|
||||
|
||||
const ACTIVE_TURN_COMPLETION_NOTIFICATION_MARKERS = [
|
||||
"[BACKGROUND TASK COMPLETED]",
|
||||
"[ALL BACKGROUND TASKS COMPLETE]",
|
||||
] as const
|
||||
|
||||
function notificationAllowsActiveTurnDelivery(notification: string): boolean {
|
||||
return ACTIVE_TURN_COMPLETION_NOTIFICATION_MARKERS.some((marker) => notification.includes(marker))
|
||||
}
|
||||
|
||||
function pendingWakeAllowsActiveTurnDelivery(wake: PendingParentWake): boolean {
|
||||
return wake.notifications.length > 0 && wake.notifications.every(notificationAllowsActiveTurnDelivery)
|
||||
}
|
||||
|
||||
function unrefTimerHandle(handle: ReturnType<typeof setTimeout>): void {
|
||||
const maybeUnref = (handle as Unrefable).unref
|
||||
if (typeof maybeUnref === "function") {
|
||||
@@ -179,8 +166,7 @@ export class ParentWakeNotifier {
|
||||
if (!latestWake) {
|
||||
return
|
||||
}
|
||||
const canDeliverDuringActiveTurn = sessionActive && pendingWakeAllowsActiveTurnDelivery(latestWake)
|
||||
if (sessionActive && !canDeliverDuringActiveTurn) {
|
||||
if (sessionActive) {
|
||||
this.schedulePendingParentWakeFlush(sessionID)
|
||||
return
|
||||
}
|
||||
@@ -234,12 +220,12 @@ export class ParentWakeNotifier {
|
||||
source: "background-agent-parent-wake",
|
||||
settleMs: 0,
|
||||
queueBehavior: "defer",
|
||||
checkStatus: !canDeliverDuringActiveTurn,
|
||||
checkStatus: true,
|
||||
checkToolState: !toolWaitDecision.skipPromptGateToolStateCheck,
|
||||
input: {
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
noReply: canDeliverDuringActiveTurn ? true : !latestWake.shouldReply,
|
||||
noReply: !latestWake.shouldReply,
|
||||
...latestWake.promptContext,
|
||||
parts: [createInternalAgentTextPart(notificationContent)],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user