fix(background-agent): preserve parent activity across idle events

Cubic caught that clearing recent parent activity on session.idle could reopen the stale-idle race. Keep the fresh activity marker until its normal expiry so background completions still defer while the parent turn is actively reasoning.

Red: parent idle after fresh reasoning delta dispatched an unexpected parent wake. Green: focused parent-wake regression suite passes.
This commit is contained in:
YeonGyu-Kim
2026-05-21 12:07:40 +09:00
parent a8201726ef
commit 0ad4d974f1
3 changed files with 35 additions and 5 deletions
-1
View File
@@ -1625,7 +1625,6 @@ 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 })
})
@@ -141,4 +141,39 @@ describe("BackgroundManager parent wake active turn events", () => {
expect(promptAsyncCalls).toHaveLength(0)
expect(getPendingParentWakes(manager).has("parent-1")).toBe(true)
})
test("#when parent idle event follows fresh reasoning delta #then background completion still does not fork a reply", async () => {
// given
const sessionStatuses: Record<string, { type: string }> = {
"parent-1": { type: "idle" },
}
const { manager, promptAsyncCalls } = createManager(sessionStatuses)
managerUnderTest = manager
manager.handleEvent({
type: "message.part.delta",
properties: {
sessionID: "parent-1",
field: "reasoning",
delta: "still thinking",
},
})
const task = createTask({
id: "task-a",
parentSessionId: "parent-1",
description: "task A",
status: "completed",
completedAt: new Date("2026-05-20T14:19:14.625Z"),
})
getTasks(manager).set(task.id, task)
getPendingByParent(manager).set(task.parentSessionId, new Set([task.id]))
// when
await notifyParentSessionForTest(manager, task)
manager.handleEvent({ type: "session.idle", properties: { sessionID: "parent-1" } })
await flushPendingParentWakeForTest(manager, "parent-1")
// then
expect(promptAsyncCalls).toHaveLength(0)
expect(getPendingParentWakes(manager).has("parent-1")).toBe(true)
})
})
@@ -122,10 +122,6 @@ export class ParentWakeNotifier {
this.recentParentSessionActivity.set(sessionID, Date.now())
}
clearParentSessionActivity(sessionID: string): void {
this.recentParentSessionActivity.delete(sessionID)
}
queuePendingParentWake(
sessionID: string,
notification: string,