Skip to content

Commit a19c9ca

Browse files
authored
Merge pull request #72 from MaxLinCode/claude/phase2-write-interpretation
[codex] Implement phase 2 write interpretation
2 parents c6cb566 + 5469dcf commit a19c9ca

16 files changed

Lines changed: 1094 additions & 547 deletions

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

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const {
4444
summarizeConversationMemoryWithResponsesMock,
4545
respondToConversationTurnWithResponsesMock,
4646
recoverConfirmedMutationWithResponsesMock,
47-
extractSlotsWithResponsesMock,
47+
interpretWriteTurnWithResponsesMock,
4848
} = vi.hoisted(() => ({
4949
editTelegramMessageMock: vi.fn(),
5050
sendTelegramMessageMock: vi.fn(),
@@ -54,7 +54,7 @@ const {
5454
summarizeConversationMemoryWithResponsesMock: vi.fn(),
5555
respondToConversationTurnWithResponsesMock: vi.fn(),
5656
recoverConfirmedMutationWithResponsesMock: vi.fn(),
57-
extractSlotsWithResponsesMock: vi.fn(),
57+
interpretWriteTurnWithResponsesMock: vi.fn(),
5858
}));
5959

6060
vi.mock("@atlas/integrations", async () => {
@@ -102,7 +102,7 @@ vi.mock("@atlas/integrations", async () => {
102102
recoverConfirmedMutationWithResponsesMock,
103103
classifyTurnWithResponses: classifyTurnWithResponsesMock,
104104
routeTurnWithResponses: routeTurnWithResponsesMock,
105-
extractSlotsWithResponses: extractSlotsWithResponsesMock,
105+
interpretWriteTurnWithResponses: interpretWriteTurnWithResponsesMock,
106106
sendTelegramChatAction: sendTelegramChatActionMock,
107107
sendTelegramMessage: sendTelegramMessageMock,
108108
summarizeConversationMemoryWithResponses:
@@ -367,14 +367,25 @@ beforeEach(async () => {
367367
summarizeConversationMemoryWithResponsesMock.mockReset();
368368
respondToConversationTurnWithResponsesMock.mockReset();
369369
recoverConfirmedMutationWithResponsesMock.mockReset();
370-
extractSlotsWithResponsesMock.mockReset();
371-
extractSlotsWithResponsesMock.mockResolvedValue({
372-
time: { kind: "absolute", hour: 9, minute: 0 },
373-
day: { kind: "relative", value: "tomorrow" },
374-
duration: null,
375-
target: null,
376-
confidence: { day: 0.95, time: 0.95 },
377-
unresolvable: [],
370+
interpretWriteTurnWithResponsesMock.mockReset();
371+
interpretWriteTurnWithResponsesMock.mockResolvedValue({
372+
operationKind: "plan",
373+
actionDomain: "task",
374+
targetRef: null,
375+
taskName: null,
376+
fields: {
377+
scheduleFields: {
378+
time: { kind: "absolute", hour: 9, minute: 0 },
379+
day: { kind: "relative", value: "tomorrow" },
380+
duration: null,
381+
},
382+
taskFields: null,
383+
},
384+
confidence: {
385+
"scheduleFields.day": 0.95,
386+
"scheduleFields.time": 0.95,
387+
},
388+
unresolvedFields: [],
378389
});
379390
routeTurnWithResponsesMock.mockResolvedValue({
380391
route: "mutation",
@@ -842,13 +853,17 @@ describe("telegram webhook route", () => {
842853

843854
it("normalizes a Telegram text message and routes to clarification when slots are missing", async () => {
844855
process.env.TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret";
845-
extractSlotsWithResponsesMock.mockResolvedValueOnce({
846-
time: null,
847-
day: null,
848-
duration: null,
849-
target: null,
856+
interpretWriteTurnWithResponsesMock.mockResolvedValueOnce({
857+
operationKind: "plan",
858+
actionDomain: "task",
859+
targetRef: null,
860+
taskName: null,
861+
fields: {
862+
scheduleFields: null,
863+
taskFields: null,
864+
},
850865
confidence: {},
851-
unresolvable: [],
866+
unresolvedFields: [],
852867
});
853868

854869
const response = await handleTelegramWebhook(
@@ -1058,13 +1073,25 @@ describe("telegram webhook route", () => {
10581073

10591074
it("does not keep clear scheduling requests in discuss-first mode", async () => {
10601075
process.env.TELEGRAM_WEBHOOK_SECRET = "test-webhook-secret";
1061-
extractSlotsWithResponsesMock.mockResolvedValueOnce({
1062-
time: { kind: "absolute", hour: 18, minute: 0 },
1063-
day: { kind: "relative", value: "tomorrow" },
1064-
duration: { minutes: 60 },
1065-
target: null,
1066-
confidence: { day: 0.95, time: 0.95, duration: 0.9 },
1067-
unresolvable: [],
1076+
interpretWriteTurnWithResponsesMock.mockResolvedValueOnce({
1077+
operationKind: "plan",
1078+
actionDomain: "task",
1079+
targetRef: null,
1080+
taskName: null,
1081+
fields: {
1082+
scheduleFields: {
1083+
time: { kind: "absolute", hour: 18, minute: 0 },
1084+
day: { kind: "relative", value: "tomorrow" },
1085+
duration: { minutes: 60 },
1086+
},
1087+
taskFields: null,
1088+
},
1089+
confidence: {
1090+
"scheduleFields.day": 0.95,
1091+
"scheduleFields.time": 0.95,
1092+
"scheduleFields.duration": 0.9,
1093+
},
1094+
unresolvedFields: [],
10681095
});
10691096

10701097
const response = await handleTelegramWebhook(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const emptyCommit: CommitPolicyOutput = {
2020
needsClarification: [],
2121
missingFields: [],
2222
workflowChanged: false,
23+
committedFieldPaths: [],
2324
};
2425

2526
function input(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
normalizeRawWriteInterpretation,
3+
rawWriteInterpretationSchema,
4+
type WriteInterpretation,
5+
type WriteInterpretationInput,
6+
} from "@atlas/core";
7+
import {
8+
interpretWriteTurnWithResponses,
9+
type OpenAIResponsesClient,
10+
} from "@atlas/integrations";
11+
12+
export async function interpretWriteTurn(
13+
input: WriteInterpretationInput,
14+
client?: OpenAIResponsesClient,
15+
): Promise<WriteInterpretation> {
16+
try {
17+
const raw = await interpretWriteTurnWithResponses(input, client);
18+
const parsed = rawWriteInterpretationSchema.safeParse(raw);
19+
20+
if (!parsed.success) {
21+
return fallbackInterpretation(input);
22+
}
23+
24+
return normalizeRawWriteInterpretation(parsed.data, input.currentTurnText);
25+
} catch {
26+
return fallbackInterpretation(input);
27+
}
28+
}
29+
30+
function fallbackInterpretation(
31+
input: WriteInterpretationInput,
32+
): WriteInterpretation {
33+
return {
34+
operationKind:
35+
input.priorPendingWriteOperation?.operationKind ?? inferFallbackOperation(input.turnType),
36+
actionDomain: "task",
37+
targetRef: input.priorPendingWriteOperation?.targetRef ?? null,
38+
taskName: null,
39+
fields: {},
40+
sourceText: input.currentTurnText,
41+
confidence: {},
42+
unresolvedFields: [],
43+
};
44+
}
45+
46+
function inferFallbackOperation(turnType: WriteInterpretationInput["turnType"]) {
47+
switch (turnType) {
48+
case "edit_request":
49+
return "edit" as const;
50+
case "clarification_answer":
51+
case "planning_request":
52+
default:
53+
return "plan" as const;
54+
}
55+
}

0 commit comments

Comments
 (0)