From 0691779d2efd62482ad5ebfbd628ab5813dfcc21 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 29 May 2026 13:36:38 +0900 Subject: [PATCH] fix(background-agent): unblock stale parent wakes --- .../parent-wake-assistant-blocking.test.ts | 79 +++++++++++++++++++ .../parent-wake-history-deferral.test.ts | 4 +- .../background-agent/parent-wake-notifier.ts | 2 +- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/features/background-agent/parent-wake-assistant-blocking.test.ts b/src/features/background-agent/parent-wake-assistant-blocking.test.ts index e70b0ed7b..c1081693e 100644 --- a/src/features/background-agent/parent-wake-assistant-blocking.test.ts +++ b/src/features/background-agent/parent-wake-assistant-blocking.test.ts @@ -166,4 +166,83 @@ describe("ParentWakeNotifier — assistant turn blocking", () => { releaseAllPromptAsyncReservationsForTesting() } }) + + test("#given stale completed unknown assistant turn has only step metadata #when flushing pending wake #then parent wake bypasses the second prompt gate check", async () => { + // given + const originalDateNow = Date.now + Date.now = () => 100_000 + const promptAsyncCalls: PromptAsyncCall[] = [] + const client = unsafeTestValue({ + session: { + messages: async () => ({ + data: [ + { + info: { + role: "user", + time: { created: 10_000 }, + }, + parts: [{ type: "text", text: "start work" }], + }, + { + info: { + role: "assistant", + finish: "unknown", + time: { created: 20_000, completed: 30_000 }, + }, + parts: [ + { type: "step-start" }, + { type: "step-finish", reason: "unknown" }, + ], + }, + ], + }), + status: async () => ({ data: { "parent-completed-unknown": { type: "idle" } } }), + promptAsync: async (call: PromptAsyncCall) => { + promptAsyncCalls.push(call) + return { data: {} } + }, + }, + }) + const notifier = new ParentWakeNotifier( + { + client, + directory: "/tmp/test-omo", + enqueueNotificationForParent: async (_sessionID, operation) => { + await operation() + }, + }, + { + pendingRetryMs: 1_000, + acceptedMessageSkewMs: 5_000, + toolCallDeferMaxMs: 5_000, + failureRequeueWindowMs: 5_000, + userMessageInProgressWindowMs: 2_000, + }, + ) + notifier.queuePendingParentWake( + "parent-completed-unknown", + "task complete", + { agent: "sisyphus" }, + true, + ) + const pendingWake = notifier.getPendingParentWakes().get("parent-completed-unknown") + expect(pendingWake).toBeDefined() + if (!pendingWake) { + throw new Error("Missing pending parent wake") + } + pendingWake.toolCallDeferralStartedAt = 1_000 + + try { + // when + await notifier.flushPendingParentWake("parent-completed-unknown") + + // then + expect(promptAsyncCalls).toHaveLength(1) + expect(notifier.getPendingParentWakes().has("parent-completed-unknown")).toBe(false) + } finally { + Date.now = originalDateNow + notifier.shutdown() + releaseAllPromptAsyncReservationsForTesting() + } + }) }) diff --git a/src/features/background-agent/parent-wake-history-deferral.test.ts b/src/features/background-agent/parent-wake-history-deferral.test.ts index c73450aa3..f54af5225 100644 --- a/src/features/background-agent/parent-wake-history-deferral.test.ts +++ b/src/features/background-agent/parent-wake-history-deferral.test.ts @@ -8,7 +8,7 @@ import { ParentWakeNotifier } from "./parent-wake-notifier" type ParentWakeClient = ConstructorParameters[0]["client"] describe("ParentWakeNotifier — assistant history deferral", () => { - test("#given stale unfinished assistant text has no pending tool call #when checking parent wake history #then parent wake dispatches after defer max", async () => { + test("#given stale unfinished assistant text has no pending tool call #when checking parent wake history #then parent wake dispatches after defer max without second gate check", async () => { // given const originalDateNow = Date.now Date.now = () => 100_000 @@ -66,7 +66,7 @@ describe("ParentWakeNotifier — assistant history deferral", () => { const decision = await notifier["shouldDeferParentWakeForSessionHistory"]("parent-stale-text", pendingWake) // then - expect(decision).toEqual({ defer: false, skipPromptGateToolStateCheck: false }) + expect(decision).toEqual({ defer: false, skipPromptGateToolStateCheck: true }) } finally { Date.now = originalDateNow notifier.shutdown() diff --git a/src/features/background-agent/parent-wake-notifier.ts b/src/features/background-agent/parent-wake-notifier.ts index fa6c80ce1..e40197cce 100644 --- a/src/features/background-agent/parent-wake-notifier.ts +++ b/src/features/background-agent/parent-wake-notifier.ts @@ -588,7 +588,7 @@ export class ParentWakeNotifier { sessionID, deferAgeMs: deferAge, }) - return { defer: false, skipPromptGateToolStateCheck: false } + return { defer: false, skipPromptGateToolStateCheck: true } } log("[background-agent] Deferred parent wake because latest assistant turn blocks internal prompts:", { sessionID,