1import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
2import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay";
3import * as Random from "effect/Random";
4import * as EnvironmentKeys from "./cloud/environmentKeys.ts";
5import * as AssetSigner from "./assets/AssetSigner.ts";
6import * as DatabaseStorage from "./persistence/DatabaseStorage.ts";
7import * as StorageCleanup from "./storageCleanup.ts";
8import * as PullRequestSyncReactor from "./orchestration-v2/PullRequestSyncReactor.ts";
9import * as PullRequestWatchReactor from "./orchestration-v2/PullRequestWatchReactor.ts";
10import * as DelegatedTaskQuestionReactor from "./orchestration-v2/DelegatedTaskQuestionReactor.ts";
11// Effect wraps this raw Node HTTP server after its startup and write-error guards attach.
12// @effect-diagnostics nodeBuiltinImport:off
13import * as NodeHttp from "node:http";
14
15import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
16import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
17import * as NodeServices from "@effect/platform-node/NodeServices";
18import { EnvironmentHttpApi, type RepositoryIdentity } from "@t3tools/contracts";
19import * as HostProcess from "@t3tools/shared/HostProcess";
20import * as Cause from "effect/Cause";
21import * as Duration from "effect/Duration";
22import * as Deferred from "effect/Deferred";
23import * as Effect from "effect/Effect";
24import * as Option from "effect/Option";
25import * as Layer from "effect/Layer";
26import * as Stream from "effect/Stream";
27import { FetchHttpClient, HttpRouter, HttpServer } from "effect/http";
28import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
29
30import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
31import * as HostPowerMonitor from "./background/HostPowerMonitor.ts";
32import * as KeepAwake from "./background/KeepAwake.ts";
33import * as ServerConfig from "./config.ts";
34import { withUntracedRequests } from "./http.ts";
35import * as ServerHttp from "./http.ts";
36import {
37 guardHttpResponseWriteErrors,
38 keepIdleConnections,
39 rejectRequestsUntilServing,
40} from "./httpResponseErrorGuard.ts";
41import { resolveLauncherPath } from "./cloud/pinnedRuntime.ts";
42import { fixPath } from "./os-jank.ts";
43import * as Ws from "./ws.ts";
44import * as AgentScopeLive from "./process/agentScope.ts";
45import * as ExternalLauncher from "./process/externalLauncher.ts";
46import * as NodePtyAdapter from "./terminal/NodePtyAdapter.ts";
47import * as PullRequestHttp from "./pullRequest/http.ts";
48import * as SpeechModelStore from "./speech/SpeechModelStore.ts";
49import * as SpeechService from "./speech/SpeechService.ts";
50import * as CloudSpeech from "./speech/CloudSpeech.ts";
51import { speechHttpApiLayer } from "./speech/http.ts";
52import { speechStreamRouteLayer } from "./speech/stream.ts";
53import * as PullRequestProviderRegistry from "./pullRequest/PullRequestProviderRegistry.ts";
54import * as PullRequestService from "./pullRequest/PullRequestService.ts";
55import * as SqlitePersistence from "./persistence/Sqlite.ts";
56import * as PullRequestFilesViewed from "./persistence/PullRequestFilesViewed.ts";
57import * as PullRequestReviewGuides from "./persistence/PullRequestReviewGuides.ts";
58import * as PullRequestReviewGuideService from "./pullRequest/PullRequestReviewGuideService.ts";
59import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
60import * as AnalyticsService from "./telemetry/AnalyticsService.ts";
61import * as DelegatedTaskAnalytics from "./telemetry/DelegatedTaskAnalytics.ts";
62import * as ProviderUsageAnalytics from "./telemetry/ProviderUsageAnalytics.ts";
63import * as ModelManifest from "./provider/ModelManifest.ts";
64import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";
65import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";
66import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
67import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
68import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
69import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
70import * as ProviderHostLive from "./provider/ProviderHostLive.ts";
71import * as DocumentText from "./provider/DocumentText.ts";
72import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport";
73import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";
74import * as NativeChildWorkQuery from "./orchestration-v2/NativeChildWorkQuery.ts";
75import * as CheckpointStore from "./checkpointing/CheckpointStore.ts";
76import * as SourceControlBuiltInDrivers from "./sourceControl/builtInDrivers.ts";
77import * as SourceControlSignIn from "./sourceControl/SourceControlSignIn.ts";
78import { BUILT_IN_SOURCE_CONTROL_DRIVER_LIST } from "./sourceControl/sourceControlDefinitions.ts";
79import * as TextGeneration from "./textGeneration/TextGeneration.ts";
80import * as SecretCheck from "./secrets/SecretCheck.ts";
81import * as SecretResolver from "./secrets/SecretResolver.ts";
82import * as ProviderInstanceRegistryHydration from "./provider/ProviderInstanceRegistryHydration.ts";
83import * as ProjectSettingsOverrideSweep from "./provider/ProjectSettingsOverrideSweep.ts";
84import * as TerminalManager from "./terminal/Manager.ts";
85import * as McpHttpServer from "./mcp/McpHttpServer.ts";
86import * as PeerForwarding from "./peer/PeerForwarding.ts";
87import * as RemoteDelegation from "./peer/RemoteDelegation.ts";
88import * as PeerLinkRequests from "./peer/PeerLinkRequests.ts";
89import * as PeerLinks from "./peer/PeerLinks.ts";
90import * as PeerMcpClient from "./peer/PeerMcpClient.ts";
91import * as McpSessionRegistry from "./mcp/McpSessionRegistry.ts";
92import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts";
93import * as DeviceService from "./device/DeviceService.ts";
94import * as DeviceHubProxy from "./device/DeviceHubProxy.ts";
95import * as PreviewManager from "./preview/Manager.ts";
96import * as PortScanner from "./preview/PortScanner.ts";
97import * as ServerBrowser from "./preview/ServerBrowser.ts";
98import * as PreviewAgentActivity from "./preview/PreviewAgentActivity.ts";
99import * as DesktopBrowserChannel from "./preview/DesktopBrowserChannel.ts";
100import * as ServerBrowserStream from "./preview/ServerBrowserStream.ts";
101import * as PreviewBrowser from "./preview/PreviewBrowser.ts";
102import * as ProcessRunner from "./processRunner.ts";
103import * as GitManager from "./git/GitManager.ts";
104import * as EnvironmentTheme from "./environmentTheme.ts";
105import * as Keybindings from "./keybindings.ts";
106import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
107import * as ServerOwnership from "./serverOwnership.ts";
108import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
109import { hasCloudPublicConfig } from "./cloud/publicConfig.ts";
110import * as ServerSettings from "./serverSettings.ts";
111import * as ManagedLimits from "./policy/managedLimits.ts";
112import * as ManagedPolicy from "./policy/ManagedPolicy.ts";
113import * as ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts";
114import * as NativeAppIconResolver from "./assets/NativeAppIconResolver.ts";
115import * as AntigravityInstallation from "./provider/AntigravityInstallation.ts";
116import * as ProviderInstallation from "./provider/providerInstallation.ts";
117import * as CodexInstallation from "./provider/CodexInstallation.ts";
118import * as ProviderInstallationGate from "./provider/ProviderInstallationGate.ts";
119import * as ProviderInstanceRegistry from "./provider/ProviderInstanceRegistry.ts";
120import * as ProviderAdapterRegistry from "./orchestration-v2/ProviderAdapterRegistry.ts";
121import * as ProviderRegistry from "./provider/ProviderRegistry.ts";
122import * as ProviderUsageLimitsIngestion from "./provider/ProviderUsageLimitsIngestion.ts";
123import * as UsageLimitSources from "./usage/UsageLimitSources.ts";
124import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts";
125import * as T3ProjectFileLoader from "./project/T3ProjectFileLoader.ts";
126import * as RepositoryIdentityResolver from "./project/RepositoryIdentityResolver.ts";
127import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts";
128import * as KnownWorkspaces from "./workspace/KnownWorkspaces.ts";
129import * as WorkspaceFileSystem from "./workspace/WorkspaceFileSystem.ts";
130import * as WorkspacePaths from "./workspace/WorkspacePaths.ts";
131import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";
132import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts";
133import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts";
134import * as VcsProcess from "./vcs/VcsProcess.ts";
135import * as VcsProvisioningService from "./vcs/VcsProvisioningService.ts";
136import * as VcsStatusBroadcaster from "./vcs/VcsStatusBroadcaster.ts";
137import * as ProjectCloneTracker from "./project/ProjectCloneTracker.ts";
138import * as GitWorkflowService from "./git/GitWorkflowService.ts";
139import * as ReviewService from "./review/ReviewService.ts";
140import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts";
141import * as PullRequestReadCache from "./pullRequest/PullRequestReadCache.ts";
142import * as SourceControlRateLimit from "@t3tools/source-control-core/server/SourceControlRateLimit";
143import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts";
144import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts";
145import * as Observability from "./observability/Observability.ts";
146import * as HeapSnapshot from "./observability/HeapSnapshot.ts";
147import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts";
148import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
149import * as DirectEndpoints from "./environment/DirectEndpoints.ts";
150import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
151import * as AuthHttp from "./auth/http.ts";
152import * as ReplayMarkers from "./auth/replayMarkers.ts";
153import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
154import * as CodexManagedTokenBroker from "./provider/CodexManagedTokenBroker.ts";
155import * as WebhookRoute from "./scheduledTasks/webhookRoute.ts";
156import * as RelayDeliveryProof from "./scheduledTasks/RelayDeliveryProof.ts";
157import * as HeldHooksWaker from "./relay/HeldHooksWaker.ts";
158import * as McpOAuth from "./auth/McpOAuth.ts";
159import * as McpOAuthHttp from "./auth/mcpOAuthHttp.ts";
160import {
161 relayHookBaseUrl,
162 ScheduledTaskWebhookOrigin,
163} from "./scheduledTasks/ScheduledTaskService.ts";
164import {
165 CLOUD_ENDPOINT_RUNTIME_CONFIG,
166 decodeRuntimeConfig,
167 RELAY_URL_SECRET,
168} from "./cloud/config.ts";
169import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
170import * as EnvironmentAuthPolicy from "./auth/EnvironmentAuthPolicy.ts";
171import * as CloudHttp from "./cloud/http.ts";
172import * as GuestHttp from "./guest/http.ts";
173import * as CloudLink from "./cloud/CloudLink.ts";
174import * as ConnectReachability from "./cloud/ConnectReachability.ts";
175import { pendingServiceUpdateExists } from "./cloud/updateHandoff.ts";
176import * as RelayTracing from "./cloud/relayTracing.ts";
177import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts";
178import {
179 MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER,
180 makeManagedTunnelRecovery,
181 managedTunnelStartupAction,
182 retryDesiredLinkReconcile,
183 retryManagedTunnelRegistration,
184} from "./cloud/managedTunnelStartup.ts";
185import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts";
186import * as CloudCliState from "./cloud/CliState.ts";
187import * as ServerSelfUpdate from "./cloud/selfUpdate.ts";
188import * as DesktopAppUpdate from "./desktopUpdate/DesktopAppUpdate.ts";
189import * as ServiceLauncherClient from "./cloud/serviceLauncherClient.ts";
190import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
191import * as HostResources from "./resourceTelemetry/HostResources.ts";
192import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
193import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts";
194import * as TriageService from "./triage/TriageService.ts";
195import * as DesktopTelemetryReceiver from "./resourceTelemetry/DesktopTelemetryReceiver.ts";
196import * as NativeTelemetryClient from "./resourceTelemetry/NativeTelemetryClient.ts";
197import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts";
198import * as ResourceMonitorBinary from "./resourceTelemetry/ResourceMonitorBinary.ts";
199import * as ProcessGroupMetrics from "./resourceTelemetry/ProcessGroupMetrics.ts";
200import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
201import * as AntigravityUsage from "./provider/Drivers/AntigravityUsage.ts";
202import { MuseUsageHome } from "@t3tools/provider-muse/server";
203import * as CursorAccountReader from "@t3tools/provider-cursor/server/CursorAccountReader";
204import * as CursorUsageAccounts from "@t3tools/provider-cursor/server/CursorUsageAccounts";
205import * as UsageService from "./usage/UsageService.ts";
206import * as CodexUsageHistory from "./usage/CodexUsageHistory.ts";
207import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts";
208import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
209import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts";
210import * as ThreadSettlementService from "./orchestration-v2/ThreadSettlementService.ts";
211import * as IdleCompactionService from "./orchestration-v2/IdleCompactionService.ts";
212import * as ThreadPullRequestService from "./orchestration-v2/ThreadPullRequestService.ts";
213import * as RunFinalizationService from "./orchestration-v2/RunFinalizationService.ts";
214import * as ProjectionMaintenance from "./orchestration-v2/ProjectionMaintenance.ts";
215import * as ProjectionStoreV2 from "./orchestration-v2/ProjectionStore.ts";
216import { makePersistedServerRuntimeState } from "./serverRuntimeState.ts";
217import { acceptsPrivateNetworkConnection, loopbackUrlHostForAddress } from "./startupAccess.ts";
218import * as OrchestrationHttp from "./orchestration-v2/http.ts";
219import * as ProjectHttp from "./project/http.ts";
220import * as NetService from "@t3tools/shared/Net";
221import * as RelayClient from "@t3tools/shared/relayClient";
222import {
223 disableTailscaleServe,
224 ensureTailscaleServe,
225 readTailscaleServeRootHolder,
226 readTailscaleServeRoutesTo,
227 readTailscaleStatus,
228} from "@t3tools/tailscale";
229import * as ServerActivation from "./serverActivation.ts";
230
231// Escaped MCP handoff provenance can exceed find-my-way's 100-character segment default.
232const HTTP_ROUTER_CONFIG = {
233 maxParamLength: 512,
234} as const;
235
236// Skip Effect's default 20 s grace; WebSocket scope finalizers already close connections gracefully.
237const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
238const layerResourceAttribution = ResourceAttribution.layer;
239const layerApplicationObservability = EventLoopMonitor.layer.pipe(
240 Layer.provideMerge(Observability.layer),
241 Layer.provideMerge(layerResourceAttribution),
242);
243
244// Share rotation state so driver loggers cannot race.
245const layerProviderEventLoggers = Layer.effect(
246 ProviderEventLoggers.ProviderEventLoggers,
247 Effect.gen(function* () {
248 const { providerEventLogPath } = yield* ServerConfig.ServerConfig;
249 const attribution = yield* ResourceAttribution.ResourceAttribution;
250 const store = yield* EventNdjsonLogger.makeEventNdjsonLogStore(providerEventLogPath, {
251 attribution,
252 }).pipe(
253 Effect.catch((error) =>
254 Effect.logWarning(error.message, { error }).pipe(
255 Effect.annotateLogs({ scope: "provider-observability" }),
256 Effect.as(undefined),
257 ),
258 ),
259 );
260 if (!store) return ProviderEventLoggers.NoOpProviderEventLoggers;
261 yield* Effect.addFinalizer(() => store.close());
262 return { native: store.logger("native"), canonical: store.logger("canonical") };
263 }),
264);
265
266const layerPtyAdapter = NodePtyAdapter.layer;
267
268const layerServerSettings = ServerSettings.layer.pipe(
269 Layer.provide(ServerSecretStore.layer),
270 Layer.provideMerge(ManagedPolicy.layer),
271 Layer.provideMerge(SqlitePersistence.layerConfig),
272);
273
274const layerNativeTelemetry = NativeTelemetryClient.layer.pipe(
275 Layer.provide(ResourceMonitorBinary.layer),
276);
277const layerDesktopTelemetryReceiver = DesktopTelemetryReceiver.layer.pipe(
278 Layer.provideMerge(layerServerSettings),
279);
280
281const layerResourceTelemetry = ResourceTelemetry.layer.pipe(
282 Layer.provideMerge(layerNativeTelemetry),
283 Layer.provideMerge(layerDesktopTelemetryReceiver),
284);
285
286const layerHostPowerMonitor = HostPowerMonitor.layer.pipe(
287 Layer.provide(layerDesktopTelemetryReceiver),
288);
289
290// A fresh receiver layer would open a second reader on the desktop telemetry fd.
291const layerDesktopAppUpdate = DesktopAppUpdate.layer.pipe(
292 Layer.provide(layerDesktopTelemetryReceiver),
293);
294
295const layerBackground = BackgroundPolicy.layer.pipe(
296 Layer.provide(layerHostPowerMonitor),
297 Layer.provideMerge(layerServerSettings),
298);
299
300const layerUsage = UsageService.layer.pipe(
301 Layer.provide(
302 Layer.mergeAll(
303 AntigravityUsage.layer,
304 MuseUsageHome.layer,
305 CodexUsageHistory.layer,
306 CursorUsageAccounts.layer.pipe(Layer.provide(CursorAccountReader.layer)),
307 ).pipe(Layer.provide(ProviderHostLive.layer.pipe(Layer.provide(ServerSecretStore.layer)))),
308 ),
309 Layer.provide(layerServerSettings),
310);
311
312const layerResourceDiagnostics = Layer.mergeAll(
313 HostResources.layer,
314 layerResourceTelemetry,
315 ProcessDiagnostics.layer.pipe(Layer.provide(layerResourceTelemetry)),
316 ProcessResourceMonitor.layer.pipe(Layer.provide(layerResourceTelemetry)),
317 ProcessGroupMetrics.layer.pipe(Layer.provide(layerResourceTelemetry)),
318);
319
320const layerRelayClient = Layer.unwrap(
321 Effect.gen(function* () {
322 const config = yield* ServerConfig.ServerConfig;
323 const policy = yield* ManagedPolicy.ManagedPolicy;
324 const layerCloudflared = RelayClient.layerCloudflared({ baseDir: config.baseDir });
325 if (ManagedLimits.allowsConnect(policy)) return layerCloudflared;
326 // The connector only carries T3 Connect, so a policy without Connect never installs one.
327 const refused = Effect.fn("RelayClient.installRefusedByPolicy")(function* () {
328 return yield* new RelayClient.RelayClientInstallError({
329 reason: "managed_by_policy",
330 message: ManagedLimits.refuseConnect(policy).message,
331 });
332 });
333 return Layer.effect(
334 RelayClient.RelayClient,
335 Effect.gen(function* () {
336 const relayClient = yield* RelayClient.RelayClient;
337 return RelayClient.RelayClient.of({
338 ...relayClient,
339 install: refused(),
340 installWithProgress: refused,
341 });
342 }),
343 ).pipe(Layer.provide(layerCloudflared));
344 }),
345).pipe(Layer.provide(ManagedPolicy.layer));
346
347const layerHttpServer = Layer.unwrap(
348 Effect.gen(function* () {
349 const config = yield* ServerConfig.ServerConfig;
350 // Before binding: a host the policy does not allow must never listen, not even briefly.
351 const policy = yield* ManagedPolicy.ManagedPolicy;
352 const bindHost = yield* ManagedLimits.checkServerReachability(policy, {
353 authPolicy: EnvironmentAuthPolicy.resolveServerAuthPolicy(config),
354 host: config.host,
355 tailscaleServeEnabled: config.tailscaleServeEnabled,
356 privateNetworkOnly: config.privateNetworkOnly,
357 });
358 yield* ManagedLimits.closeTailscaleServeTo(policy, { host: bindHost, port: config.port });
359 return NodeHttpServer.layer(
360 () => {
361 const server = rejectRequestsUntilServing(
362 keepIdleConnections(guardHttpResponseWriteErrors(NodeHttp.createServer())),
363 );
364 if (config.privateNetworkOnly) {
365 server.on("connection", (socket) => {
366 if (!acceptsPrivateNetworkConnection(socket)) {
367 socket.destroy();
368 }
369 });
370 }
371 return server;
372 },
373 {
374 host: bindHost,
375 port: config.port,
376 gracefulShutdownTimeout: HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS,
377 // Context takeover stays enabled (ws default) so the compression
378 // window is shared across frames — that also makes small frames cheap
379 // to compress, so no size threshold is set (ws only honors
380 // `threshold` when context takeover is disabled).
381 websocket: { perMessageDeflate: true },
382 },
383 );
384 }),
385).pipe(Layer.provide(ManagedPolicy.layer));
386
387const layerPlatformServices = DocumentText.layer.pipe(
388 Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
389);
390
391const layerPersistence = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig));
392
393const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProjectConfig.layer));
394
395const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.pipe(
396 Layer.provideMerge(SourceControlBuiltInDrivers.layer),
397 Layer.provideMerge(GitVcsDriver.layer),
398 Layer.provideMerge(layerVcsDriverRegistry),
399);
400
401const layerRepositoryIdentityResolver = Layer.effect(
402 RepositoryIdentityResolver.RepositoryIdentityResolver,
403 Effect.gen(function* () {
404 const registry = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
405 return yield* RepositoryIdentityResolver.make({
406 refine: Effect.fn(function* (identity: RepositoryIdentity) {
407 for (const { kind } of BUILT_IN_SOURCE_CONTROL_DRIVER_LIST) {
408 const provider = yield* registry.get(kind);
409 if (provider.refineRepositoryIdentity === undefined) continue;
410 const refined = yield* provider.refineRepositoryIdentity({
411 identity,
412 resolveContext: (input) =>
413 registry.resolveHandle(input).pipe(Effect.map((handle) => handle.context)),
414 });
415 if (refined !== identity) return refined;
416 }
417 return identity;
418 }),
419 });
420 }),
421).pipe(Layer.provide(layerSourceControlProviderRegistry), Layer.provide(ProcessRunner.layer));
422
423const layerPullRequestService = PullRequestService.layer.pipe(
424 Layer.provide(PullRequestProviderRegistry.layer),
425 Layer.provide(PullRequestFilesViewed.layer),
426 Layer.provide(PullRequestReadCache.layer),
427 Layer.provide(layerSourceControlProviderRegistry),
428 Layer.provide(SourceControlRateLimit.layer),
429);
430
431const layerGitManager = GitManager.layer.pipe(
432 Layer.provide(Layer.merge(ProjectionStoreV2.layer, ProjectStore.layer)),
433 Layer.provideMerge(RuntimeLayer.layerProjectSetupScriptRunner),
434 Layer.provideMerge(WorktreeSetupTracker.layer),
435 Layer.provideMerge(GitVcsDriver.layer),
436 Layer.provideMerge(layerSourceControlProviderRegistry),
437 Layer.provideMerge(TextGeneration.layer.pipe(Layer.provide(layerSourceControlProviderRegistry))),
438);
439
440const layerGit = Layer.empty.pipe(
441 Layer.provideMerge(layerGitManager),
442 Layer.provideMerge(GitVcsDriver.layer),
443);
444
445const layerGitWorkflow = GitWorkflowService.layer.pipe(
446 Layer.provideMerge(layerVcsDriverRegistry),
447 Layer.provideMerge(layerGit),
448);
449
450const layerSourceControlRepositoryService = SourceControlRepositoryService.layer.pipe(
451 Layer.provideMerge(GitVcsDriver.layer),
452 Layer.provideMerge(layerSourceControlProviderRegistry),
453);
454
455const layerProjectCloneTracker = ProjectCloneTracker.layer.pipe(
456 Layer.provide(layerSourceControlRepositoryService),
457);
458
459const layerKnownWorkspaces = KnownWorkspaces.layer.pipe(
460 Layer.provide(ProjectStore.layer),
461 Layer.provide(GitVcsDriver.layer),
462);
463
464const layerReview = ReviewService.layer.pipe(
465 Layer.provideMerge(layerKnownWorkspaces),
466 Layer.provideMerge(GitVcsDriver.layer),
467 Layer.provideMerge(layerVcsDriverRegistry),
468);
469
470const layerVcs = Layer.empty.pipe(
471 Layer.provideMerge(VcsProjectConfig.layer),
472 Layer.provideMerge(layerVcsDriverRegistry),
473 Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(layerVcsDriverRegistry))),
474 Layer.provideMerge(layerGitWorkflow),
475 Layer.provideMerge(layerReview),
476 Layer.provideMerge(layerSourceControlRepositoryService),
477 Layer.provideMerge(
478 SourceControlSignIn.layer.pipe(Layer.provide(layerSourceControlProviderRegistry)),
479 ),
480 Layer.provideMerge(layerProjectCloneTracker),
481 Layer.provideMerge(
482 VcsStatusBroadcaster.layer.pipe(
483 Layer.provide(layerGitWorkflow),
484 Layer.provide(VcsStatusBroadcaster.layerAutomaticFetchInterval),
485 // The orchestration runtime also consumes the broadcaster (run
486 // finalization), so the policy cannot read the store from the runtime's
487 // output.
488 Layer.provide(
489 VcsStatusBroadcaster.layerAutoPullPolicy.pipe(Layer.provide(ProjectStore.layer)),
490 ),
491 ),
492 ),
493);
494
495const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
496
497const layerPortScanner = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
498
499const layerTerminal = TerminalManager.layer(
500 ProviderInstanceRegistryHydration.deriveProviderInstanceConfigMap,
501).pipe(
502 Layer.provide(layerPtyAdapter),
503 Layer.provide(layerPortScanner),
504 Layer.provide(layerNativeTelemetry),
505);
506
507const layerPreview = Layer.empty.pipe(
508 Layer.provideMerge(PreviewManager.layer),
509 Layer.provideMerge(layerPortScanner),
510);
511
512const layerDevice = DeviceService.layer.pipe(
513 Layer.provide(layerServerSettings),
514 Layer.provide(ProcessRunner.layer),
515 Layer.provide(NetService.layer),
516);
517
518const layerWorkspaceEntries = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
519
520const layerWorkspaceFileSystem = WorkspaceFileSystem.layer.pipe(
521 Layer.provide(WorkspacePaths.layer),
522 Layer.provide(layerWorkspaceEntries),
523 Layer.provide(layerKnownWorkspaces),
524);
525
526const layerWorkspace = Layer.mergeAll(
527 WorkspacePaths.layer,
528 layerWorkspaceEntries,
529 layerWorkspaceFileSystem,
530);
531
532const layerProjectFaviconResolver = ProjectFaviconResolver.layer.pipe(
533 Layer.provide(WorkspacePaths.layer),
534 Layer.provide(T3ProjectFileLoader.layer),
535);
536
537const layerServerEnvironment = ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer));
538
539const layerAuth = EnvironmentAuth.layer.pipe(
540 Layer.provideMerge(layerPersistence),
541 Layer.provide(layerServerEnvironment),
542 Layer.provide(ServerSecretStore.layer),
543);
544
545const layerCloudManagedEndpointRuntime = Layer.mergeAll(
546 layerRelayClient,
547 CloudManagedEndpointRuntime.layer.pipe(
548 Layer.provide(ServerSecretStore.layer),
549 Layer.provide(layerRelayClient),
550 ),
551);
552
553const layerScheduledTaskWebhookOrigin = Layer.effect(
554 ScheduledTaskWebhookOrigin,
555 Effect.gen(function* () {
556 const secrets = yield* ServerSecretStore.ServerSecretStore;
557 return Effect.gen(function* () {
558 const [relayUrl, tunnelConfig] = yield* Effect.all([
559 secrets.get(RELAY_URL_SECRET),
560 secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
561 ]).pipe(Effect.orElseSucceed(() => [Option.none(), Option.none()] as const));
562 if (Option.isNone(relayUrl) || Option.isNone(tunnelConfig)) {
563 return { relayHookBaseUrl: null };
564 }
565 const config = decodeRuntimeConfig(new TextDecoder().decode(tunnelConfig.value));
566 return {
567 relayHookBaseUrl: relayHookBaseUrl({
568 relayUrl: new TextDecoder().decode(relayUrl.value),
569 tunnelName: Option.isSome(config) ? config.value.tunnelName : undefined,
570 }),
571 };
572 });
573 }),
574);
575
576const layerOrchestrationV2Runtime = RuntimeLayer.layerProduction.pipe(
577 Layer.provide(layerScheduledTaskWebhookOrigin),
578 Layer.provide(ProviderUsageAnalytics.layer),
579 Layer.provide(layerCheckpointStore),
580 Layer.provide(layerGitWorkflow),
581 Layer.provide(
582 RunFinalizationService.layerObserver.pipe(
583 Layer.provide(ProjectionStoreV2.layer),
584 Layer.provide(layerPullRequestService),
585 Layer.provide(RuntimeLayer.layerProjectService),
586 ),
587 ),
588);
589
590const layerOrchestrationApplication = Layer.merge(
591 CheckpointDiffQuery.layer,
592 NativeChildWorkQuery.layer,
593).pipe(Layer.provideMerge(layerCheckpointStore), Layer.provideMerge(layerOrchestrationV2Runtime));
594
595const layerThreadSettlementWorker = Layer.effectDiscard(
596 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),
597).pipe(Layer.provide(layerPullRequestService), Layer.provide(ProjectionStoreV2.layer));
598
599const layerIdleCompactionWorker = Layer.effectDiscard(
600 IdleCompactionService.make.pipe(Effect.flatMap((service) => service.start())),
601).pipe(Layer.provide(ProjectionStoreV2.layer));
602
603const layerThreadPullRequestWorker = Layer.effectDiscard(
604 ThreadPullRequestService.make.pipe(Effect.flatMap((service) => service.start())),
605).pipe(Layer.provide(layerPullRequestService));
606
607const layerProviderInstallationRefresh = Layer.effectDiscard(
608 Effect.gen(function* () {
609 const antigravity = yield* AntigravityInstallation.AntigravityInstallation;
610 const codex = yield* CodexInstallation.CodexInstallation;
611 const instances = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
612 const providers = yield* ProviderRegistry.ProviderRegistry;
613 yield* Stream.merge(
614 antigravity.changes.pipe(
615 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
616 Stream.drop(1),
617 ),
618 codex.changes.pipe(
619 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
620 Stream.drop(1),
621 ),
622 ).pipe(
623 Stream.runForEach((state) =>
624 instances.listInstances.pipe(
625 Effect.flatMap((entries) =>
626 Effect.forEach(
627 entries.filter((instance) => instance.driverKind === state.driver),
628 (instance) => providers.refreshInstance(instance.instanceId),
629 { discard: true },
630 ),
631 ),
632 ),
633 ),
634 Effect.forkScoped,
635 );
636 }),
637);
638
639// The desktop app and the background service start the server again after it exits.
640const layerServerRestart = Layer.effect(
641 DatabaseStorage.ServerRestart,
642 Effect.gen(function* () {
643 const config = yield* ServerConfig.ServerConfig;
644 const launcher = yield* ServiceLauncherClient.ServiceLauncherClient;
645 // As in runServerCommand, only the desktop app passes a bootstrap token; `serve --mode desktop` by hand has no supervisor.
646 const desktopLaunched = config.mode === "desktop" && config.desktopBootstrapToken !== undefined;
647 if (!desktopLaunched && !launcher.managed) return null;
648 // Stop as a SIGTERM would: runMain's listener interrupts the server, so its shutdown path runs.
649 return Effect.sync(() => void process.emit("SIGTERM"));
650 }),
651);
652
653const layerRuntimeCoreDependenciesBase = Layer.mergeAll(
654 AgentAwarenessRelay.layer,
655 HeldHooksWaker.layer,
656 layerThreadSettlementWorker,
657 layerIdleCompactionWorker,
658 StorageCleanup.layer.pipe(Layer.provide(ProjectionStoreV2.layer)),
659 DatabaseStorage.layer.pipe(
660 Layer.provide(ProjectionMaintenance.layer),
661 Layer.provide(layerServerRestart),
662 ),
663 layerThreadPullRequestWorker,
664 KeepAwake.layer,
665 DelegatedTaskAnalytics.layer,
666 Layer.effectDiscard(
667 Effect.gen(function* () {
668 const service = yield* PullRequestSyncReactor.PullRequestSyncReactor;
669 yield* service.start();
670 }),
671 ).pipe(
672 Layer.provideMerge(PullRequestSyncReactor.layer),
673 Layer.provide(layerPullRequestService),
674 Layer.provide(ProjectionStoreV2.layer),
675 ),
676 Layer.effectDiscard(
677 Effect.gen(function* () {
678 const service = yield* PullRequestWatchReactor.PullRequestWatchReactor;
679 yield* service.start();
680 }),
681 ).pipe(
682 Layer.provide(PullRequestWatchReactor.layer),
683 Layer.provide(layerPullRequestService),
684 Layer.provide(ProjectionStoreV2.layer),
685 ),
686 Layer.effectDiscard(
687 Effect.gen(function* () {
688 const service = yield* DelegatedTaskQuestionReactor.DelegatedTaskQuestionReactor;
689 yield* service.start();
690 }),
691 ).pipe(Layer.provide(DelegatedTaskQuestionReactor.layer), Layer.provide(ProjectionStoreV2.layer)),
692 ProviderUsageLimitsIngestion.layer,
693 layerProviderInstallationRefresh,
694 ProviderInstallation.layer,
695 ReplayMarkers.layer,
696 Layer.effectDiscard(ProjectSettingsOverrideSweep.sweep),
697).pipe(
698 Layer.provideMerge(layerOrchestrationApplication),
699 Layer.provideMerge(RuntimeLayer.layerEventInfrastructure),
700 Layer.provideMerge(Layer.merge(ProjectStore.layer, ThreadSearch.layer)),
701 Layer.provideMerge(SecretCheck.layer),
702 Layer.provideMerge(SecretResolver.layer),
703 Layer.provideMerge(layerServerSettings),
704 Layer.provideMerge(layerSourceControlProviderRegistry),
705 Layer.provideMerge(layerGit),
706 Layer.provideMerge(layerVcs),
707 Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)),
708 Layer.provideMerge(layerPersistence),
709 Layer.provideMerge(
710 Layer.mergeAll(Keybindings.layer, EnvironmentTheme.layer, UsageLimitSources.layer),
711 ),
712 Layer.provideMerge(ProviderRegistry.layer),
713 Layer.provideMerge(
714 ProviderInstanceRegistryHydration.layer.pipe(Layer.provide(SqlitePersistence.layerConfig)),
715 ),
716 // One gate for the server, below hydration so the Claude adapter sees it: layers memoize by
717 // reference, so the runtime layer shares it.
718 Layer.provideMerge(ProviderInstallationGate.layer),
719 // Provider hydration builds the Claude runner, which reports agents to the same scanner the
720 // terminal and preview layers share.
721 Layer.provideMerge(layerPortScanner),
722 Layer.provideMerge(
723 Layer.mergeAll(
724 AntigravityInstallation.AntigravityInstallation.layer.pipe(
725 Layer.provide(layerServerSettings),
726 ),
727 CodexInstallation.CodexInstallation.layer,
728 ),
729 ),
730);
731
732const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
733 Layer.provideMerge(layerPtyAdapter),
734 Layer.provideMerge(AcpRegistrySupport.layerFromHost.pipe(Layer.provide(ProviderHostLive.layer))),
735 Layer.provideMerge(
736 Layer.mergeAll(
737 layerProviderEventLoggers,
738 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),
739 ResetCreditCoordinator.layer,
740 ProviderLatestVersions.layer,
741 McpProviderSessions.layer,
742 ),
743 ),
744 Layer.provideMerge(
745 OpenCodeRuntime.layer.pipe(
746 Layer.provide(
747 Layer.unwrap(
748 Effect.gen(function* () {
749 const config = yield* ServerConfig.ServerConfig;
750 return OpenCodeServerLedger.layer({ stateDir: config.stateDir });
751 }),
752 ),
753 ),
754 ),
755 ),
756 Layer.provideMerge(layerWorkspace),
757 Layer.provideMerge(ProjectEnrichmentService.layer),
758 Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)),
759 Layer.provideMerge(layerRepositoryIdentityResolver),
760 Layer.provideMerge(CodexManagedTokenBroker.layer),
761 Layer.provideMerge(layerServerEnvironment),
762 Layer.provideMerge(layerAuth),
763 Layer.provideMerge(Layer.mergeAll(AssetSigner.layer, EnvironmentKeys.layer)),
764 Layer.provideMerge(ServerSecretStore.layer),
765 Layer.provideMerge(
766 Layer.mergeAll(
767 CloudCliTokenManager.layer.pipe(
768 Layer.provide(ServerSecretStore.layer),
769 Layer.provide(ExternalLauncher.layer),
770 ),
771 layerCloudManagedEndpointRuntime,
772 ),
773 ),
774);
775
776const layerRuntimeDependencies = layerRuntimeCoreDependencies.pipe(
777 Layer.provideMerge(layerUsage),
778 Layer.provideMerge(layerBackground),
779 Layer.provideMerge(layerResourceDiagnostics),
780 Layer.provideMerge(TraceDiagnostics.layer),
781 Layer.provideMerge(TriageService.layer.pipe(Layer.provide(GitVcsDriver.layer))),
782 // Layers memoize by reference, so this is the same policy the settings layer reads.
783 Layer.provideMerge(AnalyticsService.layer.pipe(Layer.provide(ManagedPolicy.layer))),
784 Layer.provideMerge(ExternalLauncher.layer),
785 Layer.provideMerge(RemoteOpenTargets.layer),
786 Layer.provideMerge(DirectEndpoints.layer.pipe(Layer.provide(layerServerEnvironment))),
787 Layer.provideMerge(ServerLifecycleEvents.layer),
788 Layer.provide(NetService.layer),
789);
790
791const layerCommandReadiness = HttpRouter.middleware(
792 (httpEffect) =>
793 Effect.flatMap(ServerRuntimeStartup.ServerRuntimeStartup, (startup) =>
794 startup.awaitCommandReady.pipe(Effect.orDie, Effect.andThen(httpEffect)),
795 ),
796 { global: true },
797);
798
799const layerMakeRoutes = Layer.mergeAll(
800 Layer.mergeAll(
801 HttpApiBuilder.layer(EnvironmentHttpApi).pipe(
802 Layer.provide(AuthHttp.layer),
803 Layer.provide(McpOAuthHttp.layer.pipe(Layer.provide(McpOAuth.layer))),
804 Layer.provide(CloudHttp.layer),
805 Layer.provide(OrchestrationHttp.layer),
806 Layer.provide(PullRequestHttp.layer),
807 Layer.provide(ProjectHttp.layer),
808 Layer.provide(speechHttpApiLayer),
809 Layer.provide(ServerHttp.layerServerEnvironmentHttpApi),
810 Layer.provide(WebhookRoute.layer.pipe(Layer.provide(RelayDeliveryProof.layer))),
811 Layer.provide(AuthHttp.layerAuthenticatedAuth),
812 ),
813 ServerHttp.layerOtlpTracesProxyRoute,
814 ServerHttp.layerAssetRoute,
815 ServerHttp.layerAttachmentUploadRoute,
816 GuestHttp.layer,
817 DeviceHubProxy.layer,
818 ServerBrowserStream.routeLayer,
819 ServerHttp.layerStaticAndDevRoute,
820 Ws.layer,
821 speechStreamRouteLayer,
822 ),
823 McpHttpServer.layer.pipe(
824 Layer.provide(ProviderAdapterRegistry.layerFromProviderInstanceRegistry),
825 Layer.provide(McpOAuth.layerMcpClientAuthenticator),
826 ),
827 // Follows delegated tasks running in linked environments, including after a restart.
828 Layer.effectDiscard(
829 RemoteDelegation.RemoteDelegation.pipe(Effect.flatMap((remote) => remote.start())),
830 ),
831).pipe(
832 Layer.provide(
833 PullRequestReviewGuideService.layer.pipe(Layer.provide(PullRequestReviewGuides.layer)),
834 ),
835 // delegate_task to a linked environment, from /mcp and the follower above.
836 Layer.provide(RemoteDelegation.layer.pipe(Layer.provide(ProjectionStoreV2.layer))),
837 // Links agents ask for: shown in a thread from /mcp, answered over WebSocket.
838 Layer.provide(PeerLinkRequests.layer),
839 // Share the service so HTTP/WS clients single-flight reads and invalidate each other's cached patches.
840 Layer.provide(layerPullRequestService),
841 // The same activity instance RunExecutionService reports to, by layer memoization.
842 Layer.provide(
843 ServerBrowser.layer.pipe(
844 Layer.provide(Layer.merge(DesktopBrowserChannel.layer, PreviewAgentActivity.layer)),
845 ),
846 ),
847 Layer.provide(PreviewBrowser.layer),
848 // Links to other environments, shared by Settings (WebSocket) and agents (/mcp).
849 Layer.provide(
850 PeerForwarding.layer.pipe(
851 Layer.provideMerge(PeerMcpClient.layer),
852 Layer.provideMerge(PeerLinks.layer),
853 ),
854 ),
855 Layer.provide(
856 SpeechService.layer.pipe(
857 Layer.provide(layerServerSettings),
858 Layer.provide(SpeechModelStore.layer),
859 ),
860 ),
861 Layer.provide(CloudSpeech.layer.pipe(Layer.provide(SecretResolver.layer))),
862 Layer.provide(layerServerSettings),
863 Layer.provide(PreviewAutomationBroker.layer),
864 Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(layerDesktopAppUpdate))),
865 Layer.provide(layerCommandReadiness),
866 Layer.provide(ServerHttp.layerBrowserApiCors),
867 Layer.provide(ServerHttp.layerHttpCompression),
868);
869
870const layerMakeServer = Layer.unwrap(
871 Effect.gen(function* () {
872 const config = yield* ServerConfig.ServerConfig;
873 // Before anything opens the database: one server per T3 home.
874 const ownership = yield* ServerOwnership.acquireServerOwnership(config.serverRuntimeStatePath);
875 const activation = yield* Deferred.make<void>();
876 const awaitActivation = Deferred.await(activation);
877 const layerActivation = Layer.succeed(ServerActivation.ServerActivation, awaitActivation);
878 const runtimeStateParked = yield* Deferred.make<void>();
879 const tailscaleParked = yield* Deferred.make<void>();
880 const cloudLinkParked = yield* Deferred.make<void>();
881 const routesReady = yield* Deferred.make<void>();
882 const layerLauncher = ServiceLauncherClient.layer;
883
884 // fixPath rewrites PATH, so pin the launcher the shell chose before it does.
885 const invokedAs = (yield* resolveLauncherPath) ?? (yield* HostProcess.InvokedAs);
886 yield* fixPath({ shellEnvironmentPrepared: config.shellEnvironmentPrepared });
887
888 const layerHttpListening = Layer.effectDiscard(
889 Effect.gen(function* () {
890 yield* HttpServer.HttpServer;
891 const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
892 yield* startup.markHttpListening;
893 }),
894 );
895 const layerRuntimeState = Layer.effectDiscard(
896 Effect.gen(function* () {
897 yield* Deferred.succeed(runtimeStateParked, undefined).pipe(Effect.orDie);
898 yield* awaitActivation;
899 const server = yield* HttpServer.HttpServer;
900 const address = server.address;
901 if (typeof address === "string" || !("port" in address)) {
902 return;
903 }
904
905 const launcher = yield* ServiceLauncherClient.ServiceLauncherClient;
906 const state = yield* makePersistedServerRuntimeState({
907 config,
908 port: address.port,
909 serviceManaged: launcher.managed,
910 managedPolicy: yield* ManagedPolicy.loadedPolicy(yield* ManagedPolicy.ManagedPolicy),
911 });
912 yield* ownership
913 .publish(state)
914 .pipe(
915 Effect.catchCause((cause) =>
916 Effect.logWarning("Failed to persist server runtime state", { cause }),
917 ),
918 );
919 }),
920 );
921 const tailscaleServeRootHolder = (target: { readonly host: string; readonly port: number }) =>
922 readTailscaleServeRootHolder(config.tailscaleServePort, target);
923 const layerTailscaleServe = config.tailscaleServeEnabled
924 ? Layer.effectDiscard(
925 Effect.acquireRelease(
926 Effect.gen(function* () {
927 yield* Deferred.succeed(tailscaleParked, undefined).pipe(Effect.orDie);
928 yield* awaitActivation;
929 const server = yield* HttpServer.HttpServer;
930 const address = server.address;
931 if (typeof address === "string" || !("port" in address)) {
932 return null;
933 }
934
935 const localPort = address.port;
936 const localHost = loopbackUrlHostForAddress(address);
937 const route = {
938 localPort,
939 urlHost: localHost,
940 localHost: localHost.replace(/^\[|\]$/gu, ""),
941 servePort: config.tailscaleServePort,
942 };
943 const holder = yield* tailscaleServeRootHolder({
944 host: route.localHost,
945 port: localPort,
946 }).pipe(Effect.orElseSucceed(() => "unreadable" as const));
947 if (holder === "other" || holder === "unreadable") {
948 yield* Effect.logWarning(
949 holder === "other"
950 ? "Tailscale Serve left off; its port already serves another server or app"
951 : "Tailscale Serve left off; its current routes could not be read",
952 { localPort, servePort: config.tailscaleServePort },
953 );
954 return route;
955 }
956 return yield* ensureTailscaleServe({
957 localPort,
958 servePort: config.tailscaleServePort,
959 localHost,
960 }).pipe(
961 Effect.as(route),
962 Effect.tap(() =>
963 Effect.logInfo("Tailscale Serve configured", {
964 localPort,
965 servePort: config.tailscaleServePort,
966 }),
967 ),
968 Effect.catch((cause) =>
969 Effect.logWarning(
970 cause._tag === "TailscaleCommandExitError" &&
971 cause.stderrDiagnostic === "unsupported-target"
972 ? `Tailscale Serve left off; this Tailscale install can only proxy to localhost, not ${localHost}. Bind --host 127.0.0.1 or 0.0.0.0 instead`
973 : "Failed to configure Tailscale Serve",
974 { cause, localPort, servePort: config.tailscaleServePort },
975 ).pipe(Effect.as(null)),
976 ),
977 );
978 }),
979 (configured) =>
980 configured
981 ? Effect.all(
982 [
983 readTailscaleServeRoutesTo({
984 host: configured.localHost,
985 port: configured.localPort,
986 }),
987 readTailscaleStatus,
988 ],
989 { concurrency: 2 },
990 ).pipe(
991 // Another service may hold the root route by now, or it may sit under a
992 // former name, where `off` would act on the current one; only ours goes.
993 Effect.map(([routes, status]) =>
994 routes.some(
995 (route) =>
996 route.servePort === configured.servePort &&
997 route.mount === "/" &&
998 route.owner === undefined &&
999 route.ambiguous === undefined &&
1000 route.host?.toLowerCase() === status.magicDnsName?.toLowerCase(),
1001 ),
1002 ),
1003 Effect.orElseSucceed(() => false),
1004 Effect.flatMap((ours) =>
1005 ours
1006 ? disableTailscaleServe({ servePort: configured.servePort }).pipe(
1007 Effect.tap(() =>
1008 Effect.logInfo("Tailscale Serve disabled", {
1009 servePort: configured.servePort,
1010 }),
1011 ),
1012 )
1013 : Effect.logInfo(
1014 "Tailscale Serve left on; its root route is not known to be ours",
1015 {
1016 servePort: configured.servePort,
1017 },
1018 ),
1019 ),
1020 Effect.catch((cause) =>
1021 Effect.logWarning("Failed to disable Tailscale Serve", {
1022 cause,
1023 servePort: configured.servePort,
1024 }),
1025 ),
1026 )
1027 : Effect.void,
1028 ).pipe(
1029 // Startup is not the only time the route can go stale, so whoever finds it on a dead
1030 // port takes it back.
1031 Effect.tap((configured) =>
1032 configured === null
1033 ? Effect.void
1034 : Effect.sleep("30 seconds").pipe(
1035 Effect.andThen(
1036 tailscaleServeRootHolder({
1037 host: configured.localHost,
1038 port: configured.localPort,
1039 }),
1040 ),
1041 Effect.flatMap((holder) =>
1042 holder === "dead"
1043 ? ensureTailscaleServe({
1044 localPort: configured.localPort,
1045 servePort: configured.servePort,
1046 localHost: configured.urlHost,
1047 }).pipe(
1048 Effect.andThen(
1049 Effect.logInfo("Tailscale Serve repaired", {
1050 localPort: configured.localPort,
1051 servePort: configured.servePort,
1052 }),
1053 ),
1054 )
1055 : Effect.void,
1056 ),
1057 Effect.ignore,
1058 Effect.forever,
1059 Effect.forkScoped,
1060 ),
1061 ),
1062 ),
1063 ).pipe(Layer.provide(NetService.layer))
1064 : Layer.empty;
1065 const layerCloudDesiredLinkReconcile = Layer.effectDiscard(
1066 Effect.gen(function* () {
1067 const cloudLink = yield* CloudLink.CloudLink;
1068 // CloudLink unlinks on startup when the policy turns Connect off; nothing to restore.
1069 if (!ManagedLimits.allowsConnect(yield* ManagedPolicy.ManagedPolicy)) {
1070 yield* Deferred.succeed(cloudLinkParked, undefined).pipe(Effect.orDie);
1071 return;
1072 }
1073 const releaseManagedTunnel = cloudLink.releaseManagedTunnelOnShutdown().pipe(
1074 Effect.timeout("10 seconds"),
1075 Effect.tap((released) =>
1076 released ? Effect.logInfo("Released the managed tunnel on shutdown") : Effect.void,
1077 ),
1078 Effect.catchCause((cause) =>
1079 Effect.logWarning(
1080 "Failed to release the managed tunnel on shutdown; the next link reuses it",
1081 { errors: Cause.prettyErrors(cause).map((error) => error.message) },
1082 ),
1083 ),
1084 Effect.asVoid,
1085 );
1086 // Trials own cleanup immediately after the old server exits, retaining tunnels for commit/rollback unless explicitly stopped.
1087 // Other runtimes wait for activation so failed standbys cannot tear down active tunnels.
1088 const cleanupBeforeActivation = yield* pendingServiceUpdateExists;
1089 if (cleanupBeforeActivation) {
1090 yield* Effect.addFinalizer(() => releaseManagedTunnel);
1091 }
1092 yield* ServerActivation.forkParked(
1093 Effect.gen(function* () {
1094 if (!cleanupBeforeActivation) {
1095 yield* Effect.addFinalizer(() => releaseManagedTunnel);
1096 }
1097 const server = yield* HttpServer.HttpServer;
1098 const address = server.address;
1099 if (typeof address === "string" || !("port" in address)) return;
1100 const localOrigin = `http://${loopbackUrlHostForAddress(address)}:${address.port}`;
1101 const endpointRuntime = yield* CloudManagedEndpointRuntime.CloudManagedEndpointRuntime;
1102 const runManagedTunnelRecovery = yield* makeManagedTunnelRecovery({
1103 recover: (config: RelayManagedEndpointRuntimeConfig) =>
1104 cloudLink.recoverManagedTunnel(localOrigin, config, {
1105 retryRuntimeFailures: true,
1106 }),
1107 isRetryable: (error) =>
1108 CloudLink.shouldRetryCloudLink(error) &&
1109 error._tag !== "CloudLinkEndpointUnavailableError",
1110 });
1111 const recoverManagedTunnel = (config: RelayManagedEndpointRuntimeConfig) =>
1112 runManagedTunnelRecovery(config).pipe(
1113 Effect.tap((recovered) =>
1114 recovered ? Effect.logInfo("T3 Connect managed tunnel recovered") : Effect.void,
1115 ),
1116 Effect.catchCause((cause) =>
1117 Cause.hasInterrupts(cause)
1118 ? Effect.interrupt
1119 : Effect.logWarning("Failed to recover the T3 Connect managed tunnel", {
1120 cause,
1121 }),
1122 ),
1123 );
1124 yield* endpointRuntime.recoveryRequests.pipe(
1125 Stream.runForEach(recoverManagedTunnel),
1126 Effect.forkScoped,
1127 );
1128 const wantsCliLink = hasCloudPublicConfig
1129 ? yield* CloudCliState.readCliDesiredCloudLink.pipe(
1130 Effect.catch((cause) =>
1131 Effect.logWarning("Failed to read the desired T3 Connect link", { cause }).pipe(
1132 Effect.as(false),
1133 ),
1134 ),
1135 )
1136 : false;
1137 // Recover reads so this fiber still registers and consumes recovery requests.
1138 const desiredCliLinkMode = wantsCliLink
1139 ? yield* CloudCliState.readCliDesiredLinkMode.pipe(
1140 Effect.catch((cause) =>
1141 Effect.logWarning("Failed to read the desired T3 Connect link mode", {
1142 cause,
1143 }).pipe(Effect.as("managed" as const)),
1144 ),
1145 )
1146 : null;
1147 // Publish-only links must not expose the host through old managed configs.
1148 const startedConfirmed =
1149 desiredCliLinkMode === "publish_only"
1150 ? false
1151 : yield* cloudLink.startManagedTunnelIfOriginConfirmed(localOrigin).pipe(
1152 Effect.catch((cause) =>
1153 Effect.logWarning("Failed to start the confirmed T3 Connect tunnel", {
1154 cause,
1155 }).pipe(Effect.as(false)),
1156 ),
1157 );
1158 const startStoredManagedTunnel = cloudLink
1159 .startManagedTunnelIfOriginConfirmed(localOrigin, {
1160 requireConfirmedOrigin: false,
1161 })
1162 .pipe(
1163 Effect.tap((started) =>
1164 started
1165 ? Effect.logWarning(
1166 "T3 Connect started the stored tunnel without relay confirmation",
1167 )
1168 : Effect.void,
1169 ),
1170 Effect.catch((cause) =>
1171 Effect.logWarning("Failed to start the stored T3 Connect tunnel", { cause }),
1172 ),
1173 Effect.asVoid,
1174 );
1175 const registerManagedTunnel = retryManagedTunnelRegistration(
1176 cloudLink.registerManagedTunnelRecovery(localOrigin, {
1177 retryRuntimeFailures: true,
1178 }),
1179 (error) =>
1180 CloudLink.shouldRetryCloudLink(error) &&
1181 error._tag !== "CloudLinkEndpointUnavailableError",
1182 startedConfirmed ? Effect.void : startStoredManagedTunnel,
1183 ).pipe(
1184 Effect.tap((result) =>
1185 result.status === "ready"
1186 ? Effect.logInfo("T3 Connect managed tunnel recovery registered")
1187 : Effect.void,
1188 ),
1189 Effect.catchCause((cause) =>
1190 Cause.hasInterrupts(cause)
1191 ? Effect.interrupt
1192 : Effect.logWarning("Failed to register T3 Connect managed tunnel recovery", {
1193 cause,
1194 }).pipe(Effect.as({ status: "unavailable" as const })),
1195 ),
1196 );
1197 // Jitter registrations so update waves do not hit the relay simultaneously.
1198 if (!startedConfirmed && desiredCliLinkMode !== "publish_only") {
1199 const jitter = yield* Random.nextIntBetween(
1200 0,
1201 Duration.toMillis(MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER),
1202 );
1203 yield* Effect.sleep(Duration.millis(jitter));
1204 }
1205 const registration =
1206 desiredCliLinkMode === "publish_only"
1207 ? { status: "not_linked" as const }
1208 : yield* registerManagedTunnel;
1209 if (registration.status === "unavailable" && !startedConfirmed) {
1210 yield* startStoredManagedTunnel;
1211 }
1212 const startupAction = managedTunnelStartupAction({ wantsCliLink, registration });
1213 if (startupAction.action === "request_recovery") {
1214 yield* endpointRuntime.requestRecovery(startupAction.config);
1215 }
1216 if (startupAction.action === "reconcile_link") {
1217 const reconciledMode = yield* cloudLink
1218 .reconcileDesiredLinkIfStillDesired(localOrigin)
1219 .pipe(
1220 (reconcile) =>
1221 retryDesiredLinkReconcile(reconcile, CloudLink.shouldRetryCloudLink, (cause) =>
1222 Effect.logWarning(
1223 "T3 Connect relay is still unavailable; retrying the desired link in the background",
1224 { cause },
1225 ),
1226 ),
1227 Effect.tap((mode) =>
1228 mode === null
1229 ? Effect.void
1230 : Effect.logInfo("T3 Connect desired link reconciled on startup"),
1231 ),
1232 Effect.catch((cause) =>
1233 Effect.logWarning("Failed to reconcile T3 Connect desired link on startup", {
1234 cause,
1235 }).pipe(Effect.as(null)),
1236 ),
1237 );
1238 if (reconciledMode === "managed") {
1239 const afterReconcile = yield* registerManagedTunnel;
1240 if (afterReconcile.status === "recovery_required") {
1241 yield* endpointRuntime.requestRecovery(afterReconcile.config);
1242 }
1243 }
1244 }
1245 }),
1246 );
1247 yield* Deferred.succeed(cloudLinkParked, undefined).pipe(Effect.orDie);
1248 }),
1249 );
1250
1251 const layerRuntimeServices = ServerRuntimeStartup.layerWithOptions({
1252 activate: Deferred.succeed(activation, undefined).pipe(Effect.asVoid),
1253 abort: (error) => Deferred.die(activation, error).pipe(Effect.asVoid),
1254 awaitAuxiliaryParked: Effect.all(
1255 [
1256 Deferred.await(runtimeStateParked),
1257 Deferred.await(cloudLinkParked),
1258 Deferred.await(routesReady),
1259 ...(config.tailscaleServeEnabled ? [Deferred.await(tailscaleParked)] : []),
1260 ],
1261 { concurrency: "unbounded" },
1262 ).pipe(Effect.asVoid),
1263 }).pipe(Layer.provideMerge(layerRuntimeDependencies), Layer.provide(layerLauncher));
1264
1265 const layerRoutes = HttpRouter.serve(layerMakeRoutes.pipe(Layer.provide(layerLauncher)), {
1266 disableLogger: !config.logWebSocketEvents,
1267 routerConfig: HTTP_ROUTER_CONFIG,
1268 }).pipe(
1269 withUntracedRequests,
1270 Layer.tap(() => Deferred.succeed(routesReady, undefined).pipe(Effect.orDie)),
1271 );
1272 const layerServerApplication = Layer.mergeAll(
1273 layerRoutes,
1274 layerHttpListening,
1275 layerRuntimeState.pipe(Layer.provide(layerLauncher)),
1276 layerTailscaleServe,
1277 layerCloudDesiredLinkReconcile,
1278 HeapSnapshot.layer,
1279 );
1280
1281 return layerServerApplication.pipe(
1282 Layer.provide(CloudLink.layer.pipe(Layer.provideMerge(ConnectReachability.layer))),
1283 Layer.provideMerge(layerRuntimeServices),
1284 Layer.provideMerge(
1285 McpSessionRegistry.layer.pipe(
1286 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),
1287 ),
1288 ),
1289 Layer.provide(layerActivation),
1290 Layer.provideMerge(
1291 RelayTracing.layerServerRelayBroker.pipe(Layer.provide(ManagedPolicy.layer)),
1292 ),
1293 Layer.provideMerge(layerHttpServer),
1294 Layer.provide(layerApplicationObservability),
1295 Layer.provideMerge(FetchHttpClient.layer),
1296 Layer.provide(VcsProcess.layer),
1297 Layer.provideMerge(AgentScopeLive.layer),
1298 Layer.provideMerge(layerPlatformServices),
1299 Layer.provide(Layer.succeed(HostProcess.InvokedAs, invokedAs)),
1300 );
1301 }),
1302).pipe(Layer.provide(ProcessRunner.layer));
1303
1304export const runServer = Layer.launch(layerMakeServer);