Skip to content

Commit e60a700

Browse files
ly-wang19steipete
authored andcommitted
fix(telegram): clean up split reasoning previews
1 parent 920b29b commit e60a700

2 files changed

Lines changed: 39 additions & 12 deletions

File tree

extensions/telegram/src/bot-message-dispatch.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4892,6 +4892,37 @@ describe("dispatchTelegramMessage draft streaming", () => {
48924892
expect(updates.join("\n")).not.toContain("CheckingReading");
48934893
});
48944894

4895+
it("repositions split reasoning before deleting the prior preview", async () => {
4896+
const answerDraftStream = createDraftStream(2001);
4897+
const reasoningDraftStream = createSequencedDraftStream(3001);
4898+
createTelegramDraftStream
4899+
.mockImplementationOnce(() => answerDraftStream)
4900+
.mockImplementationOnce(() => reasoningDraftStream);
4901+
let replacementMessageId: number | undefined;
4902+
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
4903+
await replyOptions?.onReasoningStream?.({ text: "<think>First thought</think>" });
4904+
await replyOptions?.onReasoningEnd?.();
4905+
await replyOptions?.onReasoningStream?.({ text: "<think>Second thought</think>" });
4906+
replacementMessageId = reasoningDraftStream.messageId();
4907+
return { queuedFinal: false };
4908+
});
4909+
4910+
await dispatchWithContext({ context: createReasoningStreamContext() });
4911+
4912+
expect(reasoningDraftStream.update).toHaveBeenNthCalledWith(1, "🧠 _First thought_");
4913+
expect(reasoningDraftStream.update).toHaveBeenNthCalledWith(2, "🧠 _Second thought_");
4914+
expect(reasoningDraftStream.rotateToNewMessageDeferringDelete).toHaveBeenCalledTimes(1);
4915+
expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
4916+
expect(reasoningDraftStream.clear).toHaveBeenCalledTimes(1);
4917+
expect(
4918+
reasoningDraftStream.rotateToNewMessageDeferringDelete.mock.invocationCallOrder[0],
4919+
).toBeLessThan(reasoningDraftStream.update.mock.invocationCallOrder[1]);
4920+
expect(reasoningDraftStream.update.mock.invocationCallOrder[1]).toBeLessThan(
4921+
reasoningDraftStream.clear.mock.invocationCallOrder[0],
4922+
);
4923+
expect(replacementMessageId).toBe(3002);
4924+
});
4925+
48954926
it("streams reasoning from configured defaults", async () => {
48964927
const { answerDraftStream, reasoningDraftStream } = setupDraftStreams({
48974928
answerMessageId: 2001,

extensions/telegram/src/bot-message-dispatch.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,12 @@ export const dispatchTelegramMessage = async ({
12871287
lastAnswerBlockButtons = undefined;
12881288
}
12891289
};
1290+
const repositionDraftLaneForNewMessage = (lane: DraftLaneState) => {
1291+
// Reposition instead of delete-then-repost: the replacement must land
1292+
// before deferred cleanup or Telegram can jump and retain a stale preview.
1293+
lane.stream?.rotateToNewMessageDeferringDelete();
1294+
resetDraftLaneState(lane);
1295+
};
12901296
const rotateLaneForNewMessage = async (lane: DraftLaneState) => {
12911297
if (!lane.hasStreamedMessage && typeof lane.stream?.messageId() !== "number") {
12921298
resetDraftLaneState(lane);
@@ -1306,16 +1312,7 @@ export const dispatchTelegramMessage = async ({
13061312
if (!activeAnswerDraftIsToolProgressOnly) {
13071313
return false;
13081314
}
1309-
// Reposition, don't delete-then-repost: rewind so the replacement message
1310-
// sends below, and defer the tool-progress window's delete until after it
1311-
// lands. Deleting first (clear) scroll-jumps the client when a durable 🧠
1312-
// was posted between the window and the replacement (the on-off jump).
1313-
if (answerLane.stream?.rotateToNewMessageDeferringDelete) {
1314-
answerLane.stream.rotateToNewMessageDeferringDelete();
1315-
} else {
1316-
answerLane.stream?.forceNewMessage();
1317-
}
1318-
resetDraftLaneState(answerLane);
1315+
repositionDraftLaneForNewMessage(answerLane);
13191316
suppressProgressDraftState();
13201317
rotateAnswerLaneWhenQueuedBlocksSettle = false;
13211318
return true;
@@ -2693,8 +2690,7 @@ export const dispatchTelegramMessage = async ({
26932690
? (payload) =>
26942691
enqueueDraftLaneEvent(async () => {
26952692
if (splitReasoningOnNextStream) {
2696-
reasoningLane.stream?.forceNewMessage();
2697-
resetDraftLaneState(reasoningLane);
2693+
repositionDraftLaneForNewMessage(reasoningLane);
26982694
splitReasoningOnNextStream = false;
26992695
}
27002696
await ingestDraftLaneSegments(payload, true);

0 commit comments

Comments
 (0)