Skip to content

Commit c08f167

Browse files
committed
refactor(telegram): trim progress draft scope
1 parent 1900d41 commit c08f167

4 files changed

Lines changed: 5 additions & 178 deletions

File tree

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

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
640640
chatId: 123,
641641
thread: { id: 777, scope: "dm" },
642642
minInitialChars: 30,
643-
minInitialDelayMs: 5000,
644643
});
645644
expect(draftStream.update).toHaveBeenCalledWith("Hello");
646645
const delivery = expectDeliverRepliesParams({ thread: { id: 777, scope: "dm" } });
@@ -2640,27 +2639,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
26402639
expect(editMessageTelegram).not.toHaveBeenCalled();
26412640
});
26422641

2643-
it("shows a stable progress placeholder for progress-mode answer activity", async () => {
2644-
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
2645-
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
2646-
await replyOptions?.onPartialReply?.({ text: "Short" });
2647-
await replyOptions?.onPartialReply?.({ text: "Short answer" });
2648-
return { queuedFinal: false };
2649-
});
2650-
2651-
await dispatchWithContext({
2652-
context: createContext(),
2653-
streamMode: "progress",
2654-
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
2655-
});
2656-
2657-
expect(answerDraftStream.update).not.toHaveBeenCalledWith("Short");
2658-
expect(answerDraftStream.update).not.toHaveBeenCalledWith("Short answer");
2659-
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
2660-
telegramProgressPreview("Shelling", "<b>Shelling</b>"),
2661-
);
2662-
});
2663-
26642642
it("replaces Telegram command progress items with matching command output", async () => {
26652643
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
26662644
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
@@ -2727,67 +2705,6 @@ describe("dispatchTelegramMessage draft streaming", () => {
27272705
expectDeliveredReply(0, { text: "Branch is up to date" });
27282706
});
27292707

2730-
it("clears progress drafts before durable verbose tool output", async () => {
2731-
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
2732-
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
2733-
async ({ dispatcherOptions, replyOptions }) => {
2734-
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
2735-
replyOptions?.onVerboseProgressVisibility?.(() => true);
2736-
await dispatcherOptions.deliver(
2737-
{ text: "Tool output visible to Telegram" },
2738-
{ kind: "tool" },
2739-
);
2740-
await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" });
2741-
return { queuedFinal: true };
2742-
},
2743-
);
2744-
2745-
await dispatchWithContext({
2746-
context: createContext(),
2747-
streamMode: "progress",
2748-
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
2749-
});
2750-
2751-
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
2752-
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
2753-
);
2754-
expectDeliveredReply(0, { text: "Tool output visible to Telegram" });
2755-
expectDeliveredReply(0, { text: "Final answer" }, 1);
2756-
expect(answerDraftStream.clear.mock.invocationCallOrder[0]).toBeLessThan(
2757-
deliverReplies.mock.invocationCallOrder[0],
2758-
);
2759-
});
2760-
2761-
it("clears progress drafts before visible tool artifacts", async () => {
2762-
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
2763-
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
2764-
async ({ dispatcherOptions, replyOptions }) => {
2765-
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
2766-
await dispatcherOptions.deliver(
2767-
{ mediaUrl: "https://example.com/validation.txt" },
2768-
{ kind: "tool" },
2769-
);
2770-
await dispatcherOptions.deliver({ text: "Final answer" }, { kind: "final" });
2771-
return { queuedFinal: true };
2772-
},
2773-
);
2774-
2775-
await dispatchWithContext({
2776-
context: createContext(),
2777-
streamMode: "progress",
2778-
telegramCfg: { streaming: { mode: "progress", progress: { label: "Shelling" } } },
2779-
});
2780-
2781-
expect(answerDraftStream.updatePreview).toHaveBeenCalledWith(
2782-
telegramProgressPreview("Shelling\n\n🛠️ Exec", "<b>Shelling</b>\n<b>🛠️ Exec</b>"),
2783-
);
2784-
expectDeliveredReply(0, { mediaUrl: "https://example.com/validation.txt" });
2785-
expectDeliveredReply(0, { text: "Final answer" }, 1);
2786-
expect(answerDraftStream.clear.mock.invocationCallOrder[0]).toBeLessThan(
2787-
deliverReplies.mock.invocationCallOrder[0],
2788-
);
2789-
});
2790-
27912708
it("does not stream text-only tool results into progress drafts", async () => {
27922709
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
27932710
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
@@ -2819,7 +2736,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
28192736
expect(deliverReplies).not.toHaveBeenCalled();
28202737
});
28212738

2822-
it("renders api progress item edge cases as plain transport previews", async () => {
2739+
it("renders api progress item edge cases as HTML transport previews", async () => {
28232740
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
28242741
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
28252742
await replyOptions?.onItemEvent?.({ kind: "api", progressText: "GET /v1/users" });

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ const silentReplyDispatchLogger = createSubsystemLogger("telegram/silent-reply-d
153153

154154
/** Minimum chars before sending first streaming message (improves push notification UX) */
155155
const DRAFT_MIN_INITIAL_CHARS = 30;
156-
const DRAFT_MIN_INITIAL_DELAY_MS = 5_000;
157156

158157
type DraftPartialTextUpdate = {
159158
text: string;
@@ -1037,7 +1036,6 @@ export const dispatchTelegramMessage = async ({
10371036
replyToMessageId: draftReplyToMessageId,
10381037
richMessages: telegramCfg.richMessages,
10391038
minInitialChars: draftMinInitialChars,
1040-
minInitialDelayMs: draftMinInitialChars > 0 ? DRAFT_MIN_INITIAL_DELAY_MS : undefined,
10411039
renderText: renderStreamText,
10421040
onSupersededPreview: (superseded) => {
10431041
if (superseded.retain) {
@@ -1397,7 +1395,7 @@ export const dispatchTelegramMessage = async ({
13971395
recomputeQueuedAnswerBlockRotations();
13981396
}
13991397
};
1400-
const updateDraftFromPartial = async (lane: DraftLaneState, update: DraftPartialTextUpdate) => {
1398+
const updateDraftFromPartial = (lane: DraftLaneState, update: DraftPartialTextUpdate) => {
14011399
const laneStream = lane.stream;
14021400
if (!laneStream || !update.text) {
14031401
return;
@@ -1409,7 +1407,6 @@ export const dispatchTelegramMessage = async ({
14091407
}
14101408
if (lane === answerLane) {
14111409
if (streamMode === "progress") {
1412-
await progressDraft.noteActivity();
14131410
return;
14141411
}
14151412
resetAnswerToolProgressDraft();
@@ -1436,7 +1433,7 @@ export const dispatchTelegramMessage = async ({
14361433
reasoningStepState.noteReasoningHint();
14371434
reasoningStepState.noteReasoningDelivered();
14381435
}
1439-
await updateDraftFromPartial(lanes[segment.lane], segment.update);
1436+
updateDraftFromPartial(lanes[segment.lane], segment.update);
14401437
}
14411438
};
14421439
const flushDraftLane = async (lane: DraftLaneState) => {
@@ -2153,9 +2150,6 @@ export const dispatchTelegramMessage = async ({
21532150
}
21542151
if (segment.lane === "answer" && info.kind === "tool") {
21552152
if (verboseProgressActive()) {
2156-
if (streamMode === "progress") {
2157-
await rotateAnswerLaneAfterToolProgress();
2158-
}
21592153
if (
21602154
await sendPayload(
21612155
applyTextToPayload(effectivePayload, segment.update.text),
@@ -2307,9 +2301,6 @@ export const dispatchTelegramMessage = async ({
23072301
}
23082302
return;
23092303
}
2310-
if (streamMode === "progress" && info.kind === "tool") {
2311-
await rotateAnswerLaneAfterToolProgress();
2312-
}
23132304
const delivered = await sendPayload(effectivePayload, {
23142305
durable: info.kind === "final",
23152306
});

extensions/telegram/src/draft-stream.test.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -901,16 +901,11 @@ describe("createTelegramDraftStream", () => {
901901
describe("draft stream initial message debounce", () => {
902902
const createMockApi = () => createMockDraftApi(async () => ({ message_id: 42 }));
903903

904-
function createDebouncedStream(
905-
api: ReturnType<typeof createMockApi>,
906-
minInitialChars = 30,
907-
minInitialDelayMs?: number,
908-
) {
904+
function createDebouncedStream(api: ReturnType<typeof createMockApi>, minInitialChars = 30) {
909905
return createTelegramDraftStream({
910906
api: api as unknown as Bot["api"],
911907
chatId: 123,
912908
minInitialChars,
913-
minInitialDelayMs,
914909
});
915910
}
916911

@@ -979,36 +974,6 @@ describe("draft stream initial message debounce", () => {
979974
expect(api.sendMessage).toHaveBeenCalled();
980975
});
981976

982-
it("materializes a short first message after the initial delay", async () => {
983-
const api = createMockApi();
984-
const stream = createDebouncedStream(api, 30, 5000);
985-
986-
stream.update("Processing");
987-
await stream.flush();
988-
expect(api.sendMessage).not.toHaveBeenCalled();
989-
990-
await vi.advanceTimersByTimeAsync(5000);
991-
992-
expectPreviewSend(api, "Processing");
993-
});
994-
995-
it("cancels a delayed first message when clear() removes the draft", async () => {
996-
const api = createMockApi();
997-
const stream = createDebouncedStream(api, 30, 5000);
998-
999-
stream.update("Processing");
1000-
await stream.flush();
1001-
expect(api.sendMessage).not.toHaveBeenCalled();
1002-
expect(vi.getTimerCount()).toBe(1);
1003-
1004-
await stream.clear();
1005-
expect(vi.getTimerCount()).toBe(0);
1006-
1007-
await vi.advanceTimersByTimeAsync(5000);
1008-
expect(api.sendMessage).not.toHaveBeenCalled();
1009-
expect(api.editMessageText).not.toHaveBeenCalled();
1010-
});
1011-
1012977
it("works with longer text above threshold", async () => {
1013978
const api = createMockApi();
1014979
const stream = createDebouncedStream(api);

extensions/telegram/src/draft-stream.ts

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,6 @@ export function createTelegramDraftStream(params: {
182182
throttleMs?: number;
183183
/** Minimum chars before sending first message (debounce for push notifications) */
184184
minInitialChars?: number;
185-
/** Maximum time to hold a short first preview before materializing it anyway. */
186-
minInitialDelayMs?: number;
187185
/** Optional preview renderer (e.g. markdown -> HTML + parse mode). */
188186
renderText?: (text: string) => TelegramDraftPreview;
189187
/** Called when a late send resolves after forceNewMessage() switched generations. */
@@ -196,7 +194,6 @@ export function createTelegramDraftStream(params: {
196194
const maxChars = Math.min(params.maxChars ?? transportLimit, transportLimit);
197195
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
198196
const minInitialChars = params.minInitialChars;
199-
const minInitialDelayMs = params.minInitialDelayMs;
200197
const chatId = params.chatId;
201198
const threadParams = buildTelegramThreadParams(params.thread);
202199
const replyToMessageId = normalizeTelegramReplyToMessageId(params.replyToMessageId);
@@ -231,8 +228,6 @@ export function createTelegramDraftStream(params: {
231228
let lastDeliveredText = "";
232229
let lastRequestedText = "";
233230
let lastRequestedPreview: TelegramDraftPreview | undefined;
234-
let firstShortPreviewSeenMs: number | undefined;
235-
let initialPreviewTimer: ReturnType<typeof setTimeout> | undefined;
236231
let previewRevision = 0;
237232
let generation = 0;
238233
let deliveredTextOffset = 0;
@@ -328,26 +323,6 @@ export function createTelegramDraftStream(params: {
328323
streamVisibleSinceMs = visibleSinceMs;
329324
return true;
330325
};
331-
const clearInitialPreviewTimer = () => {
332-
if (initialPreviewTimer) {
333-
clearTimeout(initialPreviewTimer);
334-
initialPreviewTimer = undefined;
335-
}
336-
};
337-
const scheduleInitialPreviewFlush = (delayMs: number) => {
338-
if (initialPreviewTimer) {
339-
return;
340-
}
341-
initialPreviewTimer = setTimeout(
342-
() => {
343-
initialPreviewTimer = undefined;
344-
void flushInitialPreview().catch((err: unknown) => {
345-
params.warn?.(`telegram stream preview delayed send failed: ${formatErrorMessage(err)}`);
346-
});
347-
},
348-
Math.max(0, delayMs),
349-
);
350-
};
351326
const stopOversizedPreview = (payloadLength: number): false => {
352327
streamState.stopped = true;
353328
params.warn?.(`telegram stream preview stopped (text length ${payloadLength} > ${maxChars})`);
@@ -433,24 +408,8 @@ export function createTelegramDraftStream(params: {
433408

434409
if (typeof streamMessageId !== "number" && minInitialChars != null && !streamState.final) {
435410
if (renderedText.length < minInitialChars) {
436-
if (minInitialDelayMs == null) {
437-
return false;
438-
}
439-
const now = Date.now();
440-
firstShortPreviewSeenMs ??= now;
441-
const remainingDelayMs = minInitialDelayMs - (now - firstShortPreviewSeenMs);
442-
if (remainingDelayMs > 0) {
443-
scheduleInitialPreviewFlush(remainingDelayMs);
444-
return false;
445-
}
446-
clearInitialPreviewTimer();
447-
} else {
448-
firstShortPreviewSeenMs = undefined;
449-
clearInitialPreviewTimer();
411+
return false;
450412
}
451-
} else {
452-
firstShortPreviewSeenMs = undefined;
453-
clearInitialPreviewTimer();
454413
}
455414

456415
const previousSentPreviewKey = lastSentPreviewKey;
@@ -511,7 +470,6 @@ export function createTelegramDraftStream(params: {
511470
state: streamState,
512471
sendOrEditStreamMessage,
513472
});
514-
const flushInitialPreview = loop.flush;
515473

516474
const requestDraftUpdate = (text: string, preview?: TelegramDraftPreview) => {
517475
if (streamState.stopped || streamState.final) {
@@ -558,8 +516,6 @@ export function createTelegramDraftStream(params: {
558516
messageSendAttempted = false;
559517
streamMessageId = undefined;
560518
streamVisibleSinceMs = undefined;
561-
firstShortPreviewSeenMs = undefined;
562-
clearInitialPreviewTimer();
563519
lastSentPreviewKey = "";
564520
if (options?.resetOffset !== false) {
565521
deliveredTextOffset = 0;
@@ -573,7 +529,6 @@ export function createTelegramDraftStream(params: {
573529
};
574530

575531
const clear = async () => {
576-
clearInitialPreviewTimer();
577532
const messageId = await takeMessageIdAfterStop({
578533
stopForClear,
579534
readMessageId: () => streamMessageId,
@@ -592,7 +547,6 @@ export function createTelegramDraftStream(params: {
592547
};
593548

594549
const discard = async () => {
595-
clearInitialPreviewTimer();
596550
await stopForClear();
597551
};
598552

0 commit comments

Comments
 (0)