|
1 | 1 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
2 | | -import type { InboxPlanningOutput } from "@atlas/core"; |
| 2 | +import type { InboxPlanningOutput, RoutedTurn, TurnPolicyAction, TurnInterpretationType } from "@atlas/core"; |
3 | 3 | import { |
4 | 4 | getDefaultGoogleCalendarConnectionStore, |
5 | 5 | getDefaultFollowUpRuntimeStore, |
@@ -108,6 +108,37 @@ function buildRequest(body: unknown, secret = "test-webhook-secret") { |
108 | 108 | }); |
109 | 109 | } |
110 | 110 |
|
| 111 | +function buildRoutedTurn(input: { |
| 112 | + turnType: TurnInterpretationType; |
| 113 | + action: TurnPolicyAction; |
| 114 | + confidence?: number; |
| 115 | + ambiguity?: "none" | "low" | "high"; |
| 116 | + resolvedProposalId?: string; |
| 117 | +}) : RoutedTurn { |
| 118 | + return { |
| 119 | + interpretation: { |
| 120 | + turnType: input.turnType, |
| 121 | + confidence: input.confidence ?? 0.9, |
| 122 | + resolvedEntityIds: [], |
| 123 | + ...(input.resolvedProposalId ? { resolvedProposalId: input.resolvedProposalId } : {}), |
| 124 | + ambiguity: input.ambiguity ?? "none" |
| 125 | + }, |
| 126 | + policy: { |
| 127 | + action: input.action, |
| 128 | + reason: "test", |
| 129 | + requiresWrite: input.action === "execute_mutation" || input.action === "recover_and_execute", |
| 130 | + requiresConfirmation: input.action === "present_proposal", |
| 131 | + useMutationPipeline: input.action === "execute_mutation" || input.action === "recover_and_execute", |
| 132 | + ...(input.resolvedProposalId ? { targetProposalId: input.resolvedProposalId } : {}), |
| 133 | + ...(input.action === "recover_and_execute" |
| 134 | + ? { mutationInputSource: "recovered_proposal" as const } |
| 135 | + : input.action === "execute_mutation" |
| 136 | + ? { mutationInputSource: "direct_user_turn" as const } |
| 137 | + : {}) |
| 138 | + } |
| 139 | + }; |
| 140 | +} |
| 141 | + |
111 | 142 | async function seedOutstandingFollowUpBundle(titles: string[]) { |
112 | 143 | const store = getDefaultInboxProcessingStore(); |
113 | 144 | const followUpStore = getDefaultFollowUpRuntimeStore(); |
@@ -749,10 +780,9 @@ describe("telegram webhook route", () => { |
749 | 780 | store: getDefaultInboxProcessingStore(), |
750 | 781 | calendar: getDefaultCalendarAdapter(), |
751 | 782 | primeProcessingStore: seedInboxItemForProcessingTests, |
752 | | - turnRouter: async () => ({ |
753 | | - route: "mutation", |
754 | | - reason: "Direct scheduling request.", |
755 | | - writesAllowed: true |
| 783 | + turnRouter: async () => buildRoutedTurn({ |
| 784 | + turnType: "planning_request", |
| 785 | + action: "execute_mutation" |
756 | 786 | }) |
757 | 787 | } |
758 | 788 | ); |
@@ -863,10 +893,10 @@ describe("telegram webhook route", () => { |
863 | 893 | summary: "The assistant proposed a concrete 3pm schedule and the user is now confirming it." |
864 | 894 | }), |
865 | 895 | confirmedMutationRecoverer, |
866 | | - turnRouter: async () => ({ |
867 | | - route: "confirmed_mutation", |
868 | | - reason: "The user is confirming a recent concrete proposal.", |
869 | | - writesAllowed: true |
| 896 | + turnRouter: async () => buildRoutedTurn({ |
| 897 | + turnType: "confirmation", |
| 898 | + action: "recover_and_execute", |
| 899 | + resolvedProposalId: "proposal-1" |
870 | 900 | }) |
871 | 901 | } |
872 | 902 | ); |
@@ -1019,10 +1049,10 @@ describe("telegram webhook route", () => { |
1019 | 1049 | reason: "The user refined the recent concrete proposal.", |
1020 | 1050 | userReplyMessage: "Done - I've moved it to 4pm." |
1021 | 1051 | }), |
1022 | | - turnRouter: async () => ({ |
1023 | | - route: "confirmed_mutation", |
1024 | | - reason: "The user is refining a recent concrete proposal.", |
1025 | | - writesAllowed: true |
| 1052 | + turnRouter: async () => buildRoutedTurn({ |
| 1053 | + turnType: "confirmation", |
| 1054 | + action: "recover_and_execute", |
| 1055 | + resolvedProposalId: "proposal-1" |
1026 | 1056 | }) |
1027 | 1057 | } |
1028 | 1058 | ); |
@@ -1155,10 +1185,10 @@ describe("telegram webhook route", () => { |
1155 | 1185 | reason: "The latest turn clearly reports completion of the recent journaling task.", |
1156 | 1186 | userReplyMessage: "Got it." |
1157 | 1187 | }), |
1158 | | - turnRouter: async () => ({ |
1159 | | - route: "confirmed_mutation", |
1160 | | - reason: "The latest turn clearly completes one recent task.", |
1161 | | - writesAllowed: true |
| 1188 | + turnRouter: async () => buildRoutedTurn({ |
| 1189 | + turnType: "confirmation", |
| 1190 | + action: "recover_and_execute", |
| 1191 | + resolvedProposalId: "proposal-1" |
1162 | 1192 | }) |
1163 | 1193 | } |
1164 | 1194 | ); |
@@ -1223,10 +1253,10 @@ describe("telegram webhook route", () => { |
1223 | 1253 | reason: "I have two recent proposals in view. Which one do you want me to apply?", |
1224 | 1254 | userReplyMessage: "I have two recent proposals in view. Which one do you want me to apply?" |
1225 | 1255 | }), |
1226 | | - turnRouter: async () => ({ |
1227 | | - route: "confirmed_mutation", |
1228 | | - reason: "This looks like a confirmation but the target is ambiguous.", |
1229 | | - writesAllowed: true |
| 1256 | + turnRouter: async () => buildRoutedTurn({ |
| 1257 | + turnType: "confirmation", |
| 1258 | + action: "recover_and_execute", |
| 1259 | + resolvedProposalId: "proposal-1" |
1230 | 1260 | }) |
1231 | 1261 | } |
1232 | 1262 | ); |
@@ -1405,10 +1435,9 @@ describe("telegram webhook route", () => { |
1405 | 1435 | primeProcessingStore: seedInboxItemForProcessingTests, |
1406 | 1436 | conversationMemorySummarizer, |
1407 | 1437 | conversationResponder, |
1408 | | - turnRouter: async () => ({ |
1409 | | - route: "conversation", |
1410 | | - reason: "Planning dialogue request.", |
1411 | | - writesAllowed: false |
| 1438 | + turnRouter: async () => buildRoutedTurn({ |
| 1439 | + turnType: "informational", |
| 1440 | + action: "reply_only" |
1412 | 1441 | }) |
1413 | 1442 | } |
1414 | 1443 | ); |
@@ -1557,10 +1586,11 @@ describe("telegram webhook route", () => { |
1557 | 1586 | summary: "The user is discussing a possible move to tomorrow morning." |
1558 | 1587 | }), |
1559 | 1588 | conversationResponder, |
1560 | | - turnRouter: async () => ({ |
1561 | | - route: "conversation_then_mutation", |
1562 | | - reason: "Mixed turn should discuss first.", |
1563 | | - writesAllowed: false |
| 1589 | + turnRouter: async () => buildRoutedTurn({ |
| 1590 | + turnType: "planning_request", |
| 1591 | + action: "present_proposal", |
| 1592 | + confidence: 0.68, |
| 1593 | + ambiguity: "low" |
1564 | 1594 | }) |
1565 | 1595 | } |
1566 | 1596 | ); |
@@ -1647,10 +1677,9 @@ describe("telegram webhook route", () => { |
1647 | 1677 | throw new Error("summary unavailable"); |
1648 | 1678 | }, |
1649 | 1679 | conversationResponder, |
1650 | | - turnRouter: async () => ({ |
1651 | | - route: "conversation", |
1652 | | - reason: "Discussion turn.", |
1653 | | - writesAllowed: false |
| 1680 | + turnRouter: async () => buildRoutedTurn({ |
| 1681 | + turnType: "informational", |
| 1682 | + action: "reply_only" |
1654 | 1683 | }) |
1655 | 1684 | } |
1656 | 1685 | ); |
@@ -1713,10 +1742,9 @@ describe("telegram webhook route", () => { |
1713 | 1742 | conversationResponder: async () => ({ |
1714 | 1743 | reply: "From our recent exchange, it sounds like you mean the dentist reminder, but I am not treating that as confirmed state." |
1715 | 1744 | }), |
1716 | | - turnRouter: async () => ({ |
1717 | | - route: "conversation", |
1718 | | - reason: "Write-adjacent question still needs cautious conversational handling.", |
1719 | | - writesAllowed: false |
| 1745 | + turnRouter: async () => buildRoutedTurn({ |
| 1746 | + turnType: "informational", |
| 1747 | + action: "reply_only" |
1720 | 1748 | }) |
1721 | 1749 | } |
1722 | 1750 | ); |
|
0 commit comments