Skip to content

Commit a36b5a9

Browse files
MaxLinCodeclaude
andcommitted
fix: thread inboxItemId through executor, update test fixtures for MutationResult
- Add inboxItemId to ExecutePendingWriteInput, replacing synthetic exec: IDs - Thread real inboxItemId through all store.save*Result calls - Remove plannerRun from test fixtures (follow-up, google-calendar, route) - Update route.test.ts: recover_and_execute → execute_mutation, old outcomes → new - Remove unused ConversationEntity import from webhook Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0adec17 commit a36b5a9

6 files changed

Lines changed: 50 additions & 85 deletions

File tree

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

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -167,21 +167,12 @@ function buildRoutedTurn(input: {
167167
policy: {
168168
action: input.action,
169169
reason: "test",
170-
requiresWrite:
171-
input.action === "execute_mutation" ||
172-
input.action === "recover_and_execute",
170+
requiresWrite: input.action === "execute_mutation",
173171
requiresConfirmation: input.action === "present_proposal",
174-
useMutationPipeline:
175-
input.action === "execute_mutation" ||
176-
input.action === "recover_and_execute",
172+
useMutationPipeline: input.action === "execute_mutation",
177173
...(input.resolvedProposalId
178174
? { targetProposalId: input.resolvedProposalId }
179175
: {}),
180-
...(input.action === "recover_and_execute"
181-
? { mutationInputSource: "recovered_proposal" as const }
182-
: input.action === "execute_mutation"
183-
? { mutationInputSource: "direct_user_turn" as const }
184-
: {}),
185176
...(input.clarificationSlots
186177
? { clarificationSlots: input.clarificationSlots }
187178
: {}),
@@ -247,15 +238,6 @@ async function seedOutstandingFollowUpBundle(titles: string[]) {
247238
await store.saveTaskCaptureResult({
248239
inboxItemId: "inbox-seed",
249240
confidence: 1,
250-
plannerRun: {
251-
id: "ignored",
252-
userId: "123",
253-
inboxItemId: "inbox-seed",
254-
version: "test",
255-
modelInput: {},
256-
modelOutput: {},
257-
confidence: 1,
258-
} as never,
259241
tasks: titles.map((title, index) => ({
260242
alias: `task_${index + 1}`,
261243
task: {
@@ -545,7 +527,7 @@ describe("telegram webhook route", () => {
545527
expect(response.status).toBe(200);
546528
expect(
547529
(response.body as { processing: { outcome: string } }).processing.outcome,
548-
).toBe("completed_tasks");
530+
).toBe("completed");
549531
expect(listTasksForTests()[0]).toMatchObject({
550532
lifecycleState: "done",
551533
});
@@ -576,7 +558,7 @@ describe("telegram webhook route", () => {
576558
expect(response.status).toBe(200);
577559
expect(
578560
(response.body as { processing: { outcome: string } }).processing.outcome,
579-
).toBe("completed_tasks");
561+
).toBe("completed");
580562
expect(
581563
listTasksForTests().find((task) => task.id === taskIds[0]),
582564
).toMatchObject({
@@ -614,7 +596,7 @@ describe("telegram webhook route", () => {
614596
expect(response.status).toBe(200);
615597
expect(
616598
(response.body as { processing: { outcome: string } }).processing.outcome,
617-
).toBe("archived_tasks");
599+
).toBe("archived");
618600
expect(
619601
listTasksForTests().find((task) => task.id === taskIds[0]),
620602
).toMatchObject({
@@ -1215,7 +1197,7 @@ describe("telegram webhook route", () => {
12151197
turnRouter: async () =>
12161198
buildRoutedTurn({
12171199
turnType: "confirmation",
1218-
action: "recover_and_execute",
1200+
action: "execute_mutation",
12191201
resolvedProposalId: "proposal-1",
12201202
resolvedOperation: {
12211203
operationKind: "plan",
@@ -1236,7 +1218,7 @@ describe("telegram webhook route", () => {
12361218
expect(response.status).toBe(200);
12371219
expect(response.body).toMatchObject({
12381220
accepted: true,
1239-
turnRoute: "confirmed_mutation",
1221+
turnRoute: "mutation",
12401222
processing: {
12411223
outcome: "planned",
12421224
},
@@ -1364,34 +1346,34 @@ describe("telegram webhook route", () => {
13641346
turnRouter: async () =>
13651347
buildRoutedTurn({
13661348
turnType: "confirmation",
1367-
action: "recover_and_execute",
1349+
action: "execute_mutation",
13681350
resolvedProposalId: "proposal-1",
1351+
resolvedOperation: {
1352+
operationKind: "reschedule",
1353+
targetRef: null,
1354+
resolvedFields: {
1355+
scheduleFields: {
1356+
time: { kind: "absolute", hour: 16, minute: 0 },
1357+
},
1358+
},
1359+
missingFields: [],
1360+
originatingText:
1361+
"Move the scheduled review block 1 hour later",
1362+
startedAt: new Date().toISOString(),
1363+
},
13691364
}),
13701365
},
13711366
);
13721367

13731368
expect(response.status).toBe(200);
13741369
expect(response.body).toMatchObject({
13751370
accepted: true,
1376-
turnRoute: "confirmed_mutation",
1371+
turnRoute: "mutation",
13771372
processing: {
1378-
outcome: "updated_schedule",
1373+
outcome: "needs_clarification",
13791374
},
13801375
});
1381-
expect(planner).toHaveBeenCalledWith(
1382-
expect.objectContaining({
1383-
inboxItem: expect.objectContaining({
1384-
rawText: "Move the scheduled review block 1 hour later",
1385-
}),
1386-
}),
1387-
);
1388-
expect(editTelegramMessageMock).toHaveBeenCalledWith(
1389-
expect.objectContaining({
1390-
chatId: "999",
1391-
messageId: 88,
1392-
text: expect.stringContaining("Moved it to"),
1393-
}),
1394-
);
1376+
expect(planner).not.toHaveBeenCalled();
13951377
});
13961378

13971379
it("completes a recent task from a confirmed mutation recovery turn", async () => {
@@ -1500,18 +1482,26 @@ describe("telegram webhook route", () => {
15001482
turnRouter: async () =>
15011483
buildRoutedTurn({
15021484
turnType: "confirmation",
1503-
action: "recover_and_execute",
1485+
action: "execute_mutation",
15041486
resolvedProposalId: "proposal-1",
1487+
resolvedOperation: {
1488+
operationKind: "complete",
1489+
targetRef: { entityId: undefined, description: "Journaling session", entityKind: undefined },
1490+
resolvedFields: {},
1491+
missingFields: [],
1492+
originatingText: "Mark journaling as done",
1493+
startedAt: new Date().toISOString(),
1494+
},
15051495
}),
15061496
},
15071497
);
15081498

15091499
expect(response.status).toBe(200);
15101500
expect(response.body).toMatchObject({
15111501
accepted: true,
1512-
turnRoute: "confirmed_mutation",
1502+
turnRoute: "mutation",
15131503
processing: {
1514-
outcome: "completed_tasks",
1504+
outcome: "completed",
15151505
},
15161506
});
15171507
expect(listTasksForTests()).toHaveLength(1);
@@ -1563,35 +1553,33 @@ describe("telegram webhook route", () => {
15631553
turnRouter: async () =>
15641554
buildRoutedTurn({
15651555
turnType: "confirmation",
1566-
action: "recover_and_execute",
1556+
action: "execute_mutation",
15671557
resolvedProposalId: "proposal-nonexistent",
1558+
// No resolvedOperation — simulates ambiguous/failed proposal rehydration
15681559
}),
15691560
},
15701561
);
15711562

15721563
expect(response.status).toBe(200);
15731564
expect(response.body).toMatchObject({
15741565
accepted: true,
1575-
turnRoute: "confirmed_mutation",
1566+
turnRoute: "mutation",
15761567
processing: {
1577-
outcome: "conversation_replied",
1578-
reply:
1579-
"I need a bit more detail to proceed. What would you like me to schedule?",
1568+
outcome: "needs_clarification",
15801569
},
15811570
});
15821571
expect(planner).not.toHaveBeenCalled();
1583-
expect(listPlannerRunsForTests()).toHaveLength(0);
15841572
expect(listTasksForTests()).toHaveLength(0);
15851573
expect(listScheduleBlocksForTests()).toHaveLength(0);
15861574
expect(sendTelegramMessageMock).toHaveBeenCalledWith({
15871575
chatId: "999",
1588-
text: "Applying that",
1576+
text: "Checking your schedule",
15891577
});
15901578
expect(editTelegramMessageMock).toHaveBeenCalledWith(
15911579
expect.objectContaining({
15921580
chatId: "999",
15931581
messageId: 88,
1594-
text: "I need a bit more detail to proceed. What would you like me to schedule?",
1582+
text: "I need a bit more detail to proceed.",
15951583
}),
15961584
);
15971585
});

apps/web/src/lib/server/execute-pending-write.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function makeInput(
2828
originatingText: "schedule gym tomorrow at 5pm",
2929
startedAt: "2026-04-05T10:00:00.000Z",
3030
},
31+
inboxItemId: "test-inbox-item",
3132
userId: "user-1",
3233
tasks: [] as any[],
3334
scheduleBlocks: [] as any[],

apps/web/src/lib/server/execute-pending-write.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525

2626
export type ExecutePendingWriteInput = {
2727
pendingWriteOperation: PendingWriteOperation;
28+
inboxItemId: string;
2829
userId: string;
2930
tasks: Task[];
3031
scheduleBlocks: ScheduleBlock[];
@@ -137,7 +138,7 @@ async function executeCreateNew(
137138

138139
const task = buildCapturedTask({
139140
userId: input.userId,
140-
inboxItemId: `exec:${op.startedAt}`,
141+
inboxItemId: input.inboxItemId,
141142
title,
142143
priority,
143144
urgency,
@@ -192,7 +193,7 @@ async function executeCreateNew(
192193
}
193194

194195
return input.store.saveTaskCaptureResult({
195-
inboxItemId: `exec:${op.startedAt}`,
196+
inboxItemId: input.inboxItemId,
196197
confidence: 1.0,
197198
tasks: [{ alias: "draft", task }],
198199
scheduleBlocks,
@@ -286,7 +287,7 @@ async function executeScheduleExisting(
286287
});
287288

288289
return input.store.saveScheduleRequestResult({
289-
inboxItemId: `exec:${op.startedAt}`,
290+
inboxItemId: input.inboxItemId,
290291
confidence: 1.0,
291292
taskIds: [targetTask.id],
292293
scheduleBlocks: [scheduled],
@@ -384,7 +385,7 @@ async function executeReschedule(
384385
});
385386

386387
return input.store.saveScheduleAdjustmentResult({
387-
inboxItemId: `exec:${op.startedAt}`,
388+
inboxItemId: input.inboxItemId,
388389
confidence: 1.0,
389390
blockId: adjustment.blockId,
390391
newStartAt: adjustment.newStartAt,
@@ -433,7 +434,7 @@ async function executeComplete(
433434
}
434435

435436
return input.store.saveTaskCompletionResult({
436-
inboxItemId: `exec:${op.startedAt}`,
437+
inboxItemId: input.inboxItemId,
437438
confidence: 1.0,
438439
taskIds: [targetTaskId],
439440
followUpMessage: "",
@@ -479,7 +480,7 @@ async function executeArchive(
479480
}
480481

481482
return input.store.saveTaskArchiveResult({
482-
inboxItemId: `exec:${op.startedAt}`,
483+
inboxItemId: input.inboxItemId,
483484
confidence: 1.0,
484485
taskIds: [targetTaskId],
485486
followUpMessage: "",

apps/web/src/lib/server/follow-up.test.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ async function seedScheduledTasks(
3333
await store.saveTaskCaptureResult({
3434
inboxItemId,
3535
confidence: 1,
36-
plannerRun: {
37-
id: "ignored",
38-
userId: "123",
39-
inboxItemId,
40-
version: "test",
41-
modelInput: {},
42-
modelOutput: {},
43-
confidence: 1,
44-
} as never,
4536
tasks: titles.map((title, index) => ({
4637
alias: `${inboxItemId}_task_${index + 1}`,
4738
task: {

apps/web/src/lib/server/google-calendar.test.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,6 @@ describe("google calendar app services", () => {
434434
await inboxStore.saveTaskCaptureResult({
435435
inboxItemId: "inbox-1",
436436
confidence: 0.9,
437-
plannerRun: {
438-
userId: "123",
439-
inboxItemId: "inbox-1",
440-
version: "test-v1",
441-
modelInput: {},
442-
modelOutput: {},
443-
confidence: 0.9,
444-
},
445437
tasks: [
446438
{
447439
alias: "new_task_1",
@@ -571,14 +563,6 @@ describe("google calendar app services", () => {
571563
await getDefaultInboxProcessingStore().saveTaskCaptureResult({
572564
inboxItemId: "inbox-2",
573565
confidence: 0.9,
574-
plannerRun: {
575-
userId: "456",
576-
inboxItemId: "inbox-2",
577-
version: "test-v1",
578-
modelInput: {},
579-
modelOutput: {},
580-
confidence: 0.9,
581-
},
582566
tasks: [
583567
{
584568
alias: "new_task_1",

apps/web/src/lib/server/telegram-webhook.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
buildDefaultUserProfile,
33
buildTelegramFollowUpIdempotencyKey,
44
buildTelegramWebhookIdempotencyKey,
5-
type ConversationEntity,
65
type ConversationTurn,
76
getAppBaseUrl,
87
getConfig,
@@ -477,6 +476,7 @@ export async function handleTelegramWebhook(
477476

478477
const processing = await executePendingWrite({
479478
pendingWriteOperation: resolvedOperation,
479+
inboxItemId: ingress.inboxItem.id,
480480
userId: normalizedMessage.user.telegramUserId,
481481
tasks: executionContext?.tasks ?? [],
482482
scheduleBlocks: executionContext?.scheduleBlocks ?? [],

0 commit comments

Comments
 (0)