@@ -23,13 +23,23 @@ import { getDefaultCalendarAdapter, resetCalendarAdapterForTests } from "@atlas/
2323
2424import { handleTelegramWebhook } from "@/lib/server/telegram-webhook" ;
2525
26- const { editTelegramMessageMock, sendTelegramMessageMock, sendTelegramChatActionMock, routeTurnWithResponsesMock } =
27- vi . hoisted ( ( ) => ( {
28- editTelegramMessageMock : vi . fn ( ) ,
26+ const {
27+ editTelegramMessageMock,
28+ sendTelegramMessageMock,
29+ sendTelegramChatActionMock,
30+ routeTurnWithResponsesMock,
31+ summarizeConversationMemoryWithResponsesMock,
32+ respondToConversationTurnWithResponsesMock,
33+ recoverConfirmedMutationWithResponsesMock
34+ } = vi . hoisted ( ( ) => ( {
35+ editTelegramMessageMock : vi . fn ( ) ,
2936 sendTelegramMessageMock : vi . fn ( ) ,
3037 sendTelegramChatActionMock : vi . fn ( ) ,
31- routeTurnWithResponsesMock : vi . fn ( )
32- } ) ) ;
38+ routeTurnWithResponsesMock : vi . fn ( ) ,
39+ summarizeConversationMemoryWithResponsesMock : vi . fn ( ) ,
40+ respondToConversationTurnWithResponsesMock : vi . fn ( ) ,
41+ recoverConfirmedMutationWithResponsesMock : vi . fn ( )
42+ } ) ) ;
3343
3444vi . mock ( "@atlas/integrations" , async ( ) => {
3545 const actual = await vi . importActual < typeof import ( "@atlas/integrations" ) > ( "@atlas/integrations" ) ;
@@ -68,9 +78,12 @@ vi.mock("@atlas/integrations", async () => {
6878 ]
6979 } ) ,
7080 editTelegramMessage : editTelegramMessageMock ,
81+ respondToConversationTurnWithResponses : respondToConversationTurnWithResponsesMock ,
82+ recoverConfirmedMutationWithResponses : recoverConfirmedMutationWithResponsesMock ,
7183 routeTurnWithResponses : routeTurnWithResponsesMock ,
7284 sendTelegramChatAction : sendTelegramChatActionMock ,
73- sendTelegramMessage : sendTelegramMessageMock
85+ sendTelegramMessage : sendTelegramMessageMock ,
86+ summarizeConversationMemoryWithResponses : summarizeConversationMemoryWithResponsesMock
7487 } ;
7588} ) ;
7689
@@ -262,10 +275,25 @@ beforeEach(async () => {
262275 sendTelegramMessageMock . mockReset ( ) ;
263276 sendTelegramChatActionMock . mockReset ( ) ;
264277 routeTurnWithResponsesMock . mockReset ( ) ;
278+ summarizeConversationMemoryWithResponsesMock . mockReset ( ) ;
279+ respondToConversationTurnWithResponsesMock . mockReset ( ) ;
280+ recoverConfirmedMutationWithResponsesMock . mockReset ( ) ;
265281 routeTurnWithResponsesMock . mockResolvedValue ( {
266282 route : "mutation" ,
267283 reason : "Direct scheduling request."
268284 } ) ;
285+ summarizeConversationMemoryWithResponsesMock . mockResolvedValue ( {
286+ summary : "Recent conversation summary."
287+ } ) ;
288+ respondToConversationTurnWithResponsesMock . mockResolvedValue ( {
289+ reply : "Mocked conversation reply."
290+ } ) ;
291+ recoverConfirmedMutationWithResponsesMock . mockResolvedValue ( {
292+ outcome : "needs_clarification" ,
293+ recoveredText : null ,
294+ reason : "Mocked recovery fallback." ,
295+ userReplyMessage : "Could you clarify what you want me to change?"
296+ } ) ;
269297 sendTelegramMessageMock . mockResolvedValue ( {
270298 ok : true ,
271299 result : {
@@ -491,6 +519,9 @@ describe("telegram webhook route", () => {
491519
492520 it ( "lets mixed-intent messages fall through to the normal router even with outstanding followups" , async ( ) => {
493521 const { store, followUpStore } = await seedOutstandingFollowUpBundle ( [ "Review launch checklist" , "Send investor update" ] ) ;
522+ const conversationResponder = vi . fn ( async ( ) => ( {
523+ reply : "What time tomorrow should I schedule the dentist?"
524+ } ) ) ;
494525
495526 const response = await handleTelegramWebhook (
496527 buildRequest ( {
@@ -507,14 +538,25 @@ describe("telegram webhook route", () => {
507538 store,
508539 followUpStore,
509540 calendar : getDefaultCalendarAdapter ( ) ,
510- primeProcessingStore : seedInboxItemForProcessingTests
541+ primeProcessingStore : seedInboxItemForProcessingTests ,
542+ conversationResponder
511543 }
512544 ) ;
513545
514546 expect ( response . status ) . toBe ( 200 ) ;
515- expect ( routeTurnWithResponsesMock ) . toHaveBeenCalledTimes ( 1 ) ;
516- expect ( ( response . body as { processing : { outcome : string } } ) . processing . outcome ) . toBe ( "planned" ) ;
547+ expect ( response . body ) . toMatchObject ( {
548+ processing : {
549+ outcome : "conversation_replied" ,
550+ reply : "What time tomorrow should I schedule the dentist?"
551+ } ,
552+ routing : {
553+ policy : {
554+ action : "ask_clarification"
555+ }
556+ }
557+ } ) ;
517558 expect ( listTasksForTests ( ) . filter ( ( task ) => task . lifecycleState === "done" ) ) . toHaveLength ( 0 ) ;
559+ expect ( conversationResponder ) . toHaveBeenCalledTimes ( 1 ) ;
518560 } ) ;
519561
520562 it ( "also gates /start for unlinked users" , async ( ) => {
@@ -806,6 +848,58 @@ describe("telegram webhook route", () => {
806848 expect ( editTelegramMessageMock ) . toHaveBeenCalledTimes ( 1 ) ;
807849 } ) ;
808850
851+ it ( "does not keep clear scheduling requests in discuss-first mode" , async ( ) => {
852+ process . env . TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret" ;
853+
854+ const response = await handleTelegramWebhook (
855+ buildRequest ( {
856+ update_id : 145 ,
857+ message : {
858+ message_id : 110 ,
859+ date : 1_700_000_020 ,
860+ text : "Schedule gym tomorrow at 6pm for 1 hour" ,
861+ chat : {
862+ id : 999 ,
863+ type : "private"
864+ } ,
865+ from : {
866+ id : 123 ,
867+ is_bot : false ,
868+ first_name : "Max" ,
869+ last_name : "Lin"
870+ }
871+ }
872+ } ) ,
873+ {
874+ store : getDefaultInboxProcessingStore ( ) ,
875+ calendar : getDefaultCalendarAdapter ( ) ,
876+ primeProcessingStore : seedInboxItemForProcessingTests
877+ }
878+ ) ;
879+
880+ expect ( response . status ) . toBe ( 200 ) ;
881+ expect ( response . body ) . toMatchObject ( {
882+ accepted : true ,
883+ turnRoute : "mutation" ,
884+ routing : {
885+ interpretation : {
886+ turnType : "planning_request" ,
887+ ambiguity : "none"
888+ } ,
889+ policy : {
890+ action : "execute_mutation"
891+ }
892+ } ,
893+ processing : {
894+ outcome : "planned"
895+ }
896+ } ) ;
897+ expect ( sendTelegramMessageMock ) . toHaveBeenCalledWith ( {
898+ chatId : "999" ,
899+ text : "Checking your schedule"
900+ } ) ;
901+ } ) ;
902+
809903 it ( "treats confirmed mutation turns as write-capable when recovery finds one concrete proposal" , async ( ) => {
810904 process . env . TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret" ;
811905 await recordOutgoingTelegramMessageIfNew ( {
0 commit comments