11import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
2- import type { InboxPlanningOutput } from "@atlas/core" ;
2+ import type { InboxPlanningOutput , RoutedTurn , TurnPolicyAction , TurnInterpretationType } from "@atlas/core" ;
33import {
44 getDefaultGoogleCalendarConnectionStore ,
55 getDefaultFollowUpRuntimeStore ,
@@ -108,6 +108,37 @@ function buildRequest(body: unknown, secret = "test-webhook-secret") {
108108 } ) ;
109109}
110110
111+ function buildRoutedTurn ( input : {
112+ turnType : TurnInterpretationType ;
113+ action : TurnPolicyAction ;
114+ confidence ?: number ;
115+ ambiguity ?: "none" | "low" | "high" ;
116+ resolvedProposalId ?: string ;
117+ } ) : RoutedTurn {
118+ return {
119+ interpretation : {
120+ turnType : input . turnType ,
121+ confidence : input . confidence ?? 0.9 ,
122+ resolvedEntityIds : [ ] ,
123+ ...( input . resolvedProposalId ? { resolvedProposalId : input . resolvedProposalId } : { } ) ,
124+ ambiguity : input . ambiguity ?? "none"
125+ } ,
126+ policy : {
127+ action : input . action ,
128+ reason : "test" ,
129+ requiresWrite : input . action === "execute_mutation" || input . action === "recover_and_execute" ,
130+ requiresConfirmation : input . action === "present_proposal" ,
131+ useMutationPipeline : input . action === "execute_mutation" || input . action === "recover_and_execute" ,
132+ ...( input . resolvedProposalId ? { targetProposalId : input . resolvedProposalId } : { } ) ,
133+ ...( input . action === "recover_and_execute"
134+ ? { mutationInputSource : "recovered_proposal" as const }
135+ : input . action === "execute_mutation"
136+ ? { mutationInputSource : "direct_user_turn" as const }
137+ : { } )
138+ }
139+ } ;
140+ }
141+
111142async function seedOutstandingFollowUpBundle ( titles : string [ ] ) {
112143 const store = getDefaultInboxProcessingStore ( ) ;
113144 const followUpStore = getDefaultFollowUpRuntimeStore ( ) ;
@@ -749,10 +780,9 @@ describe("telegram webhook route", () => {
749780 store : getDefaultInboxProcessingStore ( ) ,
750781 calendar : getDefaultCalendarAdapter ( ) ,
751782 primeProcessingStore : seedInboxItemForProcessingTests ,
752- turnRouter : async ( ) => ( {
753- route : "mutation" ,
754- reason : "Direct scheduling request." ,
755- writesAllowed : true
783+ turnRouter : async ( ) => buildRoutedTurn ( {
784+ turnType : "planning_request" ,
785+ action : "execute_mutation"
756786 } )
757787 }
758788 ) ;
@@ -863,10 +893,10 @@ describe("telegram webhook route", () => {
863893 summary : "The assistant proposed a concrete 3pm schedule and the user is now confirming it."
864894 } ) ,
865895 confirmedMutationRecoverer,
866- turnRouter : async ( ) => ( {
867- route : "confirmed_mutation " ,
868- reason : "The user is confirming a recent concrete proposal. " ,
869- writesAllowed : true
896+ turnRouter : async ( ) => buildRoutedTurn ( {
897+ turnType : "confirmation " ,
898+ action : "recover_and_execute " ,
899+ resolvedProposalId : "proposal-1"
870900 } )
871901 }
872902 ) ;
@@ -1019,10 +1049,10 @@ describe("telegram webhook route", () => {
10191049 reason : "The user refined the recent concrete proposal." ,
10201050 userReplyMessage : "Done - I've moved it to 4pm."
10211051 } ) ,
1022- turnRouter : async ( ) => ( {
1023- route : "confirmed_mutation " ,
1024- reason : "The user is refining a recent concrete proposal. " ,
1025- writesAllowed : true
1052+ turnRouter : async ( ) => buildRoutedTurn ( {
1053+ turnType : "confirmation " ,
1054+ action : "recover_and_execute " ,
1055+ resolvedProposalId : "proposal-1"
10261056 } )
10271057 }
10281058 ) ;
@@ -1155,10 +1185,10 @@ describe("telegram webhook route", () => {
11551185 reason : "The latest turn clearly reports completion of the recent journaling task." ,
11561186 userReplyMessage : "Got it."
11571187 } ) ,
1158- turnRouter : async ( ) => ( {
1159- route : "confirmed_mutation " ,
1160- reason : "The latest turn clearly completes one recent task. " ,
1161- writesAllowed : true
1188+ turnRouter : async ( ) => buildRoutedTurn ( {
1189+ turnType : "confirmation " ,
1190+ action : "recover_and_execute " ,
1191+ resolvedProposalId : "proposal-1"
11621192 } )
11631193 }
11641194 ) ;
@@ -1223,10 +1253,10 @@ describe("telegram webhook route", () => {
12231253 reason : "I have two recent proposals in view. Which one do you want me to apply?" ,
12241254 userReplyMessage : "I have two recent proposals in view. Which one do you want me to apply?"
12251255 } ) ,
1226- turnRouter : async ( ) => ( {
1227- route : "confirmed_mutation " ,
1228- reason : "This looks like a confirmation but the target is ambiguous. " ,
1229- writesAllowed : true
1256+ turnRouter : async ( ) => buildRoutedTurn ( {
1257+ turnType : "confirmation " ,
1258+ action : "recover_and_execute " ,
1259+ resolvedProposalId : "proposal-1"
12301260 } )
12311261 }
12321262 ) ;
@@ -1405,10 +1435,9 @@ describe("telegram webhook route", () => {
14051435 primeProcessingStore : seedInboxItemForProcessingTests ,
14061436 conversationMemorySummarizer,
14071437 conversationResponder,
1408- turnRouter : async ( ) => ( {
1409- route : "conversation" ,
1410- reason : "Planning dialogue request." ,
1411- writesAllowed : false
1438+ turnRouter : async ( ) => buildRoutedTurn ( {
1439+ turnType : "informational" ,
1440+ action : "reply_only"
14121441 } )
14131442 }
14141443 ) ;
@@ -1469,6 +1498,7 @@ describe("telegram webhook route", () => {
14691498 } ) ;
14701499 expect ( conversationResponder ) . toHaveBeenCalledWith ( {
14711500 route : "conversation" ,
1501+ rawText : "How should I prioritize this week?" ,
14721502 normalizedText : "How should I prioritize this week?" ,
14731503 recentTurns : [
14741504 {
@@ -1557,10 +1587,11 @@ describe("telegram webhook route", () => {
15571587 summary : "The user is discussing a possible move to tomorrow morning."
15581588 } ) ,
15591589 conversationResponder,
1560- turnRouter : async ( ) => ( {
1561- route : "conversation_then_mutation" ,
1562- reason : "Mixed turn should discuss first." ,
1563- writesAllowed : false
1590+ turnRouter : async ( ) => buildRoutedTurn ( {
1591+ turnType : "planning_request" ,
1592+ action : "present_proposal" ,
1593+ confidence : 0.68 ,
1594+ ambiguity : "low"
15641595 } )
15651596 }
15661597 ) ;
@@ -1586,6 +1617,7 @@ describe("telegram webhook route", () => {
15861617 expect ( listOutgoingBotEventsForTests ( ) ) . toHaveLength ( 1 ) ;
15871618 expect ( conversationResponder ) . toHaveBeenCalledWith ( {
15881619 route : "conversation_then_mutation" ,
1620+ rawText : "Could we move it to tomorrow morning?" ,
15891621 normalizedText : "Could we move it to tomorrow morning?" ,
15901622 recentTurns : [
15911623 {
@@ -1647,17 +1679,17 @@ describe("telegram webhook route", () => {
16471679 throw new Error ( "summary unavailable" ) ;
16481680 } ,
16491681 conversationResponder,
1650- turnRouter : async ( ) => ( {
1651- route : "conversation" ,
1652- reason : "Discussion turn." ,
1653- writesAllowed : false
1682+ turnRouter : async ( ) => buildRoutedTurn ( {
1683+ turnType : "informational" ,
1684+ action : "reply_only"
16541685 } )
16551686 }
16561687 ) ;
16571688
16581689 expect ( response . status ) . toBe ( 200 ) ;
16591690 expect ( conversationResponder ) . toHaveBeenCalledWith ( {
16601691 route : "conversation" ,
1692+ rawText : "Should we talk this through first?" ,
16611693 normalizedText : "Should we talk this through first?" ,
16621694 recentTurns : [
16631695 {
@@ -1713,10 +1745,9 @@ describe("telegram webhook route", () => {
17131745 conversationResponder : async ( ) => ( {
17141746 reply : "From our recent exchange, it sounds like you mean the dentist reminder, but I am not treating that as confirmed state."
17151747 } ) ,
1716- turnRouter : async ( ) => ( {
1717- route : "conversation" ,
1718- reason : "Write-adjacent question still needs cautious conversational handling." ,
1719- writesAllowed : false
1748+ turnRouter : async ( ) => buildRoutedTurn ( {
1749+ turnType : "informational" ,
1750+ action : "reply_only"
17201751 } )
17211752 }
17221753 ) ;
0 commit comments