From b9f889ccf76370ea609b5581fd366dac2a834fb6 Mon Sep 17 00:00:00 2001 From: sidreyas Date: Sat, 7 Nov 2026 04:27:04 +0530 Subject: [PATCH] fix(provider-acp): preserve Grok answers after early task completion --- .../Adapters/AcpAdapterV2.test.ts | 63 +++++++++++++++++-- packages/provider-acp/src/server/adapter.ts | 16 ++--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index 807675034189c..1d6b9d5765d9c 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -8088,7 +8088,7 @@ describe("AcpAdapterV2", () => { ); it.effect( - "staggered pre-settle completion keeps midTurn marks until last background task ends post-finalize", + "staggered pre-settle completion waits for native wake output after the last background task ends", () => Effect.gen(function* () { const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; @@ -8284,12 +8284,19 @@ describe("AcpAdapterV2", () => { "mutation-only end-notice must not count as post-settle wake evidence", ); yield* sessionUpdateHandler!(bEndNotice); - yield* Effect.yieldNow; - yield* Effect.yieldNow; + assert.lengthOf(continuationRequests, 0, "a task end notice alone cannot start a wake"); + assert.isTrue(yield* runtime.hasPendingBackgroundWork!); + yield* sessionUpdateHandler!({ + sessionId: "mock-session-1", + update: { + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "Both background tasks finished." }, + }, + }); assert.lengthOf( continuationRequests, 1, - "exactly one continuation when the last running task ends post-finalize with kept midTurn marks", + "the native report must start exactly one continuation", ); }).pipe(Effect.provide(layerTest), Effect.scoped), ); @@ -8953,6 +8960,11 @@ describe("AcpAdapterV2", () => { yield* complete("early"); assert.lengthOf(continuationRequests, 0); + yield* update({ + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "EARLY_SUCCESS_DONE" }, + }); + let backgroundItem: ProviderAdapter.ProviderAdapterV2Event | undefined; let checkpoint = 0; const assertCommand = Effect.fnUntraced(function* (expectedStatus: string, output: string) { @@ -9015,14 +9027,55 @@ describe("AcpAdapterV2", () => { assert.equal(event.status, "completed"); break; } - yield* Deferred.await(continuationOffered); yield* complete("early"); yield* complete("blocker"); + assert.lengthOf( + continuationRequests, + 0, + "a task completion without native wake output must not replace the root answer", + ); + assert.isTrue(yield* runtime.hasPendingBackgroundWork!); + + yield* update({ + sessionUpdate: "agent_thought_chunk", + content: { type: "text", text: "Preparing the background report" }, + }); + yield* complete("early"); + assert.lengthOf(continuationRequests, 0, "reasoning alone is not a result-bearing wake"); + yield* update({ + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "LATE_NATIVE_ANSWER" }, + }); + yield* Deferred.await(continuationOffered); assert.lengthOf( continuationRequests, 1, "duplicate completions must not offer another continuation", ); + yield* runtime.startTurn( + makeTurnInput({ + threadId, + providerThread, + instanceId, + runtimePolicy, + now: yield* DateTime.now, + ordinal: 2, + messageCreatedBy: "agent", + messageCreationSource: "provider", + messageText: "Background task completed.", + }), + ); + yield* TestClock.adjust(yield* Deferred.await(finalizeScheduled)); + let nativeAnswerSeen = false; + while (true) { + const event = yield* Queue.take(events); + if (event.type === "message.updated" && event.message.text === "LATE_NATIVE_ANSWER") { + nativeAnswerSeen = true; + } + if (event.type === "turn.terminal") break; + } + assert.isTrue(nativeAnswerSeen); + assert.isFalse(yield* runtime.hasPendingBackgroundWork!); }).pipe(Effect.provide(layerTest), Effect.scoped), ); diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index f10be3c952dc8..046a9228dd54b 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -4635,8 +4635,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( postSettleContinuationEnabled && (yield* Ref.get(activeSessionId)) === sessionId && (yield* Ref.get(runningBackgroundTaskIds)).size === 0 && - ((yield* Ref.get(wakeBuffer)).length > 0 || - (yield* Ref.get(midTurnUnreportedCompletedTaskIds)).size > 0) + (yield* Ref.get(wakeBuffer)).some((notification) => + acpPostSettleContinuationOfferEvidence(notification, flavor), + ) ) { yield* offerContinuationRun(sessionId); } @@ -7740,18 +7741,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( settledStatus === "completed" && !directStopQuarantine && (yield* Ref.get(runningBackgroundTaskIds)).size === 0 && - ((yield* Ref.get(wakeBuffer)).length > 0 || - (yield* Ref.get(midTurnUnreportedCompletedTaskIds)).size > 0) + (yield* Ref.get(wakeBuffer)).some((notification) => + acpPostSettleContinuationOfferEvidence(notification, flavor), + ) ) { const sessionId = yield* Ref.get(activeSessionId); if (sessionId !== null) { yield* offerContinuationRun(sessionId); } } - if ( - settledStatus !== "completed" || - (yield* Ref.get(runningBackgroundTaskIds)).size === 0 - ) { + if (settledStatus !== "completed") { yield* Ref.set(midTurnUnreportedCompletedTaskIds, new Set()); } yield* Deferred.succeed(context.completed, undefined).pipe(Effect.ignore); @@ -8592,6 +8591,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ) return true; if ((yield* Ref.get(wakeBuffer)).length > 0) return true; + if ((yield* Ref.get(midTurnUnreportedCompletedTaskIds)).size > 0) return true; if (yield* Ref.get(continuationRequested)) return true; if ((yield* Ref.get(runningBackgroundTaskIds)).size > 0) return true; const active = yield* Ref.get(activeTurn);