From 8f4b3197d7c477035939d81d79ab614d1e4e5346 Mon Sep 17 00:00:00 2001 From: "@daniel-lxs" <57051444+daniel-lxs@users.noreply.github.com> Date: Sat, 12 Sep 2026 05:48:47 +0000 Subject: [PATCH] fix: keep Telegram Fast activity visible --- .../src/server/lib/fast-agent-parent-event.ts | 28 +++++---- .../server/lib/fast-agent-surface-reply.ts | 20 +++++-- .../lib/fast-agent-telegram-activity.test.ts | 23 ++++++++ .../lib/fast-agent-telegram-activity.ts | 11 ++++ .../fast-agent-telegram-title-sync.test.ts | 57 ++++++++++++++++++- .../lib/fast-agent-telegram-title-sync.ts | 22 ++++--- 6 files changed, 135 insertions(+), 26 deletions(-) diff --git a/packages/sdk/src/server/lib/fast-agent-parent-event.ts b/packages/sdk/src/server/lib/fast-agent-parent-event.ts index 4dc4f6ae6..f2853e20c 100644 --- a/packages/sdk/src/server/lib/fast-agent-parent-event.ts +++ b/packages/sdk/src/server/lib/fast-agent-parent-event.ts @@ -118,7 +118,10 @@ import { type TelegramLiveTaskStreamProvider, } from './telegram-live-task-stream'; import { createFastAgentTypingActivity } from './fast-agent-typing-activity'; -import { createFastAgentTelegramActivity } from './fast-agent-telegram-activity'; +import { + createFastAgentTelegramActivity, + runWithFastAgentTelegramActivityReassertion, +} from './fast-agent-telegram-activity'; import { findTeamsConversationRoute } from '../automations/destination'; import { isFastAgentManagedTelegramTopic, @@ -1903,20 +1906,25 @@ async function createTelegramFastAgentParentTurn( sessionId: session.id, footerContext: params.footerContext, }); + const launchTask = createFastAgentCommunicationTaskLauncher({ + userId: actorUserId, + conversation, + telegramLiveTaskProvider: provider, + automation: await resolveFastAutomationLaunchContext({ + event: params.event, + conversation, + }), + }); return { userId: actorUserId, conversation, adapter: { activity, - launchTask: createFastAgentCommunicationTaskLauncher({ - userId: actorUserId, - conversation, - telegramLiveTaskProvider: provider, - automation: await resolveFastAutomationLaunchContext({ - event: params.event, - conversation, - }), - }), + launchTask: async (input) => { + return runWithFastAgentTelegramActivityReassertion(activity, () => + launchTask(input), + ); + }, replaceReply: async (handle, reply) => { const result = await replaceReply(handle, reply); activity.reassert(); diff --git a/packages/sdk/src/server/lib/fast-agent-surface-reply.ts b/packages/sdk/src/server/lib/fast-agent-surface-reply.ts index 01e32b7bb..9f628cf4c 100644 --- a/packages/sdk/src/server/lib/fast-agent-surface-reply.ts +++ b/packages/sdk/src/server/lib/fast-agent-surface-reply.ts @@ -78,7 +78,10 @@ import { } from './source-control-fast-delivery'; import { buildFastAgentArtifactCreator } from './artifacts/fast-agent-artifact-creator'; import { createFastAgentTypingActivity } from './fast-agent-typing-activity'; -import { createFastAgentTelegramActivity } from './fast-agent-telegram-activity'; +import { + createFastAgentTelegramActivity, + runWithFastAgentTelegramActivityReassertion, +} from './fast-agent-telegram-activity'; import { addFastAgentTelegramTopicTitleSync } from './fast-agent-telegram-title-sync'; const SLACK_QUOTE_MAX_LENGTH = 100; @@ -640,6 +643,11 @@ export async function buildFastAgentSurfaceReplyDelivery(params: { sessionId: session.id, footerContext, }); + const launchTask = createFastAgentCommunicationTaskLauncher({ + userId: params.userId, + conversation, + telegramLiveTaskProvider: provider, + }); const postReply: FastAgentTurnAdapter['postReply'] = async ({ message, }) => { @@ -679,11 +687,11 @@ export async function buildFastAgentSurfaceReplyDelivery(params: { } : {}), createArtifact, - launchTask: createFastAgentCommunicationTaskLauncher({ - userId: params.userId, - conversation, - telegramLiveTaskProvider: provider, - }), + launchTask: async (input) => { + return runWithFastAgentTelegramActivityReassertion(activity, () => + launchTask(input), + ); + }, postReply, replaceReply: async (handle, reply) => { const result = await replaceReply(handle, reply); diff --git a/packages/sdk/src/server/lib/fast-agent-telegram-activity.test.ts b/packages/sdk/src/server/lib/fast-agent-telegram-activity.test.ts index fc8571b97..ed1417b80 100644 --- a/packages/sdk/src/server/lib/fast-agent-telegram-activity.test.ts +++ b/packages/sdk/src/server/lib/fast-agent-telegram-activity.test.ts @@ -4,6 +4,7 @@ import { FAST_AGENT_TELEGRAM_STREAM_INTERVAL_MS, FAST_AGENT_TELEGRAM_TYPING_REFRESH_MS, createFastAgentTelegramActivity, + runWithFastAgentTelegramActivityReassertion, } from './fast-agent-telegram-activity'; describe('Fast Telegram activity', () => { @@ -302,4 +303,26 @@ describe('Fast Telegram activity', () => { await activity.dispose(); warn.mockRestore(); }); + + it.each(['success', 'failure'] as const)( + 'reasserts after a draft-clearing operation %s', + async (outcome) => { + const activity = { reassert: vi.fn() }; + const operation = + outcome === 'success' + ? vi.fn().mockResolvedValue('result') + : vi.fn().mockRejectedValue(new Error('failed')); + + if (outcome === 'success') { + await expect( + runWithFastAgentTelegramActivityReassertion(activity, operation), + ).resolves.toBe('result'); + } else { + await expect( + runWithFastAgentTelegramActivityReassertion(activity, operation), + ).rejects.toThrow('failed'); + } + expect(activity.reassert).toHaveBeenCalledOnce(); + }, + ); }); diff --git a/packages/sdk/src/server/lib/fast-agent-telegram-activity.ts b/packages/sdk/src/server/lib/fast-agent-telegram-activity.ts index 9f8b91f0c..d8df836ca 100644 --- a/packages/sdk/src/server/lib/fast-agent-telegram-activity.ts +++ b/packages/sdk/src/server/lib/fast-agent-telegram-activity.ts @@ -21,6 +21,17 @@ export const FAST_AGENT_TELEGRAM_PROCESSING_DELAY_MS = 300; export const FAST_AGENT_TELEGRAM_STREAM_INTERVAL_MS = 800; const FAST_AGENT_TELEGRAM_THINKING_TEXT = 'Roomote is working...'; +export async function runWithFastAgentTelegramActivityReassertion( + activity: { reassert: () => void }, + operation: () => Promise, +): Promise { + try { + return await operation(); + } finally { + activity.reassert(); + } +} + function isTelegramPrivateChatId(channelId: string): boolean { const parsed = Number(channelId); return Number.isSafeInteger(parsed) && parsed > 0; diff --git a/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.test.ts b/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.test.ts index 503726fd3..56fea1844 100644 --- a/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.test.ts +++ b/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.test.ts @@ -11,6 +11,10 @@ import { getTelegramTopicIconEmojiPreferences, syncFastAgentTelegramTopicTitleBestEffort, } from './fast-agent-telegram-title-sync'; +import { + FAST_AGENT_TELEGRAM_PROCESSING_DELAY_MS, + createFastAgentTelegramActivity, +} from './fast-agent-telegram-activity'; const CONFIRMED_TELEGRAM_TOPIC_ICON_EMOJIS = new Set([ '💡', @@ -184,6 +188,57 @@ describe('Telegram Fast topic title sync', () => { expect(dispose).toHaveBeenCalledTimes(1); }); + it('restores working activity after a topic update clears the draft', async () => { + vi.useFakeTimers(); + try { + const sendMessageDraft = vi.fn().mockResolvedValue(undefined); + const currentSession = session('Generated title'); + if (currentSession.conversation.surface !== 'telegram') { + throw new Error('Expected a Telegram session.'); + } + currentSession.conversation.replyTarget = { + channelId: '123', + threadId: '77', + }; + const baseActivity = createFastAgentTelegramActivity({ + provider: { sendMessageDraft, sendChatAction: vi.fn() }, + replyTarget: { channelId: '123', threadId: '77' }, + }); + const activity = addFastAgentTelegramTopicTitleSync({ + activity: baseActivity, + provider: { + editForumTopic: vi.fn().mockResolvedValue(undefined), + resolveForumTopicIconCustomEmojiId: vi + .fn() + .mockResolvedValue(undefined), + } as never, + sessionId: 'session-1', + channelId: '123', + threadId: '77', + resolveSession: vi.fn().mockResolvedValue(currentSession), + }); + + activity.start(); + await vi.advanceTimersByTimeAsync( + FAST_AGENT_TELEGRAM_PROCESSING_DELAY_MS, + ); + expect(sendMessageDraft).toHaveBeenCalledOnce(); + + activity.updateTitle?.('Generated title', { titleChanged: true }); + await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync( + FAST_AGENT_TELEGRAM_PROCESSING_DELAY_MS, + ); + expect(sendMessageDraft).toHaveBeenCalledTimes(2); + expect(sendMessageDraft).toHaveBeenLastCalledWith( + expect.objectContaining({ text: 'Roomote is working...' }), + ); + await activity.dispose(); + } finally { + vi.useRealTimers(); + } + }); + it('updates only the icon when a generated canonical title is unchanged', async () => { const editForumTopic = vi.fn().mockResolvedValue(undefined); @@ -246,7 +301,7 @@ describe('Telegram Fast topic title sync', () => { threadId: '77', resolveSession: vi.fn().mockResolvedValue(session('Generated title')), }), - ).resolves.toBeUndefined(); + ).resolves.toBe(false); expect(warn).toHaveBeenCalledWith( expect.stringContaining('Failed to sync Telegram topic title'), ); diff --git a/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.ts b/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.ts index 08b63e391..34bc49e3a 100644 --- a/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.ts +++ b/packages/sdk/src/server/lib/fast-agent-telegram-title-sync.ts @@ -41,7 +41,8 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { category?: TaskTitleCategory | null; titleChanged?: boolean; resolveSession: () => Promise; -}): Promise { +}): Promise { + let updated = false; try { for (let attempt = 0; attempt < 2; attempt += 1) { const session = await input.resolveSession(); @@ -51,7 +52,7 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { session.conversation.replyTarget.channelId !== input.channelId || session.conversation.replyTarget.threadId !== input.threadId ) { - return; + return updated; } const title = buildCommunicationTaskThreadName(session.title); @@ -63,7 +64,7 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { .catch(() => undefined) : undefined; if (input.titleChanged === false && !iconCustomEmojiId) { - return; + return updated; } await input.provider.editForumTopic({ channelId: input.channelId, @@ -71,9 +72,10 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { ...(input.titleChanged === false ? {} : { name: title }), ...(iconCustomEmojiId ? { iconCustomEmojiId } : {}), }); + updated = true; if (input.titleChanged === false) { - return; + return updated; } const latest = await input.resolveSession(); @@ -81,7 +83,7 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { !latest?.title || buildCommunicationTaskThreadName(latest.title) === title ) { - return; + return updated; } } } catch (error) { @@ -89,6 +91,7 @@ export async function syncFastAgentTelegramTopicTitleBestEffort(input: { `[Fast Agent] Failed to sync Telegram topic title for session ${input.sessionId}: ${error instanceof Error ? error.message : String(error)}`, ); } + return updated; } export function addFastAgentTelegramTopicTitleSync< @@ -126,13 +129,14 @@ export function addFastAgentTelegramTopicTitleSync< lastRequestedTitle = title; lastRequestedCategory = category; lastRequestedTitleChanged = titleChanged; - titleUpdate = titleUpdate.then(() => - syncFastAgentTelegramTopicTitleBestEffort({ + titleUpdate = titleUpdate.then(async () => { + const updated = await syncFastAgentTelegramTopicTitleBestEffort({ ...input, category, titleChanged, - }), - ); + }); + if (updated) input.activity.reassert(); + }); }, async dispose() { await Promise.all([input.activity.dispose(), titleUpdate]);