























@@ -225,6 +225,56 @@ describe("dispatchReplyFromConfig reply_dispatch hook", () => {
225225expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryContext).toBeUndefined();
226226});
227227228+it("clears pending final delivery when abort fires after a successful final send (#89115)", async () => {
229+// Regression for #89115: an abort that lands after the final reply has
230+// shipped (here, during sendFinalReply) must still clear the pending-final
231+// bookkeeping — otherwise pendingFinalDelivery stays true and the get-reply
232+// redelivery short-circuit silently blocks every later inbound.
233+hookMocks.runner.hasHooks.mockReturnValue(false);
234+sessionStoreMocks.currentEntry = {
235+sessionKey: "agent:test:session",
236+pendingFinalDelivery: true,
237+pendingFinalDeliveryText: "durable reply",
238+pendingFinalDeliveryCreatedAt: 1,
239+pendingFinalDeliveryLastAttemptAt: 2,
240+pendingFinalDeliveryAttemptCount: 3,
241+pendingFinalDeliveryLastError: "previous failure",
242+pendingFinalDeliveryContext: { source: "heartbeat" },
243+pendingFinalDeliveryIntentId: "intent-89115",
244+};
245+sessionStoreMocks.resolveSessionStoreEntry.mockReturnValue({
246+existing: sessionStoreMocks.currentEntry,
247+});
248+const abortController = new AbortController();
249+const dispatcher = createDispatcher();
250+vi.mocked(dispatcher.sendFinalReply).mockImplementation(() => {
251+abortController.abort();
252+return true;
253+});
254+255+const result = await dispatchReplyFromConfig({
256+ctx: createHookCtx(),
257+cfg: emptyConfig,
258+ dispatcher,
259+replyOptions: { abortSignal: abortController.signal },
260+replyResolver: async () => ({ text: "durable reply" }),
261+});
262+263+// Abort landed after delivery: the run is still surfaced as aborted
264+// (queuedFinal:false), but the pending-final state is fully cleared.
265+expect(dispatcher.sendFinalReply).toHaveBeenCalledOnce();
266+expect(result.queuedFinal).toBe(false);
267+expect(sessionStoreMocks.updateSessionStoreEntry).toHaveBeenCalledOnce();
268+expect(sessionStoreMocks.currentEntry?.pendingFinalDelivery).toBeUndefined();
269+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryText).toBeUndefined();
270+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryCreatedAt).toBeUndefined();
271+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryLastAttemptAt).toBeUndefined();
272+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryAttemptCount).toBeUndefined();
273+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryLastError).toBeUndefined();
274+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryContext).toBeUndefined();
275+expect(sessionStoreMocks.currentEntry?.pendingFinalDeliveryIntentId).toBeUndefined();
276+});
277+228278it("preserves pending final delivery when final dispatch fails", async () => {
229279hookMocks.runner.hasHooks.mockReturnValue(false);
230280sessionStoreMocks.currentEntry = {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。