fix(background-agent): coalesce rapid-fire idle parent notifications
When many background tasks complete in rapid succession while the parent session is idle, each completion fired its own promptAsync call, stacking N consecutive `<system-reminder>` user messages with no assistant turn between. Hyperplan + many parallel explore subagents made this very visible to the user. Route the idle-path through the existing pendingParentWakes queue with a 100ms debounce window. Notifications arriving during the debounce join the same batch, the 150ms settle window also coalesces newcomers, and a single batched prompt fires to the parent. Busy-path semantics are unchanged (still 1s retry). Prior attempts (1c05c60dc,ea55c385b,a337635e3) all coalesced only the busy-defer path, leaving the idle-immediate-send path uncoalesced.
This commit is contained in:
@@ -278,6 +278,10 @@ async function flushBackgroundNotifications(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function waitForCoalescedFlush(): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, 400))
|
||||
}
|
||||
|
||||
function createToastRemoveTaskTracker(): { removeTaskCalls: string[]; resetToastManager: () => void } {
|
||||
_resetTaskToastManagerForTesting()
|
||||
const toastManager = initTaskToastManager(cast<PluginInput["client"]>({
|
||||
@@ -1303,6 +1307,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (value: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
expect(capturedBody?.agent).toBe("sisyphus")
|
||||
@@ -1459,6 +1464,7 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => {
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (task: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
expect(promptCalled).toBe(true)
|
||||
@@ -1501,6 +1507,7 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => {
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (task: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
expect(promptCalled).toBe(true)
|
||||
@@ -1541,6 +1548,7 @@ describe("BackgroundManager.notifyParentSession - aborted parent", () => {
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (task: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
const queuedNotifications = getPendingNotifications(manager).get("session-parent") ?? []
|
||||
@@ -1652,6 +1660,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (task: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(1)
|
||||
@@ -1693,6 +1702,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
|
||||
//#when
|
||||
await (cast<{ notifyParentSession: (task: BackgroundTask) => Promise<void> }>(manager))
|
||||
.notifyParentSession(task)
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(1)
|
||||
@@ -2117,7 +2127,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
|
||||
|
||||
// then
|
||||
expect(rejectedCount).toBe(0)
|
||||
expect(promptBodies.length).toBe(2)
|
||||
expect(promptBodies.length).toBe(1)
|
||||
expect(promptBodies.filter((body) => body.noReply === false)).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
@@ -5410,6 +5420,7 @@ describe("BackgroundManager.pruneStaleTasksAndNotifications - removes pruned tas
|
||||
//#when
|
||||
pruneStaleTasksAndNotificationsForTest(manager)
|
||||
await flushBackgroundNotifications()
|
||||
await waitForCoalescedFlush()
|
||||
|
||||
//#then
|
||||
const retainedTask = getTaskMap(manager).get(staleTask.id)
|
||||
|
||||
Reference in New Issue
Block a user