authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-25 01:10:55-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-25 01:10:55-07:00
log06de9e90ab0832727968c58ce72282b94a95ae17
treedb45b2074de7e33495a8a56ecc9efbe9d86099bf
parent589a9d0e29e26b290024899c16f0a4e0d6aebe26
signature Signed by PGP key B5690EEEBB952194

fix(server): keep previously used providers working after upgrades (#8176)


4 files changed, 426 insertions(+), 16 deletions(-)

apps/server/src/server.ts+4-1
......@@ -143,7 +143,10 @@ const PtyAdapterLive = Layer.unwrap(
143143 }),
144144);
145145
146const ServerSettingsLayerLive = ServerSettings.layer.pipe(Layer.provide(ServerSecretStore.layer));
146const ServerSettingsLayerLive = ServerSettings.layer.pipe(
147 Layer.provide(ServerSecretStore.layer),
148 Layer.provideMerge(SqlitePersistenceLayerLive),
149);
147150
148151const NativeTelemetryLayerLive = NativeTelemetryClient.layer.pipe(
149152 Layer.provide(ResourceMonitorBinary.layer),
apps/server/src/serverSettings.test.ts+296-1
......@@ -3,6 +3,7 @@ import {
33 DEFAULT_SERVER_SETTINGS,
44 ProviderDriverKind,
55 ProviderInstanceId,
6 resolveProviderInstanceEnabled,
67 ServerSettings,
78 ServerSettingsPatch,
89} from "@t3tools/contracts";
......@@ -16,8 +17,10 @@ import * as Option from "effect/Option";
1617import * as PlatformError from "effect/PlatformError";
1718import * as Schema from "effect/Schema";
1819import * as Stream from "effect/Stream";
20import * as SqlClient from "effect/unstable/sql/SqlClient";
1921import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
2022import * as ServerConfig from "./config.ts";
23import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts";
2124import * as ServerSettingsModule from "./serverSettings.ts";
2225
2326const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch);
......@@ -26,6 +29,7 @@ const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings);
2629const makeServerSettingsLayer = () =>
2730 ServerSettingsModule.layer.pipe(
2831 Layer.provide(ServerSecretStore.layer),
32 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
2933 Layer.provideMerge(
3034 Layer.fresh(
3135 ServerConfig.layerTest(process.cwd(), {
......@@ -47,6 +51,27 @@ const makeFailingSecretStoreLayer = (cause: ServerSecretStore.SecretStoreError)
4751 }),
4852 );
4953
54const recordProviderUsage = (provider: string, instanceId: string | null = provider) =>
55 Effect.gen(function* () {
56 const sql = yield* SqlClient.SqlClient;
57 yield* sql`
58 INSERT INTO projection_thread_sessions (
59 thread_id,
60 status,
61 provider_name,
62 provider_instance_id,
63 updated_at
64 )
65 VALUES (
66 ${`thread-${instanceId ?? provider}`},
67 ${"ready"},
68 ${provider},
69 ${instanceId},
70 ${"2026-08-25T00:00:00.000Z"}
71 )
72 `;
73 });
74
5075it.layer(NodeServices.layer)("server settings", (it) => {
5176 it.effect("preserves context when reading a provider environment secret fails", () => {
5277 const platformCause = PlatformError.systemError({
......@@ -67,6 +92,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
6792 );
6893 const settingsLayer = ServerSettingsModule.layer.pipe(
6994 Layer.provide(makeFailingSecretStoreLayer(cause)),
95 Layer.provideMerge(Layer.fresh(SqlitePersistenceMemory)),
7096 Layer.provideMerge(configLayer),
7197 );
7298
......@@ -92,6 +118,23 @@ it.layer(NodeServices.layer)("server settings", (it) => {
92118 }).pipe(Effect.provide(settingsLayer));
93119 });
94120
121 it.effect("identifies provider history query failures", () =>
122 Effect.gen(function* () {
123 const serverConfig = yield* ServerConfig.ServerConfig;
124 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
125 const sql = yield* SqlClient.SqlClient;
126 yield* sql`DROP TABLE projection_thread_sessions`;
127
128 const error = yield* Effect.flip(serverSettings.getSettings);
129
130 assert.deepInclude(error, {
131 _tag: "ServerSettingsError",
132 operation: "read-provider-history",
133 settingsPath: serverConfig.settingsPath,
134 });
135 }).pipe(Effect.provide(makeServerSettingsLayer())),
136 );
137
95138 it.effect("decodes nested settings patches", () =>
96139 Effect.gen(function* () {
97140 assert.deepEqual(
......@@ -488,6 +531,251 @@ it.layer(NodeServices.layer)("server settings", (it) => {
488531 }).pipe(Effect.provide(makeServerSettingsLayer())),
489532 );
490533
534 it.effect("enables previously used providers from sparse settings files", () =>
535 Effect.gen(function* () {
536 const serverConfig = yield* ServerConfig.ServerConfig;
537 const fileSystem = yield* FileSystem.FileSystem;
538 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
539 yield* fileSystem.writeFileString(
540 serverConfig.settingsPath,
541 '{"providers":{"opencode":{"serverUrl":"http://127.0.0.1:4096"}}}',
542 );
543 yield* recordProviderUsage("opencode");
544
545 const settings = yield* serverSettings.getSettings;
546
547 assert.isFalse(settings.providers.grok.enabled);
548 assert.isTrue(settings.providers.opencode.enabled);
549 assert.isFalse(settings.providers.cursor.enabled);
550 assert.equal(settings.providers.opencode.serverUrl, "http://127.0.0.1:4096");
551 }).pipe(Effect.provide(makeServerSettingsLayer())),
552 );
553
554 it.effect("preserves existing provider instances without explicit enabled flags", () =>
555 Effect.gen(function* () {
556 const serverConfig = yield* ServerConfig.ServerConfig;
557 const fileSystem = yield* FileSystem.FileSystem;
558 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
559 yield* fileSystem.writeFileString(
560 serverConfig.settingsPath,
561 '{"providerInstances":{"cursor_work":{"driver":"cursor","config":{}},"grok":{"driver":"grok","config":{}},"opencode_work":{"driver":"opencode","config":{"serverUrl":"http://127.0.0.1:4096"}},"opencode_unused":{"driver":"opencode","config":{}}}}',
562 );
563 yield* recordProviderUsage("cursor", "cursor_work");
564 yield* recordProviderUsage("grok", null);
565 yield* recordProviderUsage("opencode", "opencode_work");
566
567 const settings = yield* serverSettings.getSettings;
568
569 assert.isTrue(settings.providers.cursor.enabled);
570 assert.isTrue(settings.providerInstances[ProviderInstanceId.make("cursor_work")]?.enabled);
571 assert.isTrue(settings.providerInstances[ProviderInstanceId.make("grok")]?.enabled);
572 assert.isTrue(settings.providerInstances[ProviderInstanceId.make("opencode_work")]?.enabled);
573 const unused = settings.providerInstances[ProviderInstanceId.make("opencode_unused")];
574 assert.isDefined(unused);
575 assert.isFalse(resolveProviderInstanceEnabled(unused));
576 }).pipe(Effect.provide(makeServerSettingsLayer())),
577 );
578
579 it.effect("preserves explicit provider disables in existing settings files", () =>
580 Effect.gen(function* () {
581 const serverConfig = yield* ServerConfig.ServerConfig;
582 const fileSystem = yield* FileSystem.FileSystem;
583 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
584 yield* fileSystem.writeFileString(
585 serverConfig.settingsPath,
586 '{"providers":{"grok":{"enabled":false},"opencode":{"enabled":false},"cursor":{"enabled":false}},"providerInstances":{"grok":{"driver":"grok","enabled":false,"config":{}},"opencode":{"driver":"opencode","config":{"enabled":false}},"cursor":{"driver":"cursor","enabled":false,"config":{}}}}',
587 );
588 yield* recordProviderUsage("grok");
589 yield* recordProviderUsage("opencode");
590 yield* recordProviderUsage("cursor");
591
592 const settings = yield* serverSettings.getSettings;
593
594 assert.isFalse(settings.providers.grok.enabled);
595 assert.isFalse(settings.providers.opencode.enabled);
596 assert.isFalse(settings.providers.cursor.enabled);
597 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("grok")]?.enabled);
598 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("opencode")]?.enabled);
599 assert.isFalse(settings.providerInstances[ProviderInstanceId.make("cursor")]?.enabled);
600 }).pipe(Effect.provide(makeServerSettingsLayer())),
601 );
602
603 it.effect("keeps unused providers disabled in existing sparse settings files", () =>
604 Effect.gen(function* () {
605 const serverConfig = yield* ServerConfig.ServerConfig;
606 const fileSystem = yield* FileSystem.FileSystem;
607 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
608 yield* fileSystem.writeFileString(serverConfig.settingsPath, "{}");
609
610 const settings = yield* serverSettings.getSettings;
611
612 assert.isFalse(settings.providers.grok.enabled);
613 assert.isFalse(settings.providers.opencode.enabled);
614 assert.isFalse(settings.providers.cursor.enabled);
615 }).pipe(Effect.provide(makeServerSettingsLayer())),
616 );
617
618 it.effect("preserves provider history when no settings file exists", () =>
619 Effect.gen(function* () {
620 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
621 yield* recordProviderUsage("grok");
622
623 const settings = yield* serverSettings.getSettings;
624
625 assert.isTrue(settings.providers.grok.enabled);
626 assert.isFalse(settings.providers.opencode.enabled);
627 assert.isFalse(settings.providers.cursor.enabled);
628 }).pipe(Effect.provide(makeServerSettingsLayer())),
629 );
630
631 it.effect("preserves provider history when the settings file is invalid", () =>
632 Effect.gen(function* () {
633 const serverConfig = yield* ServerConfig.ServerConfig;
634 const fileSystem = yield* FileSystem.FileSystem;
635 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
636 yield* fileSystem.writeFileString(serverConfig.settingsPath, "{invalid json");
637 yield* recordProviderUsage("cursor");
638
639 const settings = yield* serverSettings.getSettings;
640
641 assert.isTrue(settings.providers.cursor.enabled);
642 assert.isFalse(settings.providers.grok.enabled);
643 assert.isFalse(settings.providers.opencode.enabled);
644 }).pipe(Effect.provide(makeServerSettingsLayer())),
645 );
646
647 it.effect("preserves valid provider flags when another settings field is invalid", () =>
648 Effect.gen(function* () {
649 const serverConfig = yield* ServerConfig.ServerConfig;
650 const fileSystem = yield* FileSystem.FileSystem;
651 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
652 yield* fileSystem.writeFileString(
653 serverConfig.settingsPath,
654 '{"addProjectBaseDirectory":42,"providers":{"cursor":{"enabled":false},"grok":{"enabled":true}}}',
655 );
656 yield* recordProviderUsage("cursor");
657
658 const settings = yield* serverSettings.getSettings;
659
660 assert.isFalse(settings.providers.cursor.enabled);
661 assert.isTrue(settings.providers.grok.enabled);
662 assert.isFalse(settings.providers.opencode.enabled);
663 }).pipe(Effect.provide(makeServerSettingsLayer())),
664 );
665
666 it.effect("restores providers from persisted runtime sessions", () =>
667 Effect.gen(function* () {
668 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
669 const sql = yield* SqlClient.SqlClient;
670 yield* sql`
671 INSERT INTO provider_session_runtime (
672 thread_id,
673 provider_name,
674 provider_instance_id,
675 adapter_key,
676 status,
677 last_seen_at
678 )
679 VALUES (
680 ${"thread-opencode-runtime"},
681 ${"opencode"},
682 ${"opencode"},
683 ${"opencode"},
684 ${"ready"},
685 ${"2026-08-25T00:00:00.000Z"}
686 )
687 `;
688
689 const settings = yield* serverSettings.getSettings;
690
691 assert.isFalse(settings.providers.grok.enabled);
692 assert.isTrue(settings.providers.opencode.enabled);
693 assert.isFalse(settings.providers.cursor.enabled);
694 }).pipe(Effect.provide(makeServerSettingsLayer())),
695 );
696
697 it.effect("persists explicit disables after a provider has been used", () =>
698 Effect.gen(function* () {
699 const serverConfig = yield* ServerConfig.ServerConfig;
700 const fileSystem = yield* FileSystem.FileSystem;
701 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
702 yield* recordProviderUsage("grok");
703
704 assert.isTrue((yield* serverSettings.getSettings).providers.grok.enabled);
705
706 const settings = yield* serverSettings.updateSettings({
707 providers: { grok: { enabled: false } },
708 });
709 assert.isFalse(settings.providers.grok.enabled);
710
711 const raw = yield* fileSystem.readFileString(serverConfig.settingsPath);
712 // @effect-diagnostics-next-line preferSchemaOverJson:off
713 assert.isFalse(JSON.parse(raw).providers.grok.enabled);
714 }).pipe(Effect.provide(makeServerSettingsLayer())),
715 );
716
717 it.effect("persists explicit provider enables before their first use", () =>
718 Effect.gen(function* () {
719 const serverConfig = yield* ServerConfig.ServerConfig;
720 const fileSystem = yield* FileSystem.FileSystem;
721 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
722
723 yield* serverSettings.updateSettings({
724 providers: {
725 cursor: { enabled: true },
726 grok: { enabled: true },
727 opencode: { enabled: true },
728 },
729 });
730 yield* serverSettings.updateSettings({ addProjectBaseDirectory: "~/Development" });
731
732 const raw = yield* fileSystem.readFileString(serverConfig.settingsPath);
733 // @effect-diagnostics-next-line preferSchemaOverJson:off
734 const persisted = JSON.parse(raw);
735 assert.isTrue(persisted.providers.cursor.enabled);
736 assert.isTrue(persisted.providers.grok.enabled);
737 assert.isTrue(persisted.providers.opencode.enabled);
738 }).pipe(Effect.provide(makeServerSettingsLayer())),
739 );
740
741 it.effect("keeps optional providers disabled after a new installation writes settings", () =>
742 Effect.gen(function* () {
743 const serverConfig = yield* ServerConfig.ServerConfig;
744 const fileSystem = yield* FileSystem.FileSystem;
745 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
746
747 const initial = yield* serverSettings.getSettings;
748 assert.isFalse(initial.providers.grok.enabled);
749 assert.isFalse(initial.providers.opencode.enabled);
750 assert.isFalse(initial.providers.cursor.enabled);
751
752 const next = yield* serverSettings.updateSettings({
753 addProjectBaseDirectory: "~/Development",
754 providerInstances: {
755 [ProviderInstanceId.make("grok")]: {
756 driver: ProviderDriverKind.make("grok"),
757 config: {},
758 },
759 },
760 });
761
762 assert.isFalse(next.providers.grok.enabled);
763 assert.isFalse(next.providers.opencode.enabled);
764 assert.isFalse(next.providers.cursor.enabled);
765 const grok = next.providerInstances[ProviderInstanceId.make("grok")];
766 assert.isDefined(grok);
767 assert.isFalse(resolveProviderInstanceEnabled(grok));
768
769 const raw = yield* fileSystem.readFileString(serverConfig.settingsPath);
770 // @effect-diagnostics-next-line preferSchemaOverJson:off
771 const persisted = JSON.parse(raw);
772 assert.isFalse(persisted.providers.cursor.enabled);
773 assert.isFalse(persisted.providers.grok.enabled);
774 assert.isFalse(persisted.providers.opencode.enabled);
775 assert.isUndefined(persisted.providerInstances.grok.enabled);
776 }).pipe(Effect.provide(makeServerSettingsLayer())),
777 );
778
491779 it.effect("folds a legacy in-config enabled flag into the envelope on load", () =>
492780 Effect.gen(function* () {
493781 const serverConfig = yield* ServerConfig.ServerConfig;
......@@ -635,7 +923,7 @@ it.layer(NodeServices.layer)("server settings", (it) => {
635923 }).pipe(Effect.provide(makeServerSettingsLayer())),
636924 );
637925
638 it.effect("writes only non-default server settings to disk", () =>
926 it.effect("writes non-default settings and explicit optional provider defaults to disk", () =>
639927 Effect.gen(function* () {
640928 const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
641929 const serverConfig = yield* ServerConfig.ServerConfig;
......@@ -672,7 +960,14 @@ it.layer(NodeServices.layer)("server settings", (it) => {
672960 codex: {
673961 binaryPath: "/opt/homebrew/bin/codex",
674962 },
963 cursor: {
964 enabled: false,
965 },
966 grok: {
967 enabled: false,
968 },
675969 opencode: {
970 enabled: false,
676971 serverUrl: "http://127.0.0.1:4096",
677972 serverPassword: "secret-password",
678973 },
apps/server/src/serverSettings.ts+125-14
......@@ -42,6 +42,7 @@ import * as Schema from "effect/Schema";
4242import * as Semaphore from "effect/Semaphore";
4343import * as Scope from "effect/Scope";
4444import * as Stream from "effect/Stream";
45import * as SqlClient from "effect/unstable/sql/SqlClient";
4546import { writeFileStringAtomically } from "./atomicWrite.ts";
4647import * as ServerConfig from "./config.ts";
4748import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct";
......@@ -230,6 +231,66 @@ export const layerTest = (overrides: DeepPartial<ServerSettings> = {}) =>
230231
231232const ServerSettingsJson = fromLenientJson(ServerSettings);
232233const decodeServerSettingsJsonExit = Schema.decodeUnknownExit(ServerSettingsJson);
234const PersistedOptionalProviderSettings = Schema.Struct({
235 providers: Schema.optionalKey(
236 Schema.Struct({
237 cursor: Schema.optionalKey(Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) })),
238 grok: Schema.optionalKey(Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) })),
239 opencode: Schema.optionalKey(Schema.Struct({ enabled: Schema.optionalKey(Schema.Boolean) })),
240 }),
241 ),
242});
243const decodePersistedOptionalProviderSettingsJsonExit = Schema.decodeUnknownExit(
244 fromLenientJson(PersistedOptionalProviderSettings),
245);
246
247function restoreUsedProviders(
248 settings: ServerSettings,
249 persisted: typeof PersistedOptionalProviderSettings.Type,
250 providerHistory: ReadonlyArray<{
251 readonly providerName: string;
252 readonly providerInstanceId: string | null;
253 }>,
254): ServerSettings {
255 const usedProviders = new Set(providerHistory.map(({ providerName }) => providerName));
256 const usedProviderInstances = new Set(
257 providerHistory.map(
258 ({ providerName, providerInstanceId }) => providerInstanceId ?? providerName,
259 ),
260 );
261 const providerInstances = Object.fromEntries(
262 Object.entries(settings.providerInstances).map(([instanceId, instance]) => [
263 instanceId,
264 instance.enabled === undefined &&
265 (instance.driver === "cursor" ||
266 instance.driver === "grok" ||
267 instance.driver === "opencode") &&
268 usedProviderInstances.has(instanceId)
269 ? { ...instance, enabled: true }
270 : instance,
271 ]),
272 );
273
274 return {
275 ...settings,
276 providers: {
277 ...settings.providers,
278 cursor: {
279 ...settings.providers.cursor,
280 enabled: persisted.providers?.cursor?.enabled ?? usedProviders.has("cursor"),
281 },
282 grok: {
283 ...settings.providers.grok,
284 enabled: persisted.providers?.grok?.enabled ?? usedProviders.has("grok"),
285 },
286 opencode: {
287 ...settings.providers.opencode,
288 enabled: persisted.providers?.opencode?.enabled ?? usedProviders.has("opencode"),
289 },
290 },
291 providerInstances,
292 };
293}
233294
234295function resolveTextGenerationProvider(settings: ServerSettings): ServerSettings {
235296 return isModelSelectionProviderEnabled(settings, settings.textGenerationModelSelection)
......@@ -265,6 +326,17 @@ const ATOMIC_SETTINGS_KEYS: ReadonlySet<string> = new Set([
265326 "textGenerationModelSelection",
266327]);
267328
329// Preserve both enabled states because provider history cannot recover a new opt-in.
330const PERSISTED_SERVER_SETTINGS_DEFAULTS = {
331 ...DEFAULT_SERVER_SETTINGS,
332 providers: {
333 ...DEFAULT_SERVER_SETTINGS.providers,
334 cursor: { ...DEFAULT_SERVER_SETTINGS.providers.cursor, enabled: undefined },
335 grok: { ...DEFAULT_SERVER_SETTINGS.providers.grok, enabled: undefined },
336 opencode: { ...DEFAULT_SERVER_SETTINGS.providers.opencode, enabled: undefined },
337 },
338};
339
268340function stripDefaultServerSettings(current: unknown, defaults: unknown): unknown | undefined {
269341 if (Array.isArray(current) || Array.isArray(defaults)) {
270342 return Equal.equals(current, defaults) ? undefined : current;
......@@ -304,6 +376,7 @@ const make = Effect.gen(function* () {
304376 const fs = yield* FileSystem.FileSystem;
305377 const pathService = yield* Path.Path;
306378 const secretStore = yield* ServerSecretStore.ServerSecretStore;
379 const sql = yield* SqlClient.SqlClient;
307380 const writeSemaphore = yield* Semaphore.make(1);
308381 const cacheKey = "settings" as const;
309382 const changesPubSub = yield* PubSub.unbounded<ServerSettings>();
......@@ -338,21 +411,59 @@ const make = Effect.gen(function* () {
338411 );
339412
340413 const loadSettingsFromDisk = Effect.gen(function* () {
341 if (!(yield* readConfigExists)) {
342 return DEFAULT_SERVER_SETTINGS;
414 let settings = DEFAULT_SERVER_SETTINGS;
415 let persisted: typeof PersistedOptionalProviderSettings.Type = {};
416
417 if (yield* readConfigExists) {
418 const raw = yield* readRawConfig;
419 const decoded = decodeServerSettingsJsonExit(raw);
420 const persistedSettings = decodePersistedOptionalProviderSettingsJsonExit(raw);
421 if (persistedSettings._tag === "Success") {
422 persisted = persistedSettings.value;
423 }
424 if (decoded._tag === "Failure" || persistedSettings._tag === "Failure") {
425 const failure = decoded._tag === "Failure" ? decoded : persistedSettings;
426 if (failure._tag === "Failure") {
427 yield* Effect.logWarning("failed to parse settings.json, using defaults", {
428 path: settingsPath,
429 issues: Cause.pretty(failure.cause),
430 cause: failure.cause,
431 });
432 }
433 } else {
434 settings = decoded.value;
435 }
343436 }
344437
345 const raw = yield* readRawConfig;
346 const decoded = decodeServerSettingsJsonExit(raw);
347 if (decoded._tag === "Failure") {
348 yield* Effect.logWarning("failed to parse settings.json, using defaults", {
349 path: settingsPath,
350 issues: Cause.pretty(decoded.cause),
351 cause: decoded.cause,
352 });
353 return DEFAULT_SERVER_SETTINGS;
354 }
355 return foldProviderInstanceEnabledFlags(decoded.value);
438 const providerHistory = yield* sql<{
439 readonly providerName: string;
440 readonly providerInstanceId: string | null;
441 }>`
442 SELECT DISTINCT
443 provider_name AS "providerName",
444 provider_instance_id AS "providerInstanceId"
445 FROM projection_thread_sessions
446 WHERE provider_name IN ('cursor', 'grok', 'opencode')
447 UNION
448 SELECT DISTINCT
449 provider_name AS "providerName",
450 provider_instance_id AS "providerInstanceId"
451 FROM provider_session_runtime
452 WHERE provider_name IN ('cursor', 'grok', 'opencode')
453 `.pipe(
454 Effect.mapError(
455 (cause) =>
456 new ServerSettingsError({
457 settingsPath,
458 operation: "read-provider-history",
459 cause,
460 }),
461 ),
462 );
463
464 return foldProviderInstanceEnabledFlags(
465 restoreUsedProviders(settings, persisted, providerHistory),
466 );
356467 });
357468
358469 const settingsCache = yield* Cache.make<typeof cacheKey, ServerSettings, ServerSettingsError>({
......@@ -528,7 +639,7 @@ const make = Effect.gen(function* () {
528639 const writeSettingsAtomically = Effect.fnUntraced(
529640 function* (settings: ServerSettings) {
530641 const sparseSettingsJson = yield* encodeServerSettingsJson(
531 stripDefaultServerSettings(settings, DEFAULT_SERVER_SETTINGS) ?? {},
642 stripDefaultServerSettings(settings, PERSISTED_SERVER_SETTINGS_DEFAULTS) ?? {},
532643 );
533644
534645 return yield* writeFileStringAtomically({
packages/contracts/src/settings.ts+1
......@@ -755,6 +755,7 @@ export const ServerSettingsOperation = Schema.Literals([
755755 "normalize",
756756 "check-exists",
757757 "read-file",
758 "read-provider-history",
758759 "read-secret",
759760 "remove-secret",
760761 "remove-stale-secret",