Merge pull request #4301 from code-yeongyu/fix/issue-4256-duplicate-prompt-dispatch
fix(parent-wake-notifier): suppress duplicate parent wakes during promptAsync gate hold (#4256, #4019)
This commit is contained in:
@@ -238,6 +238,13 @@ export class ParentWakeNotifier {
|
|||||||
throw promptResult.error
|
throw promptResult.error
|
||||||
}
|
}
|
||||||
if (promptResult.status === "reserved" && promptResult.reservedBy === "background-agent-parent-wake") {
|
if (promptResult.status === "reserved" && promptResult.reservedBy === "background-agent-parent-wake") {
|
||||||
|
const dispatchedWake = this.dispatchedParentWakes.get(sessionID)
|
||||||
|
if (dispatchedWake && this.isSameParentWake(latestWake, dispatchedWake)) {
|
||||||
|
// #4256/#4019: duplicated completion edges can enqueue the same wake
|
||||||
|
// during the gate hold. Replaying it later starts a second assistant stream.
|
||||||
|
log("[background-agent] Suppressed duplicate parent wake during promptAsync gate hold:", { sessionID })
|
||||||
|
return
|
||||||
|
}
|
||||||
this.requeueWake(sessionID, latestWake)
|
this.requeueWake(sessionID, latestWake)
|
||||||
this.schedulePendingParentWakeFlush(sessionID, 2_000)
|
this.schedulePendingParentWakeFlush(sessionID, 2_000)
|
||||||
log("[background-agent] Requeued parent wake flush reserved by promptAsync gate hold:", { sessionID })
|
log("[background-agent] Requeued parent wake flush reserved by promptAsync gate hold:", { sessionID })
|
||||||
@@ -399,6 +406,12 @@ export class ParentWakeNotifier {
|
|||||||
this.dispatchedParentWakeTimers.set(sessionID, timer)
|
this.dispatchedParentWakeTimers.set(sessionID, timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isSameParentWake(left: PendingParentWake, right: PendingParentWake): boolean {
|
||||||
|
return left.shouldReply === right.shouldReply
|
||||||
|
&& JSON.stringify(left.notifications) === JSON.stringify(right.notifications)
|
||||||
|
&& JSON.stringify(left.promptContext) === JSON.stringify(right.promptContext)
|
||||||
|
}
|
||||||
|
|
||||||
private async loadParentWakeSessionMessages(sessionID: string): Promise<ParentWakeSessionMessage[]> {
|
private async loadParentWakeSessionMessages(sessionID: string): Promise<ParentWakeSessionMessage[]> {
|
||||||
try {
|
try {
|
||||||
const messagesResp = await messagesInDirectory(this.deps.client, {
|
const messagesResp = await messagesInDirectory(this.deps.client, {
|
||||||
|
|||||||
@@ -84,6 +84,31 @@ function releaseParentWakeHold(sessionID: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("ParentWakeNotifier — same-source reservation requeue (BUG-E)", () => {
|
describe("ParentWakeNotifier — same-source reservation requeue (BUG-E)", () => {
|
||||||
|
test("#given a duplicate parent wake is in post-dispatch hold #when the duplicate fires again #then it is dropped instead of requeued", async () => {
|
||||||
|
// given
|
||||||
|
const { notifier, promptAsyncCalls } = createNotifier()
|
||||||
|
const sessionID = "parent-hold-duplicate-wake"
|
||||||
|
notifier.queuePendingParentWake(sessionID, "wake A", { agent: "sisyphus" }, true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await notifier.flushPendingParentWake(sessionID)
|
||||||
|
expect(promptAsyncCalls).toHaveLength(1)
|
||||||
|
|
||||||
|
// when
|
||||||
|
notifier.queuePendingParentWake(sessionID, "wake A", { agent: "sisyphus" }, true)
|
||||||
|
await notifier.flushPendingParentWake(sessionID)
|
||||||
|
releaseParentWakeHold(sessionID)
|
||||||
|
await notifier.flushPendingParentWake(sessionID)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(promptAsyncCalls).toHaveLength(1)
|
||||||
|
expect(notifier.getPendingParentWakes().has(sessionID)).toBe(false)
|
||||||
|
} finally {
|
||||||
|
notifier.shutdown()
|
||||||
|
releaseAllPromptAsyncReservationsForTesting()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("#given a parent wake is in post-dispatch hold #when a new pending wake fires within the hold window #then the new wake is re-enqueued and dispatched after the hold expires", async () => {
|
test("#given a parent wake is in post-dispatch hold #when a new pending wake fires within the hold window #then the new wake is re-enqueued and dispatched after the hold expires", async () => {
|
||||||
// given
|
// given
|
||||||
const { notifier, promptAsyncCalls } = createNotifier()
|
const { notifier, promptAsyncCalls } = createNotifier()
|
||||||
|
|||||||
Reference in New Issue
Block a user