@@ -46,6 +46,28 @@ describe("decideTurnPolicy", () => {
4646 } ) ;
4747 } ) ;
4848
49+ it ( "keeps clarification answers in clarification when required slots remain" , ( ) => {
50+ expect (
51+ decideTurnPolicy ( {
52+ interpretation : {
53+ turnType : "clarification_answer" ,
54+ confidence : 0.84 ,
55+ resolvedEntityIds : [ ] ,
56+ ambiguity : "high" ,
57+ missingSlots : [ "time" ]
58+ } ,
59+ routingContext : {
60+ rawText : "Tomorrow" ,
61+ normalizedText : "Tomorrow" ,
62+ recentTurns : [ ]
63+ }
64+ } )
65+ ) . toMatchObject ( {
66+ action : "ask_clarification" ,
67+ clarificationSlots : [ "time" ]
68+ } ) ;
69+ } ) ;
70+
4971 it ( "does not force proposal mode for low-confidence writes alone" , ( ) => {
5072 expect (
5173 decideTurnPolicy ( {
@@ -126,6 +148,46 @@ describe("decideTurnPolicy", () => {
126148 } ) ;
127149 } ) ;
128150
151+ it ( "treats affirmative consent on a pending proposal as execution" , ( ) => {
152+ expect (
153+ decideTurnPolicy ( {
154+ interpretation : {
155+ turnType : "confirmation" ,
156+ confidence : 0.97 ,
157+ resolvedEntityIds : [ "task-1" ] ,
158+ resolvedProposalId : "proposal-1" ,
159+ ambiguity : "none"
160+ } ,
161+ routingContext : {
162+ rawText : "yes" ,
163+ normalizedText : "yes" ,
164+ recentTurns : [ ] ,
165+ entityRegistry : [
166+ {
167+ id : "proposal-1" ,
168+ conversationId : "conversation-1" ,
169+ kind : "proposal_option" ,
170+ label : "Move it to 3pm" ,
171+ status : "active" ,
172+ createdAt : "2026-03-20T16:00:00.000Z" ,
173+ updatedAt : "2026-03-20T16:00:00.000Z" ,
174+ data : {
175+ route : "conversation_then_mutation" ,
176+ replyText : "Would you like me to move it to 3pm?" ,
177+ confirmationRequired : true ,
178+ targetEntityId : "task-1"
179+ }
180+ }
181+ ]
182+ }
183+ } )
184+ ) . toMatchObject ( {
185+ action : "recover_and_execute" ,
186+ targetProposalId : "proposal-1" ,
187+ mutationInputSource : "recovered_proposal"
188+ } ) ;
189+ } ) ;
190+
129191 it ( "asks for clarification on ambiguous write-like turns" , ( ) => {
130192 expect (
131193 decideTurnPolicy ( {
@@ -185,4 +247,124 @@ describe("decideTurnPolicy", () => {
185247 requiresConfirmation : true
186248 } ) ;
187249 } ) ;
250+
251+ it ( "routes ready clarification answers to present_proposal when deterministic consent is still required" , ( ) => {
252+ expect (
253+ decideTurnPolicy ( {
254+ interpretation : {
255+ turnType : "clarification_answer" ,
256+ confidence : 0.92 ,
257+ resolvedEntityIds : [ "task-1" ] ,
258+ resolvedProposalId : "proposal-1" ,
259+ ambiguity : "none"
260+ } ,
261+ routingContext : {
262+ rawText : "3:15pm" ,
263+ normalizedText : "3:15pm" ,
264+ recentTurns : [ ] ,
265+ entityRegistry : [
266+ {
267+ id : "proposal-1" ,
268+ conversationId : "conversation-1" ,
269+ kind : "proposal_option" ,
270+ label : "Move it to 3:15pm" ,
271+ status : "active" ,
272+ createdAt : "2026-03-20T16:00:00.000Z" ,
273+ updatedAt : "2026-03-20T16:00:00.000Z" ,
274+ data : {
275+ route : "conversation_then_mutation" ,
276+ replyText : "Would you like me to move it to 3:15pm?" ,
277+ confirmationRequired : true ,
278+ targetEntityId : "task-1"
279+ }
280+ }
281+ ]
282+ }
283+ } )
284+ ) . toMatchObject ( {
285+ action : "present_proposal" ,
286+ requiresConfirmation : true ,
287+ targetProposalId : "proposal-1"
288+ } ) ;
289+ } ) ;
290+
291+ it ( "recomputes a parameter edit on an active proposal instead of executing it" , ( ) => {
292+ const result = decideTurnPolicy ( {
293+ interpretation : {
294+ turnType : "edit_request" ,
295+ confidence : 0.9 ,
296+ resolvedEntityIds : [ "task-1" ] ,
297+ resolvedProposalId : "proposal-1" ,
298+ ambiguity : "none"
299+ } ,
300+ routingContext : {
301+ rawText : "make it 3 instead" ,
302+ normalizedText : "make it 3 instead" ,
303+ recentTurns : [ ] ,
304+ entityRegistry : [
305+ {
306+ id : "proposal-1" ,
307+ conversationId : "conversation-1" ,
308+ kind : "proposal_option" ,
309+ label : "Move it to tomorrow 2pm" ,
310+ status : "active" ,
311+ createdAt : "2026-03-20T16:00:00.000Z" ,
312+ updatedAt : "2026-03-20T16:00:00.000Z" ,
313+ data : {
314+ route : "conversation_then_mutation" ,
315+ replyText : "Would you like me to move it to tomorrow 2pm?" ,
316+ confirmationRequired : true ,
317+ targetEntityId : "task-1" ,
318+ originatingTurnText : "move it to tomorrow 2pm"
319+ }
320+ }
321+ ]
322+ }
323+ } ) ;
324+
325+ expect ( result ) . toMatchObject ( {
326+ action : "present_proposal" ,
327+ requiresConfirmation : true
328+ } ) ;
329+ expect ( result . targetProposalId ) . toBeUndefined ( ) ;
330+ } ) ;
331+
332+ it ( "does not bind consent to a stale proposal with a different target" , ( ) => {
333+ expect (
334+ decideTurnPolicy ( {
335+ interpretation : {
336+ turnType : "edit_request" ,
337+ confidence : 0.91 ,
338+ resolvedEntityIds : [ "task-2" ] ,
339+ resolvedProposalId : "proposal-1" ,
340+ ambiguity : "none"
341+ } ,
342+ routingContext : {
343+ rawText : "move that to 3pm" ,
344+ normalizedText : "move that to 3pm" ,
345+ recentTurns : [ ] ,
346+ entityRegistry : [
347+ {
348+ id : "proposal-1" ,
349+ conversationId : "conversation-1" ,
350+ kind : "proposal_option" ,
351+ label : "Move task one to 2pm" ,
352+ status : "active" ,
353+ createdAt : "2026-03-20T16:00:00.000Z" ,
354+ updatedAt : "2026-03-20T16:00:00.000Z" ,
355+ data : {
356+ route : "conversation_then_mutation" ,
357+ replyText : "Would you like me to move task one to 2pm?" ,
358+ confirmationRequired : true ,
359+ targetEntityId : "task-1"
360+ }
361+ }
362+ ]
363+ }
364+ } )
365+ ) . toMatchObject ( {
366+ action : "execute_mutation" ,
367+ mutationInputSource : "direct_user_turn"
368+ } ) ;
369+ } ) ;
188370} ) ;
0 commit comments