1import {
2 DEFAULT_SERVER_SETTINGS,
3 EnvironmentId,
4 ProjectId,
5 ProviderDriverKind,
6 ProviderInstanceId,
7} from "@t3tools/contracts";
8import { describe, expect, it } from "@effect/vitest";
9
10import {
11 filterSharedServerPatch,
12 findSharedSettingsMismatches,
13 pickSharedServerSettings,
14 splitSharedServerPatch,
15 supportsSharedSettingsSync,
16} from "./sharedSettings.ts";
17
18const primaryId = EnvironmentId.make("env-primary");
19const laptopId = EnvironmentId.make("env-laptop");
20const boxId = EnvironmentId.make("env-box");
21const restartCapabilities = { threadRestartContinuation: true };
22
23describe("supportsSharedSettingsSync", () => {
24 it("accepts only connected servers that advertise the shared-settings capability", () => {
25 expect(
26 supportsSharedSettingsSync({
27 connection: { phase: "connected" },
28 serverConfig: { environment: { capabilities: { threadAutoSettlement: true } } },
29 }),
30 ).toBe(true);
31 expect(
32 supportsSharedSettingsSync({
33 connection: { phase: "connected" },
34 serverConfig: { environment: { capabilities: {} } },
35 }),
36 ).toBe(false);
37 expect(
38 supportsSharedSettingsSync({
39 connection: { phase: "reconnecting" },
40 serverConfig: { environment: { capabilities: { threadAutoSettlement: true } } },
41 }),
42 ).toBe(false);
43 });
44});
45
46describe("splitSharedServerPatch", () => {
47 it("keeps project overrides local: project ids belong to one environment", () => {
48 const patch = {
49 projectSettingsOverrides: { [ProjectId.make("project")]: { defaultAutoPull: true } },
50 sidebarAutoSettleOnMerge: false,
51 };
52 expect(splitSharedServerPatch(patch)).toEqual({
53 sharedPatch: { sidebarAutoSettleOnMerge: false },
54 localPatch: { projectSettingsOverrides: patch.projectSettingsOverrides },
55 });
56 });
57
58 it.each([
59 {
60 instanceId: ProviderInstanceId.make("codex"),
61 model: "gpt-5.6-sol",
62 options: [{ id: "reasoningEffort", value: "low" }],
63 },
64 {
65 instanceId: ProviderInstanceId.make("claudeAgent"),
66 model: "claude-sonnet-4-6",
67 options: [{ id: "effort", value: "high" }],
68 },
69 DEFAULT_SERVER_SETTINGS.textGenerationModelSelection,
70 ])("shares the text generation model and options, including reset (%j)", (selection) => {
71 const patch = { textGenerationModelSelection: selection };
72 expect(splitSharedServerPatch(patch)).toEqual({ sharedPatch: patch, localPatch: {} });
73 expect(pickSharedServerSettings({ ...DEFAULT_SERVER_SETTINGS, ...patch })).toMatchObject(patch);
74 const environment = {
75 environmentId: boxId,
76 label: "Remote Box",
77 syncEligible: true,
78 settings: {
79 ...DEFAULT_SERVER_SETTINGS,
80 textGenerationModelSelection: { ...selection, model: "different-model" },
81 },
82 };
83 const input = {
84 primaryEnvironmentId: primaryId,
85 primarySettings: { ...DEFAULT_SERVER_SETTINGS, ...patch },
86 environments: [environment],
87 };
88 expect(findSharedSettingsMismatches(input)).toEqual([
89 { environmentId: boxId, label: "Remote Box" },
90 ]);
91 expect(
92 findSharedSettingsMismatches({
93 ...input,
94 environments: [{ ...environment, settings: input.primarySettings }],
95 }),
96 ).toEqual([]);
97 });
98
99 it("routes preference keys to the shared patch and machine keys to the local patch", () => {
100 const { sharedPatch, localPatch } = splitSharedServerPatch({
101 sidebarAutoSettleAfterDays: 7,
102 sidebarAutoSettleOnMerge: false,
103 continueThreadsAfterServerUpdate: true,
104 enableAgentBrowserAccess: false,
105 defaultThreadEnvMode: "worktree",
106 newWorktreesStartFromOrigin: true,
107 });
108 expect(sharedPatch).toEqual({
109 sidebarAutoSettleAfterDays: 7,
110 sidebarAutoSettleOnMerge: false,
111 continueThreadsAfterServerUpdate: true,
112 newWorktreesStartFromOrigin: true,
113 });
114 expect(localPatch).toEqual({
115 enableAgentBrowserAccess: false,
116 defaultThreadEnvMode: "worktree",
117 });
118 });
119});
120
121describe("pickSharedServerSettings", () => {
122 it("returns only the shared keys", () => {
123 expect(
124 Object.keys(pickSharedServerSettings(DEFAULT_SERVER_SETTINGS, restartCapabilities)).sort(),
125 ).toEqual([
126 "autoResumeLimitedThreads",
127 "continueThreadsAfterServerUpdate",
128 "newWorktreesStartFromOrigin",
129 "sidebarAutoSettleAfterDays",
130 "sidebarAutoSettleOnMerge",
131 "snoozeLimitedThreads",
132 "sourceControlWritingStyle",
133 "textGenerationModelSelection",
134 ]);
135 });
136});
137
138describe("filterSharedServerPatch", () => {
139 it.each([true, false])(
140 "resets a disabled default provider only on the originating environment (%s)",
141 (targetIsSource) => {
142 const settings = {
143 ...DEFAULT_SERVER_SETTINGS,
144 providerInstances: {
145 codex: { driver: ProviderDriverKind.make("codex"), enabled: false, config: {} },
146 claudeAgent: {
147 driver: ProviderDriverKind.make("claudeAgent"),
148 enabled: true,
149 config: {},
150 },
151 },
152 textGenerationModelSelection: {
153 instanceId: ProviderInstanceId.make("claudeAgent"),
154 model: "claude-opus-4-6",
155 },
156 };
157 const patch = {
158 textGenerationModelSelection: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection,
159 continueThreadsAfterServerUpdate: true,
160 sidebarAutoSettleAfterDays: 7,
161 };
162 expect(filterSharedServerPatch(patch, undefined, settings, settings, targetIsSource)).toEqual(
163 {
164 ...(targetIsSource
165 ? { textGenerationModelSelection: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection }
166 : {}),
167 sidebarAutoSettleAfterDays: 7,
168 },
169 );
170 },
171 );
172
173 it.each(["missing", "disabled", "different-driver", "enabled"] as const)(
174 "shares a custom model only when its target provider is enabled (%s)",
175 (availability) => {
176 const instanceId = ProviderInstanceId.make("codex_personal");
177 const selection = {
178 instanceId,
179 model: "gpt-5.6-luna",
180 options: [{ id: "reasoningEffort", value: "low" }],
181 };
182 const instance = {
183 driver: ProviderDriverKind.make(
184 availability === "different-driver" ? "claudeAgent" : "codex",
185 ),
186 enabled: availability !== "disabled",
187 config: {},
188 };
189 const settings = {
190 ...DEFAULT_SERVER_SETTINGS,
191 providerInstances: availability === "missing" ? {} : { [instanceId]: instance },
192 };
193 const patch = { sidebarAutoSettleAfterDays: 7, textGenerationModelSelection: selection };
194 const sourceSettings = {
195 ...settings,
196 providerInstances: {
197 [instanceId]: { ...instance, driver: ProviderDriverKind.make("codex"), enabled: true },
198 },
199 };
200 expect(filterSharedServerPatch(patch, restartCapabilities, settings, sourceSettings)).toEqual(
201 availability === "enabled" ? patch : { sidebarAutoSettleAfterDays: 7 },
202 );
203 const primarySettings = {
204 ...sourceSettings,
205 textGenerationModelSelection: selection,
206 };
207 expect(
208 findSharedSettingsMismatches({
209 primaryEnvironmentId: primaryId,
210 primarySettings,
211 environments: [
212 { environmentId: boxId, label: "Remote Box", syncEligible: true, settings },
213 ],
214 }),
215 ).toEqual(availability === "enabled" ? [{ environmentId: boxId, label: "Remote Box" }] : []);
216 },
217 );
218
219 it.each([true, false])("preserves supported restart preference %s", (enabled) => {
220 const patch = { continueThreadsAfterServerUpdate: enabled, sidebarAutoSettleAfterDays: 7 };
221 expect(filterSharedServerPatch(patch, restartCapabilities)).toEqual(patch);
222 });
223
224 it.each([undefined, {}, { threadRestartContinuation: false }])(
225 "omits only the unsupported restart preference with capabilities %j",
226 (capabilities) => {
227 expect(
228 filterSharedServerPatch(
229 { continueThreadsAfterServerUpdate: true, sidebarAutoSettleAfterDays: 7 },
230 capabilities,
231 ),
232 ).toEqual({ sidebarAutoSettleAfterDays: 7 });
233 expect(pickSharedServerSettings(DEFAULT_SERVER_SETTINGS, capabilities)).not.toHaveProperty(
234 "continueThreadsAfterServerUpdate",
235 );
236 },
237 );
238});
239
240describe("findSharedSettingsMismatches", () => {
241 const primarySettings = { ...DEFAULT_SERVER_SETTINGS, sidebarAutoSettleAfterDays: 7 };
242
243 it.each([true, false])(
244 "detects remote restart continuation drift when the preference is %s",
245 (enabled) => {
246 const settings = { ...primarySettings, continueThreadsAfterServerUpdate: enabled };
247 const remoteSettings = { ...settings, continueThreadsAfterServerUpdate: !enabled };
248 const environment = {
249 environmentId: boxId,
250 label: "Remote Box",
251 syncEligible: true,
252 settings: remoteSettings,
253 capabilities: restartCapabilities,
254 };
255 expect(
256 findSharedSettingsMismatches({
257 primaryEnvironmentId: primaryId,
258 primarySettings: settings,
259 primaryCapabilities: restartCapabilities,
260 environments: [environment],
261 }),
262 ).toEqual([{ environmentId: boxId, label: "Remote Box" }]);
263 expect(
264 findSharedSettingsMismatches({
265 primaryEnvironmentId: primaryId,
266 primarySettings: settings,
267 primaryCapabilities: restartCapabilities,
268 environments: [
269 {
270 ...environment,
271 settings: Object.assign(
272 {},
273 remoteSettings,
274 pickSharedServerSettings(settings, restartCapabilities),
275 ),
276 },
277 ],
278 }),
279 ).toEqual([]);
280 },
281 );
282
283 it.each([
284 [undefined, restartCapabilities],
285 [restartCapabilities, undefined],
286 [undefined, undefined],
287 ])(
288 "ignores restart drift unless both servers support it (%j, %j)",
289 (primaryCapabilities, capabilities) => {
290 const environment = {
291 environmentId: boxId,
292 label: "Remote Box",
293 syncEligible: true,
294 capabilities,
295 settings: { ...primarySettings, continueThreadsAfterServerUpdate: true },
296 };
297 const input = {
298 primaryEnvironmentId: primaryId,
299 primarySettings,
300 primaryCapabilities,
301 environments: [environment],
302 };
303 expect(findSharedSettingsMismatches(input)).toEqual([]);
304 expect(
305 findSharedSettingsMismatches({
306 ...input,
307 environments: [
308 {
309 ...environment,
310 settings: { ...environment.settings, sidebarAutoSettleAfterDays: 14 },
311 },
312 ],
313 }),
314 ).toEqual([{ environmentId: boxId, label: "Remote Box" }]);
315 },
316 );
317
318 it("lists sync-eligible environments whose shared settings differ", () => {
319 const mismatches = findSharedSettingsMismatches({
320 primaryEnvironmentId: primaryId,
321 primarySettings,
322 environments: [
323 {
324 environmentId: primaryId,
325 label: "Desktop",
326 syncEligible: true,
327 settings: primarySettings,
328 },
329 {
330 environmentId: laptopId,
331 label: "Laptop",
332 syncEligible: true,
333 settings: primarySettings,
334 },
335 {
336 environmentId: boxId,
337 label: "Remote Box",
338 syncEligible: true,
339 settings: DEFAULT_SERVER_SETTINGS,
340 },
341 ],
342 });
343 expect(mismatches).toEqual([{ environmentId: boxId, label: "Remote Box" }]);
344 });
345
346 it("ignores machine-only differences", () => {
347 const mismatches = findSharedSettingsMismatches({
348 primaryEnvironmentId: primaryId,
349 primarySettings,
350 environments: [
351 {
352 environmentId: boxId,
353 label: "Remote Box",
354 syncEligible: true,
355 settings: {
356 ...primarySettings,
357 enableAgentBrowserAccess: false,
358 defaultThreadEnvMode:
359 primarySettings.defaultThreadEnvMode === "local" ? "worktree" : "local",
360 },
361 },
362 ],
363 });
364 expect(mismatches).toEqual([]);
365 });
366
367 it("reports nothing until the primary environment's settings are loaded", () => {
368 const environments = [
369 {
370 environmentId: boxId,
371 label: "Remote Box",
372 syncEligible: true,
373 settings: primarySettings,
374 },
375 ];
376 expect(
377 findSharedSettingsMismatches({ primaryEnvironmentId: null, primarySettings, environments }),
378 ).toEqual([]);
379 expect(
380 findSharedSettingsMismatches({
381 primaryEnvironmentId: primaryId,
382 primarySettings: null,
383 environments,
384 }),
385 ).toEqual([]);
386 });
387
388 it("skips ineligible environments and environments without a loaded config", () => {
389 const mismatches = findSharedSettingsMismatches({
390 primaryEnvironmentId: primaryId,
391 primarySettings,
392 environments: [
393 {
394 environmentId: laptopId,
395 label: "Laptop",
396 syncEligible: false,
397 settings: DEFAULT_SERVER_SETTINGS,
398 },
399 { environmentId: boxId, label: "Remote Box", syncEligible: true, settings: null },
400 ],
401 });
402 expect(mismatches).toEqual([]);
403 });
404});