| author | |
| committer | |
| log | 6496aedc92513fe138a511c7afb9f51152d08e88 |
| tree | 37f8e5fad9adcfab4a1ad169063c368c5cde5301 |
| parent | e972fad30075aeff9831f38052ef05badabd236f |
Co-authored-by: codex <codex@users.noreply.github.com>23 files changed, 48 insertions(+), 506 deletions(-)
apps/server/integration/OrchestrationEngineHarness.integration.ts+1-1| ... | @@ -188,7 +188,7 @@ export interface OrchestrationIntegrationHarness { | ... | @@ -188,7 +188,7 @@ export interface OrchestrationIntegrationHarness { |
| 188 | } | 188 | } |
| 189 | 189 | ||
| 190 | interface MakeOrchestrationIntegrationHarnessOptions { | 190 | interface MakeOrchestrationIntegrationHarnessOptions { |
| 191 | readonly provider?: "codex" | "claudeCode"; | 191 | readonly provider?: "codex" | "cursor"; |
| 192 | readonly realCodex?: boolean; | 192 | readonly realCodex?: boolean; |
| 193 | } | 193 | } |
| 194 | 194 |
apps/server/integration/TestProviderAdapter.integration.ts+5-5| ... | @@ -35,7 +35,7 @@ export interface TestTurnResponse { | ... | @@ -35,7 +35,7 @@ export interface TestTurnResponse { |
| 35 | export type FixtureProviderRuntimeEvent = { | 35 | export type FixtureProviderRuntimeEvent = { |
| 36 | readonly type: string; | 36 | readonly type: string; |
| 37 | readonly eventId: EventId; | 37 | readonly eventId: EventId; |
| 38 | readonly provider: "codex" | "claudeCode" | "cursor"; | 38 | readonly provider: "codex" | "cursor"; |
| 39 | readonly createdAt: string; | 39 | readonly createdAt: string; |
| 40 | readonly threadId: string; | 40 | readonly threadId: string; |
| 41 | readonly turnId?: string | undefined; | 41 | readonly turnId?: string | undefined; |
| ... | @@ -178,7 +178,7 @@ function normalizeFixtureEvent(rawEvent: Record<string, unknown>): ProviderRunti | ... | @@ -178,7 +178,7 @@ function normalizeFixtureEvent(rawEvent: Record<string, unknown>): ProviderRunti |
| 178 | 178 | ||
| 179 | export interface TestProviderAdapterHarness { | 179 | export interface TestProviderAdapterHarness { |
| 180 | readonly adapter: ProviderAdapterShape<ProviderAdapterError>; | 180 | readonly adapter: ProviderAdapterShape<ProviderAdapterError>; |
| 181 | readonly provider: "codex" | "claudeCode"; | 181 | readonly provider: "codex" | "cursor"; |
| 182 | readonly queueTurnResponse: ( | 182 | readonly queueTurnResponse: ( |
| 183 | threadId: ThreadId, | 183 | threadId: ThreadId, |
| 184 | response: TestTurnResponse, | 184 | response: TestTurnResponse, |
| ... | @@ -198,7 +198,7 @@ export interface TestProviderAdapterHarness { | ... | @@ -198,7 +198,7 @@ export interface TestProviderAdapterHarness { |
| 198 | } | 198 | } |
| 199 | 199 | ||
| 200 | interface MakeTestProviderAdapterHarnessOptions { | 200 | interface MakeTestProviderAdapterHarnessOptions { |
| 201 | readonly provider?: "codex" | "claudeCode"; | 201 | readonly provider?: "codex" | "cursor"; |
| 202 | } | 202 | } |
| 203 | 203 | ||
| 204 | function nowIso(): string { | 204 | function nowIso(): string { |
| ... | @@ -206,7 +206,7 @@ function nowIso(): string { | ... | @@ -206,7 +206,7 @@ function nowIso(): string { |
| 206 | } | 206 | } |
| 207 | 207 | ||
| 208 | function sessionNotFound( | 208 | function sessionNotFound( |
| 209 | provider: "codex" | "claudeCode", | 209 | provider: "codex" | "cursor", |
| 210 | threadId: ThreadId, | 210 | threadId: ThreadId, |
| 211 | ): ProviderAdapterSessionNotFoundError { | 211 | ): ProviderAdapterSessionNotFoundError { |
| 212 | return new ProviderAdapterSessionNotFoundError({ | 212 | return new ProviderAdapterSessionNotFoundError({ |
| ... | @@ -216,7 +216,7 @@ function sessionNotFound( | ... | @@ -216,7 +216,7 @@ function sessionNotFound( |
| 216 | } | 216 | } |
| 217 | 217 | ||
| 218 | function missingSessionEffect( | 218 | function missingSessionEffect( |
| 219 | provider: "codex" | "claudeCode", | 219 | provider: "codex" | "cursor", |
| 220 | threadId: ThreadId, | 220 | threadId: ThreadId, |
| 221 | ): Effect.Effect<never, ProviderAdapterError> { | 221 | ): Effect.Effect<never, ProviderAdapterError> { |
| 222 | return Effect.fail(sessionNotFound(provider, threadId)); | 222 | return Effect.fail(sessionNotFound(provider, threadId)); |
apps/server/integration/orchestrationEngine.integration.test.ts+1-414| ... | @@ -32,7 +32,7 @@ const PROJECT_ID = asProjectId("project-1"); | ... | @@ -32,7 +32,7 @@ const PROJECT_ID = asProjectId("project-1"); |
| 32 | const THREAD_ID = ThreadId.makeUnsafe("thread-1"); | 32 | const THREAD_ID = ThreadId.makeUnsafe("thread-1"); |
| 33 | const FIXTURE_TURN_ID = "fixture-turn"; | 33 | const FIXTURE_TURN_ID = "fixture-turn"; |
| 34 | const APPROVAL_REQUEST_ID = asApprovalRequestId("req-approval-1"); | 34 | const APPROVAL_REQUEST_ID = asApprovalRequestId("req-approval-1"); |
| 35 | type IntegrationProvider = "codex" | "claudeCode"; | 35 | type IntegrationProvider = "codex" | "cursor"; |
| 36 | 36 | ||
| 37 | function nowIso() { | 37 | function nowIso() { |
| 38 | return new Date().toISOString(); | 38 | return new Date().toISOString(); |
| ... | @@ -842,416 +842,3 @@ it.live( | ... | @@ -842,416 +842,3 @@ it.live( |
| 842 | }), | 842 | }), |
| 843 | ), | 843 | ), |
| 844 | ); | 844 | ); |
| 845 | |||
| 846 | it.live("starts a claudeCode session on first turn when provider is requested", () => | ||
| 847 | withHarness( | ||
| 848 | (harness) => | ||
| 849 | Effect.gen(function* () { | ||
| 850 | yield* seedProjectAndThread(harness); | ||
| 851 | |||
| 852 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 853 | events: [ | ||
| 854 | { | ||
| 855 | type: "turn.started", | ||
| 856 | ...runtimeBase("evt-claude-start-1", "2026-02-24T10:10:00.000Z", "claudeCode"), | ||
| 857 | threadId: THREAD_ID, | ||
| 858 | turnId: FIXTURE_TURN_ID, | ||
| 859 | }, | ||
| 860 | { | ||
| 861 | type: "message.delta", | ||
| 862 | ...runtimeBase("evt-claude-start-2", "2026-02-24T10:10:00.050Z", "claudeCode"), | ||
| 863 | threadId: THREAD_ID, | ||
| 864 | turnId: FIXTURE_TURN_ID, | ||
| 865 | delta: "Claude first turn.\n", | ||
| 866 | }, | ||
| 867 | { | ||
| 868 | type: "turn.completed", | ||
| 869 | ...runtimeBase("evt-claude-start-3", "2026-02-24T10:10:00.100Z", "claudeCode"), | ||
| 870 | threadId: THREAD_ID, | ||
| 871 | turnId: FIXTURE_TURN_ID, | ||
| 872 | status: "completed", | ||
| 873 | }, | ||
| 874 | ], | ||
| 875 | }); | ||
| 876 | |||
| 877 | yield* startTurn({ | ||
| 878 | harness, | ||
| 879 | commandId: "cmd-turn-start-claude-initial", | ||
| 880 | messageId: "msg-user-claude-initial", | ||
| 881 | text: "Use Claude", | ||
| 882 | provider: "claudeCode", | ||
| 883 | }); | ||
| 884 | |||
| 885 | const thread = yield* harness.waitForThread( | ||
| 886 | THREAD_ID, | ||
| 887 | (entry) => | ||
| 888 | entry.session?.providerName === "claudeCode" && | ||
| 889 | entry.session.status === "ready" && | ||
| 890 | entry.messages.some( | ||
| 891 | (message) => message.role === "assistant" && message.text === "Claude first turn.\n", | ||
| 892 | ), | ||
| 893 | ); | ||
| 894 | assert.equal(thread.session?.providerName, "claudeCode"); | ||
| 895 | }), | ||
| 896 | "claudeCode", | ||
| 897 | ), | ||
| 898 | ); | ||
| 899 | |||
| 900 | it.live("recovers claudeCode sessions after provider stopAll using persisted resume state", () => | ||
| 901 | withHarness( | ||
| 902 | (harness) => | ||
| 903 | Effect.gen(function* () { | ||
| 904 | yield* seedProjectAndThread(harness); | ||
| 905 | |||
| 906 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 907 | events: [ | ||
| 908 | { | ||
| 909 | type: "turn.started", | ||
| 910 | ...runtimeBase("evt-claude-recover-1", "2026-02-24T10:11:00.000Z", "claudeCode"), | ||
| 911 | threadId: THREAD_ID, | ||
| 912 | turnId: FIXTURE_TURN_ID, | ||
| 913 | }, | ||
| 914 | { | ||
| 915 | type: "message.delta", | ||
| 916 | ...runtimeBase("evt-claude-recover-2", "2026-02-24T10:11:00.050Z", "claudeCode"), | ||
| 917 | threadId: THREAD_ID, | ||
| 918 | turnId: FIXTURE_TURN_ID, | ||
| 919 | delta: "Turn before restart.\n", | ||
| 920 | }, | ||
| 921 | { | ||
| 922 | type: "turn.completed", | ||
| 923 | ...runtimeBase("evt-claude-recover-3", "2026-02-24T10:11:00.100Z", "claudeCode"), | ||
| 924 | threadId: THREAD_ID, | ||
| 925 | turnId: FIXTURE_TURN_ID, | ||
| 926 | status: "completed", | ||
| 927 | }, | ||
| 928 | ], | ||
| 929 | }); | ||
| 930 | |||
| 931 | yield* startTurn({ | ||
| 932 | harness, | ||
| 933 | commandId: "cmd-turn-start-claude-recover-1", | ||
| 934 | messageId: "msg-user-claude-recover-1", | ||
| 935 | text: "Before restart", | ||
| 936 | provider: "claudeCode", | ||
| 937 | }); | ||
| 938 | |||
| 939 | yield* harness.waitForThread( | ||
| 940 | THREAD_ID, | ||
| 941 | (entry) => entry.latestTurn?.turnId === "turn-1" && entry.session?.threadId === "thread-1", | ||
| 942 | ); | ||
| 943 | |||
| 944 | yield* harness.providerService.stopAll(); | ||
| 945 | yield* waitForSync( | ||
| 946 | () => harness.adapterHarness.listActiveSessionIds(), | ||
| 947 | (sessionIds) => sessionIds.length === 0, | ||
| 948 | "provider stopAll", | ||
| 949 | ); | ||
| 950 | |||
| 951 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 952 | events: [ | ||
| 953 | { | ||
| 954 | type: "turn.started", | ||
| 955 | ...runtimeBase("evt-claude-recover-4", "2026-02-24T10:11:01.000Z", "claudeCode"), | ||
| 956 | threadId: THREAD_ID, | ||
| 957 | turnId: FIXTURE_TURN_ID, | ||
| 958 | }, | ||
| 959 | { | ||
| 960 | type: "message.delta", | ||
| 961 | ...runtimeBase("evt-claude-recover-5", "2026-02-24T10:11:01.050Z", "claudeCode"), | ||
| 962 | threadId: THREAD_ID, | ||
| 963 | turnId: FIXTURE_TURN_ID, | ||
| 964 | delta: "Turn after restart.\n", | ||
| 965 | }, | ||
| 966 | { | ||
| 967 | type: "turn.completed", | ||
| 968 | ...runtimeBase("evt-claude-recover-6", "2026-02-24T10:11:01.100Z", "claudeCode"), | ||
| 969 | threadId: THREAD_ID, | ||
| 970 | turnId: FIXTURE_TURN_ID, | ||
| 971 | status: "completed", | ||
| 972 | }, | ||
| 973 | ], | ||
| 974 | }); | ||
| 975 | |||
| 976 | yield* startTurn({ | ||
| 977 | harness, | ||
| 978 | commandId: "cmd-turn-start-claude-recover-2", | ||
| 979 | messageId: "msg-user-claude-recover-2", | ||
| 980 | text: "After restart", | ||
| 981 | }); | ||
| 982 | yield* waitForSync( | ||
| 983 | () => harness.adapterHarness.getStartCount(), | ||
| 984 | (count) => count === 2, | ||
| 985 | "claude provider recovery start", | ||
| 986 | ); | ||
| 987 | |||
| 988 | const recoveredThread = yield* harness.waitForThread( | ||
| 989 | THREAD_ID, | ||
| 990 | (entry) => | ||
| 991 | entry.session?.providerName === "claudeCode" && | ||
| 992 | entry.messages.some( | ||
| 993 | (message) => message.role === "user" && message.text === "After restart", | ||
| 994 | ) && | ||
| 995 | !entry.activities.some((activity) => activity.kind === "provider.turn.start.failed"), | ||
| 996 | ); | ||
| 997 | assert.equal(recoveredThread.session?.providerName, "claudeCode"); | ||
| 998 | assert.equal(recoveredThread.session?.threadId, "thread-1"); | ||
| 999 | }), | ||
| 1000 | "claudeCode", | ||
| 1001 | ), | ||
| 1002 | ); | ||
| 1003 | |||
| 1004 | it.live("forwards claudeCode approval responses to the provider session", () => | ||
| 1005 | withHarness( | ||
| 1006 | (harness) => | ||
| 1007 | Effect.gen(function* () { | ||
| 1008 | yield* seedProjectAndThread(harness); | ||
| 1009 | |||
| 1010 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 1011 | events: [ | ||
| 1012 | { | ||
| 1013 | type: "turn.started", | ||
| 1014 | ...runtimeBase("evt-claude-approval-1", "2026-02-24T10:12:00.000Z", "claudeCode"), | ||
| 1015 | threadId: THREAD_ID, | ||
| 1016 | turnId: FIXTURE_TURN_ID, | ||
| 1017 | }, | ||
| 1018 | { | ||
| 1019 | type: "approval.requested", | ||
| 1020 | ...runtimeBase("evt-claude-approval-2", "2026-02-24T10:12:00.050Z", "claudeCode"), | ||
| 1021 | threadId: THREAD_ID, | ||
| 1022 | turnId: FIXTURE_TURN_ID, | ||
| 1023 | requestId: APPROVAL_REQUEST_ID, | ||
| 1024 | requestKind: "command", | ||
| 1025 | detail: "Approve Claude tool call", | ||
| 1026 | }, | ||
| 1027 | { | ||
| 1028 | type: "turn.completed", | ||
| 1029 | ...runtimeBase("evt-claude-approval-3", "2026-02-24T10:12:00.100Z", "claudeCode"), | ||
| 1030 | threadId: THREAD_ID, | ||
| 1031 | turnId: FIXTURE_TURN_ID, | ||
| 1032 | status: "completed", | ||
| 1033 | }, | ||
| 1034 | ], | ||
| 1035 | }); | ||
| 1036 | |||
| 1037 | yield* startTurn({ | ||
| 1038 | harness, | ||
| 1039 | commandId: "cmd-turn-start-claude-approval", | ||
| 1040 | messageId: "msg-user-claude-approval", | ||
| 1041 | text: "Need approval", | ||
| 1042 | provider: "claudeCode", | ||
| 1043 | }); | ||
| 1044 | |||
| 1045 | const thread = yield* harness.waitForThread(THREAD_ID, (entry) => | ||
| 1046 | entry.activities.some((activity) => activity.kind === "approval.requested"), | ||
| 1047 | ); | ||
| 1048 | assert.equal(thread.session?.threadId, "thread-1"); | ||
| 1049 | |||
| 1050 | yield* harness.engine.dispatch({ | ||
| 1051 | type: "thread.approval.respond", | ||
| 1052 | commandId: CommandId.makeUnsafe("cmd-claude-approval-respond"), | ||
| 1053 | threadId: THREAD_ID, | ||
| 1054 | requestId: APPROVAL_REQUEST_ID, | ||
| 1055 | decision: "accept", | ||
| 1056 | createdAt: nowIso(), | ||
| 1057 | }); | ||
| 1058 | |||
| 1059 | yield* harness.waitForPendingApproval( | ||
| 1060 | "req-approval-1", | ||
| 1061 | (row) => row.status === "resolved" && row.decision === "accept", | ||
| 1062 | ); | ||
| 1063 | |||
| 1064 | const approvalResponses = yield* waitForSync( | ||
| 1065 | () => harness.adapterHarness.getApprovalResponses(THREAD_ID), | ||
| 1066 | (responses) => responses.length === 1, | ||
| 1067 | "claude provider approval response", | ||
| 1068 | ); | ||
| 1069 | assert.equal(approvalResponses[0]?.decision, "accept"); | ||
| 1070 | }), | ||
| 1071 | "claudeCode", | ||
| 1072 | ), | ||
| 1073 | ); | ||
| 1074 | |||
| 1075 | it.live("forwards thread.turn.interrupt to claudeCode provider sessions", () => | ||
| 1076 | withHarness( | ||
| 1077 | (harness) => | ||
| 1078 | Effect.gen(function* () { | ||
| 1079 | yield* seedProjectAndThread(harness); | ||
| 1080 | |||
| 1081 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 1082 | events: [ | ||
| 1083 | { | ||
| 1084 | type: "turn.started", | ||
| 1085 | ...runtimeBase("evt-claude-interrupt-1", "2026-02-24T10:13:00.000Z", "claudeCode"), | ||
| 1086 | threadId: THREAD_ID, | ||
| 1087 | turnId: FIXTURE_TURN_ID, | ||
| 1088 | }, | ||
| 1089 | { | ||
| 1090 | type: "message.delta", | ||
| 1091 | ...runtimeBase("evt-claude-interrupt-2", "2026-02-24T10:13:00.050Z", "claudeCode"), | ||
| 1092 | threadId: THREAD_ID, | ||
| 1093 | turnId: FIXTURE_TURN_ID, | ||
| 1094 | delta: "Long running output.\n", | ||
| 1095 | }, | ||
| 1096 | { | ||
| 1097 | type: "turn.completed", | ||
| 1098 | ...runtimeBase("evt-claude-interrupt-3", "2026-02-24T10:13:00.100Z", "claudeCode"), | ||
| 1099 | threadId: THREAD_ID, | ||
| 1100 | turnId: FIXTURE_TURN_ID, | ||
| 1101 | status: "completed", | ||
| 1102 | }, | ||
| 1103 | ], | ||
| 1104 | }); | ||
| 1105 | |||
| 1106 | yield* startTurn({ | ||
| 1107 | harness, | ||
| 1108 | commandId: "cmd-turn-start-claude-interrupt", | ||
| 1109 | messageId: "msg-user-claude-interrupt", | ||
| 1110 | text: "Start long turn", | ||
| 1111 | provider: "claudeCode", | ||
| 1112 | }); | ||
| 1113 | |||
| 1114 | const thread = yield* harness.waitForThread( | ||
| 1115 | THREAD_ID, | ||
| 1116 | (entry) => entry.session?.threadId === "thread-1", | ||
| 1117 | ); | ||
| 1118 | assert.equal(thread.session?.threadId, "thread-1"); | ||
| 1119 | |||
| 1120 | yield* harness.engine.dispatch({ | ||
| 1121 | type: "thread.turn.interrupt", | ||
| 1122 | commandId: CommandId.makeUnsafe("cmd-turn-interrupt-claude"), | ||
| 1123 | threadId: THREAD_ID, | ||
| 1124 | createdAt: nowIso(), | ||
| 1125 | }); | ||
| 1126 | yield* harness.waitForDomainEvent((event) => event.type === "thread.turn-interrupt-requested"); | ||
| 1127 | |||
| 1128 | const interruptCalls = yield* waitForSync( | ||
| 1129 | () => harness.adapterHarness.getInterruptCalls(THREAD_ID), | ||
| 1130 | (calls) => calls.length === 1, | ||
| 1131 | "claude provider interrupt call", | ||
| 1132 | ); | ||
| 1133 | assert.equal(interruptCalls.length, 1); | ||
| 1134 | }), | ||
| 1135 | "claudeCode", | ||
| 1136 | ), | ||
| 1137 | ); | ||
| 1138 | |||
| 1139 | it.live("reverts claudeCode turns and rolls back provider conversation state", () => | ||
| 1140 | withHarness( | ||
| 1141 | (harness) => | ||
| 1142 | Effect.gen(function* () { | ||
| 1143 | yield* seedProjectAndThread(harness); | ||
| 1144 | |||
| 1145 | yield* harness.adapterHarness.queueTurnResponseForNextSession({ | ||
| 1146 | events: [ | ||
| 1147 | { | ||
| 1148 | type: "turn.started", | ||
| 1149 | ...runtimeBase("evt-claude-revert-1", "2026-02-24T10:14:00.000Z", "claudeCode"), | ||
| 1150 | threadId: THREAD_ID, | ||
| 1151 | turnId: FIXTURE_TURN_ID, | ||
| 1152 | }, | ||
| 1153 | { | ||
| 1154 | type: "message.delta", | ||
| 1155 | ...runtimeBase("evt-claude-revert-2", "2026-02-24T10:14:00.050Z", "claudeCode"), | ||
| 1156 | threadId: THREAD_ID, | ||
| 1157 | turnId: FIXTURE_TURN_ID, | ||
| 1158 | delta: "README -> v2\n", | ||
| 1159 | }, | ||
| 1160 | { | ||
| 1161 | type: "turn.completed", | ||
| 1162 | ...runtimeBase("evt-claude-revert-3", "2026-02-24T10:14:00.100Z", "claudeCode"), | ||
| 1163 | threadId: THREAD_ID, | ||
| 1164 | turnId: FIXTURE_TURN_ID, | ||
| 1165 | status: "completed", | ||
| 1166 | }, | ||
| 1167 | ], | ||
| 1168 | mutateWorkspace: ({ cwd }) => | ||
| 1169 | Effect.sync(() => { | ||
| 1170 | fs.writeFileSync(path.join(cwd, "README.md"), "v2\n", "utf8"); | ||
| 1171 | }), | ||
| 1172 | }); | ||
| 1173 | |||
| 1174 | yield* startTurn({ | ||
| 1175 | harness, | ||
| 1176 | commandId: "cmd-turn-start-claude-revert-1", | ||
| 1177 | messageId: "msg-user-claude-revert-1", | ||
| 1178 | text: "First Claude edit", | ||
| 1179 | provider: "claudeCode", | ||
| 1180 | }); | ||
| 1181 | |||
| 1182 | yield* harness.waitForThread( | ||
| 1183 | THREAD_ID, | ||
| 1184 | (entry) => entry.latestTurn?.turnId === "turn-1" && entry.session?.threadId === "thread-1", | ||
| 1185 | ); | ||
| 1186 | |||
| 1187 | yield* harness.adapterHarness.queueTurnResponse(THREAD_ID, { | ||
| 1188 | events: [ | ||
| 1189 | { | ||
| 1190 | type: "turn.started", | ||
| 1191 | ...runtimeBase("evt-claude-revert-4", "2026-02-24T10:14:01.000Z", "claudeCode"), | ||
| 1192 | threadId: THREAD_ID, | ||
| 1193 | turnId: FIXTURE_TURN_ID, | ||
| 1194 | }, | ||
| 1195 | { | ||
| 1196 | type: "message.delta", | ||
| 1197 | ...runtimeBase("evt-claude-revert-5", "2026-02-24T10:14:01.050Z", "claudeCode"), | ||
| 1198 | threadId: THREAD_ID, | ||
| 1199 | turnId: FIXTURE_TURN_ID, | ||
| 1200 | delta: "README -> v3\n", | ||
| 1201 | }, | ||
| 1202 | { | ||
| 1203 | type: "turn.completed", | ||
| 1204 | ...runtimeBase("evt-claude-revert-6", "2026-02-24T10:14:01.100Z", "claudeCode"), | ||
| 1205 | threadId: THREAD_ID, | ||
| 1206 | turnId: FIXTURE_TURN_ID, | ||
| 1207 | status: "completed", | ||
| 1208 | }, | ||
| 1209 | ], | ||
| 1210 | mutateWorkspace: ({ cwd }) => | ||
| 1211 | Effect.sync(() => { | ||
| 1212 | fs.writeFileSync(path.join(cwd, "README.md"), "v3\n", "utf8"); | ||
| 1213 | }), | ||
| 1214 | }); | ||
| 1215 | |||
| 1216 | yield* startTurn({ | ||
| 1217 | harness, | ||
| 1218 | commandId: "cmd-turn-start-claude-revert-2", | ||
| 1219 | messageId: "msg-user-claude-revert-2", | ||
| 1220 | text: "Second Claude edit", | ||
| 1221 | }); | ||
| 1222 | |||
| 1223 | yield* harness.waitForThread( | ||
| 1224 | THREAD_ID, | ||
| 1225 | (entry) => | ||
| 1226 | entry.latestTurn?.turnId === "turn-2" && | ||
| 1227 | entry.checkpoints.length === 2 && | ||
| 1228 | entry.session?.providerName === "claudeCode", | ||
| 1229 | ); | ||
| 1230 | |||
| 1231 | yield* harness.engine.dispatch({ | ||
| 1232 | type: "thread.checkpoint.revert", | ||
| 1233 | commandId: CommandId.makeUnsafe("cmd-checkpoint-revert-claude"), | ||
| 1234 | threadId: THREAD_ID, | ||
| 1235 | turnCount: 1, | ||
| 1236 | createdAt: nowIso(), | ||
| 1237 | }); | ||
| 1238 | |||
| 1239 | const revertedThread = yield* harness.waitForThread( | ||
| 1240 | THREAD_ID, | ||
| 1241 | (entry) => | ||
| 1242 | entry.checkpoints.length === 1 && entry.checkpoints[0]?.checkpointTurnCount === 1, | ||
| 1243 | ); | ||
| 1244 | assert.equal(revertedThread.checkpoints[0]?.checkpointTurnCount, 1); | ||
| 1245 | assert.equal( | ||
| 1246 | gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 1)), | ||
| 1247 | true, | ||
| 1248 | ); | ||
| 1249 | assert.equal( | ||
| 1250 | gitRefExists(harness.workspaceDir, checkpointRefForThreadTurn(THREAD_ID, 2)), | ||
| 1251 | false, | ||
| 1252 | ); | ||
| 1253 | assert.deepEqual(harness.adapterHarness.getRollbackCalls(THREAD_ID), [1]); | ||
| 1254 | }), | ||
| 1255 | "claudeCode", | ||
| 1256 | ), | ||
| 1257 | ); |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+1-1| ... | @@ -43,7 +43,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); | ... | @@ -43,7 +43,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); |
| 43 | type LegacyProviderRuntimeEvent = { | 43 | type LegacyProviderRuntimeEvent = { |
| 44 | readonly type: string; | 44 | readonly type: string; |
| 45 | readonly eventId: EventId; | 45 | readonly eventId: EventId; |
| 46 | readonly provider: "codex" | "claudeCode" | "cursor"; | 46 | readonly provider: "codex" | "cursor"; |
| 47 | readonly createdAt: string; | 47 | readonly createdAt: string; |
| 48 | readonly threadId: ThreadId; | 48 | readonly threadId: ThreadId; |
| 49 | readonly turnId?: string | undefined; | 49 | readonly turnId?: string | undefined; |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts-1| ... | @@ -212,7 +212,6 @@ const make = Effect.gen(function* () { | ... | @@ -212,7 +212,6 @@ const make = Effect.gen(function* () { |
| 212 | const desiredRuntimeMode = thread.runtimeMode; | 212 | const desiredRuntimeMode = thread.runtimeMode; |
| 213 | const currentProvider: ProviderKind | undefined = | 213 | const currentProvider: ProviderKind | undefined = |
| 214 | thread.session?.providerName === "codex" || | 214 | thread.session?.providerName === "codex" || |
| 215 | thread.session?.providerName === "claudeCode" || | ||
| 216 | thread.session?.providerName === "cursor" | 215 | thread.session?.providerName === "cursor" |
| 217 | ? thread.session.providerName | 216 | ? thread.session.providerName |
| 218 | : undefined; | 217 | : undefined; |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+1-1| ... | @@ -41,7 +41,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); | ... | @@ -41,7 +41,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); |
| 41 | type LegacyProviderRuntimeEvent = { | 41 | type LegacyProviderRuntimeEvent = { |
| 42 | readonly type: string; | 42 | readonly type: string; |
| 43 | readonly eventId: EventId; | 43 | readonly eventId: EventId; |
| 44 | readonly provider: "codex" | "claudeCode" | "cursor"; | 44 | readonly provider: "codex" | "cursor"; |
| 45 | readonly createdAt: string; | 45 | readonly createdAt: string; |
| 46 | readonly threadId: ThreadId; | 46 | readonly threadId: ThreadId; |
| 47 | readonly turnId?: string | undefined; | 47 | readonly turnId?: string | undefined; |
apps/server/src/provider/Layers/CodexAdapter.test.ts+2-2| ... | @@ -162,7 +162,7 @@ validationLayer("CodexAdapterLive validation", (it) => { | ... | @@ -162,7 +162,7 @@ validationLayer("CodexAdapterLive validation", (it) => { |
| 162 | const adapter = yield* CodexAdapter; | 162 | const adapter = yield* CodexAdapter; |
| 163 | const result = yield* adapter | 163 | const result = yield* adapter |
| 164 | .startSession({ | 164 | .startSession({ |
| 165 | provider: "claudeCode", | 165 | provider: "cursor", |
| 166 | threadId: asThreadId("thread-1"), | 166 | threadId: asThreadId("thread-1"), |
| 167 | runtimeMode: "full-access", | 167 | runtimeMode: "full-access", |
| 168 | }) | 168 | }) |
| ... | @@ -174,7 +174,7 @@ validationLayer("CodexAdapterLive validation", (it) => { | ... | @@ -174,7 +174,7 @@ validationLayer("CodexAdapterLive validation", (it) => { |
| 174 | new ProviderAdapterValidationError({ | 174 | new ProviderAdapterValidationError({ |
| 175 | provider: "codex", | 175 | provider: "codex", |
| 176 | operation: "startSession", | 176 | operation: "startSession", |
| 177 | issue: "Expected provider 'codex' but received 'claudeCode'.", | 177 | issue: "Expected provider 'codex' but received 'cursor'.", |
| 178 | }), | 178 | }), |
| 179 | ); | 179 | ); |
| 180 | assert.equal(validationManager.startSessionImpl.mock.calls.length, 0); | 180 | assert.equal(validationManager.startSessionImpl.mock.calls.length, 0); |
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | /** | 1 | /** |
| 2 | * ProviderAdapterRegistryLive - In-memory provider adapter lookup layer. | 2 | * ProviderAdapterRegistryLive - In-memory provider adapter lookup layer. |
| 3 | * | 3 | * |
| 4 | * Binds provider kinds (codex/claudeCode/...) to concrete adapter services. | 4 | * Binds provider kinds (codex/cursor/...) to concrete adapter services. |
| 5 | * This layer only performs adapter lookup; it does not route session-scoped | 5 | * This layer only performs adapter lookup; it does not route session-scoped |
| 6 | * calls or own provider lifecycle workflows. | 6 | * calls or own provider lifecycle workflows. |
| 7 | * | 7 | * |
apps/server/src/provider/Layers/ProviderService.test.ts+1-1| ... | @@ -51,7 +51,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); | ... | @@ -51,7 +51,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); |
| 51 | type LegacyProviderRuntimeEvent = { | 51 | type LegacyProviderRuntimeEvent = { |
| 52 | readonly type: string; | 52 | readonly type: string; |
| 53 | readonly eventId: EventId; | 53 | readonly eventId: EventId; |
| 54 | readonly provider: "codex" | "claudeCode" | "cursor"; | 54 | readonly provider: "codex" | "cursor"; |
| 55 | readonly createdAt: string; | 55 | readonly createdAt: string; |
| 56 | readonly threadId: ThreadId; | 56 | readonly threadId: ThreadId; |
| 57 | readonly turnId?: string | undefined; | 57 | readonly turnId?: string | undefined; |
apps/server/src/provider/Layers/ProviderSessionDirectory.ts+1-1| ... | @@ -25,7 +25,7 @@ function decodeProviderKind( | ... | @@ -25,7 +25,7 @@ function decodeProviderKind( |
| 25 | providerName: string, | 25 | providerName: string, |
| 26 | operation: string, | 26 | operation: string, |
| 27 | ): Effect.Effect<ProviderKind, ProviderSessionDirectoryPersistenceError> { | 27 | ): Effect.Effect<ProviderKind, ProviderSessionDirectoryPersistenceError> { |
| 28 | if (providerName === "codex" || providerName === "claudeCode" || providerName === "cursor") { | 28 | if (providerName === "codex" || providerName === "cursor") { |
| 29 | return Effect.succeed(providerName); | 29 | return Effect.succeed(providerName); |
| 30 | } | 30 | } |
| 31 | return Effect.fail( | 31 | return Effect.fail( |
apps/web/src/appSettings.test.ts+3-9| ... | @@ -16,11 +16,7 @@ describe("normalizeCustomModelSlugs", () => { | ... | @@ -16,11 +16,7 @@ describe("normalizeCustomModelSlugs", () => { |
| 16 | ).toEqual(["custom/internal-model"]); | 16 | ).toEqual(["custom/internal-model"]); |
| 17 | }); | 17 | }); |
| 18 | 18 | ||
| 19 | it("normalizes provider-specific aliases for claude and cursor", () => { | 19 | it("normalizes provider-specific aliases for cursor", () => { |
| 20 | expect(normalizeCustomModelSlugs(["sonnet"], "claudeCode")).toEqual([]); | ||
| 21 | expect(normalizeCustomModelSlugs(["claude/custom-sonnet"], "claudeCode")).toEqual([ | ||
| 22 | "claude/custom-sonnet", | ||
| 23 | ]); | ||
| 24 | expect(normalizeCustomModelSlugs(["composer"], "cursor")).toEqual([]); | 20 | expect(normalizeCustomModelSlugs(["composer"], "cursor")).toEqual([]); |
| 25 | expect(normalizeCustomModelSlugs(["cursor/custom-model"], "cursor")).toEqual([ | 21 | expect(normalizeCustomModelSlugs(["cursor/custom-model"], "cursor")).toEqual([ |
| 26 | "cursor/custom-model", | 22 | "cursor/custom-model", |
| ... | @@ -53,9 +49,9 @@ describe("getAppModelOptions", () => { | ... | @@ -53,9 +49,9 @@ describe("getAppModelOptions", () => { |
| 53 | }); | 49 | }); |
| 54 | 50 | ||
| 55 | it("keeps a saved custom provider model available as an exact slug option", () => { | 51 | it("keeps a saved custom provider model available as an exact slug option", () => { |
| 56 | const options = getAppModelOptions("claudeCode", ["claude/custom-opus"], "claude/custom-opus"); | 52 | const options = getAppModelOptions("cursor", ["cursor/custom-model"], "cursor/custom-model"); |
| 57 | 53 | ||
| 58 | expect(options.some((option) => option.slug === "claude/custom-opus" && option.isCustom)).toBe( | 54 | expect(options.some((option) => option.slug === "cursor/custom-model" && option.isCustom)).toBe( |
| 59 | true, | 55 | true, |
| 60 | ); | 56 | ); |
| 61 | }); | 57 | }); |
| ... | @@ -85,10 +81,8 @@ describe("getSlashModelOptions", () => { | ... | @@ -85,10 +81,8 @@ describe("getSlashModelOptions", () => { |
| 85 | }); | 81 | }); |
| 86 | 82 | ||
| 87 | it("includes provider-specific custom slugs in non-codex model lists", () => { | 83 | it("includes provider-specific custom slugs in non-codex model lists", () => { |
| 88 | const claudeOptions = getAppModelOptions("claudeCode", ["claude/custom-opus"]); | ||
| 89 | const cursorOptions = getAppModelOptions("cursor", ["cursor/custom-model"]); | 84 | const cursorOptions = getAppModelOptions("cursor", ["cursor/custom-model"]); |
| 90 | 85 | ||
| 91 | expect(claudeOptions.some((option) => option.slug === "claude/custom-opus")).toBe(true); | ||
| 92 | expect(cursorOptions.some((option) => option.slug === "cursor/custom-model")).toBe(true); | 86 | expect(cursorOptions.some((option) => option.slug === "cursor/custom-model")).toBe(true); |
| 93 | }); | 87 | }); |
| 94 | }); | 88 | }); |
apps/web/src/appSettings.ts-5| ... | @@ -8,7 +8,6 @@ const MAX_CUSTOM_MODEL_COUNT = 32; | ... | @@ -8,7 +8,6 @@ const MAX_CUSTOM_MODEL_COUNT = 32; |
| 8 | export const MAX_CUSTOM_MODEL_LENGTH = 256; | 8 | export const MAX_CUSTOM_MODEL_LENGTH = 256; |
| 9 | const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record<ProviderKind, ReadonlySet<string>> = { | 9 | const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record<ProviderKind, ReadonlySet<string>> = { |
| 10 | codex: new Set(getModelOptions("codex").map((option) => option.slug)), | 10 | codex: new Set(getModelOptions("codex").map((option) => option.slug)), |
| 11 | claudeCode: new Set(getModelOptions("claudeCode").map((option) => option.slug)), | ||
| 12 | cursor: new Set(getModelOptions("cursor").map((option) => option.slug)), | 11 | cursor: new Set(getModelOptions("cursor").map((option) => option.slug)), |
| 13 | }; | 12 | }; |
| 14 | 13 | ||
| ... | @@ -26,9 +25,6 @@ const AppSettingsSchema = Schema.Struct({ | ... | @@ -26,9 +25,6 @@ const AppSettingsSchema = Schema.Struct({ |
| 26 | customCodexModels: Schema.Array(Schema.String).pipe( | 25 | customCodexModels: Schema.Array(Schema.String).pipe( |
| 27 | Schema.withConstructorDefault(() => Option.some([])), | 26 | Schema.withConstructorDefault(() => Option.some([])), |
| 28 | ), | 27 | ), |
| 29 | customClaudeModels: Schema.Array(Schema.String).pipe( | ||
| 30 | Schema.withConstructorDefault(() => Option.some([])), | ||
| 31 | ), | ||
| 32 | customCursorModels: Schema.Array(Schema.String).pipe( | 28 | customCursorModels: Schema.Array(Schema.String).pipe( |
| 33 | Schema.withConstructorDefault(() => Option.some([])), | 29 | Schema.withConstructorDefault(() => Option.some([])), |
| 34 | ), | 30 | ), |
| ... | @@ -79,7 +75,6 @@ function normalizeAppSettings(settings: AppSettings): AppSettings { | ... | @@ -79,7 +75,6 @@ function normalizeAppSettings(settings: AppSettings): AppSettings { |
| 79 | return { | 75 | return { |
| 80 | ...settings, | 76 | ...settings, |
| 81 | customCodexModels: normalizeCustomModelSlugs(settings.customCodexModels, "codex"), | 77 | customCodexModels: normalizeCustomModelSlugs(settings.customCodexModels, "codex"), |
| 82 | customClaudeModels: normalizeCustomModelSlugs(settings.customClaudeModels, "claudeCode"), | ||
| 83 | customCursorModels: normalizeCustomModelSlugs(settings.customCursorModels, "cursor"), | 78 | customCursorModels: normalizeCustomModelSlugs(settings.customCursorModels, "cursor"), |
| 84 | }; | 79 | }; |
| 85 | } | 80 | } |
apps/web/src/components/ChatView.tsx+16-11| ... | @@ -77,6 +77,7 @@ import { | ... | @@ -77,6 +77,7 @@ import { |
| 77 | findLatestProposedPlan, | 77 | findLatestProposedPlan, |
| 78 | type PendingApproval, | 78 | type PendingApproval, |
| 79 | type PendingUserInput, | 79 | type PendingUserInput, |
| 80 | type ProviderPickerKind, | ||
| 80 | PROVIDER_OPTIONS, | 81 | PROVIDER_OPTIONS, |
| 81 | deriveWorkLogEntries, | 82 | deriveWorkLogEntries, |
| 82 | hasToolActivityForTurn, | 83 | hasToolActivityForTurn, |
| ... | @@ -847,9 +848,8 @@ export default function ChatView({ threadId }: ChatViewProps) { | ... | @@ -847,9 +848,8 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 847 | }, [modelOptionsByProvider, selectedModelForPicker, selectedProvider]); | 848 | }, [modelOptionsByProvider, selectedModelForPicker, selectedProvider]); |
| 848 | const searchableModelOptions = useMemo( | 849 | const searchableModelOptions = useMemo( |
| 849 | () => | 850 | () => |
| 850 | PROVIDER_OPTIONS.filter( | 851 | AVAILABLE_PROVIDER_OPTIONS.filter( |
| 851 | (option) => | 852 | (option) => lockedProvider === null || option.value === lockedProvider, |
| 852 | option.available && (lockedProvider === null || option.value === lockedProvider), | ||
| 853 | ).flatMap((option) => | 853 | ).flatMap((option) => |
| 854 | modelOptionsByProvider[option.value].map(({ slug, name }) => ({ | 854 | modelOptionsByProvider[option.value].map(({ slug, name }) => ({ |
| 855 | provider: option.value, | 855 | provider: option.value, |
| ... | @@ -5278,7 +5278,17 @@ const MessagesTimeline = memo(function MessagesTimeline({ | ... | @@ -5278,7 +5278,17 @@ const MessagesTimeline = memo(function MessagesTimeline({ |
| 5278 | ); | 5278 | ); |
| 5279 | }); | 5279 | }); |
| 5280 | 5280 | ||
| 5281 | const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => option.available); | 5281 | function isAvailableProviderOption( |
| 5282 | option: (typeof PROVIDER_OPTIONS)[number], | ||
| 5283 | ): option is { | ||
| 5284 | value: ProviderKind; | ||
| 5285 | label: string; | ||
| 5286 | available: true; | ||
| 5287 | } { | ||
| 5288 | return option.available && option.value !== "claudeCode"; | ||
| 5289 | } | ||
| 5290 | |||
| 5291 | const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter(isAvailableProviderOption); | ||
| 5282 | const UNAVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => !option.available); | 5292 | const UNAVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => !option.available); |
| 5283 | const COMING_SOON_PROVIDER_OPTIONS = [ | 5293 | const COMING_SOON_PROVIDER_OPTIONS = [ |
| 5284 | { id: "opencode", label: "OpenCode", icon: OpenCodeIcon }, | 5294 | { id: "opencode", label: "OpenCode", icon: OpenCodeIcon }, |
| ... | @@ -5287,13 +5297,11 @@ const COMING_SOON_PROVIDER_OPTIONS = [ | ... | @@ -5287,13 +5297,11 @@ const COMING_SOON_PROVIDER_OPTIONS = [ |
| 5287 | 5297 | ||
| 5288 | function getCustomModelOptionsByProvider(settings: { | 5298 | function getCustomModelOptionsByProvider(settings: { |
| 5289 | customCodexModels: readonly string[]; | 5299 | customCodexModels: readonly string[]; |
| 5290 | customClaudeModels: readonly string[]; | ||
| 5291 | customCursorModels: readonly string[]; | 5300 | customCursorModels: readonly string[]; |
| 5292 | }): Record<ProviderKind, ReadonlyArray<{ slug: string; name: string }>> { | 5301 | }): Record<ProviderKind, ReadonlyArray<{ slug: string; name: string }>> { |
| 5293 | const cursorFamilyOptions = getCursorModelFamilyOptions(); | 5302 | const cursorFamilyOptions = getCursorModelFamilyOptions(); |
| 5294 | return { | 5303 | return { |
| 5295 | codex: getAppModelOptions("codex", settings.customCodexModels), | 5304 | codex: getAppModelOptions("codex", settings.customCodexModels), |
| 5296 | claudeCode: getAppModelOptions("claudeCode", settings.customClaudeModels), | ||
| 5297 | cursor: [ | 5305 | cursor: [ |
| 5298 | ...cursorFamilyOptions, | 5306 | ...cursorFamilyOptions, |
| 5299 | ...getAppModelOptions("cursor", settings.customCursorModels).filter( | 5307 | ...getAppModelOptions("cursor", settings.customCursorModels).filter( |
| ... | @@ -5304,7 +5312,7 @@ function getCustomModelOptionsByProvider(settings: { | ... | @@ -5304,7 +5312,7 @@ function getCustomModelOptionsByProvider(settings: { |
| 5304 | }; | 5312 | }; |
| 5305 | } | 5313 | } |
| 5306 | 5314 | ||
| 5307 | const PROVIDER_ICON_BY_PROVIDER: Record<ProviderKind, Icon> = { | 5315 | const PROVIDER_ICON_BY_PROVIDER: Record<ProviderPickerKind, Icon> = { |
| 5308 | codex: OpenAI, | 5316 | codex: OpenAI, |
| 5309 | claudeCode: ClaudeAI, | 5317 | claudeCode: ClaudeAI, |
| 5310 | cursor: CursorIcon, | 5318 | cursor: CursorIcon, |
| ... | @@ -5385,10 +5393,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { | ... | @@ -5385,10 +5393,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 5385 | <span className="flex min-w-0 items-center gap-2"> | 5393 | <span className="flex min-w-0 items-center gap-2"> |
| 5386 | <ProviderIcon | 5394 | <ProviderIcon |
| 5387 | aria-hidden="true" | 5395 | aria-hidden="true" |
| 5388 | className={cn( | 5396 | className="size-4 shrink-0 text-muted-foreground/70" |
| 5389 | "size-4 shrink-0", | ||
| 5390 | props.provider === "claudeCode" ? "" : "text-muted-foreground/70", | ||
| 5391 | )} | ||
| 5392 | /> | 5397 | /> |
| 5393 | <span className="truncate">{selectedModelLabel}</span> | 5398 | <span className="truncate">{selectedModelLabel}</span> |
| 5394 | <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" /> | 5399 | <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" /> |
apps/web/src/composerDraftStore.ts+1-1| ... | @@ -208,7 +208,7 @@ function shouldRemoveDraft(draft: ComposerThreadDraftState): boolean { | ... | @@ -208,7 +208,7 @@ function shouldRemoveDraft(draft: ComposerThreadDraftState): boolean { |
| 208 | } | 208 | } |
| 209 | 209 | ||
| 210 | function normalizeProviderKind(value: unknown): ProviderKind | null { | 210 | function normalizeProviderKind(value: unknown): ProviderKind | null { |
| 211 | return value === "codex" || value === "claudeCode" || value === "cursor" ? value : null; | 211 | return value === "codex" || value === "cursor" ? value : null; |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | function revokeObjectPreviewUrl(previewUrl: string): void { | 214 | function revokeObjectPreviewUrl(previewUrl: string): void { |
apps/web/src/routes/_chat.settings.tsx-7| ... | @@ -54,8 +54,6 @@ function getCustomModelsForProvider( | ... | @@ -54,8 +54,6 @@ function getCustomModelsForProvider( |
| 54 | provider: ProviderKind, | 54 | provider: ProviderKind, |
| 55 | ) { | 55 | ) { |
| 56 | switch (provider) { | 56 | switch (provider) { |
| 57 | case "claudeCode": | ||
| 58 | return settings.customClaudeModels; | ||
| 59 | case "cursor": | 57 | case "cursor": |
| 60 | return settings.customCursorModels; | 58 | return settings.customCursorModels; |
| 61 | case "codex": | 59 | case "codex": |
| ... | @@ -69,8 +67,6 @@ function getDefaultCustomModelsForProvider( | ... | @@ -69,8 +67,6 @@ function getDefaultCustomModelsForProvider( |
| 69 | provider: ProviderKind, | 67 | provider: ProviderKind, |
| 70 | ) { | 68 | ) { |
| 71 | switch (provider) { | 69 | switch (provider) { |
| 72 | case "claudeCode": | ||
| 73 | return defaults.customClaudeModels; | ||
| 74 | case "cursor": | 70 | case "cursor": |
| 75 | return defaults.customCursorModels; | 71 | return defaults.customCursorModels; |
| 76 | case "codex": | 72 | case "codex": |
| ... | @@ -81,8 +77,6 @@ function getDefaultCustomModelsForProvider( | ... | @@ -81,8 +77,6 @@ function getDefaultCustomModelsForProvider( |
| 81 | 77 | ||
| 82 | function patchCustomModels(provider: ProviderKind, models: string[]) { | 78 | function patchCustomModels(provider: ProviderKind, models: string[]) { |
| 83 | switch (provider) { | 79 | switch (provider) { |
| 84 | case "claudeCode": | ||
| 85 | return { customClaudeModels: models }; | ||
| 86 | case "cursor": | 80 | case "cursor": |
| 87 | return { customCursorModels: models }; | 81 | return { customCursorModels: models }; |
| 88 | case "codex": | 82 | case "codex": |
| ... | @@ -101,7 +95,6 @@ function SettingsRouteView() { | ... | @@ -101,7 +95,6 @@ function SettingsRouteView() { |
| 101 | Record<ProviderKind, string> | 95 | Record<ProviderKind, string> |
| 102 | >({ | 96 | >({ |
| 103 | codex: "", | 97 | codex: "", |
| 104 | claudeCode: "", | ||
| 105 | cursor: "", | 98 | cursor: "", |
| 106 | }); | 99 | }); |
| 107 | const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState< | 100 | const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState< |
apps/web/src/session-logic.ts+3-1| ... | @@ -10,8 +10,10 @@ import { | ... | @@ -10,8 +10,10 @@ import { |
| 10 | 10 | ||
| 11 | import type { ChatMessage, ProposedPlan, SessionPhase, ThreadSession, TurnDiffSummary } from "./types"; | 11 | import type { ChatMessage, ProposedPlan, SessionPhase, ThreadSession, TurnDiffSummary } from "./types"; |
| 12 | 12 | ||
| 13 | export type ProviderPickerKind = ProviderKind | "claudeCode"; | ||
| 14 | |||
| 13 | export const PROVIDER_OPTIONS: Array<{ | 15 | export const PROVIDER_OPTIONS: Array<{ |
| 14 | value: ProviderKind; | 16 | value: ProviderPickerKind; |
| 15 | label: string; | 17 | label: string; |
| 16 | available: boolean; | 18 | available: boolean; |
| 17 | }> = [ | 19 | }> = [ |
apps/web/src/store.test.ts+2-24| ... | @@ -129,7 +129,7 @@ describe("store pure functions", () => { | ... | @@ -129,7 +129,7 @@ describe("store pure functions", () => { |
| 129 | }); | 129 | }); |
| 130 | 130 | ||
| 131 | describe("store read model sync", () => { | 131 | describe("store read model sync", () => { |
| 132 | it("preserves claude model slugs without an active session", () => { | 132 | it("falls back to the codex default for unsupported provider models without an active session", () => { |
| 133 | const initialState = makeState(makeThread()); | 133 | const initialState = makeState(makeThread()); |
| 134 | const readModel = makeReadModel( | 134 | const readModel = makeReadModel( |
| 135 | makeReadModelThread({ | 135 | makeReadModelThread({ |
| ... | @@ -139,29 +139,7 @@ describe("store read model sync", () => { | ... | @@ -139,29 +139,7 @@ describe("store read model sync", () => { |
| 139 | 139 | ||
| 140 | const next = syncServerReadModel(initialState, readModel); | 140 | const next = syncServerReadModel(initialState, readModel); |
| 141 | 141 | ||
| 142 | expect(next.threads[0]?.model).toBe("claude-opus-4-6"); | 142 | expect(next.threads[0]?.model).toBe("gpt-5.3-codex"); |
| 143 | }); | ||
| 144 | |||
| 145 | it("resolves claude aliases when session provider is claudeCode", () => { | ||
| 146 | const initialState = makeState(makeThread()); | ||
| 147 | const readModel = makeReadModel( | ||
| 148 | makeReadModelThread({ | ||
| 149 | model: "sonnet", | ||
| 150 | session: { | ||
| 151 | threadId: ThreadId.makeUnsafe("thread-1"), | ||
| 152 | status: "ready", | ||
| 153 | providerName: "claudeCode", | ||
| 154 | runtimeMode: "approval-required", | ||
| 155 | activeTurnId: null, | ||
| 156 | lastError: null, | ||
| 157 | updatedAt: "2026-02-27T00:00:00.000Z", | ||
| 158 | }, | ||
| 159 | }), | ||
| 160 | ); | ||
| 161 | |||
| 162 | const next = syncServerReadModel(initialState, readModel); | ||
| 163 | |||
| 164 | expect(next.threads[0]?.model).toBe("claude-sonnet-4-6"); | ||
| 165 | }); | 143 | }); |
| 166 | 144 | ||
| 167 | it("resolves cursor aliases when session provider is cursor", () => { | 145 | it("resolves cursor aliases when session provider is cursor", () => { |
apps/web/src/store.ts+3-11| ... | @@ -145,19 +145,16 @@ function toLegacySessionStatus( | ... | @@ -145,19 +145,16 @@ function toLegacySessionStatus( |
| 145 | } | 145 | } |
| 146 | 146 | ||
| 147 | function toLegacyProvider(providerName: string | null): ProviderKind { | 147 | function toLegacyProvider(providerName: string | null): ProviderKind { |
| 148 | if (providerName === "codex" || providerName === "claudeCode" || providerName === "cursor") { | 148 | if (providerName === "codex" || providerName === "cursor") { |
| 149 | return providerName; | 149 | return providerName; |
| 150 | } | 150 | } |
| 151 | return "codex"; | 151 | return "codex"; |
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | const CODEX_MODEL_SLUGS = new Set<string>(getModelOptions("codex").map((option) => option.slug)); | 154 | const CODEX_MODEL_SLUGS = new Set<string>(getModelOptions("codex").map((option) => option.slug)); |
| 155 | const CLAUDE_MODEL_SLUGS = new Set<string>(getModelOptions("claudeCode").map((option) => option.slug)); | ||
| 156 | const CURSOR_MODEL_SLUGS = new Set<string>(getModelOptions("cursor").map((option) => option.slug)); | 155 | const CURSOR_MODEL_SLUGS = new Set<string>(getModelOptions("cursor").map((option) => option.slug)); |
| 157 | const CURSOR_DISTINCT_MODEL_SLUGS = new Set( | 156 | const CURSOR_DISTINCT_MODEL_SLUGS = new Set( |
| 158 | [...CURSOR_MODEL_SLUGS].filter( | 157 | [...CURSOR_MODEL_SLUGS].filter((slug) => !CODEX_MODEL_SLUGS.has(slug)), |
| 159 | (slug) => !CODEX_MODEL_SLUGS.has(slug) && !CLAUDE_MODEL_SLUGS.has(slug), | ||
| 160 | ), | ||
| 161 | ); | 158 | ); |
| 162 | 159 | ||
| 163 | function inferProviderForThreadModel(input: { | 160 | function inferProviderForThreadModel(input: { |
| ... | @@ -166,7 +163,6 @@ function inferProviderForThreadModel(input: { | ... | @@ -166,7 +163,6 @@ function inferProviderForThreadModel(input: { |
| 166 | }): ProviderKind { | 163 | }): ProviderKind { |
| 167 | if ( | 164 | if ( |
| 168 | input.sessionProviderName === "codex" || | 165 | input.sessionProviderName === "codex" || |
| 169 | input.sessionProviderName === "claudeCode" || | ||
| 170 | input.sessionProviderName === "cursor" | 166 | input.sessionProviderName === "cursor" |
| 171 | ) { | 167 | ) { |
| 172 | return input.sessionProviderName; | 168 | return input.sessionProviderName; |
| ... | @@ -175,10 +171,6 @@ function inferProviderForThreadModel(input: { | ... | @@ -175,10 +171,6 @@ function inferProviderForThreadModel(input: { |
| 175 | if (normalizedCursor && CURSOR_DISTINCT_MODEL_SLUGS.has(normalizedCursor)) { | 171 | if (normalizedCursor && CURSOR_DISTINCT_MODEL_SLUGS.has(normalizedCursor)) { |
| 176 | return "cursor"; | 172 | return "cursor"; |
| 177 | } | 173 | } |
| 178 | const normalizedClaude = normalizeModelSlug(input.model, "claudeCode"); | ||
| 179 | if (normalizedClaude && CLAUDE_MODEL_SLUGS.has(normalizedClaude)) { | ||
| 180 | return "claudeCode"; | ||
| 181 | } | ||
| 182 | const normalizedCodex = normalizeModelSlug(input.model, "codex"); | 174 | const normalizedCodex = normalizeModelSlug(input.model, "codex"); |
| 183 | if (normalizedCodex && CODEX_MODEL_SLUGS.has(normalizedCodex)) { | 175 | if (normalizedCodex && CODEX_MODEL_SLUGS.has(normalizedCodex)) { |
| 184 | return "codex"; | 176 | return "codex"; |
| ... | @@ -190,7 +182,7 @@ function inferProviderForThreadModel(input: { | ... | @@ -190,7 +182,7 @@ function inferProviderForThreadModel(input: { |
| 190 | ) { | 182 | ) { |
| 191 | return "cursor"; | 183 | return "cursor"; |
| 192 | } | 184 | } |
| 193 | return input.model.trim().startsWith("claude-") ? "claudeCode" : "codex"; | 185 | return "codex"; |
| 194 | } | 186 | } |
| 195 | 187 | ||
| 196 | function resolveWsHttpOrigin(): string { | 188 | function resolveWsHttpOrigin(): string { |
packages/contracts/src/orchestration.test.ts+2-2| ... | @@ -128,11 +128,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( | ... | @@ -128,11 +128,11 @@ it.effect("preserves explicit provider and runtime mode in thread.turn.start", ( |
| 128 | text: "hello", | 128 | text: "hello", |
| 129 | attachments: [], | 129 | attachments: [], |
| 130 | }, | 130 | }, |
| 131 | provider: "claudeCode", | 131 | provider: "cursor", |
| 132 | runtimeMode: "full-access", | 132 | runtimeMode: "full-access", |
| 133 | createdAt: "2026-01-01T00:00:00.000Z", | 133 | createdAt: "2026-01-01T00:00:00.000Z", |
| 134 | }); | 134 | }); |
| 135 | assert.strictEqual(parsed.provider, "claudeCode"); | 135 | assert.strictEqual(parsed.provider, "cursor"); |
| 136 | assert.strictEqual(parsed.runtimeMode, "full-access"); | 136 | assert.strictEqual(parsed.runtimeMode, "full-access"); |
| 137 | assert.strictEqual(parsed.interactionMode, DEFAULT_PROVIDER_INTERACTION_MODE); | 137 | assert.strictEqual(parsed.interactionMode, DEFAULT_PROVIDER_INTERACTION_MODE); |
| 138 | }), | 138 | }), |
packages/contracts/src/orchestration.ts+1-1| ... | @@ -27,7 +27,7 @@ export const ORCHESTRATION_WS_CHANNELS = { | ... | @@ -27,7 +27,7 @@ export const ORCHESTRATION_WS_CHANNELS = { |
| 27 | domainEvent: "orchestration.domainEvent", | 27 | domainEvent: "orchestration.domainEvent", |
| 28 | } as const; | 28 | } as const; |
| 29 | 29 | ||
| 30 | export const ProviderKind = Schema.Literals(["codex", "claudeCode", "cursor"]); | 30 | export const ProviderKind = Schema.Literals(["codex", "cursor"]); |
| 31 | export type ProviderKind = typeof ProviderKind.Type; | 31 | export type ProviderKind = typeof ProviderKind.Type; |
| 32 | export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; | 32 | export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; |
| 33 | export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); | 33 | export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); |
packages/contracts/src/providerRuntime.test.ts+3-3| ... | @@ -10,7 +10,7 @@ describe("ProviderRuntimeEvent", () => { | ... | @@ -10,7 +10,7 @@ describe("ProviderRuntimeEvent", () => { |
| 10 | const parsed = decodeRuntimeEvent({ | 10 | const parsed = decodeRuntimeEvent({ |
| 11 | type: "turn.plan.updated", | 11 | type: "turn.plan.updated", |
| 12 | eventId: "event-1", | 12 | eventId: "event-1", |
| 13 | provider: "claudeCode", | 13 | provider: "codex", |
| 14 | sessionId: "runtime-session-1", | 14 | sessionId: "runtime-session-1", |
| 15 | createdAt: "2026-02-28T00:00:00.000Z", | 15 | createdAt: "2026-02-28T00:00:00.000Z", |
| 16 | threadId: "thread-1", | 16 | threadId: "thread-1", |
| ... | @@ -56,7 +56,7 @@ describe("ProviderRuntimeEvent", () => { | ... | @@ -56,7 +56,7 @@ describe("ProviderRuntimeEvent", () => { |
| 56 | const parsed = decodeRuntimeEvent({ | 56 | const parsed = decodeRuntimeEvent({ |
| 57 | type: "user-input.requested", | 57 | type: "user-input.requested", |
| 58 | eventId: "event-2", | 58 | eventId: "event-2", |
| 59 | provider: "claudeCode", | 59 | provider: "codex", |
| 60 | sessionId: "runtime-session-2", | 60 | sessionId: "runtime-session-2", |
| 61 | createdAt: "2026-02-28T00:00:01.000Z", | 61 | createdAt: "2026-02-28T00:00:01.000Z", |
| 62 | threadId: "thread-2", | 62 | threadId: "thread-2", |
| ... | @@ -94,7 +94,7 @@ describe("ProviderRuntimeEvent", () => { | ... | @@ -94,7 +94,7 @@ describe("ProviderRuntimeEvent", () => { |
| 94 | const parsed = decodeRuntimeEvent({ | 94 | const parsed = decodeRuntimeEvent({ |
| 95 | type: "user-input.resolved", | 95 | type: "user-input.resolved", |
| 96 | eventId: "event-3", | 96 | eventId: "event-3", |
| 97 | provider: "claudeCode", | 97 | provider: "codex", |
| 98 | sessionId: "runtime-session-2", | 98 | sessionId: "runtime-session-2", |
| 99 | createdAt: "2026-02-28T00:00:02.000Z", | 99 | createdAt: "2026-02-28T00:00:02.000Z", |
| 100 | threadId: "thread-2", | 100 | threadId: "thread-2", |
packages/contracts/src/providerRuntime.ts-2| ... | @@ -19,8 +19,6 @@ const RuntimeEventRawSource = Schema.Literals([ | ... | @@ -19,8 +19,6 @@ const RuntimeEventRawSource = Schema.Literals([ |
| 19 | "codex.app-server.notification", | 19 | "codex.app-server.notification", |
| 20 | "codex.app-server.request", | 20 | "codex.app-server.request", |
| 21 | "codex.eventmsg", | 21 | "codex.eventmsg", |
| 22 | "claude.sdk.message", | ||
| 23 | "claude.sdk.permission", | ||
| 24 | "codex.sdk.thread-event", | 22 | "codex.sdk.thread-event", |
| 25 | "cursor.acp.notification", | 23 | "cursor.acp.notification", |
| 26 | "cursor.acp.request", | 24 | "cursor.acp.request", |
packages/shared/src/model.test.ts-1| ... | @@ -84,7 +84,6 @@ describe("resolveModelSlug", () => { | ... | @@ -84,7 +84,6 @@ describe("resolveModelSlug", () => { |
| 84 | it("keeps codex defaults for backward compatibility", () => { | 84 | it("keeps codex defaults for backward compatibility", () => { |
| 85 | expect(getDefaultModel()).toBe(DEFAULT_MODEL); | 85 | expect(getDefaultModel()).toBe(DEFAULT_MODEL); |
| 86 | expect(getModelOptions()).toEqual(MODEL_OPTIONS); | 86 | expect(getModelOptions()).toEqual(MODEL_OPTIONS); |
| 87 | expect(getModelOptions("claudeCode")).toEqual([]); | ||
| 88 | expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor); | 87 | expect(getModelOptions("cursor")).toEqual(MODEL_OPTIONS_BY_PROVIDER.cursor); |
| 89 | expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS); | 88 | expect(getCursorModelFamilyOptions()).toEqual(CURSOR_MODEL_FAMILY_OPTIONS); |
| 90 | }); | 89 | }); |