Skip to content

Commit 653abb9

Browse files
committed
Clean up blocking slots
1 parent 9b23962 commit 653abb9

3 files changed

Lines changed: 140 additions & 3 deletions

File tree

apps/web/src/lib/server/interpret-turn.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,70 @@ describe("interpretTurn", () => {
198198
});
199199
});
200200

201+
it("clears a satisfied time clarification slot when the answer provides a clock time", async () => {
202+
const result = await interpretTurn({
203+
rawText: "5pm",
204+
normalizedText: "5pm",
205+
recentTurns: [],
206+
discourseState: {
207+
focus_entity_id: null,
208+
currently_editable_entity_id: null,
209+
last_user_mentioned_entity_ids: [],
210+
last_presented_items: [],
211+
pending_clarifications: [
212+
{
213+
id: "clar-1",
214+
slot: "time",
215+
question: "What time should I schedule it?",
216+
status: "pending",
217+
blocking: true,
218+
createdAt: "2026-03-20T16:00:00.000Z",
219+
createdTurnId: "assistant:1"
220+
}
221+
],
222+
mode: "clarifying"
223+
}
224+
});
225+
226+
expect(result).toMatchObject({
227+
turnType: "clarification_answer",
228+
ambiguity: "none"
229+
});
230+
expect(result.missingSlots).toBeUndefined();
231+
});
232+
233+
it("treats punctuated consent as confirmation when one active proposal exists", async () => {
234+
const result = await interpretTurn({
235+
rawText: "Ok,",
236+
normalizedText: "Ok,",
237+
recentTurns: [],
238+
entityRegistry: [
239+
{
240+
id: "proposal-1",
241+
conversationId: "conversation-1",
242+
kind: "proposal_option",
243+
label: "Schedule it at 5pm",
244+
status: "active",
245+
createdAt: "2026-03-20T16:00:00.000Z",
246+
updatedAt: "2026-03-20T16:00:00.000Z",
247+
data: {
248+
route: "conversation_then_mutation",
249+
replyText: "Would you like me to schedule it at 5pm?",
250+
confirmationRequired: true,
251+
targetEntityId: null,
252+
mutationInputSource: null
253+
}
254+
}
255+
]
256+
});
257+
258+
expect(result).toMatchObject({
259+
turnType: "confirmation",
260+
resolvedProposalId: "proposal-1",
261+
ambiguity: "none"
262+
});
263+
});
264+
201265
it("treats informational questions as informational", async () => {
202266
const result = await interpretTurn({
203267
rawText: "What do I have tomorrow?",

apps/web/src/lib/server/interpret-turn.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ export async function interpretTurn(input: InterpretTurnInput): Promise<TurnInte
5858
}
5959

6060
if (clarificationLike) {
61-
const ambiguity = blockingSlots.length > 0 ? "high" : "none";
61+
const unresolvedBlockingSlots = deriveClarificationAnswerMissingSlots({
62+
blockingSlots,
63+
writeSignals,
64+
resolvedEntityIds
65+
});
66+
const ambiguity = unresolvedBlockingSlots.length > 0 ? "high" : "none";
6267

6368
return {
6469
turnType: "clarification_answer",
@@ -69,7 +74,7 @@ export async function interpretTurn(input: InterpretTurnInput): Promise<TurnInte
6974
...(ambiguity === "high"
7075
? { ambiguityReason: "Blocking clarification slots are still open." }
7176
: {}),
72-
...(blockingSlots.length > 0 ? { missingSlots: blockingSlots } : {})
77+
...(unresolvedBlockingSlots.length > 0 ? { missingSlots: unresolvedBlockingSlots } : {})
7378
};
7479
}
7580

@@ -156,7 +161,7 @@ function unique(values: string[]) {
156161
}
157162

158163
function isConfirmationTurn(lower: string) {
159-
return /^(yes|yeah|yep|ok|okay|do it|sounds good|works|that works|go ahead|please do|confirm)([.! ]*)?$/.test(
164+
return /^(yes|yeah|yep|ok|okay|do it|sounds good|works|that works|go ahead|please do|confirm)([.,!? ]*)?$/.test(
160165
lower
161166
);
162167
}
@@ -299,6 +304,30 @@ function deriveMissingSlots(input: {
299304
return unique(missingSlots);
300305
}
301306

307+
function deriveClarificationAnswerMissingSlots(input: {
308+
blockingSlots: string[];
309+
writeSignals: ReturnType<typeof analyzeWriteSignals>;
310+
resolvedEntityIds: string[];
311+
}) {
312+
return unique(
313+
input.blockingSlots.filter((slot) => {
314+
switch (slot) {
315+
case "day":
316+
return !input.writeSignals.hasDayReference;
317+
case "time":
318+
return !input.writeSignals.hasClockTime;
319+
case "target":
320+
return input.resolvedEntityIds.length === 0 && !input.writeSignals.hasConcreteSubject;
321+
case "proposal":
322+
case "unknown":
323+
return true;
324+
default:
325+
return true;
326+
}
327+
})
328+
);
329+
}
330+
302331
function deriveWriteAmbiguity(input: {
303332
normalizedText: string;
304333
resolvedEntityIds: string[];

apps/web/src/lib/server/turn-router.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,48 @@ describe("turn router", () => {
6666
}
6767
});
6868
});
69+
70+
it("routes punctuated consent on an active proposal to recover-and-execute", async () => {
71+
const result = await routeMessageTurn({
72+
rawText: "Ok,",
73+
normalizedText: "Ok,",
74+
recentTurns: [
75+
{
76+
role: "assistant",
77+
text: "Would you like me to schedule it at 5pm?",
78+
createdAt: "2026-03-17T16:00:00.000Z"
79+
}
80+
],
81+
entityRegistry: [
82+
{
83+
id: "proposal-1",
84+
conversationId: "conversation-1",
85+
kind: "proposal_option",
86+
label: "Schedule it at 5pm",
87+
status: "active",
88+
createdAt: "2026-03-17T16:00:00.000Z",
89+
updatedAt: "2026-03-17T16:00:00.000Z",
90+
data: {
91+
route: "conversation_then_mutation",
92+
replyText: "Would you like me to schedule it at 5pm?",
93+
confirmationRequired: true,
94+
originatingTurnText: "Schedule Malaysia trip planning at 5pm",
95+
targetEntityId: null,
96+
mutationInputSource: null
97+
}
98+
}
99+
]
100+
});
101+
102+
expect(result).toMatchObject({
103+
interpretation: {
104+
turnType: "confirmation",
105+
resolvedProposalId: "proposal-1"
106+
},
107+
policy: {
108+
action: "recover_and_execute",
109+
targetProposalId: "proposal-1"
110+
}
111+
});
112+
});
69113
});

0 commit comments

Comments
 (0)