Skip to content
Source
Sign inSign up
Open History to see the last commit

server.ts

1,303 lines57.2 KB

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 Cause from "effect/Cause";
20import * as Duration from "effect/Duration";
21import * as Deferred from "effect/Deferred";
22import * as Effect from "effect/Effect";
23import * as Option from "effect/Option";
24import * as Layer from "effect/Layer";
25import * as Stream from "effect/Stream";
26import { FetchHttpClient, HttpRouter, HttpServer } from "effect/http";
27import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
28​
29import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
30import * as HostPowerMonitor from "./background/HostPowerMonitor.ts";
31import * as ServerConfig from "./config.ts";
32import { withUntracedRequests } from "./http.ts";
33import * as ServerHttp from "./http.ts";
34import {
35 guardHttpResponseWriteErrors,
36 keepIdleConnections,
37 rejectRequestsUntilServing,
38} from "./httpResponseErrorGuard.ts";
39import { fixPath } from "./os-jank.ts";
40import * as Ws from "./ws.ts";
41import * as AgentScopeLive from "./process/agentScope.ts";
42import * as ExternalLauncher from "./process/externalLauncher.ts";
43import * as NodePtyAdapter from "./terminal/NodePtyAdapter.ts";
44import * as PullRequestHttp from "./pullRequest/http.ts";
45import * as SpeechModelStore from "./speech/SpeechModelStore.ts";
46import * as SpeechService from "./speech/SpeechService.ts";
47import * as CloudSpeech from "./speech/CloudSpeech.ts";
48import { speechHttpApiLayer } from "./speech/http.ts";
49import { speechStreamRouteLayer } from "./speech/stream.ts";
50import * as PullRequestProviderRegistry from "./pullRequest/PullRequestProviderRegistry.ts";
51import * as PullRequestService from "./pullRequest/PullRequestService.ts";
52import * as SqlitePersistence from "./persistence/Sqlite.ts";
53import * as PullRequestFilesViewed from "./persistence/PullRequestFilesViewed.ts";
54import * as PullRequestReviewGuides from "./persistence/PullRequestReviewGuides.ts";
55import * as PullRequestReviewGuideService from "./pullRequest/PullRequestReviewGuideService.ts";
56import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
57import * as AnalyticsService from "./telemetry/AnalyticsService.ts";
58import * as DelegatedTaskAnalytics from "./telemetry/DelegatedTaskAnalytics.ts";
59import * as ProviderUsageAnalytics from "./telemetry/ProviderUsageAnalytics.ts";
60import * as ModelManifest from "./provider/ModelManifest.ts";
61import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";
62import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";
63import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
64import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
65import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
66import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
67import * as ProviderHostLive from "./provider/ProviderHostLive.ts";
68import * as DocumentText from "./provider/DocumentText.ts";
69import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport";
70import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";
71import * as NativeChildWorkQuery from "./orchestration-v2/NativeChildWorkQuery.ts";
72import * as CheckpointStore from "./checkpointing/CheckpointStore.ts";
73import * as SourceControlBuiltInDrivers from "./sourceControl/builtInDrivers.ts";
74import * as SourceControlSignIn from "./sourceControl/SourceControlSignIn.ts";
75import { BUILT_IN_SOURCE_CONTROL_DRIVER_LIST } from "./sourceControl/sourceControlDefinitions.ts";
76import * as TextGeneration from "./textGeneration/TextGeneration.ts";
77import * as SecretResolver from "./secrets/SecretResolver.ts";
78import * as ProviderInstanceRegistryHydration from "./provider/ProviderInstanceRegistryHydration.ts";
79import * as ProjectSettingsOverrideSweep from "./provider/ProjectSettingsOverrideSweep.ts";
80import * as TerminalManager from "./terminal/Manager.ts";
81import * as McpHttpServer from "./mcp/McpHttpServer.ts";
82import * as PeerForwarding from "./peer/PeerForwarding.ts";
83import * as RemoteDelegation from "./peer/RemoteDelegation.ts";
84import * as PeerLinkRequests from "./peer/PeerLinkRequests.ts";
85import * as PeerLinks from "./peer/PeerLinks.ts";
86import * as PeerMcpClient from "./peer/PeerMcpClient.ts";
87import * as McpSessionRegistry from "./mcp/McpSessionRegistry.ts";
88import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts";
89import * as DeviceService from "./device/DeviceService.ts";
90import * as DeviceHubProxy from "./device/DeviceHubProxy.ts";
91import * as PreviewManager from "./preview/Manager.ts";
92import * as PortScanner from "./preview/PortScanner.ts";
93import * as ServerBrowser from "./preview/ServerBrowser.ts";
94import * as PreviewAgentActivity from "./preview/PreviewAgentActivity.ts";
95import * as DesktopBrowserChannel from "./preview/DesktopBrowserChannel.ts";
96import * as ServerBrowserStream from "./preview/ServerBrowserStream.ts";
97import * as PreviewBrowser from "./preview/PreviewBrowser.ts";
98import * as ProcessRunner from "./processRunner.ts";
99import * as GitManager from "./git/GitManager.ts";
100import * as EnvironmentTheme from "./environmentTheme.ts";
101import * as Keybindings from "./keybindings.ts";
102import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
103import * as ServerOwnership from "./serverOwnership.ts";
104import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
105import { hasCloudPublicConfig } from "./cloud/publicConfig.ts";
106import * as ServerSettings from "./serverSettings.ts";
107import * as ManagedLimits from "./policy/managedLimits.ts";
108import * as ManagedPolicy from "./policy/ManagedPolicy.ts";
109import * as ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts";
110import * as NativeAppIconResolver from "./assets/NativeAppIconResolver.ts";
111import * as AntigravityInstallation from "./provider/AntigravityInstallation.ts";
112import * as ProviderInstallation from "./provider/providerInstallation.ts";
113import * as CodexInstallation from "./provider/CodexInstallation.ts";
114import * as ProviderInstallationGate from "./provider/ProviderInstallationGate.ts";
115import * as ProviderInstanceRegistry from "./provider/ProviderInstanceRegistry.ts";
116import * as ProviderAdapterRegistry from "./orchestration-v2/ProviderAdapterRegistry.ts";
117import * as ProviderRegistry from "./provider/ProviderRegistry.ts";
118import * as ProviderUsageLimitsIngestion from "./provider/ProviderUsageLimitsIngestion.ts";
119import * as UsageLimitSources from "./usage/UsageLimitSources.ts";
120import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts";
121import * as T3ProjectFileLoader from "./project/T3ProjectFileLoader.ts";
122import * as RepositoryIdentityResolver from "./project/RepositoryIdentityResolver.ts";
123import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts";
124import * as KnownWorkspaces from "./workspace/KnownWorkspaces.ts";
125import * as WorkspaceFileSystem from "./workspace/WorkspaceFileSystem.ts";
126import * as WorkspacePaths from "./workspace/WorkspacePaths.ts";
127import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";
128import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts";
129import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts";
130import * as VcsProcess from "./vcs/VcsProcess.ts";
131import * as VcsProvisioningService from "./vcs/VcsProvisioningService.ts";
132import * as VcsStatusBroadcaster from "./vcs/VcsStatusBroadcaster.ts";
133import * as ProjectCloneTracker from "./project/ProjectCloneTracker.ts";
134import * as GitWorkflowService from "./git/GitWorkflowService.ts";
135import * as ReviewService from "./review/ReviewService.ts";
136import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts";
137import * as PullRequestReadCache from "./pullRequest/PullRequestReadCache.ts";
138import * as SourceControlRateLimit from "@t3tools/source-control-core/server/SourceControlRateLimit";
139import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts";
140import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts";
141import * as Observability from "./observability/Observability.ts";
142import * as HeapSnapshot from "./observability/HeapSnapshot.ts";
143import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts";
144import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
145import * as DirectEndpoints from "./environment/DirectEndpoints.ts";
146import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
147import * as AuthHttp from "./auth/http.ts";
148import * as ReplayMarkers from "./auth/replayMarkers.ts";
149import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
150import * as CodexManagedTokenBroker from "./provider/CodexManagedTokenBroker.ts";
151import * as WebhookRoute from "./scheduledTasks/webhookRoute.ts";
152import * as RelayDeliveryProof from "./scheduledTasks/RelayDeliveryProof.ts";
153import * as HeldHooksWaker from "./relay/HeldHooksWaker.ts";
154import * as McpOAuth from "./auth/McpOAuth.ts";
155import * as McpOAuthHttp from "./auth/mcpOAuthHttp.ts";
156import {
157 relayHookBaseUrl,
158 ScheduledTaskWebhookOrigin,
159} from "./scheduledTasks/ScheduledTaskService.ts";
160import {
161 CLOUD_ENDPOINT_RUNTIME_CONFIG,
162 decodeRuntimeConfig,
163 RELAY_URL_SECRET,
164} from "./cloud/config.ts";
165import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
166import * as EnvironmentAuthPolicy from "./auth/EnvironmentAuthPolicy.ts";
167import * as CloudHttp from "./cloud/http.ts";
168import * as GuestHttp from "./guest/http.ts";
169import * as CloudLink from "./cloud/CloudLink.ts";
170import * as ConnectReachability from "./cloud/ConnectReachability.ts";
171import { pendingServiceUpdateExists } from "./cloud/updateHandoff.ts";
172import * as RelayTracing from "./cloud/relayTracing.ts";
173import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts";
174import {
175 MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER,
176 makeManagedTunnelRecovery,
177 managedTunnelStartupAction,
178 retryDesiredLinkReconcile,
179 retryManagedTunnelRegistration,
180} from "./cloud/managedTunnelStartup.ts";
181import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts";
182import * as CloudCliState from "./cloud/CliState.ts";
183import * as ServerSelfUpdate from "./cloud/selfUpdate.ts";
184import * as DesktopAppUpdate from "./desktopUpdate/DesktopAppUpdate.ts";
185import * as ServiceLauncherClient from "./cloud/serviceLauncherClient.ts";
186import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
187import * as HostResources from "./resourceTelemetry/HostResources.ts";
188import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
189import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts";
190import * as TriageService from "./triage/TriageService.ts";
191import * as DesktopTelemetryReceiver from "./resourceTelemetry/DesktopTelemetryReceiver.ts";
192import * as NativeTelemetryClient from "./resourceTelemetry/NativeTelemetryClient.ts";
193import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts";
194import * as ResourceMonitorBinary from "./resourceTelemetry/ResourceMonitorBinary.ts";
195import * as ProcessGroupMetrics from "./resourceTelemetry/ProcessGroupMetrics.ts";
196import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
197import * as AntigravityUsage from "./provider/Drivers/AntigravityUsage.ts";
198import { MuseUsageHome } from "@t3tools/provider-muse/server";
199import * as CursorAccountReader from "@t3tools/provider-cursor/server/CursorAccountReader";
200import * as CursorUsageAccounts from "@t3tools/provider-cursor/server/CursorUsageAccounts";
201import * as UsageService from "./usage/UsageService.ts";
202import * as CodexUsageHistory from "./usage/CodexUsageHistory.ts";
203import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts";
204import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
205import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts";
206import * as ThreadSettlementService from "./orchestration-v2/ThreadSettlementService.ts";
207import * as IdleCompactionService from "./orchestration-v2/IdleCompactionService.ts";
208import * as ThreadPullRequestService from "./orchestration-v2/ThreadPullRequestService.ts";
209import * as RunFinalizationService from "./orchestration-v2/RunFinalizationService.ts";
210import * as ProjectionMaintenance from "./orchestration-v2/ProjectionMaintenance.ts";
211import * as ProjectionStoreV2 from "./orchestration-v2/ProjectionStore.ts";
212import { makePersistedServerRuntimeState } from "./serverRuntimeState.ts";
213import { acceptsPrivateNetworkConnection, loopbackUrlHostForAddress } from "./startupAccess.ts";
214import * as OrchestrationHttp from "./orchestration-v2/http.ts";
215import * as ProjectHttp from "./project/http.ts";
216import * as NetService from "@t3tools/shared/Net";
217import * as RelayClient from "@t3tools/shared/relayClient";
218import {
219 disableTailscaleServe,
220 ensureTailscaleServe,
221 readTailscaleServeRootHolders,
222 readTailscaleServeRoutesTo,
223 readTailscaleStatus,
224} from "@t3tools/tailscale";
225import * as ServerActivation from "./serverActivation.ts";
226​
227// Escaped MCP handoff provenance can exceed find-my-way's 100-character segment default.
228const HTTP_ROUTER_CONFIG = {
229 maxParamLength: 512,
230} as const;
231​
232// Skip Effect's default 20 s grace; WebSocket scope finalizers already close connections gracefully.
233const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
234const layerResourceAttribution = ResourceAttribution.layer;
235const layerApplicationObservability = EventLoopMonitor.layer.pipe(
236 Layer.provideMerge(Observability.layer),
237 Layer.provideMerge(layerResourceAttribution),
238);
239​
240// Share rotation state so driver loggers cannot race.
241const layerProviderEventLoggers = Layer.effect(
242 ProviderEventLoggers.ProviderEventLoggers,
243 Effect.gen(function* () {
244 const { providerEventLogPath } = yield* ServerConfig.ServerConfig;
245 const attribution = yield* ResourceAttribution.ResourceAttribution;
246 const store = yield* EventNdjsonLogger.makeEventNdjsonLogStore(providerEventLogPath, {
247 attribution,
248 }).pipe(
249 Effect.catch((error) =>
250 Effect.logWarning(error.message, { error }).pipe(
251 Effect.annotateLogs({ scope: "provider-observability" }),
252 Effect.as(undefined),
253 ),
254 ),
255 );
256 if (!store) return ProviderEventLoggers.NoOpProviderEventLoggers;
257 yield* Effect.addFinalizer(() => store.close());
258 return { native: store.logger("native"), canonical: store.logger("canonical") };
259 }),
260);
261​
262const layerPtyAdapter = NodePtyAdapter.layer;
263​
264const layerServerSettings = ServerSettings.layer.pipe(
265 Layer.provide(ServerSecretStore.layer),
266 Layer.provideMerge(ManagedPolicy.layer),
267 Layer.provideMerge(SqlitePersistence.layerConfig),
268);
269​
270const layerNativeTelemetry = NativeTelemetryClient.layer.pipe(
271 Layer.provide(ResourceMonitorBinary.layer),
272);
273const layerDesktopTelemetryReceiver = DesktopTelemetryReceiver.layer.pipe(
274 Layer.provideMerge(layerServerSettings),
275);
276​
277const layerResourceTelemetry = ResourceTelemetry.layer.pipe(
278 Layer.provideMerge(layerNativeTelemetry),
279 Layer.provideMerge(layerDesktopTelemetryReceiver),
280);
281​
282const layerHostPowerMonitor = HostPowerMonitor.layer.pipe(
283 Layer.provide(layerDesktopTelemetryReceiver),
284);
285​
286// A fresh receiver layer would open a second reader on the desktop telemetry fd.
287const layerDesktopAppUpdate = DesktopAppUpdate.layer.pipe(
288 Layer.provide(layerDesktopTelemetryReceiver),
289);
290​
291const layerBackground = BackgroundPolicy.layer.pipe(
292 Layer.provide(layerHostPowerMonitor),
293 Layer.provideMerge(layerServerSettings),
294);
295​
296const layerUsage = UsageService.layer.pipe(
297 Layer.provide(
298 Layer.mergeAll(
299 AntigravityUsage.layer,
300 MuseUsageHome.layer,
301 CodexUsageHistory.layer,
302 CursorUsageAccounts.layer.pipe(Layer.provide(CursorAccountReader.layer)),
303 ).pipe(Layer.provide(ProviderHostLive.layer.pipe(Layer.provide(ServerSecretStore.layer)))),
304 ),
305 Layer.provide(layerServerSettings),
306);
307​
308const layerResourceDiagnostics = Layer.mergeAll(
309 HostResources.layer,
310 layerResourceTelemetry,
311 ProcessDiagnostics.layer.pipe(Layer.provide(layerResourceTelemetry)),
312 ProcessResourceMonitor.layer.pipe(Layer.provide(layerResourceTelemetry)),
313 ProcessGroupMetrics.layer.pipe(Layer.provide(layerResourceTelemetry)),
314);
315​
316const layerRelayClient = Layer.unwrap(
317 Effect.gen(function* () {
318 const config = yield* ServerConfig.ServerConfig;
319 const policy = yield* ManagedPolicy.ManagedPolicy;
320 const layerCloudflared = RelayClient.layerCloudflared({ baseDir: config.baseDir });
321 if (ManagedLimits.allowsConnect(policy)) return layerCloudflared;
322 // The connector only carries T3 Connect, so a policy without Connect never installs one.
323 const refused = Effect.fn("RelayClient.installRefusedByPolicy")(function* () {
324 return yield* new RelayClient.RelayClientInstallError({
325 reason: "managed_by_policy",
326 message: ManagedLimits.refuseConnect(policy).message,
327 });
328 });
329 return Layer.effect(
330 RelayClient.RelayClient,
331 Effect.gen(function* () {
332 const relayClient = yield* RelayClient.RelayClient;
333 return RelayClient.RelayClient.of({
334 ...relayClient,
335 install: refused(),
336 installWithProgress: refused,
337 });
338 }),
339 ).pipe(Layer.provide(layerCloudflared));
340 }),
341).pipe(Layer.provide(ManagedPolicy.layer));
342​
343const layerHttpServer = Layer.unwrap(
344 Effect.gen(function* () {
345 const config = yield* ServerConfig.ServerConfig;
346 // Before binding: a host the policy does not allow must never listen, not even briefly.
347 const policy = yield* ManagedPolicy.ManagedPolicy;
348 const bindHost = yield* ManagedLimits.checkServerReachability(policy, {
349 authPolicy: EnvironmentAuthPolicy.resolveServerAuthPolicy(config),
350 host: config.host,
351 tailscaleServeEnabled: config.tailscaleServeEnabled,
352 privateNetworkOnly: config.privateNetworkOnly,
353 });
354 yield* ManagedLimits.closeTailscaleServeTo(policy, { host: bindHost, port: config.port });
355 return NodeHttpServer.layer(
356 () => {
357 const server = rejectRequestsUntilServing(
358 keepIdleConnections(guardHttpResponseWriteErrors(NodeHttp.createServer())),
359 );
360 if (config.privateNetworkOnly) {
361 server.on("connection", (socket) => {
362 if (!acceptsPrivateNetworkConnection(socket)) {
363 socket.destroy();
364 }
365 });
366 }
367 return server;
368 },
369 {
370 host: bindHost,
371 port: config.port,
372 gracefulShutdownTimeout: HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS,
373 // Context takeover stays enabled (ws default) so the compression
374 // window is shared across frames — that also makes small frames cheap
375 // to compress, so no size threshold is set (ws only honors
376 // `threshold` when context takeover is disabled).
377 websocket: { perMessageDeflate: true },
378 },
379 );
380 }),
381).pipe(Layer.provide(ManagedPolicy.layer));
382​
383const layerPlatformServices = DocumentText.layer.pipe(
384 Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
385);
386​
387const layerPersistence = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig));
388​
389const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProjectConfig.layer));
390​
391const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.pipe(
392 Layer.provideMerge(SourceControlBuiltInDrivers.layer),
393 Layer.provideMerge(GitVcsDriver.layer),
394 Layer.provideMerge(layerVcsDriverRegistry),
395);
396​
397const layerRepositoryIdentityResolver = Layer.effect(
398 RepositoryIdentityResolver.RepositoryIdentityResolver,
399 Effect.gen(function* () {
400 const registry = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
401 return yield* RepositoryIdentityResolver.make({
402 refine: Effect.fn(function* (identity: RepositoryIdentity) {
403 for (const { kind } of BUILT_IN_SOURCE_CONTROL_DRIVER_LIST) {
404 const provider = yield* registry.get(kind);
405 if (provider.refineRepositoryIdentity === undefined) continue;
406 const refined = yield* provider.refineRepositoryIdentity({
407 identity,
408 resolveContext: (input) =>
409 registry.resolveHandle(input).pipe(Effect.map((handle) => handle.context)),
410 });
411 if (refined !== identity) return refined;
412 }
413 return identity;
414 }),
415 });
416 }),
417).pipe(Layer.provide(layerSourceControlProviderRegistry), Layer.provide(ProcessRunner.layer));
418​
419const layerPullRequestService = PullRequestService.layer.pipe(
420 Layer.provide(PullRequestProviderRegistry.layer),
421 Layer.provide(PullRequestFilesViewed.layer),
422 Layer.provide(PullRequestReadCache.layer),
423 Layer.provide(layerSourceControlProviderRegistry),
424 Layer.provide(SourceControlRateLimit.layer),
425);
426​
427const layerGitManager = GitManager.layer.pipe(
428 Layer.provide(Layer.merge(ProjectionStoreV2.layer, ProjectStore.layer)),
429 Layer.provideMerge(RuntimeLayer.layerProjectSetupScriptRunner),
430 Layer.provideMerge(WorktreeSetupTracker.layer),
431 Layer.provideMerge(GitVcsDriver.layer),
432 Layer.provideMerge(layerSourceControlProviderRegistry),
433 Layer.provideMerge(TextGeneration.layer.pipe(Layer.provide(layerSourceControlProviderRegistry))),
434);
435​
436const layerGit = Layer.empty.pipe(
437 Layer.provideMerge(layerGitManager),
438 Layer.provideMerge(GitVcsDriver.layer),
439);
440​
441const layerGitWorkflow = GitWorkflowService.layer.pipe(
442 Layer.provideMerge(layerVcsDriverRegistry),
443 Layer.provideMerge(layerGit),
444);
445​
446const layerSourceControlRepositoryService = SourceControlRepositoryService.layer.pipe(
447 Layer.provideMerge(GitVcsDriver.layer),
448 Layer.provideMerge(layerSourceControlProviderRegistry),
449);
450​
451const layerProjectCloneTracker = ProjectCloneTracker.layer.pipe(
452 Layer.provide(layerSourceControlRepositoryService),
453);
454​
455const layerKnownWorkspaces = KnownWorkspaces.layer.pipe(
456 Layer.provide(ProjectStore.layer),
457 Layer.provide(GitVcsDriver.layer),
458);
459​
460const layerReview = ReviewService.layer.pipe(
461 Layer.provideMerge(layerKnownWorkspaces),
462 Layer.provideMerge(GitVcsDriver.layer),
463 Layer.provideMerge(layerVcsDriverRegistry),
464);
465​
466const layerVcs = Layer.empty.pipe(
467 Layer.provideMerge(VcsProjectConfig.layer),
468 Layer.provideMerge(layerVcsDriverRegistry),
469 Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(layerVcsDriverRegistry))),
470 Layer.provideMerge(layerGitWorkflow),
471 Layer.provideMerge(layerReview),
472 Layer.provideMerge(layerSourceControlRepositoryService),
473 Layer.provideMerge(
474 SourceControlSignIn.layer.pipe(Layer.provide(layerSourceControlProviderRegistry)),
475 ),
476 Layer.provideMerge(layerProjectCloneTracker),
477 Layer.provideMerge(
478 VcsStatusBroadcaster.layer.pipe(
479 Layer.provide(layerGitWorkflow),
480 Layer.provide(VcsStatusBroadcaster.layerAutomaticFetchInterval),
481 // The orchestration runtime also consumes the broadcaster (run
482 // finalization), so the policy cannot read the store from the runtime's
483 // output.
484 Layer.provide(
485 VcsStatusBroadcaster.layerAutoPullPolicy.pipe(Layer.provide(ProjectStore.layer)),
486 ),
487 ),
488 ),
489);
490​
491const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
492​
493const layerPortScanner = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
494​
495const layerTerminal = TerminalManager.layer.pipe(
496 Layer.provide(layerPtyAdapter),
497 Layer.provide(layerPortScanner),
498 Layer.provide(layerNativeTelemetry),
499);
500​
501const layerPreview = Layer.empty.pipe(
502 Layer.provideMerge(PreviewManager.layer),
503 Layer.provideMerge(layerPortScanner),
504);
505​
506const layerDevice = DeviceService.layer.pipe(
507 Layer.provide(layerServerSettings),
508 Layer.provide(ProcessRunner.layer),
509 Layer.provide(NetService.layer),
510);
511​
512const layerWorkspaceEntries = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
513​
514const layerWorkspaceFileSystem = WorkspaceFileSystem.layer.pipe(
515 Layer.provide(WorkspacePaths.layer),
516 Layer.provide(layerWorkspaceEntries),
517 Layer.provide(layerKnownWorkspaces),
518);
519​
520const layerWorkspace = Layer.mergeAll(
521 WorkspacePaths.layer,
522 layerWorkspaceEntries,
523 layerWorkspaceFileSystem,
524);
525​
526const layerProjectFaviconResolver = ProjectFaviconResolver.layer.pipe(
527 Layer.provide(WorkspacePaths.layer),
528 Layer.provide(T3ProjectFileLoader.layer),
529);
530​
531const layerServerEnvironment = ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer));
532​
533const layerAuth = EnvironmentAuth.layer.pipe(
534 Layer.provideMerge(layerPersistence),
535 Layer.provide(layerServerEnvironment),
536 Layer.provide(ServerSecretStore.layer),
537);
538​
539const layerCloudManagedEndpointRuntime = Layer.mergeAll(
540 layerRelayClient,
541 CloudManagedEndpointRuntime.layer.pipe(
542 Layer.provide(ServerSecretStore.layer),
543 Layer.provide(layerRelayClient),
544 ),
545);
546​
547const layerScheduledTaskWebhookOrigin = Layer.effect(
548 ScheduledTaskWebhookOrigin,
549 Effect.gen(function* () {
550 const secrets = yield* ServerSecretStore.ServerSecretStore;
551 return Effect.gen(function* () {
552 const [relayUrl, tunnelConfig] = yield* Effect.all([
553 secrets.get(RELAY_URL_SECRET),
554 secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
555 ]).pipe(Effect.orElseSucceed(() => [Option.none(), Option.none()] as const));
556 if (Option.isNone(relayUrl) || Option.isNone(tunnelConfig)) {
557 return { relayHookBaseUrl: null };
558 }
559 const config = decodeRuntimeConfig(new TextDecoder().decode(tunnelConfig.value));
560 return {
561 relayHookBaseUrl: relayHookBaseUrl({
562 relayUrl: new TextDecoder().decode(relayUrl.value),
563 tunnelName: Option.isSome(config) ? config.value.tunnelName : undefined,
564 }),
565 };
566 });
567 }),
568);
569​
570const layerOrchestrationV2Runtime = RuntimeLayer.layerProduction.pipe(
571 Layer.provide(layerScheduledTaskWebhookOrigin),
572 Layer.provide(ProviderUsageAnalytics.layer),
573 Layer.provide(layerCheckpointStore),
574 Layer.provide(layerGitWorkflow),
575 Layer.provide(
576 RunFinalizationService.layerObserver.pipe(
577 Layer.provide(ProjectionStoreV2.layer),
578 Layer.provide(layerPullRequestService),
579 Layer.provide(RuntimeLayer.layerProjectService),
580 ),
581 ),
582);
583​
584const layerOrchestrationApplication = Layer.merge(
585 CheckpointDiffQuery.layer,
586 NativeChildWorkQuery.layer,
587).pipe(Layer.provideMerge(layerCheckpointStore), Layer.provideMerge(layerOrchestrationV2Runtime));
588​
589const layerThreadSettlementWorker = Layer.effectDiscard(
590 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),
591).pipe(Layer.provide(layerPullRequestService), Layer.provide(ProjectionStoreV2.layer));
592​
593const layerIdleCompactionWorker = Layer.effectDiscard(
594 IdleCompactionService.make.pipe(Effect.flatMap((service) => service.start())),
595).pipe(Layer.provide(ProjectionStoreV2.layer));
596​
597const layerThreadPullRequestWorker = Layer.effectDiscard(
598 ThreadPullRequestService.make.pipe(Effect.flatMap((service) => service.start())),
599).pipe(Layer.provide(layerPullRequestService));
600​
601const layerProviderInstallationRefresh = Layer.effectDiscard(
602 Effect.gen(function* () {
603 const antigravity = yield* AntigravityInstallation.AntigravityInstallation;
604 const codex = yield* CodexInstallation.CodexInstallation;
605 const instances = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
606 const providers = yield* ProviderRegistry.ProviderRegistry;
607 yield* Stream.merge(
608 antigravity.changes.pipe(
609 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
610 Stream.drop(1),
611 ),
612 codex.changes.pipe(
613 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
614 Stream.drop(1),
615 ),
616 ).pipe(
617 Stream.runForEach((state) =>
618 instances.listInstances.pipe(
619 Effect.flatMap((entries) =>
620 Effect.forEach(
621 entries.filter((instance) => instance.driverKind === state.driver),
622 (instance) => providers.refreshInstance(instance.instanceId),
623 { discard: true },
624 ),
625 ),
626 ),
627 ),
628 Effect.forkScoped,
629 );
630 }),
631);
632​
633// The desktop app and the background service start the server again after it exits.
634const layerServerRestart = Layer.effect(
635 DatabaseStorage.ServerRestart,
636 Effect.gen(function* () {
637 const config = yield* ServerConfig.ServerConfig;
638 const launcher = yield* ServiceLauncherClient.ServiceLauncherClient;
639 // As in runServerCommand, only the desktop app passes a bootstrap token; `serve --mode desktop` by hand has no supervisor.
640 const desktopLaunched = config.mode === "desktop" && config.desktopBootstrapToken !== undefined;
641 if (!desktopLaunched && !launcher.managed) return null;
642 // Stop as a SIGTERM would: runMain's listener interrupts the server, so its shutdown path runs.
643 return Effect.sync(() => void process.emit("SIGTERM"));
644 }),
645);
646​
647const layerRuntimeCoreDependenciesBase = Layer.mergeAll(
648 AgentAwarenessRelay.layer,
649 HeldHooksWaker.layer,
650 layerThreadSettlementWorker,
651 layerIdleCompactionWorker,
652 StorageCleanup.layer.pipe(Layer.provide(ProjectionStoreV2.layer)),
653 DatabaseStorage.layer.pipe(
654 Layer.provide(ProjectionMaintenance.layer),
655 Layer.provide(layerServerRestart),
656 ),
657 layerThreadPullRequestWorker,
658 DelegatedTaskAnalytics.layer,
659 Layer.effectDiscard(
660 Effect.gen(function* () {
661 const service = yield* PullRequestSyncReactor.PullRequestSyncReactor;
662 yield* service.start();
663 }),
664 ).pipe(
665 Layer.provideMerge(PullRequestSyncReactor.layer),
666 Layer.provide(layerPullRequestService),
667 Layer.provide(ProjectionStoreV2.layer),
668 ),
669 Layer.effectDiscard(
670 Effect.gen(function* () {
671 const service = yield* PullRequestWatchReactor.PullRequestWatchReactor;
672 yield* service.start();
673 }),
674 ).pipe(
675 Layer.provide(PullRequestWatchReactor.layer),
676 Layer.provide(layerPullRequestService),
677 Layer.provide(ProjectionStoreV2.layer),
678 ),
679 Layer.effectDiscard(
680 Effect.gen(function* () {
681 const service = yield* DelegatedTaskQuestionReactor.DelegatedTaskQuestionReactor;
682 yield* service.start();
683 }),
684 ).pipe(Layer.provide(DelegatedTaskQuestionReactor.layer), Layer.provide(ProjectionStoreV2.layer)),
685 ProviderUsageLimitsIngestion.layer,
686 layerProviderInstallationRefresh,
687 ProviderInstallation.layer,
688 ReplayMarkers.layer,
689 Layer.effectDiscard(ProjectSettingsOverrideSweep.sweep),
690).pipe(
691 Layer.provideMerge(layerOrchestrationApplication),
692 Layer.provideMerge(RuntimeLayer.layerEventInfrastructure),
693 Layer.provideMerge(Layer.merge(ProjectStore.layer, ThreadSearch.layer)),
694 Layer.provideMerge(SecretResolver.layer),
695 Layer.provideMerge(layerServerSettings),
696 Layer.provideMerge(layerSourceControlProviderRegistry),
697 Layer.provideMerge(layerGit),
698 Layer.provideMerge(layerVcs),
699 Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)),
700 Layer.provideMerge(layerPersistence),
701 Layer.provideMerge(
702 Layer.mergeAll(Keybindings.layer, EnvironmentTheme.layer, UsageLimitSources.layer),
703 ),
704 Layer.provideMerge(ProviderRegistry.layer),
705 Layer.provideMerge(
706 ProviderInstanceRegistryHydration.layer.pipe(Layer.provide(SqlitePersistence.layerConfig)),
707 ),
708 // One gate for the server, below hydration so the Claude adapter sees it: layers memoize by
709 // reference, so the runtime layer shares it.
710 Layer.provideMerge(ProviderInstallationGate.layer),
711 // Provider hydration builds the Claude runner, which reports agents to the same scanner the
712 // terminal and preview layers share.
713 Layer.provideMerge(layerPortScanner),
714 Layer.provideMerge(
715 Layer.mergeAll(
716 AntigravityInstallation.AntigravityInstallation.layer,
717 CodexInstallation.CodexInstallation.layer,
718 ),
719 ),
720);
721​
722const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase.pipe(
723 Layer.provideMerge(layerPtyAdapter),
724 Layer.provideMerge(AcpRegistrySupport.layerFromHost.pipe(Layer.provide(ProviderHostLive.layer))),
725 Layer.provideMerge(
726 Layer.mergeAll(
727 layerProviderEventLoggers,
728 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),
729 ResetCreditCoordinator.layer,
730 ProviderLatestVersions.layer,
731 McpProviderSessions.layer,
732 ),
733 ),
734 Layer.provideMerge(
735 OpenCodeRuntime.layer.pipe(
736 Layer.provide(
737 Layer.unwrap(
738 Effect.gen(function* () {
739 const config = yield* ServerConfig.ServerConfig;
740 return OpenCodeServerLedger.layer({ stateDir: config.stateDir });
741 }),
742 ),
743 ),
744 ),
745 ),
746 Layer.provideMerge(layerWorkspace),
747 Layer.provideMerge(ProjectEnrichmentService.layer),
748 Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)),
749 Layer.provideMerge(layerRepositoryIdentityResolver),
750 Layer.provideMerge(CodexManagedTokenBroker.layer),
751 Layer.provideMerge(layerServerEnvironment),
752 Layer.provideMerge(layerAuth),
753 Layer.provideMerge(Layer.mergeAll(AssetSigner.layer, EnvironmentKeys.layer)),
754 Layer.provideMerge(ServerSecretStore.layer),
755 Layer.provideMerge(
756 Layer.mergeAll(
757 CloudCliTokenManager.layer.pipe(
758 Layer.provide(ServerSecretStore.layer),
759 Layer.provide(ExternalLauncher.layer),
760 ),
761 layerCloudManagedEndpointRuntime,
762 ),
763 ),
764);
765​
766const layerRuntimeDependencies = layerRuntimeCoreDependencies.pipe(
767 Layer.provideMerge(layerUsage),
768 Layer.provideMerge(layerBackground),
769 Layer.provideMerge(layerResourceDiagnostics),
770 Layer.provideMerge(TraceDiagnostics.layer),
771 Layer.provideMerge(TriageService.layer.pipe(Layer.provide(GitVcsDriver.layer))),
772 // Layers memoize by reference, so this is the same policy the settings layer reads.
773 Layer.provideMerge(AnalyticsService.layer.pipe(Layer.provide(ManagedPolicy.layer))),
774 Layer.provideMerge(ExternalLauncher.layer),
775 Layer.provideMerge(RemoteOpenTargets.layer),
776 Layer.provideMerge(DirectEndpoints.layer),
777 Layer.provideMerge(ServerLifecycleEvents.layer),
778 Layer.provide(NetService.layer),
779);
780​
781const layerCommandReadiness = HttpRouter.middleware(
782 (httpEffect) =>
783 Effect.flatMap(ServerRuntimeStartup.ServerRuntimeStartup, (startup) =>
784 startup.awaitCommandReady.pipe(Effect.orDie, Effect.andThen(httpEffect)),
785 ),
786 { global: true },
787);
788​
789const layerMakeRoutes = Layer.mergeAll(
790 Layer.mergeAll(
791 HttpApiBuilder.layer(EnvironmentHttpApi).pipe(
792 Layer.provide(AuthHttp.layer),
793 Layer.provide(McpOAuthHttp.layer.pipe(Layer.provide(McpOAuth.layer))),
794 Layer.provide(CloudHttp.layer),
795 Layer.provide(OrchestrationHttp.layer),
796 Layer.provide(PullRequestHttp.layer),
797 Layer.provide(ProjectHttp.layer),
798 Layer.provide(speechHttpApiLayer),
799 Layer.provide(ServerHttp.layerServerEnvironmentHttpApi),
800 Layer.provide(WebhookRoute.layer.pipe(Layer.provide(RelayDeliveryProof.layer))),
801 Layer.provide(AuthHttp.layerAuthenticatedAuth),
802 ),
803 ServerHttp.layerOtlpTracesProxyRoute,
804 ServerHttp.layerAssetRoute,
805 ServerHttp.layerAttachmentUploadRoute,
806 GuestHttp.layer,
807 DeviceHubProxy.layer,
808 ServerBrowserStream.routeLayer,
809 ServerHttp.layerStaticAndDevRoute,
810 Ws.layer,
811 speechStreamRouteLayer,
812 ),
813 McpHttpServer.layer.pipe(
814 Layer.provide(ProviderAdapterRegistry.layerFromProviderInstanceRegistry),
815 Layer.provide(McpOAuth.layerMcpClientAuthenticator),
816 ),
817 // Follows delegated tasks running in linked environments, including after a restart.
818 Layer.effectDiscard(
819 RemoteDelegation.RemoteDelegation.pipe(Effect.flatMap((remote) => remote.start())),
820 ),
821).pipe(
822 Layer.provide(
823 PullRequestReviewGuideService.layer.pipe(Layer.provide(PullRequestReviewGuides.layer)),
824 ),
825 // delegate_task to a linked environment, from /mcp and the follower above.
826 Layer.provide(RemoteDelegation.layer.pipe(Layer.provide(ProjectionStoreV2.layer))),
827 // Links agents ask for: shown in a thread from /mcp, answered over WebSocket.
828 Layer.provide(PeerLinkRequests.layer),
829 // Share the service so HTTP/WS clients single-flight reads and invalidate each other's cached patches.
830 Layer.provide(layerPullRequestService),
831 // The same activity instance RunExecutionService reports to, by layer memoization.
832 Layer.provide(
833 ServerBrowser.layer.pipe(
834 Layer.provide(Layer.merge(DesktopBrowserChannel.layer, PreviewAgentActivity.layer)),
835 ),
836 ),
837 Layer.provide(PreviewBrowser.layer),
838 // Links to other environments, shared by Settings (WebSocket) and agents (/mcp).
839 Layer.provide(
840 PeerForwarding.layer.pipe(
841 Layer.provideMerge(PeerMcpClient.layer),
842 Layer.provideMerge(PeerLinks.layer),
843 ),
844 ),
845 Layer.provide(
846 SpeechService.layer.pipe(
847 Layer.provide(layerServerSettings),
848 Layer.provide(SpeechModelStore.layer),
849 ),
850 ),
851 Layer.provide(CloudSpeech.layer.pipe(Layer.provide(SecretResolver.layer))),
852 Layer.provide(layerServerSettings),
853 Layer.provide(PreviewAutomationBroker.layer),
854 Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(layerDesktopAppUpdate))),
855 Layer.provide(layerCommandReadiness),
856 Layer.provide(ServerHttp.layerBrowserApiCors),
857 Layer.provide(ServerHttp.layerHttpCompression),
858);
859​
860const layerMakeServer = Layer.unwrap(
861 Effect.gen(function* () {
862 const config = yield* ServerConfig.ServerConfig;
863 // Before anything opens the database: one server per T3 home.
864 const ownership = yield* ServerOwnership.acquireServerOwnership(config.serverRuntimeStatePath);
865 const activation = yield* Deferred.make<void>();
866 const awaitActivation = Deferred.await(activation);
867 const layerActivation = Layer.succeed(ServerActivation.ServerActivation, awaitActivation);
868 const runtimeStateParked = yield* Deferred.make<void>();
869 const tailscaleParked = yield* Deferred.make<void>();
870 const cloudLinkParked = yield* Deferred.make<void>();
871 const routesReady = yield* Deferred.make<void>();
872 const layerLauncher = ServiceLauncherClient.layer;
873​
874 yield* fixPath({ shellEnvironmentPrepared: config.shellEnvironmentPrepared });
875​
876 const layerHttpListening = Layer.effectDiscard(
877 Effect.gen(function* () {
878 yield* HttpServer.HttpServer;
879 const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
880 yield* startup.markHttpListening;
881 }),
882 );
883 const layerRuntimeState = Layer.effectDiscard(
884 Effect.gen(function* () {
885 yield* Deferred.succeed(runtimeStateParked, undefined).pipe(Effect.orDie);
886 yield* awaitActivation;
887 const server = yield* HttpServer.HttpServer;
888 const address = server.address;
889 if (typeof address === "string" || !("port" in address)) {
890 return;
891 }
892​
893 const launcher = yield* ServiceLauncherClient.ServiceLauncherClient;
894 const state = yield* makePersistedServerRuntimeState({
895 config,
896 port: address.port,
897 serviceManaged: launcher.managed,
898 managedPolicy: yield* ManagedPolicy.loadedPolicy(yield* ManagedPolicy.ManagedPolicy),
899 });
900 yield* ownership
901 .publish(state)
902 .pipe(
903 Effect.catchCause((cause) =>
904 Effect.logWarning("Failed to persist server runtime state", { cause }),
905 ),
906 );
907 }),
908 );
909 // Who holds the Serve port's root: nobody, this server, only dead loopback ports (an unclean
910 // exit leaves one), or another live server or app, whose route is left alone.
911 const tailscaleServeRootHolder = Effect.fn("server.tailscaleServeRootHolder")(
912 function* (target: { readonly host: string; readonly port: number }) {
913 const net = yield* NetService.NetService;
914 const holders = yield* readTailscaleServeRootHolders(config.tailscaleServePort, target);
915 if (holders.length === 0) return "none" as const;
916 if (holders.some((holder) => holder.ours)) return "ours" as const;
917 const live = yield* Effect.forEach(holders, (holder) =>
918 holder.ours || holder.loopback === undefined
919 ? Effect.succeed(true)
920 : net.hasListenerOnHost(holder.loopback.port, holder.loopback.host),
921 );
922 return live.includes(true) ? ("other" as const) : ("dead" as const);
923 },
924 );
925 const layerTailscaleServe = config.tailscaleServeEnabled
926 ? Layer.effectDiscard(
927 Effect.acquireRelease(
928 Effect.gen(function* () {
929 yield* Deferred.succeed(tailscaleParked, undefined).pipe(Effect.orDie);
930 yield* awaitActivation;
931 const server = yield* HttpServer.HttpServer;
932 const address = server.address;
933 if (typeof address === "string" || !("port" in address)) {
934 return null;
935 }
936​
937 const localPort = address.port;
938 const localHost = loopbackUrlHostForAddress(address);
939 const route = {
940 localPort,
941 urlHost: localHost,
942 localHost: localHost.replace(/^\[|\]$/gu, ""),
943 servePort: config.tailscaleServePort,
944 };
945 const holder = yield* tailscaleServeRootHolder({
946 host: route.localHost,
947 port: localPort,
948 }).pipe(Effect.orElseSucceed(() => "unreadable" as const));
949 if (holder === "other" || holder === "unreadable") {
950 yield* Effect.logWarning(
951 holder === "other"
952 ? "Tailscale Serve left off; its port already serves another server or app"
953 : "Tailscale Serve left off; its current routes could not be read",
954 { localPort, servePort: config.tailscaleServePort },
955 );
956 return route;
957 }
958 return yield* ensureTailscaleServe({
959 localPort,
960 servePort: config.tailscaleServePort,
961 localHost,
962 }).pipe(
963 Effect.as(route),
964 Effect.tap(() =>
965 Effect.logInfo("Tailscale Serve configured", {
966 localPort,
967 servePort: config.tailscaleServePort,
968 }),
969 ),
970 Effect.catch((cause) =>
971 Effect.logWarning("Failed to configure Tailscale Serve", {
972 cause,
973 localPort,
974 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 ),
1013 : Effect.logInfo(
1014 "Tailscale Serve left on; its root route is not known to be ours",
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 ),
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 ),
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;
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);
1092 yield* ServerActivation.forkParked(
1093 Effect.gen(function* () {
1094 if (!cleanupBeforeActivation) {
1095 yield* Effect.addFinalizer(() => releaseManagedTunnel);
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 ),
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 ),
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",
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));
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;
1212 const startupAction = managedTunnelStartupAction({ wantsCliLink, registration });
1213 if (startupAction.action === "request_recovery") {
1214 yield* endpointRuntime.requestRecovery(startupAction.config);
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);
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(
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 );
1300 }),
1301).pipe(Layer.provide(ProcessRunner.layer));
1302​
1303export const runServer = Layer.launch(layerMakeServer);