Skip to content

Commit 1c3de94

Browse files
authored
Merge pull request #64 from MaxLinCode/claude/fix-slot-override
Claude/fix slot override
2 parents 7364159 + d16c2c4 commit 1c3de94

17 files changed

Lines changed: 375 additions & 203 deletions

apps/web/src/app/api/telegram/webhook/route.test.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
1-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
import type {
2+
ConversationEntity,
33
InboxPlanningOutput,
44
RoutedTurn,
5-
TurnPolicyAction,
5+
TimeSpec,
66
TurnInterpretationType,
7-
ConversationEntity,
7+
TurnPolicyAction,
88
} from "@atlas/core";
9+
10+
function t(hour: number, minute: number): TimeSpec {
11+
return { kind: "absolute", hour, minute };
12+
}
13+
914
import {
10-
getDefaultGoogleCalendarConnectionStore,
1115
getDefaultFollowUpRuntimeStore,
16+
getDefaultGoogleCalendarConnectionStore,
1217
getDefaultInboxProcessingStore,
13-
listPlannerRunsForTests,
14-
listScheduleBlocksForTests,
15-
listTasksForTests,
1618
listInboxItemsForTests,
1719
listIncomingBotEventsForTests,
1820
listOutgoingBotEventsForTests,
21+
listPlannerRunsForTests,
22+
listScheduleBlocksForTests,
23+
listTasksForTests,
1924
recordIncomingTelegramMessageIfNew,
2025
recordOutgoingTelegramMessageIfNew,
2126
resetConversationStateStoreForTests,
2227
resetGoogleCalendarConnectionStoreForTests,
2328
resetInboxProcessingStoreForTests,
2429
resetIncomingTelegramIngressStoreForTests,
25-
seedInboxItemForProcessingTests,
2630
saveConversationState,
31+
seedInboxItemForProcessingTests,
2732
updateOutgoingTelegramMessage,
2833
} from "@atlas/db";
2934
import {
3035
getDefaultCalendarAdapter,
3136
resetCalendarAdapterForTests,
3237
} from "@atlas/integrations";
38+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3339

3440
import { handleTelegramWebhook } from "@/lib/server/telegram-webhook";
3541

@@ -149,7 +155,7 @@ function buildRoutedTurn(input: {
149155
confidence?: number;
150156
ambiguity?: "none" | "low" | "high";
151157
resolvedProposalId?: string;
152-
committedSlots?: Record<string, string | number>;
158+
committedSlots?: Record<string, string | number | TimeSpec>;
153159
}): RoutedTurn {
154160
return {
155161
interpretation: {
@@ -361,7 +367,7 @@ beforeEach(async () => {
361367
recoverConfirmedMutationWithResponsesMock.mockReset();
362368
extractSlotsWithResponsesMock.mockReset();
363369
extractSlotsWithResponsesMock.mockResolvedValue({
364-
time: { hour: 9, minute: 0 },
370+
time: { kind: "absolute", hour: 9, minute: 0 },
365371
day: { kind: "relative", value: "tomorrow" },
366372
duration: null,
367373
target: null,
@@ -973,7 +979,7 @@ describe("telegram webhook route", () => {
973979
it("does not keep clear scheduling requests in discuss-first mode", async () => {
974980
process.env.TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret";
975981
extractSlotsWithResponsesMock.mockResolvedValueOnce({
976-
time: { hour: 18, minute: 0 },
982+
time: { kind: "absolute", hour: 18, minute: 0 },
977983
day: { kind: "relative", value: "tomorrow" },
978984
duration: { minutes: 60 },
979985
target: null,
@@ -1104,7 +1110,7 @@ describe("telegram webhook route", () => {
11041110
turnType: "confirmation",
11051111
action: "recover_and_execute",
11061112
resolvedProposalId: "proposal-1",
1107-
committedSlots: { time: "15:00" },
1113+
committedSlots: { time: t(15, 0) },
11081114
}),
11091115
},
11101116
);

apps/web/src/lib/server/conversation-state.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { describe, expect, it } from "vitest";
2-
import type { ConversationStateSnapshot } from "@atlas/core";
1+
import type { ConversationStateSnapshot, TimeSpec } from "@atlas/core";
32
import type { ProcessedInboxResult } from "@atlas/db";
3+
import { describe, expect, it } from "vitest";
4+
5+
function t(hour: number, minute: number): TimeSpec {
6+
return { kind: "absolute", hour, minute };
7+
}
48

59
import {
610
deriveConversationReplyState,
@@ -290,7 +294,7 @@ describe("deriveConversationReplyState", () => {
290294
snapshot,
291295
policy: {
292296
action: "present_proposal",
293-
committedSlots: { day: "tomorrow", time: "17:00" },
297+
committedSlots: { day: "tomorrow", time: t(17, 0) },
294298
},
295299
interpretation: {
296300
turnType: "clarification_answer",
@@ -428,7 +432,7 @@ describe("deriveMutationState", () => {
428432
const snapshot = buildSnapshot();
429433
snapshot.discourseState = {
430434
...snapshot.discourseState!,
431-
resolved_slots: { day: "tomorrow", time: "17:00" },
435+
resolved_slots: { day: "tomorrow", time: t(17, 0) },
432436
pending_write_contract: {
433437
requiredSlots: ["day", "time"],
434438
intentKind: "plan",

apps/web/src/lib/server/decide-turn-policy.test.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import type {
2+
CommitPolicyOutput,
3+
TimeSpec,
4+
TurnClassifierOutput,
5+
} from "@atlas/core";
16
import { describe, expect, it } from "vitest";
27

3-
import type { CommitPolicyOutput, TurnClassifierOutput } from "@atlas/core";
8+
function t(hour: number, minute: number): TimeSpec {
9+
return { kind: "absolute", hour, minute };
10+
}
411

512
import {
6-
decideTurnPolicy,
713
type DecideTurnPolicyInput,
14+
decideTurnPolicy,
815
} from "./decide-turn-policy";
916

1017
const emptyCommit: CommitPolicyOutput = {
@@ -89,7 +96,7 @@ describe("decideTurnPolicy", () => {
8996
decideTurnPolicy(
9097
input(
9198
{ turnType: "planning_request", confidence: 0.68 },
92-
{ committedSlots: { day: "tomorrow", time: "18:00" } },
99+
{ committedSlots: { day: "tomorrow", time: t(18, 0) } },
93100
{
94101
rawText: "Schedule gym tomorrow at 6pm",
95102
normalizedText: "Schedule gym tomorrow at 6pm",
@@ -318,7 +325,7 @@ describe("decideTurnPolicy", () => {
318325
resolvedEntityIds: ["task-1"],
319326
resolvedProposalId: "proposal-1",
320327
},
321-
{ committedSlots: { time: "15:00", day: "tomorrow" } },
328+
{ committedSlots: { time: t(15, 0), day: "tomorrow" } },
322329
{
323330
rawText: "make it 3 instead",
324331
normalizedText: "make it 3 instead",
@@ -338,7 +345,7 @@ describe("decideTurnPolicy", () => {
338345
confirmationRequired: true,
339346
targetEntityId: "task-1",
340347
originatingTurnText: "move it to tomorrow 2pm",
341-
slotSnapshot: { time: "14:00", day: "tomorrow" },
348+
slotSnapshot: { time: t(14, 0), day: "tomorrow" },
342349
},
343350
},
344351
],
@@ -492,7 +499,7 @@ describe("decideTurnPolicy", () => {
492499
confidence: 0.9,
493500
resolvedEntityIds: ["task-1"],
494501
},
495-
{ committedSlots: { day: "tomorrow", time: "17:00" } },
502+
{ committedSlots: { day: "tomorrow", time: t(17, 0) } },
496503
{
497504
rawText: "ok but make it 5pm",
498505
normalizedText: "ok but make it 5pm",
@@ -511,7 +518,7 @@ describe("decideTurnPolicy", () => {
511518
replyText: "Would you like me to schedule it tomorrow at 3pm?",
512519
confirmationRequired: true,
513520
targetEntityId: "task-1",
514-
slotSnapshot: { day: "tomorrow", time: "15:00" },
521+
slotSnapshot: { day: "tomorrow", time: t(15, 0) },
515522
},
516523
},
517524
],
@@ -554,7 +561,7 @@ describe("decideTurnPolicy", () => {
554561
route: "conversation_then_mutation",
555562
replyText: "Would you like me to schedule it at 3pm?",
556563
confirmationRequired: true,
557-
slotSnapshot: { time: "15:00" },
564+
slotSnapshot: { time: t(15, 0) },
558565
},
559566
},
560567
],
@@ -568,7 +575,7 @@ describe("decideTurnPolicy", () => {
568575
});
569576

570577
it("returns committedSlots on every policy decision", () => {
571-
const slots = { day: "tomorrow", time: "17:00" };
578+
const slots = { day: "tomorrow", time: t(17, 0) };
572579
const result = decideTurnPolicy(
573580
input(
574581
{ turnType: "planning_request", confidence: 0.95 },

apps/web/src/lib/server/slot-extractor.test.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import type {
2+
RawSlotExtraction,
3+
SlotExtractorInput,
4+
TimeSpec,
5+
} from "@atlas/core";
16
import { describe, expect, it, vi } from "vitest";
27

3-
import type { RawSlotExtraction, SlotExtractorInput } from "@atlas/core";
8+
function t(hour: number, minute: number): TimeSpec {
9+
return { kind: "absolute", hour, minute };
10+
}
411

512
import { extractSlots } from "./slot-extractor";
613

@@ -38,14 +45,14 @@ const baseInput: SlotExtractorInput = {
3845
describe("extractSlots", () => {
3946
it("normalizes a successful time extraction", async () => {
4047
const client = mockClient({
41-
time: { hour: 17, minute: 0 },
48+
time: { kind: "absolute", hour: 17, minute: 0 },
4249
confidence: { time: 0.9 },
4350
unresolvable: [],
4451
});
4552

4653
const result = await extractSlots(baseInput, client);
4754

48-
expect(result.extractedValues).toEqual({ time: "17:00" });
55+
expect(result.extractedValues).toEqual({ time: t(17, 0) });
4956
expect(result.confidence).toEqual({ time: 0.9 });
5057
expect(result.unresolvable).toEqual([]);
5158
});
@@ -64,7 +71,7 @@ describe("extractSlots", () => {
6471

6572
it("handles multiple slots", async () => {
6673
const client = mockClient({
67-
time: { hour: 14, minute: 30 },
74+
time: { kind: "absolute", hour: 14, minute: 30 },
6875
day: { kind: "relative", value: "Tomorrow" },
6976
confidence: { time: 0.95, day: 0.92 },
7077
unresolvable: [],
@@ -78,7 +85,10 @@ describe("extractSlots", () => {
7885

7986
const result = await extractSlots(input, client);
8087

81-
expect(result.extractedValues).toEqual({ time: "14:30", day: "tomorrow" });
88+
expect(result.extractedValues).toEqual({
89+
time: t(14, 30),
90+
day: "tomorrow",
91+
});
8292
});
8393

8494
it("degrades gracefully on LLM failure", async () => {
@@ -111,7 +121,7 @@ describe("extractSlots", () => {
111121

112122
it("passes conversation context to the client", async () => {
113123
const client = mockClient({
114-
time: { hour: 9, minute: 30 },
124+
time: { kind: "absolute", hour: 9, minute: 30 },
115125
confidence: { time: 0.88 },
116126
unresolvable: [],
117127
});
@@ -125,7 +135,7 @@ describe("extractSlots", () => {
125135

126136
const result = await extractSlots(input, client);
127137

128-
expect(result.extractedValues).toEqual({ time: "09:30" });
138+
expect(result.extractedValues).toEqual({ time: t(9, 30) });
129139
expect(client.responses.parse).toHaveBeenCalledOnce();
130140
});
131141
});

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import type { TimeSpec, TurnClassifierOutput } from "@atlas/core";
12
import { describe, expect, it, vi } from "vitest";
23

3-
import type { TurnClassifierOutput } from "@atlas/core";
4+
function t(hour: number, minute: number): TimeSpec {
5+
return { kind: "absolute", hour, minute };
6+
}
47

5-
import { routeMessageTurn, containsModificationPayload } from "./turn-router";
8+
import { containsModificationPayload, routeMessageTurn } from "./turn-router";
69

710
vi.mock("./llm-classifier", () => ({
811
classifyTurn: vi.fn(),
@@ -39,7 +42,7 @@ describe("turn router", () => {
3942
confidence: 0.95,
4043
});
4144
mockExtractSlots.mockResolvedValueOnce({
42-
extractedValues: { day: "tomorrow", time: "18:00" },
45+
extractedValues: { day: "tomorrow", time: t(18, 0) },
4346
confidence: { day: 0.95, time: 0.95 },
4447
unresolvable: [],
4548
});
@@ -186,7 +189,7 @@ describe("turn router", () => {
186189
confidence: 0.95,
187190
});
188191
mockExtractSlots.mockResolvedValueOnce({
189-
extractedValues: { day: "tomorrow", time: "18:00" },
192+
extractedValues: { day: "tomorrow", time: t(18, 0) },
190193
confidence: { day: 0.95, time: 0.95 },
191194
unresolvable: [],
192195
});
@@ -214,7 +217,7 @@ describe("turn router", () => {
214217
confidence: 0.9,
215218
});
216219
mockExtractSlots.mockResolvedValueOnce({
217-
extractedValues: { time: "17:00" },
220+
extractedValues: { time: t(17, 0) },
218221
confidence: { time: 0.92 },
219222
unresolvable: [],
220223
});
@@ -243,7 +246,7 @@ describe("turn router", () => {
243246
confidence: 0.9,
244247
});
245248
mockExtractSlots.mockResolvedValueOnce({
246-
extractedValues: { time: "17:00" },
249+
extractedValues: { time: t(17, 0) },
247250
confidence: { time: 0.92 },
248251
unresolvable: [],
249252
});
@@ -267,7 +270,7 @@ describe("turn router", () => {
267270
resolvedProposalId: "proposal-1",
268271
});
269272
mockExtractSlots.mockResolvedValueOnce({
270-
extractedValues: { time: "17:00" },
273+
extractedValues: { time: t(17, 0) },
271274
confidence: { time: 0.92 },
272275
unresolvable: [],
273276
});

0 commit comments

Comments
 (0)