11import * as NodeServices from "@effect/platform-node/NodeServices" ;
22import { assert , it } from "@effect/vitest" ;
33import {
4+ AuthOrchestrationReadScope ,
45 AuthSessionId ,
56 EnvironmentId ,
67 ProjectId ,
@@ -9,6 +10,7 @@ import {
910 ProviderInstanceId ,
1011 WS_METHODS ,
1112 WsServerRefreshProvidersRpc ,
13+ WsRpcGroup ,
1214 WsSubscribeServerConfigRpc ,
1315 type OrchestrationV2StoredEvent ,
1416 type OrchestrationV2ThreadShell ,
@@ -19,6 +21,7 @@ import {
1921} from "@t3tools/contracts" ;
2022import * as Deferred from "effect/Deferred" ;
2123import * as DateTime from "effect/DateTime" ;
24+ import * as Exit from "effect/Exit" ;
2225import * as Effect from "effect/Effect" ;
2326import * as Layer from "effect/Layer" ;
2427import * as PubSub from "effect/PubSub" ;
@@ -29,6 +32,7 @@ import { Headers } from "effect/http";
2932import { RpcGroup , RpcMessage , RpcServer } from "effect/rpc" ;
3033import * as SqlClient from "effect/sql/SqlClient" ;
3134
35+ import * as RpcAuthorization from "./auth/RpcAuthorization.ts" ;
3236import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts" ;
3337import * as ServerBrowser from "./preview/ServerBrowser.ts" ;
3438import * as AcpRegistryRuntimeCoordinator from "@t3tools/provider-acp-registry/server/AcpRegistryRuntimeCoordinator" ;
@@ -869,6 +873,79 @@ it.effect("on-demand clients get provider config without workspace snapshots", (
869873 ) ,
870874) ;
871875
876+ it . effect ( "server.getSettings returns redacted settings to installed native clients" , ( ) =>
877+ Effect . gen ( function * ( ) {
878+ const settings = yield * ServerSettings . ServerSettingsService ;
879+ const saved = yield * settings . updateSettings ( {
880+ defaultModelSelection : {
881+ instanceId : ProviderInstanceId . make ( "claudeAgent" ) ,
882+ model : "claude-haiku-4-5" ,
883+ } ,
884+ sourceControlHosts : { gitcafe : { token : "gitcafe-secret" } } ,
885+ } ) ;
886+ const exit = yield * Deferred . make < Exit . Exit < unknown , unknown > > ( ) ;
887+ const server = yield * RpcServer . makeNoSerialization ( WsRpcGroup , {
888+ ...WS_RPC_SERVER_OPTIONS ,
889+ onFromServer : ( response ) =>
890+ response . _tag === "Exit" ? Deferred . succeed ( exit , response . exit ) : Effect . void ,
891+ } ) . pipe (
892+ Effect . provide (
893+ layerWsRpc (
894+ {
895+ sessionId : AuthSessionId . make ( "settings-test" ) ,
896+ subject : "settings-test" ,
897+ method : "browser-session-cookie" ,
898+ scopes : [ ] ,
899+ } ,
900+ { } ,
901+ yield * ServerBrowser . ServerBrowser ,
902+ ) . pipe (
903+ Layer . provide (
904+ Layer . mergeAll (
905+ layerUnusedConfigRpcServices ,
906+ Layer . mock ( ExternalLauncher . ExternalLauncher ) ( { } ) ,
907+ Layer . mock ( ProviderRegistry . ProviderRegistry ) ( { } ) ,
908+ Layer . mock ( UsageLimitSources . UsageLimitSources ) ( { } ) ,
909+ Layer . mock ( RemoteOpenTargets . RemoteOpenTargets ) ( { } ) ,
910+ Layer . mock ( DirectEndpoints . DirectEndpoints ) ( { } ) ,
911+ Layer . mock ( ManagedProjectFolders . ManagedProjectFolders ) ( {
912+ scratchRoot : Effect . succeedNone ,
913+ namedProjectsRoot : process . cwd ( ) ,
914+ } ) ,
915+ Layer . mock ( ServerEnvironment . ServerEnvironment ) ( { } ) ,
916+ Layer . mock ( EnvironmentAuth . EnvironmentAuth ) ( { } ) ,
917+ ) ,
918+ ) ,
919+ ) ,
920+ ) ,
921+ ) ;
922+ // Swift TestFlight builds send this tag on connect and from the composer.
923+ yield * server . write ( 0 , {
924+ _tag : "Request" ,
925+ id : RpcMessage . RequestId ( "settings" ) ,
926+ tag : "server.getSettings" ,
927+ payload : { } ,
928+ headers : Headers . empty ,
929+ } ) ;
930+ const result = yield * Deferred . await ( exit ) ;
931+ if ( ! Exit . isSuccess ( result ) ) return yield * Effect . die ( result . cause ) ;
932+ const value = result . value as typeof saved ;
933+ assert . deepEqual ( value , ServerSettings . redactServerSettingsForClient ( saved ) ) ;
934+ assert . deepEqual ( value . defaultModelSelection , saved . defaultModelSelection ) ;
935+ assert . notInclude ( JSON . stringify ( value ) , "gitcafe-secret" ) ;
936+ } ) . pipe (
937+ Effect . provide (
938+ Layer . mergeAll (
939+ Layer . fresh ( layerConfigSources ) ,
940+ Layer . mock ( PreviewAutomationBroker . PreviewAutomationBroker ) ( { } ) ,
941+ Layer . mock ( ServerBrowser . ServerBrowser ) ( { } ) ,
942+ RpcAuthorization . layer ( [ AuthOrchestrationReadScope ] ) ,
943+ ) ,
944+ ) ,
945+ Effect . scoped ,
946+ ) ,
947+ ) ;
948+
872949// Compaction keeps the oldest and newest event per message, so a cursor older
873950// than the deleted rows still replays each message where the client would
874951// first have seen it (reducers append on first sight) and reaches the same
0 commit comments