From c5bdd64442b79b63e26b68c43db60fd8ca76746d Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 10 Jul 2026 14:36:46 -0700 Subject: [PATCH] fix(telegram): detach adopted turns from reply fence (#103952) --- extensions/telegram/src/bot-message-dispatch.test.ts | 5 ++--- extensions/telegram/src/bot-message-dispatch.ts | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index 226f26d74948..454978059b8f 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -6000,6 +6000,7 @@ describe("dispatchTelegramMessage draft streaming", () => { // a later superseding peer (authorized explicit command) cannot abort it. await adoptTurn?.(); expect(firstAbortSignal?.aborted).toBe(false); + expect(supersedeTelegramReplyFence("agent:main:telegram:group:-100123")).toBe(false); await dispatchWithContext({ context: createGroupContext(100, "/export-trajectory bundle"), @@ -6468,9 +6469,7 @@ describe("dispatchTelegramMessage draft streaming", () => { onTurnDeferred: vi.fn(), onTurnAbandoned: onRejectedTurnAbandoned, }); - await expect(captures[2]?.lifecycle?.onAdmitted?.()).rejects.toThrow( - "durable adoption failed", - ); + await expect(captures[2]?.lifecycle?.onAdmitted?.()).rejects.toThrow("durable adoption failed"); expect(supersedeTelegramReplyFence(rejectedKey)).toBe(true); expect(captures[2]?.abortSignal?.aborted).toBe(true); captures[2]?.lifecycle?.onComplete?.(); diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index ddfbf414c53e..2ceb0ae722ad 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -907,8 +907,9 @@ export const dispatchTelegramMessage = async ({ }; const adoptReplyTurn = async () => { await onTurnAdopted?.(); - // Fence abort authority ends only after durable adoption succeeds. Core - // then becomes the sole owner of killing the adopted run. + // Fence abort and supersession authority end after durable adoption. + // Core then owns all interruption of the adopted run. + releaseReplyFence(); releaseTelegramReplyFenceAbortController(activeReplyFenceKey, replyAbortController); }; // Block mode sizes preview rotation steps from streaming.preview.chunk (same