@@ -31,7 +31,8 @@ const {
3131 classifyTurnWithResponsesMock,
3232 summarizeConversationMemoryWithResponsesMock,
3333 respondToConversationTurnWithResponsesMock,
34- recoverConfirmedMutationWithResponsesMock
34+ recoverConfirmedMutationWithResponsesMock,
35+ extractSlotsWithResponsesMock
3536} = vi . hoisted ( ( ) => ( {
3637 editTelegramMessageMock : vi . fn ( ) ,
3738 sendTelegramMessageMock : vi . fn ( ) ,
@@ -40,7 +41,8 @@ const {
4041 classifyTurnWithResponsesMock : vi . fn ( ) ,
4142 summarizeConversationMemoryWithResponsesMock : vi . fn ( ) ,
4243 respondToConversationTurnWithResponsesMock : vi . fn ( ) ,
43- recoverConfirmedMutationWithResponsesMock : vi . fn ( )
44+ recoverConfirmedMutationWithResponsesMock : vi . fn ( ) ,
45+ extractSlotsWithResponsesMock : vi . fn ( )
4446} ) ) ;
4547
4648vi . mock ( "@atlas/integrations" , async ( ) => {
@@ -84,6 +86,7 @@ vi.mock("@atlas/integrations", async () => {
8486 recoverConfirmedMutationWithResponses : recoverConfirmedMutationWithResponsesMock ,
8587 classifyTurnWithResponses : classifyTurnWithResponsesMock ,
8688 routeTurnWithResponses : routeTurnWithResponsesMock ,
89+ extractSlotsWithResponses : extractSlotsWithResponsesMock ,
8790 sendTelegramChatAction : sendTelegramChatActionMock ,
8891 sendTelegramMessage : sendTelegramMessageMock ,
8992 summarizeConversationMemoryWithResponses : summarizeConversationMemoryWithResponsesMock
@@ -283,6 +286,15 @@ beforeEach(async () => {
283286 summarizeConversationMemoryWithResponsesMock . mockReset ( ) ;
284287 respondToConversationTurnWithResponsesMock . mockReset ( ) ;
285288 recoverConfirmedMutationWithResponsesMock . mockReset ( ) ;
289+ extractSlotsWithResponsesMock . mockReset ( ) ;
290+ extractSlotsWithResponsesMock . mockResolvedValue ( {
291+ time : { hour : 9 , minute : 0 } ,
292+ day : { kind : "relative" , value : "tomorrow" } ,
293+ duration : null ,
294+ target : null ,
295+ confidence : { day : 0.95 , time : 0.95 } ,
296+ unresolvable : [ ]
297+ } ) ;
286298 routeTurnWithResponsesMock . mockResolvedValue ( {
287299 route : "mutation" ,
288300 reason : "Direct scheduling request."
@@ -715,8 +727,16 @@ describe("telegram webhook route", () => {
715727 expect ( listInboxItemsForTests ( ) ) . toHaveLength ( 1 ) ;
716728 } ) ;
717729
718- it ( "normalizes a Telegram text message and hands it to app services " , async ( ) => {
730+ it ( "normalizes a Telegram text message and routes to clarification when slots are missing " , async ( ) => {
719731 process . env . TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret" ;
732+ extractSlotsWithResponsesMock . mockResolvedValueOnce ( {
733+ time : null ,
734+ day : null ,
735+ duration : null ,
736+ target : null ,
737+ confidence : { } ,
738+ unresolvable : [ ]
739+ } ) ;
720740
721741 const response = await handleTelegramWebhook (
722742 buildRequest ( {
@@ -773,42 +793,23 @@ describe("telegram webhook route", () => {
773793 processingStatus : "received" ,
774794 linkedTaskIds : [ ]
775795 } ,
776- processing : {
777- outcome : "planned"
778- } ,
779- outboundDelivery : {
780- status : "edited" ,
781- attempts : 1
782- }
783- } ) ;
784- expect ( response . body ) . toMatchObject ( {
785- outboundDelivery : {
786- idempotencyKey : expect . any ( String ) ,
787- message : {
788- message_id : 88
796+ routing : {
797+ interpretation : {
798+ turnType : "planning_request" ,
799+ ambiguity : "high"
800+ } ,
801+ policy : {
802+ action : "ask_clarification"
789803 }
804+ } ,
805+ processing : {
806+ outcome : "conversation_replied"
790807 }
791808 } ) ;
792809 expect ( listIncomingBotEventsForTests ( ) ) . toHaveLength ( 1 ) ;
793- expect ( listOutgoingBotEventsForTests ( ) ) . toHaveLength ( 1 ) ;
794810 expect ( listInboxItemsForTests ( ) ) . toHaveLength ( 1 ) ;
795- expect ( listPlannerRunsForTests ( ) ) . toHaveLength ( 1 ) ;
796- expect ( listTasksForTests ( ) ) . toHaveLength ( 1 ) ;
797- expect ( listScheduleBlocksForTests ( ) ) . toHaveLength ( 1 ) ;
798- expect ( sendTelegramChatActionMock ) . toHaveBeenCalledWith ( {
799- chatId : "999" ,
800- action : "typing"
801- } ) ;
802- expect ( sendTelegramMessageMock ) . toHaveBeenCalledTimes ( 1 ) ;
803- expect ( sendTelegramMessageMock ) . toHaveBeenCalledWith ( {
804- chatId : "999" ,
805- text : "Checking your schedule"
806- } ) ;
807- expect ( editTelegramMessageMock ) . toHaveBeenCalledWith ( {
808- chatId : "999" ,
809- messageId : 88 ,
810- text : expect . stringContaining ( "Scheduled 'Review launch checklist'" )
811- } ) ;
811+ expect ( listPlannerRunsForTests ( ) ) . toHaveLength ( 0 ) ;
812+ expect ( listTasksForTests ( ) ) . toHaveLength ( 0 ) ;
812813 } ) ;
813814
814815 it ( "preserves mutation behavior when the turn router explicitly returns mutation" , async ( ) => {
@@ -865,6 +866,14 @@ describe("telegram webhook route", () => {
865866
866867 it ( "does not keep clear scheduling requests in discuss-first mode" , async ( ) => {
867868 process . env . TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret" ;
869+ extractSlotsWithResponsesMock . mockResolvedValueOnce ( {
870+ time : { hour : 18 , minute : 0 } ,
871+ day : { kind : "relative" , value : "tomorrow" } ,
872+ duration : { minutes : 60 } ,
873+ target : null ,
874+ confidence : { day : 0.95 , time : 0.95 , duration : 0.9 } ,
875+ unresolvable : [ ]
876+ } ) ;
868877
869878 const response = await handleTelegramWebhook (
870879 buildRequest ( {
0 commit comments