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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user