1import * as TranscriptArchive from "./archive/TranscriptArchive.ts";
2import * as Hub from "./environment/Hub.ts";
3import * as HubProxy from "./environment/HubProxy.ts";
4import * as HubHttp from "./environment/HubHttp.ts";
5import * as HubInbox from "./environment/HubInbox.ts";
6import * as HubInboxHttp from "./environment/HubInboxHttp.ts";
7import * as ArchiveRuntime from "./archive/runtimeLayer.ts";
8import * as ArchiveHttp from "./archive/ArchiveHttp.ts";
9import * as ArchiveAssets from "./archive/ArchiveAssets.ts";
10import * as HostedArchiveConnection from "./archive/HostedArchiveConnection.ts";
11import * as HostedAuth from "./auth/HostedAuth.ts";
12import * as HostedAuthConfig from "./auth/HostedAuthConfig.ts";
13import * as HostedAuthHttp from "./auth/HostedAuthHttp.ts";
14import * as OutboundHub from "./environment/OutboundHub.ts";
15import * as OutboundSource from "./environment/OutboundSource.ts";
16import * as SourceConnection from "./environment/SourceConnection.ts";
17import * as SourceConnectionHttp from "./environment/SourceConnectionHttp.ts";
18import * as SharedServer from "./environment/SharedServer.ts";
19import * as SharedAccounts from "./provider/SharedAccounts.ts";
20import * as SharedAccountClient from "./provider/SharedAccountClient.ts";
21import * as ManagedProviderHome from "./provider/ManagedProviderHome.ts";
22import * as SharedProfiles from "./provider/SharedProfiles.ts";
23import * as UiSettings from "./uiSettings.ts";
24import * as ScriptProgress from "./progress/ScriptProgress.ts";
25import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
26import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay";
27import * as Clock from "effect/Clock";
28import * as Random from "effect/Random";
29import * as Semaphore from "effect/Semaphore";
30import * as StorageCleanup from "./storageCleanup.ts";
31import * as PullRequestSyncReactor from "./orchestration-v2/PullRequestSyncReactor.ts";
32import * as PullRequestWatchReactor from "./orchestration-v2/PullRequestWatchReactor.ts";
33import * as DelegatedTaskQuestionReactor from "./orchestration-v2/DelegatedTaskQuestionReactor.ts";
34// @effect-diagnostics nodeBuiltinImport:off
35import * as NodeHttp from "node:http";
36
37import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
38import * as NodeServices from "@effect/platform-node/NodeServices";
39import { EnvironmentHttpApi, type RepositoryIdentity } from "@t3tools/contracts";
40import * as Cause from "effect/Cause";
41import * as Duration from "effect/Duration";
42import * as Deferred from "effect/Deferred";
43import * as Effect from "effect/Effect";
44import * as Option from "effect/Option";
45import * as Layer from "effect/Layer";
46import * as Stream from "effect/Stream";
47import * as Schedule from "effect/Schedule";
48import { FetchHttpClient, HttpRouter, HttpServer } from "effect/http";
49import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
50
51import * as BackgroundPolicy from "./background/BackgroundPolicy.ts";
52import * as HostPowerMonitor from "./background/HostPowerMonitor.ts";
53import * as ServerConfig from "./config.ts";
54import { withUntracedRequests } from "./http.ts";
55import * as ServerHttp from "./http.ts";
56import { guardHttpResponseWriteErrors } from "./httpResponseErrorGuard.ts";
57import { fixPath } from "./os-jank.ts";
58import * as Ws from "./ws.ts";
59import * as AgentScopeLive from "./process/agentScope.ts";
60import * as ExternalLauncher from "./process/externalLauncher.ts";
61import * as NodePtyAdapter from "./terminal/NodePtyAdapter.ts";
62import * as PullRequestHttp from "./pullRequest/http.ts";
63import * as PullRequestProviderRegistry from "./pullRequest/PullRequestProviderRegistry.ts";
64import * as PullRequestService from "./pullRequest/PullRequestService.ts";
65import * as SqlitePersistence from "./persistence/Sqlite.ts";
66import * as PullRequestFilesViewed from "./persistence/PullRequestFilesViewed.ts";
67import * as PullRequestReviewGuides from "./persistence/PullRequestReviewGuides.ts";
68import * as PullRequestReviewGuideService from "./pullRequest/PullRequestReviewGuideService.ts";
69import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
70import * as ModelManifest from "./provider/ModelManifest.ts";
71import * as ResetCreditCoordinator from "./provider/resetCreditCoordinator.ts";
72import * as EventNdjsonLogger from "./provider/EventNdjsonLogger.ts";
73import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
74import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
75import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
76import * as OpenCodeServerLedger from "@t3tools/provider-opencode/server/OpenCodeServerLedger";
77import * as ProviderHostLive from "./provider/ProviderHostLive.ts";
78import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport";
79import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts";
80import * as CheckpointStore from "./checkpointing/CheckpointStore.ts";
81import * as SourceControlBuiltInDrivers from "./sourceControl/builtInDrivers.ts";
82import * as TextGeneration from "./textGeneration/TextGeneration.ts";
83import * as ProviderInstanceRegistryHydration from "./provider/ProviderInstanceRegistryHydration.ts";
84import * as ProjectSettingsOverrideSweep from "./provider/ProjectSettingsOverrideSweep.ts";
85import * as TerminalManager from "./terminal/Manager.ts";
86import * as McpHttpServer from "./mcp/McpHttpServer.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 DesktopTerminalRpc from "./terminal/DesktopTerminalRpc.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 ProjectEnrichmentService from "./project/ProjectEnrichmentService.ts";
108import * as NativeAppIconResolver from "./assets/NativeAppIconResolver.ts";
109import * as AntigravityInstallation from "./provider/AntigravityInstallation.ts";
110import * as CodexInstallation from "./provider/CodexInstallation.ts";
111import * as ProviderInstanceRegistry from "./provider/ProviderInstanceRegistry.ts";
112import * as ProviderAdapterRegistry from "./orchestration-v2/ProviderAdapterRegistry.ts";
113import * as ProviderRegistry from "./provider/ProviderRegistry.ts";
114import * as ProviderUsageLimitsIngestion from "./provider/ProviderUsageLimitsIngestion.ts";
115import * as UsageLimitSources from "./usage/UsageLimitSources.ts";
116import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts";
117import * as T3ProjectFileLoader from "./project/T3ProjectFileLoader.ts";
118import * as RepositoryIdentityResolver from "./project/RepositoryIdentityResolver.ts";
119import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts";
120import * as MergebotQueue from "./mergebot/MergebotQueue.ts";
121import * as DevServer from "./workspace/DevServer.ts";
122import * as ManagedWorkspaces from "./workspace/ManagedWorkspaces.ts";
123import * as WorkspaceFileSystem from "./workspace/WorkspaceFileSystem.ts";
124import * as WorkspacePaths from "./workspace/WorkspacePaths.ts";
125import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";
126import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts";
127import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts";
128import * as VcsProcess from "./vcs/VcsProcess.ts";
129import * as JjStackService from "./vcs/JjStackService.ts";
130import * as VcsProvisioningService from "./vcs/VcsProvisioningService.ts";
131import * as VcsStatusBroadcaster from "./vcs/VcsStatusBroadcaster.ts";
132import * as ProjectCloneTracker from "./project/ProjectCloneTracker.ts";
133import * as GitWorkflowService from "./git/GitWorkflowService.ts";
134import * as ReviewService from "./review/ReviewService.ts";
135import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts";
136import * as PullRequestReadCache from "./pullRequest/PullRequestReadCache.ts";
137import * as SourceControlRateLimit from "@t3tools/source-control-core/server/SourceControlRateLimit";
138import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts";
139import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts";
140import * as Observability from "./observability/Observability.ts";
141import * as HeapSnapshot from "./observability/HeapSnapshot.ts";
142import * as EventLoopMonitor from "./observability/EventLoopMonitor.ts";
143import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
144import * as DirectEndpoints from "./environment/DirectEndpoints.ts";
145import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts";
146import * as AuthHttp from "./auth/http.ts";
147import * as ReplayMarkers from "./auth/replayMarkers.ts";
148import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
149import * as WebhookRoute from "./scheduledTasks/webhookRoute.ts";
150import * as RelayDeliveryProof from "./scheduledTasks/RelayDeliveryProof.ts";
151import * as HeldHooksWaker from "./relay/HeldHooksWaker.ts";
152import * as McpOAuth from "./auth/McpOAuth.ts";
153import * as McpOAuthHttp from "./auth/mcpOAuthHttp.ts";
154import {
155 relayHookBaseUrl,
156 ScheduledTaskWebhookOrigin,
157} from "./scheduledTasks/ScheduledTaskService.ts";
158import {
159 CLOUD_ENDPOINT_RUNTIME_CONFIG,
160 decodeRuntimeConfig,
161 RELAY_URL_SECRET,
162} from "./cloud/config.ts";
163import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
164import * as CloudHttp from "./cloud/http.ts";
165import * as CloudLink from "./cloud/CloudLink.ts";
166import { pendingServiceUpdateExists } from "./cloud/updateHandoff.ts";
167import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts";
168import {
169 MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER,
170 MANAGED_TUNNEL_RECOVERY_COOLDOWN,
171 managedTunnelStartupAction,
172 retryManagedTunnelRegistration,
173} from "./cloud/managedTunnelStartup.ts";
174import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts";
175import * as CloudCliState from "./cloud/CliState.ts";
176import * as ServerSelfUpdate from "./cloud/selfUpdate.ts";
177import * as DesktopAppUpdate from "./desktopUpdate/DesktopAppUpdate.ts";
178import * as ServiceLauncherClient from "./cloud/serviceLauncherClient.ts";
179import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
180import * as HostResources from "./resourceTelemetry/HostResources.ts";
181import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
182import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts";
183import * as DesktopTelemetryReceiver from "./resourceTelemetry/DesktopTelemetryReceiver.ts";
184import * as NativeTelemetryClient from "./resourceTelemetry/NativeTelemetryClient.ts";
185import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts";
186import * as ResourceMonitorBinary from "./resourceTelemetry/ResourceMonitorBinary.ts";
187import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
188import * as AntigravityUsage from "./provider/Drivers/AntigravityUsage.ts";
189import * as CursorAccountReader from "@t3tools/provider-cursor/server/CursorAccountReader";
190import * as CursorKeychain from "@t3tools/provider-cursor/server/CursorKeychain";
191import * as CursorUsageAccounts from "@t3tools/provider-cursor/server/CursorUsageAccounts";
192import * as UsageService from "./usage/UsageService.ts";
193import * as RuntimeLayer from "./orchestration-v2/runtimeLayer.ts";
194import * as ProjectStore from "./orchestration-v2/ProjectStore.ts";
195import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts";
196import * as ResourceCleanupService from "./orchestration-v2/ResourceCleanupService.ts";
197import * as ThreadSettlementService from "./orchestration-v2/ThreadSettlementService.ts";
198import * as ThreadPullRequestService from "./orchestration-v2/ThreadPullRequestService.ts";
199import * as RunFinalizationService from "./orchestration-v2/RunFinalizationService.ts";
200import * as ProjectionStoreV2 from "./orchestration-v2/ProjectionStore.ts";
201import { makePersistedServerRuntimeState } from "./serverRuntimeState.ts";
202import * as OrchestrationHttp from "./orchestration-v2/http.ts";
203import * as ProjectHttp from "./project/http.ts";
204import * as NetService from "@t3tools/shared/Net";
205import * as RelayClient from "@t3tools/shared/relayClient";
206import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale";
207import * as ServerActivation from "./serverActivation.ts";
208
209// MCP handoff thread IDs include escaped provenance and can exceed find-my-way's
210// 100-character default for one path segment.
211const HTTP_ROUTER_CONFIG = {
212 maxParamLength: 512,
213} as const;
214
215// Effect's default preemptive shutdown waits 20s before finalizing request scopes.
216// T3's primary transport is long-lived WebSocket RPC, whose Effect scope finalizer
217// already closes the websocket gracefully. Do not add an artificial drain before
218// those finalizers get a chance to run.
219const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
220const layerResourceAttribution = ResourceAttribution.layer;
221const layerApplicationObservability = EventLoopMonitor.layer.pipe(
222 Layer.provideMerge(Observability.layer),
223 Layer.provideMerge(layerResourceAttribution),
224);
225
226// One rotating store behind both logger views, so driver instances never race
227// its rotation. Diagnostics must not block startup: a store that cannot open
228// degrades to the no-op loggers.
229const layerProviderEventLoggers = Layer.effect(
230 ProviderEventLoggers.ProviderEventLoggers,
231 Effect.gen(function* () {
232 const { providerEventLogPath } = yield* ServerConfig.ServerConfig;
233 const attribution = yield* ResourceAttribution.ResourceAttribution;
234 const store = yield* EventNdjsonLogger.makeEventNdjsonLogStore(providerEventLogPath, {
235 attribution,
236 }).pipe(
237 Effect.catch((error) =>
238 Effect.logWarning(error.message, { error }).pipe(
239 Effect.annotateLogs({ scope: "provider-observability" }),
240 Effect.as(undefined),
241 ),
242 ),
243 );
244 if (!store) return ProviderEventLoggers.NoOpProviderEventLoggers;
245 yield* Effect.addFinalizer(() => store.close());
246 return { native: store.logger("native"), canonical: store.logger("canonical") };
247 }),
248);
249
250const layerPtyAdapter = NodePtyAdapter.layer;
251
252const layerServerSettings = ServerSettings.layer.pipe(
253 Layer.provide(ServerSecretStore.layer),
254 Layer.provideMerge(SqlitePersistence.layerConfig),
255);
256
257const layerNativeTelemetry = NativeTelemetryClient.layer.pipe(
258 Layer.provide(ResourceMonitorBinary.layer),
259);
260const layerDesktopTelemetryReceiver = DesktopTelemetryReceiver.layer.pipe(
261 Layer.provideMerge(layerServerSettings),
262);
263
264const layerResourceTelemetry = ResourceTelemetry.layer.pipe(
265 Layer.provideMerge(layerNativeTelemetry),
266 Layer.provideMerge(layerDesktopTelemetryReceiver),
267);
268
269const layerHostPowerMonitor = HostPowerMonitor.layer.pipe(
270 Layer.provide(layerDesktopTelemetryReceiver),
271);
272
273// Reuses DesktopTelemetryReceiverLayerLive: a fresh receiver layer here
274// would open a second reader on the desktop telemetry fd.
275const layerDesktopAppUpdate = DesktopAppUpdate.layer.pipe(
276 Layer.provide(layerDesktopTelemetryReceiver),
277);
278
279const layerBackground = BackgroundPolicy.layer.pipe(
280 Layer.provide(layerHostPowerMonitor),
281 Layer.provideMerge(layerServerSettings),
282);
283
284const layerUsage = UsageService.layer.pipe(
285 Layer.provide(
286 Layer.mergeAll(
287 AntigravityUsage.layer,
288 CursorUsageAccounts.layer.pipe(
289 Layer.provide(CursorAccountReader.layer.pipe(Layer.provide(CursorKeychain.layer))),
290 ),
291 ).pipe(Layer.provide(ProviderHostLive.layer.pipe(Layer.provide(ServerSecretStore.layer)))),
292 ),
293 Layer.provide(layerServerSettings),
294);
295
296const layerResourceDiagnostics = Layer.mergeAll(
297 HostResources.layer,
298 layerResourceTelemetry,
299 ProcessDiagnostics.layer.pipe(Layer.provide(layerResourceTelemetry)),
300 ProcessResourceMonitor.layer.pipe(Layer.provide(layerResourceTelemetry)),
301);
302
303const layerRelayClient = Layer.unwrap(
304 Effect.gen(function* () {
305 const config = yield* ServerConfig.ServerConfig;
306 return RelayClient.layerCloudflared({ baseDir: config.baseDir });
307 }),
308);
309
310const layerHttpServer = Layer.unwrap(
311 Effect.gen(function* () {
312 const config = yield* ServerConfig.ServerConfig;
313 return NodeHttpServer.layer(() => guardHttpResponseWriteErrors(NodeHttp.createServer()), {
314 host: config.host ?? "127.0.0.1",
315 port: config.port,
316 gracefulShutdownTimeout: HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS,
317 // Negotiate permessage-deflate with clients that offer it; clients
318 // that don't still get uncompressed frames on their connection.
319 // Context takeover stays enabled (ws default) so the compression
320 // window is shared across frames — that also makes small frames cheap
321 // to compress, so no size threshold is set (ws only honors
322 // `threshold` when context takeover is disabled).
323 websocket: { perMessageDeflate: true },
324 });
325 }),
326);
327
328const layerPlatformServices = NodeServices.layer;
329
330const layerPersistence = Layer.empty.pipe(Layer.provideMerge(SqlitePersistence.layerConfig));
331
332const layerVcsDriverRegistry = VcsDriverRegistry.layer.pipe(Layer.provide(VcsProjectConfig.layer));
333
334const layerSourceControlProviderRegistry = SourceControlProviderRegistry.layer.pipe(
335 Layer.provideMerge(SourceControlBuiltInDrivers.layer),
336 Layer.provideMerge(GitVcsDriver.layer),
337 Layer.provideMerge(layerVcsDriverRegistry),
338);
339
340const layerRepositoryIdentityResolver = Layer.effect(
341 RepositoryIdentityResolver.RepositoryIdentityResolver,
342 Effect.gen(function* () {
343 const registry = yield* SourceControlProviderRegistry.SourceControlProviderRegistry;
344 return yield* RepositoryIdentityResolver.make({
345 // Each host that can refine an identity gets a turn; the first one that changes it wins.
346 refine: Effect.fn(function* (identity: RepositoryIdentity) {
347 for (const kind of SourceControlBuiltInDrivers.BUILT_IN_SOURCE_CONTROL_DRIVERS.map(
348 (driver) => driver.kind,
349 )) {
350 const provider = yield* registry.get(kind);
351 if (provider.refineRepositoryIdentity === undefined) continue;
352 const refined = yield* provider.refineRepositoryIdentity({
353 identity,
354 resolveContext: (input) =>
355 registry.resolveHandle(input).pipe(Effect.map((handle) => handle.context)),
356 });
357 if (refined !== identity) return refined;
358 }
359 return identity;
360 }),
361 });
362 }),
363).pipe(Layer.provide(layerSourceControlProviderRegistry), Layer.provide(ProcessRunner.layer));
364
365const layerPullRequestService = PullRequestService.layer.pipe(
366 Layer.provide(PullRequestProviderRegistry.layer),
367 // Where the viewed-file marks live for a host that keeps none of its own.
368 Layer.provide(PullRequestFilesViewed.layer),
369 Layer.provide(PullRequestReadCache.layer),
370 Layer.provide(MergebotQueue.layer),
371 Layer.provide(layerSourceControlProviderRegistry),
372 Layer.provide(SourceControlRateLimit.layer),
373);
374
375const layerGitManager = GitManager.layer.pipe(
376 Layer.provide(MergebotQueue.layer),
377 // Per-project git settings resolve the acting thread's project.
378 Layer.provide(Layer.merge(ProjectionStoreV2.layer, ProjectStore.layer)),
379 Layer.provideMerge(RuntimeLayer.layerProjectSetupScriptRunner),
380 Layer.provideMerge(WorktreeSetupTracker.layer),
381 Layer.provideMerge(GitVcsDriver.layer),
382 Layer.provideMerge(layerSourceControlProviderRegistry),
383 Layer.provideMerge(TextGeneration.layer.pipe(Layer.provide(layerSourceControlProviderRegistry))),
384);
385
386const layerGit = Layer.empty.pipe(
387 Layer.provideMerge(layerGitManager),
388 Layer.provideMerge(GitVcsDriver.layer),
389);
390
391const layerGitWorkflow = GitWorkflowService.layer.pipe(
392 Layer.provideMerge(layerVcsDriverRegistry),
393 Layer.provideMerge(layerGit),
394);
395
396const layerSourceControlRepositoryService = SourceControlRepositoryService.layer.pipe(
397 Layer.provideMerge(GitVcsDriver.layer),
398 Layer.provideMerge(layerSourceControlProviderRegistry),
399);
400
401const layerProjectCloneTracker = ProjectCloneTracker.layer.pipe(
402 Layer.provide(layerSourceControlRepositoryService),
403);
404
405const layerReview = ReviewService.layer.pipe(
406 Layer.provide(ProjectStore.layer),
407 Layer.provideMerge(GitVcsDriver.layer),
408 Layer.provideMerge(layerVcsDriverRegistry),
409);
410
411const layerVcs = Layer.empty.pipe(
412 Layer.provideMerge(VcsProjectConfig.layer),
413 Layer.provideMerge(layerVcsDriverRegistry),
414 Layer.provideMerge(VcsProvisioningService.layer.pipe(Layer.provide(layerVcsDriverRegistry))),
415 Layer.provideMerge(layerGitWorkflow),
416 Layer.provideMerge(layerReview),
417 Layer.provideMerge(layerSourceControlRepositoryService),
418 Layer.provideMerge(layerProjectCloneTracker),
419 Layer.provideMerge(
420 VcsStatusBroadcaster.layer.pipe(
421 Layer.provide(layerGitWorkflow),
422 // Auto-pull reads the project row. The orchestration runtime also
423 // consumes the broadcaster (run finalization), so the policy cannot read
424 // the store from the runtime's output.
425 Layer.provide(
426 VcsStatusBroadcaster.layerAutoPullPolicy.pipe(Layer.provide(ProjectStore.layer)),
427 ),
428 ),
429 ),
430);
431
432const layerCheckpointStore = CheckpointStore.layer.pipe(Layer.provide(layerVcsDriverRegistry));
433
434const layerPortScanner = PortScanner.layer.pipe(Layer.provide(ProcessRunner.layer));
435
436const layerTerminal = TerminalManager.layer.pipe(
437 Layer.provide(layerPtyAdapter),
438 Layer.provide(layerPortScanner),
439 Layer.provide(layerNativeTelemetry),
440);
441
442const layerPreview = Layer.empty.pipe(
443 Layer.provideMerge(PreviewManager.layer),
444 Layer.provideMerge(layerPortScanner),
445);
446
447const layerDevice = DeviceService.layer.pipe(
448 Layer.provide(layerServerSettings),
449 Layer.provide(ProcessRunner.layer),
450 Layer.provide(NetService.layer),
451);
452
453const layerWorkspaceEntries = WorkspaceEntries.layer.pipe(Layer.provide(WorkspacePaths.layer));
454
455const layerWorkspaceFileSystem = WorkspaceFileSystem.layer.pipe(
456 Layer.provide(WorkspacePaths.layer),
457 Layer.provide(layerWorkspaceEntries),
458);
459
460const layerWorkspace = Layer.mergeAll(
461 WorkspacePaths.layer,
462 layerWorkspaceEntries,
463 layerWorkspaceFileSystem,
464);
465
466const layerProjectFaviconResolver = ProjectFaviconResolver.layer.pipe(
467 Layer.provide(WorkspacePaths.layer),
468 Layer.provide(T3ProjectFileLoader.layer),
469);
470
471const layerServerEnvironment = ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer));
472
473const layerAuth = EnvironmentAuth.layer.pipe(
474 Layer.provideMerge(HostedAuthConfig.layer),
475 Layer.provideMerge(layerPersistence),
476 Layer.provide(layerServerEnvironment),
477 Layer.provide(ServerSecretStore.layer),
478);
479
480const layerCloudManagedEndpointRuntime = Layer.mergeAll(
481 layerRelayClient,
482 CloudManagedEndpointRuntime.layer.pipe(
483 Layer.provide(ServerSecretStore.layer),
484 Layer.provide(layerRelayClient),
485 ),
486);
487
488// Webhook URLs go through the relay only when the managed tunnel it forwards
489// to is configured; otherwise clients show the environment-relative path.
490const layerScheduledTaskWebhookOrigin = Layer.effect(
491 ScheduledTaskWebhookOrigin,
492 Effect.gen(function* () {
493 const secrets = yield* ServerSecretStore.ServerSecretStore;
494 // The reference holds an effect so each read sees the current link state.
495 return Effect.gen(function* () {
496 const [relayUrl, tunnelConfig] = yield* Effect.all([
497 secrets.get(RELAY_URL_SECRET),
498 secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG),
499 ]).pipe(Effect.orElseSucceed(() => [Option.none(), Option.none()] as const));
500 if (Option.isNone(relayUrl) || Option.isNone(tunnelConfig)) {
501 return { relayHookBaseUrl: null };
502 }
503 const config = decodeRuntimeConfig(new TextDecoder().decode(tunnelConfig.value));
504 return {
505 relayHookBaseUrl: relayHookBaseUrl({
506 relayUrl: new TextDecoder().decode(relayUrl.value),
507 tunnelName: Option.isSome(config) ? config.value.tunnelName : undefined,
508 }),
509 };
510 });
511 }),
512);
513
514const layerOrchestrationV2Runtime = RuntimeLayer.layerProduction.pipe(
515 Layer.provide(layerScheduledTaskWebhookOrigin),
516 Layer.provide(layerCheckpointStore),
517 Layer.provide(layerGitWorkflow),
518 Layer.provide(ResourceCleanupService.layer),
519 Layer.provide(
520 RunFinalizationService.layerObserver.pipe(
521 Layer.provide(ProjectionStoreV2.layer),
522 Layer.provide(layerPullRequestService),
523 Layer.provide(RuntimeLayer.layerProjectService),
524 ),
525 ),
526);
527
528const layerOrchestrationApplication = CheckpointDiffQuery.layer.pipe(
529 Layer.provideMerge(layerCheckpointStore),
530 Layer.provideMerge(layerOrchestrationV2Runtime),
531);
532
533// Automatic thread settlement (#8600): a server-owned sweep evaluates
534// inactivity and merged pull requests, then settles through the orchestrator
535// so every client sees the same shelf.
536const layerThreadSettlementWorker = Layer.effectDiscard(
537 ThreadSettlementService.make.pipe(Effect.flatMap((service) => service.start())),
538).pipe(Layer.provide(layerPullRequestService), Layer.provide(ProjectionStoreV2.layer));
539
540const layerThreadPullRequestWorker = Layer.effectDiscard(
541 ThreadPullRequestService.make.pipe(Effect.flatMap((service) => service.start())),
542).pipe(Layer.provide(layerPullRequestService));
543
544const layerProviderInstallationRefresh = Layer.effectDiscard(
545 Effect.gen(function* () {
546 const antigravity = yield* AntigravityInstallation.AntigravityInstallation;
547 const codex = yield* CodexInstallation.CodexInstallation;
548 const instances = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
549 const providers = yield* ProviderRegistry.ProviderRegistry;
550 yield* Stream.merge(
551 antigravity.changes.pipe(
552 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
553 Stream.drop(1),
554 ),
555 codex.changes.pipe(
556 Stream.changesWith((a, b) => a.installedVersion === b.installedVersion),
557 Stream.drop(1),
558 ),
559 ).pipe(
560 Stream.runForEach((state) =>
561 instances.listInstances.pipe(
562 Effect.flatMap((entries) =>
563 Effect.forEach(
564 entries.filter((instance) => instance.driverKind === state.driver),
565 (instance) => providers.refreshInstance(instance.instanceId),
566 { discard: true },
567 ),
568 ),
569 ),
570 ),
571 Effect.forkScoped,
572 );
573 }),
574);
575
576const layerRuntimeCoreDependenciesBase = Layer.mergeAll(
577 Layer.effectDiscard(
578 Effect.gen(function* () {
579 const archive = yield* TranscriptArchive.TranscriptArchive;
580 yield* ServerActivation.forkParked(
581 Effect.forever(
582 archive.sweep.pipe(
583 Effect.ignore({ log: true }),
584 Effect.andThen(Effect.sleep("1 minute")),
585 ),
586 ),
587 );
588 }),
589 ),
590 Layer.effectDiscard(
591 Effect.gen(function* () {
592 const profiles = yield* SharedProfiles.SharedProfiles;
593 yield* ServerActivation.forkParked(
594 Effect.forever(
595 profiles.execute({ type: "sync" }).pipe(
596 Effect.catchTags({
597 SharedAccountError: (error) =>
598 Effect.logWarning("Shared account sync could not finish", {
599 reason: error.reason,
600 }),
601 }),
602 Effect.andThen(Effect.sleep("1 minute")),
603 ),
604 ),
605 );
606 }),
607 ),
608 AgentAwarenessRelay.layer,
609 // Asks T3 Connect to deliver webhooks it held while this environment was offline.
610 HeldHooksWaker.layer,
611 layerThreadSettlementWorker,
612 StorageCleanup.layer.pipe(Layer.provide(ProjectionStoreV2.layer)),
613 layerThreadPullRequestWorker,
614 Layer.effectDiscard(
615 Effect.gen(function* () {
616 const service = yield* PullRequestSyncReactor.PullRequestSyncReactor;
617 yield* service.start();
618 }),
619 ).pipe(
620 Layer.provideMerge(PullRequestSyncReactor.layer),
621 Layer.provide(layerPullRequestService),
622 Layer.provide(ProjectionStoreV2.layer),
623 ),
624 Layer.effectDiscard(
625 Effect.gen(function* () {
626 const service = yield* PullRequestWatchReactor.PullRequestWatchReactor;
627 yield* service.start();
628 }),
629 ).pipe(
630 Layer.provide(PullRequestWatchReactor.layer),
631 Layer.provide(layerPullRequestService),
632 Layer.provide(ProjectionStoreV2.layer),
633 ),
634 Layer.effectDiscard(
635 Effect.gen(function* () {
636 const service = yield* DelegatedTaskQuestionReactor.DelegatedTaskQuestionReactor;
637 yield* service.start();
638 }),
639 ).pipe(Layer.provide(DelegatedTaskQuestionReactor.layer), Layer.provide(ProjectionStoreV2.layer)),
640 // Subscribes to `account.rate-limits.updated` so usage bars track live
641 // telemetry instead of waiting for the next status probe.
642 ProviderUsageLimitsIngestion.layer,
643 layerProviderInstallationRefresh,
644 ReplayMarkers.layer,
645 // Catches overrides left behind by a project deleted before this cleanup
646 // existed, or by a cleanup write that failed. See ProjectService.delete
647 // for the normal (on-delete) path.
648 Layer.effectDiscard(ProjectSettingsOverrideSweep.sweep),
649).pipe(
650 // Core Services
651 Layer.provideMerge(
652 ManagedWorkspaces.layer.pipe(
653 Layer.provide(ProcessRunner.layer),
654 Layer.provide(layerPullRequestService),
655 ),
656 ),
657 Layer.provideMerge(layerOrchestrationApplication),
658 Layer.provideMerge(RuntimeLayer.layerEventInfrastructure),
659 Layer.provideMerge(Layer.merge(ProjectStore.layer, ThreadSearch.layer)),
660 Layer.provideMerge(layerServerSettings),
661 Layer.provideMerge(MergebotQueue.layer),
662 Layer.provideMerge(JjStackService.layer),
663 // The asset route uses the registry's GitHub credential for private PR media, which the
664 // built-in drivers' layer provides alongside the registry.
665 Layer.provideMerge(layerSourceControlProviderRegistry),
666 Layer.provideMerge(layerGit),
667 Layer.provideMerge(layerVcs),
668 Layer.provideMerge(Layer.mergeAll(layerTerminal, layerPreview, layerDevice)),
669 Layer.provideMerge(layerPersistence),
670 Layer.provideMerge(
671 Layer.mergeAll(
672 Keybindings.layer,
673 EnvironmentTheme.layer,
674 UsageLimitSources.layer,
675 UiSettings.layer,
676 ),
677 ),
678 Layer.provideMerge(ProviderRegistry.layer),
679 // The instance registry is the new routing keystone — text generation,
680 // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId`
681 // through this layer. Built-in drivers come from `BUILT_IN_DRIVERS`;
682 // hydration adds their default instances to `providerInstances` on boot.
683 Layer.provideMerge(ProviderInstanceRegistryHydration.layer),
684 Layer.provideMerge(
685 Layer.mergeAll(
686 AntigravityInstallation.AntigravityInstallation.layer,
687 CodexInstallation.CodexInstallation.layer,
688 ),
689 ),
690);
691
692const layerRuntimeCoreDependencies = layerRuntimeCoreDependenciesBase
693 .pipe(
694 Layer.provideMerge(
695 Layer.mergeAll(HubInbox.layer.pipe(Layer.provide(layerPersistence)), HubProxy.layer),
696 ),
697 Layer.provideMerge(Hub.layer),
698 Layer.provideMerge(OutboundHub.layer),
699 Layer.provideMerge(HostedAuth.layer.pipe(Layer.provide(layerPersistence))),
700 Layer.provideMerge(HostedAuthConfig.layer),
701 Layer.provideMerge(SharedProfiles.layer.pipe(Layer.provide(layerServerSettings))),
702 Layer.provideMerge(ManagedProviderHome.layer),
703 Layer.provideMerge(SharedAccountClient.layer),
704 Layer.provideMerge(SharedAccounts.layer),
705 Layer.provideMerge(ArchiveAssets.layer),
706 Layer.provideMerge(
707 ArchiveRuntime.layer.pipe(
708 Layer.provide(layerPersistence),
709 Layer.provide(HostedArchiveConnection.layer),
710 Layer.provide(layerServerSettings),
711 ),
712 ),
713 )
714 .pipe(
715 Layer.provideMerge(SourceConnection.layer),
716 Layer.provideMerge(SharedServer.layer),
717 Layer.provideMerge(layerPtyAdapter),
718 // Search, prepare, status inspection, and turn launch share one registry
719 // cache so every client and provider instance sees the same prepared agents.
720 Layer.provideMerge(
721 AcpRegistrySupport.layerFromHost.pipe(Layer.provide(ProviderHostLive.layer)),
722 ),
723 // Provider event loggers are shared by every driver instance and the
724 // orchestration runtime, so they are provided once here.
725 // `ModelManifest.layer` is the model manifest, refreshed from the repo's
726 // `model-manifest.json` on `main`. Drivers read their entry through the
727 // `ModelCatalog` port it provides.
728 Layer.provideMerge(
729 Layer.mergeAll(
730 layerProviderEventLoggers,
731 ModelManifest.layerModelCatalog.pipe(Layer.provideMerge(ModelManifest.layer)),
732 ResetCreditCoordinator.layer,
733 ProviderLatestVersions.layer,
734 McpProviderSessions.layer,
735 ),
736 ),
737 // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old
738 // `ProviderRegistry.layer` pulled `OpenCodeRuntimeLive` in for itself, but
739 // the rewritten registry reads snapshots off the instance registry and
740 // no longer transitively provides it. Exposing it at the runtime level
741 // keeps a single Live for all opencode consumers.
742 Layer.provideMerge(
743 OpenCodeRuntime.layer.pipe(
744 Layer.provide(
745 Layer.unwrap(
746 Effect.gen(function* () {
747 const config = yield* ServerConfig.ServerConfig;
748 return OpenCodeServerLedger.layer({ stateDir: config.stateDir });
749 }),
750 ),
751 ),
752 ),
753 ),
754 Layer.provideMerge(
755 DevServer.layer.pipe(
756 Layer.provide(ProjectionStoreV2.layer),
757 Layer.provide(ProjectStore.layer),
758 Layer.provide(ProcessRunner.layer),
759 ),
760 ),
761 Layer.provideMerge(layerWorkspace),
762 Layer.provideMerge(ProjectEnrichmentService.layer),
763 Layer.provideMerge(Layer.mergeAll(NativeAppIconResolver.layer, layerProjectFaviconResolver)),
764 Layer.provideMerge(layerRepositoryIdentityResolver),
765 Layer.provideMerge(layerServerEnvironment),
766 Layer.provideMerge(layerAuth),
767 Layer.provideMerge(ServerSecretStore.layer),
768 )
769 .pipe(
770 Layer.provideMerge(
771 Layer.mergeAll(
772 CloudCliTokenManager.layer.pipe(
773 Layer.provide(ServerSecretStore.layer),
774 Layer.provide(ExternalLauncher.layer),
775 ),
776 layerCloudManagedEndpointRuntime,
777 ),
778 ),
779 );
780
781const layerRuntimeDependencies = layerRuntimeCoreDependencies.pipe(
782 // Misc.
783 // Usage reads provider history through the ProviderHost, which needs the
784 // background policy below it.
785 Layer.provideMerge(layerUsage),
786 Layer.provideMerge(layerBackground),
787 Layer.provideMerge(layerResourceDiagnostics),
788 Layer.provideMerge(TraceDiagnostics.layer),
789 Layer.provideMerge(ExternalLauncher.layer),
790 Layer.provideMerge(RemoteOpenTargets.layer),
791 Layer.provideMerge(DirectEndpoints.layer),
792 Layer.provideMerge(ServerLifecycleEvents.layer),
793 Layer.provide(NetService.layer),
794);
795
796const layerCommandReadiness = HttpRouter.middleware(
797 (httpEffect) =>
798 Effect.flatMap(ServerRuntimeStartup.ServerRuntimeStartup, (startup) =>
799 startup.awaitCommandReady.pipe(Effect.orDie, Effect.andThen(httpEffect)),
800 ),
801 { global: true },
802);
803
804const layerMakeRoutes = Layer.mergeAll(
805 Layer.mergeAll(
806 HttpApiBuilder.layer(EnvironmentHttpApi).pipe(
807 Layer.provide(AuthHttp.layer),
808 Layer.provide(McpOAuthHttp.layer.pipe(Layer.provide(McpOAuth.layer))),
809 Layer.provide(CloudHttp.layer),
810 Layer.provide(OrchestrationHttp.layer),
811 Layer.provide(PullRequestHttp.layer),
812 Layer.provide(ProjectHttp.layer),
813 Layer.provide(ServerHttp.layerServerEnvironmentHttpApi),
814 Layer.provide(WebhookRoute.layer.pipe(Layer.provide(RelayDeliveryProof.layer))),
815 Layer.provide(AuthHttp.layerAuthenticatedAuth),
816 ),
817 ServerHttp.layerOtlpTracesProxyRoute,
818 ServerHttp.layerAssetRoute,
819 ServerHttp.layerAttachmentUploadRoute,
820 ServerHttp.layerArchiveManagementRoute,
821 ServerHttp.layerArchiveUploadRoute,
822 ServerHttp.layerArchiveDownloadRoute,
823 ServerHttp.layerSharedAccountRoute,
824 ServerHttp.layerSharedProfilesRoute,
825 DeviceHubProxy.layer,
826 HubHttp.layer,
827 HubInboxHttp.layer,
828 HostedAuthHttp.layer,
829 SourceConnectionHttp.layer,
830 ArchiveHttp.layer,
831 ServerBrowserStream.routeLayer,
832 ServerHttp.layerStaticAndDevRoute,
833 Ws.layer,
834 DesktopTerminalRpc.layer,
835 ),
836 // The MCP session registry is provided globally (shared with V2 provider
837 // sessions) rather than inline here. The orchestrator toolkit resolves
838 // delegation targets through the same live adapter facade the V2
839 // orchestrator uses, so MCP capability reporting can never drift from
840 // what dispatch can actually serve.
841 McpHttpServer.layer.pipe(
842 Layer.provide(ProviderAdapterRegistry.layerFromProviderInstanceRegistry),
843 Layer.provide(McpOAuth.layerMcpClientAuthenticator),
844 ),
845).pipe(
846 // Guides read through the same pull request service as every other surface.
847 Layer.provide(
848 PullRequestReviewGuideService.layer.pipe(Layer.provide(PullRequestReviewGuides.layer)),
849 ),
850 // Both transports consume the same service instance, so caches single-flight across clients
851 // and mutations observed on WebSocket invalidate patches subsequently read over HTTP. Provided
852 // after the guide service so it reaches that too.
853 Layer.provide(layerPullRequestService),
854 // The stream route and the WebSocket RPCs share one browser.
855 Layer.provide(ServerBrowser.layer.pipe(Layer.provide(DesktopBrowserChannel.layer))),
856 // Server browser tabs and HTML render previews install and run the same headless browser.
857 Layer.provide(PreviewBrowser.layer),
858 Layer.provide(PreviewAutomationBroker.layer),
859 Layer.provide(ServerSelfUpdate.layer.pipe(Layer.provide(layerDesktopAppUpdate))),
860 Layer.provide(layerCommandReadiness),
861 Layer.provide(ServerHttp.layerBrowserApiCors),
862 Layer.provide(ServerHttp.layerHttpCompression),
863);
864
865const layerMakeServer = Layer.unwrap(
866 Effect.gen(function* () {
867 const config = yield* ServerConfig.ServerConfig;
868 // Before anything opens the database: one server per T3 home.
869 const ownership = yield* ServerOwnership.acquireServerOwnership(config.serverRuntimeStatePath);
870 const activation = yield* Deferred.make<void>();
871 const awaitActivation = Deferred.await(activation);
872 const layerActivation = Layer.succeed(ServerActivation.ServerActivation, awaitActivation);
873 const runtimeStateParked = yield* Deferred.make<void>();
874 const tailscaleParked = yield* Deferred.make<void>();
875 const cloudLinkParked = yield* Deferred.make<void>();
876 const routesReady = yield* Deferred.make<void>();
877 const layerLauncher = ServiceLauncherClient.layer;
878
879 yield* fixPath({ shellEnvironmentPrepared: config.shellEnvironmentPrepared });
880
881 const layerHttpListening = Layer.effectDiscard(
882 Effect.gen(function* () {
883 yield* HttpServer.HttpServer;
884 const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
885 yield* startup.markHttpListening;
886 }),
887 );
888 const layerRuntimeState = Layer.effectDiscard(
889 Effect.gen(function* () {
890 yield* Deferred.succeed(runtimeStateParked, undefined).pipe(Effect.orDie);
891 yield* awaitActivation;
892 const server = yield* HttpServer.HttpServer;
893 const address = server.address;
894 if (typeof address === "string" || !("port" in address)) {
895 return;
896 }
897
898 const launcher = yield* ServiceLauncherClient.ServiceLauncherClient;
899 const state = yield* makePersistedServerRuntimeState({
900 config,
901 port: address.port,
902 serviceManaged: launcher.managed,
903 });
904 // Ownership release removes this record.
905 yield* ownership
906 .publish(state)
907 .pipe(
908 Effect.catchCause((cause) =>
909 Effect.logWarning("Failed to persist server runtime state", { cause }),
910 ),
911 );
912 }),
913 );
914 const layerTailscaleServe = config.tailscaleServeEnabled
915 ? Layer.effectDiscard(
916 Effect.acquireRelease(
917 Effect.gen(function* () {
918 yield* Deferred.succeed(tailscaleParked, undefined).pipe(Effect.orDie);
919 yield* awaitActivation;
920 const server = yield* HttpServer.HttpServer;
921 const address = server.address;
922 if (typeof address === "string" || !("port" in address)) {
923 return null;
924 }
925
926 const localPort = address.port;
927 return yield* ensureTailscaleServe({
928 localPort,
929 servePort: config.tailscaleServePort,
930 localHost: "127.0.0.1",
931 }).pipe(
932 Effect.as({ localPort, servePort: config.tailscaleServePort }),
933 Effect.tap(() =>
934 Effect.logInfo("Tailscale Serve configured", {
935 localPort,
936 servePort: config.tailscaleServePort,
937 }),
938 ),
939 Effect.catch((cause) =>
940 Effect.logWarning("Failed to configure Tailscale Serve", {
941 cause,
942 localPort,
943 servePort: config.tailscaleServePort,
944 }).pipe(Effect.as(null)),
945 ),
946 );
947 }),
948 (configured) =>
949 configured
950 ? disableTailscaleServe({ servePort: configured.servePort }).pipe(
951 Effect.tap(() =>
952 Effect.logInfo("Tailscale Serve disabled", {
953 servePort: configured.servePort,
954 }),
955 ),
956 Effect.catch((cause) =>
957 Effect.logWarning("Failed to disable Tailscale Serve", {
958 cause,
959 servePort: configured.servePort,
960 }),
961 ),
962 )
963 : Effect.void,
964 ),
965 )
966 : Layer.empty;
967 const layerCloudDesiredLinkReconcile = Layer.effectDiscard(
968 Effect.gen(function* () {
969 const cloudLink = yield* CloudLink.CloudLink;
970 const releaseManagedTunnel = cloudLink.releaseManagedTunnelOnShutdown().pipe(
971 Effect.timeout("10 seconds"),
972 Effect.tap((released) =>
973 released ? Effect.logInfo("Released the managed tunnel on shutdown") : Effect.void,
974 ),
975 Effect.catchCause((cause) =>
976 Effect.logWarning(
977 "Failed to release the managed tunnel on shutdown; the next link reuses it",
978 { errors: Cause.prettyErrors(cause).map((error) => error.message) },
979 ),
980 ),
981 Effect.asVoid,
982 );
983 // A launcher trial can be stopped before activation. The previous
984 // server is already gone, so the trial owns cleanup immediately; the
985 // pending-state check keeps the tunnel for normal commit or rollback,
986 // while the launcher's explicit-stop marker allows it to be released.
987 // Other runtimes wait for activation so a failed standby cannot tear
988 // down the active runtime's tunnel.
989 const cleanupBeforeActivation = yield* pendingServiceUpdateExists;
990 if (cleanupBeforeActivation) {
991 yield* Effect.addFinalizer(() => releaseManagedTunnel);
992 }
993 yield* ServerActivation.forkParked(
994 Effect.gen(function* () {
995 if (!cleanupBeforeActivation) {
996 yield* Effect.addFinalizer(() => releaseManagedTunnel);
997 }
998 const server = yield* HttpServer.HttpServer;
999 const address = server.address;
1000 if (typeof address === "string" || !("port" in address)) return;
1001 const localOrigin = `http://127.0.0.1:${address.port}`;
1002 const endpointRuntime = yield* CloudManagedEndpointRuntime.CloudManagedEndpointRuntime;
1003 const recoveryLock = yield* Semaphore.make(1);
1004 let lastRecoveryAtMillis = 0;
1005 const recoverManagedTunnel = (config: RelayManagedEndpointRuntimeConfig) =>
1006 recoveryLock.withPermits(1)(
1007 Effect.gen(function* () {
1008 const elapsed = (yield* Clock.currentTimeMillis) - lastRecoveryAtMillis;
1009 const wait = Duration.toMillis(MANAGED_TUNNEL_RECOVERY_COOLDOWN) - elapsed;
1010 if (wait > 0) yield* Effect.sleep(Duration.millis(wait));
1011 lastRecoveryAtMillis = yield* Clock.currentTimeMillis;
1012 }).pipe(
1013 Effect.andThen(
1014 cloudLink.recoverManagedTunnel(localOrigin, config, {
1015 retryRuntimeFailures: true,
1016 }),
1017 ),
1018 Effect.retry({
1019 while: (error) =>
1020 CloudLink.shouldRetryCloudLink(error) &&
1021 error._tag !== "CloudLinkEndpointUnavailableError",
1022 schedule: Schedule.exponential("1 second").pipe(
1023 Schedule.modifyDelay(({ duration }) =>
1024 Effect.succeed(Duration.min(duration, Duration.seconds(30))),
1025 ),
1026 Schedule.jittered,
1027 ),
1028 }),
1029 Effect.tap((recovered) =>
1030 recovered ? Effect.logInfo("T3 Connect managed tunnel recovered") : Effect.void,
1031 ),
1032 Effect.catchCause((cause) =>
1033 Cause.hasInterrupts(cause)
1034 ? Effect.interrupt
1035 : Effect.logWarning("Failed to recover the T3 Connect managed tunnel", {
1036 cause,
1037 }),
1038 ),
1039 ),
1040 );
1041 yield* endpointRuntime.recoveryRequests.pipe(
1042 Stream.runForEach(recoverManagedTunnel),
1043 Effect.forkScoped,
1044 );
1045 // No settling delay before the first attempt: routes are already
1046 // serving by the time activation opens this gate (the startup
1047 // sequence awaits routesReady), and the retry schedule below
1048 // covers anything this sleep used to hedge against. Every
1049 // millisecond here is dead time on the path to remote
1050 // reachability after a restart.
1051 const wantsCliLink = hasCloudPublicConfig
1052 ? yield* CloudCliState.readCliDesiredCloudLink.pipe(
1053 Effect.catch((cause) =>
1054 Effect.logWarning("Failed to read the desired T3 Connect link", { cause }).pipe(
1055 Effect.as(false),
1056 ),
1057 ),
1058 )
1059 : false;
1060 // A failed read must not end this fiber before it registers
1061 // recovery and starts consuming recovery requests. "managed" is
1062 // what a missing value means, so it is the safe fallback.
1063 const desiredCliLinkMode = wantsCliLink
1064 ? yield* CloudCliState.readCliDesiredLinkMode.pipe(
1065 Effect.catch((cause) =>
1066 Effect.logWarning("Failed to read the desired T3 Connect link mode", {
1067 cause,
1068 }).pipe(Effect.as("managed" as const)),
1069 ),
1070 )
1071 : null;
1072 // A publish-only link must not expose the host, even if a managed
1073 // config from an earlier link is still stored.
1074 const startedConfirmed =
1075 desiredCliLinkMode === "publish_only"
1076 ? false
1077 : yield* cloudLink.startManagedTunnelIfOriginConfirmed(localOrigin).pipe(
1078 Effect.catch((cause) =>
1079 Effect.logWarning("Failed to start the confirmed T3 Connect tunnel", {
1080 cause,
1081 }).pipe(Effect.as(false)),
1082 ),
1083 );
1084 const startStoredManagedTunnel = cloudLink
1085 .startManagedTunnelIfOriginConfirmed(localOrigin, {
1086 requireConfirmedOrigin: false,
1087 })
1088 .pipe(
1089 Effect.tap((started) =>
1090 started
1091 ? Effect.logWarning(
1092 "T3 Connect started the stored tunnel without relay confirmation",
1093 )
1094 : Effect.void,
1095 ),
1096 Effect.catch((cause) =>
1097 Effect.logWarning("Failed to start the stored T3 Connect tunnel", { cause }),
1098 ),
1099 Effect.asVoid,
1100 );
1101 const registerManagedTunnel = retryManagedTunnelRegistration(
1102 cloudLink.registerManagedTunnelRecovery(localOrigin, {
1103 retryRuntimeFailures: true,
1104 }),
1105 (error) =>
1106 CloudLink.shouldRetryCloudLink(error) &&
1107 error._tag !== "CloudLinkEndpointUnavailableError",
1108 startedConfirmed ? Effect.void : startStoredManagedTunnel,
1109 ).pipe(
1110 Effect.tap((result) =>
1111 result.status === "ready"
1112 ? Effect.logInfo("T3 Connect managed tunnel recovery registered")
1113 : Effect.void,
1114 ),
1115 Effect.catchCause((cause) =>
1116 Cause.hasInterrupts(cause)
1117 ? Effect.interrupt
1118 : Effect.logWarning("Failed to register T3 Connect managed tunnel recovery", {
1119 cause,
1120 }).pipe(Effect.as({ status: "unavailable" as const })),
1121 ),
1122 );
1123 // A host without a confirmed marker is on its first boot after the
1124 // upgrade. Spread those registrations so an auto-update wave does
1125 // not hit the relay all at once.
1126 if (!startedConfirmed && desiredCliLinkMode !== "publish_only") {
1127 const jitter = yield* Random.nextIntBetween(
1128 0,
1129 Duration.toMillis(MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER),
1130 );
1131 yield* Effect.sleep(Duration.millis(jitter));
1132 }
1133 const registration =
1134 desiredCliLinkMode === "publish_only"
1135 ? { status: "not_linked" as const }
1136 : yield* registerManagedTunnel;
1137 // A terminal registration failure also allows the stored config
1138 // to start. Transient outages use the fallback above and keep
1139 // registration retrying in this scoped startup fiber.
1140 if (registration.status === "unavailable" && !startedConfirmed) {
1141 yield* startStoredManagedTunnel;
1142 }
1143 const startupAction = managedTunnelStartupAction({ wantsCliLink, registration });
1144 if (startupAction.action === "request_recovery") {
1145 yield* endpointRuntime.requestRecovery(startupAction.config);
1146 }
1147 if (startupAction.action === "reconcile_link") {
1148 const reconciledMode = yield* cloudLink
1149 .reconcileDesiredLinkIfStillDesired(localOrigin)
1150 .pipe(
1151 Effect.retry({
1152 while: CloudLink.shouldRetryCloudLink,
1153 schedule: Schedule.exponential("1 second").pipe(
1154 Schedule.modifyDelay(({ duration }) =>
1155 Effect.succeed(Duration.min(duration, Duration.seconds(30))),
1156 ),
1157 Schedule.upTo({ duration: "10 minutes" }),
1158 ),
1159 }),
1160 Effect.tap((mode) =>
1161 mode === null
1162 ? Effect.void
1163 : Effect.logInfo("T3 Connect desired link reconciled on startup"),
1164 ),
1165 Effect.catch((cause) =>
1166 Effect.logWarning("Failed to reconcile T3 Connect desired link on startup", {
1167 cause,
1168 }).pipe(Effect.as(null)),
1169 ),
1170 );
1171 if (reconciledMode === "managed") {
1172 const afterReconcile = yield* registerManagedTunnel;
1173 if (afterReconcile.status === "recovery_required") {
1174 yield* endpointRuntime.requestRecovery(afterReconcile.config);
1175 }
1176 }
1177 }
1178 }),
1179 );
1180 yield* Deferred.succeed(cloudLinkParked, undefined).pipe(Effect.orDie);
1181 }),
1182 );
1183
1184 const layerRuntimeServices = ServerRuntimeStartup.layerWithOptions({
1185 activate: Deferred.succeed(activation, undefined).pipe(Effect.asVoid),
1186 abort: (error) => Deferred.die(activation, error).pipe(Effect.asVoid),
1187 awaitAuxiliaryParked: Effect.all(
1188 [
1189 Deferred.await(runtimeStateParked),
1190 Deferred.await(cloudLinkParked),
1191 Deferred.await(routesReady),
1192 ...(config.tailscaleServeEnabled ? [Deferred.await(tailscaleParked)] : []),
1193 ],
1194 { concurrency: "unbounded" },
1195 ).pipe(Effect.asVoid),
1196 }).pipe(Layer.provideMerge(layerRuntimeDependencies), Layer.provide(layerLauncher));
1197
1198 const layerRoutes = HttpRouter.serve(layerMakeRoutes.pipe(Layer.provide(layerLauncher)), {
1199 disableLogger: !config.logWebSocketEvents,
1200 routerConfig: HTTP_ROUTER_CONFIG,
1201 }).pipe(
1202 withUntracedRequests,
1203 Layer.tap(() => Deferred.succeed(routesReady, undefined).pipe(Effect.orDie)),
1204 );
1205 const layerServerApplication = Layer.mergeAll(
1206 layerRoutes,
1207 layerHttpListening,
1208 layerRuntimeState.pipe(Layer.provide(layerLauncher)),
1209 layerTailscaleServe,
1210 layerCloudDesiredLinkReconcile,
1211 HeapSnapshot.layer,
1212 OutboundSource.layer,
1213 );
1214
1215 return layerServerApplication.pipe(
1216 // The connect routes and the startup/shutdown link work share one instance.
1217 Layer.provide(CloudLink.layer),
1218 Layer.provideMerge(layerRuntimeServices),
1219 Layer.provideMerge(
1220 McpSessionRegistry.layer.pipe(
1221 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),
1222 ),
1223 ),
1224 Layer.provide(layerActivation),
1225 Layer.provideMerge(layerHttpServer),
1226 Layer.provide(layerApplicationObservability),
1227 Layer.provideMerge(FetchHttpClient.layer),
1228 // PR reads, Git operations, and WebSocket discovery share one process limiter.
1229 Layer.provide(VcsProcess.layer),
1230 // Every agent and terminal spawn reads this, so it sits below everything.
1231 Layer.provideMerge(AgentScopeLive.layer),
1232 Layer.provideMerge(ScriptProgress.layer),
1233 Layer.provideMerge(layerPlatformServices),
1234 );
1235 }),
1236).pipe(Layer.provide(ProcessRunner.layer));
1237
1238// The CLI supplies configuration.
1239export const runServer = Layer.launch(layerMakeServer);