1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import { currentDesktopBootstrapToken } from "@t3tools/shared/desktopBootstrapToken";
4import * as Clock from "effect/Clock";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Layer from "effect/Layer";
8import * as Logger from "effect/Logger";
9import * as ManagedRuntime from "effect/ManagedRuntime";
10import * as Option from "effect/Option";
11import * as Path from "effect/Path";
12import * as PlatformError from "effect/PlatformError";
13import * as Schema from "effect/Schema";
14import { ChildProcessSpawner } from "effect/process";
15
16import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
17import * as DesktopBackendConfiguration from "./DesktopBackendConfiguration.ts";
18import * as DesktopConfig from "../app/DesktopConfig.ts";
19import * as DesktopServerExposure from "./DesktopServerExposure.ts";
20import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
21import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts";
22import * as DesktopWslServerTree from "../wsl/DesktopWslServerTree.ts";
23
24const PersistedServerObservabilitySettingsDocument = Schema.Struct({
25 observability: Schema.Struct({
26 otlpTracesUrl: Schema.String,
27 otlpMetricsUrl: Schema.String,
28 otlpLogsUrl: Schema.String,
29 }),
30});
31
32const encodePersistedServerObservabilitySettingsDocument = Schema.encodeEffect(
33 Schema.fromJsonString(PersistedServerObservabilitySettingsDocument),
34);
35
36const isDesktopBackendObservabilitySettingsReadError = Schema.is(
37 DesktopBackendConfiguration.DesktopBackendObservabilitySettingsReadError,
38);
39
40const layerServerExposure = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
41 getState: Effect.die("unexpected getState"),
42 backendConfig: Effect.succeed({
43 port: 4888,
44 bindHost: "0.0.0.0",
45 httpBaseUrl: new URL("http://127.0.0.1:4888"),
46 tailscaleServeEnabled: true,
47 tailscaleServePort: 8443,
48 }),
49 configureFromSettings: () => Effect.die("unexpected configureFromSettings"),
50 setMode: () => Effect.die("unexpected setMode"),
51 setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"),
52 getAdvertisedEndpoints: Effect.succeed([]),
53} satisfies DesktopServerExposure.DesktopServerExposure["Service"]);
54
55function layerEnvironment(
56 baseDir: string,
57 options?: {
58 readonly appPath?: string;
59 readonly dirname?: string;
60 readonly isPackaged?: boolean;
61 readonly devServerUrl?: string;
62 readonly platform?: NodeJS.Platform;
63 readonly resourcesPath?: string;
64 readonly appVersion?: string;
65 readonly processArch?: NodeJS.Architecture;
66 readonly otlpTracesUrl?: string;
67 readonly otlpMetricsUrl?: string;
68 readonly otlpLogsUrl?: string;
69 },
70) {
71 return DesktopEnvironment.layer({
72 dirname: options?.dirname ?? "/repo/apps/desktop/src",
73 homeDirectory: baseDir,
74 platform: options?.platform ?? "darwin",
75 processArch: options?.processArch ?? "x64",
76 appVersion: options?.appVersion ?? "1.2.3",
77 appPath: options?.appPath ?? "/repo",
78 isPackaged: options?.isPackaged ?? true,
79 resourcesPath: options?.resourcesPath ?? "/missing/resources",
80 runningUnderArm64Translation: false,
81 }).pipe(
82 Layer.provide(
83 Layer.mergeAll(
84 NodeServices.layer,
85 DesktopConfig.layerTest({
86 T3CODE_HOME: baseDir,
87 T3CODE_PORT: "9999",
88 T3CODE_MODE: "desktop",
89 T3CODE_DESKTOP_LAN_HOST: "192.168.1.50",
90 VITE_DEV_SERVER_URL: options?.devServerUrl,
91 T3CODE_OTLP_TRACES_URL: options?.otlpTracesUrl,
92 T3CODE_OTLP_METRICS_URL: options?.otlpMetricsUrl,
93 T3CODE_OTLP_LOGS_URL: options?.otlpLogsUrl,
94 }),
95 ),
96 ),
97 );
98}
99
100const restoreEnv = (name: string, value: string | undefined) => {
101 if (value === undefined) {
102 delete process.env[name];
103 } else {
104 process.env[name] = value;
105 }
106};
107
108const withHarness = <A, E, R>(
109 effect: Effect.Effect<
110 A,
111 E,
112 | R
113 | DesktopEnvironment.DesktopEnvironment
114 | FileSystem.FileSystem
115 | DesktopBackendConfiguration.DesktopBackendConfiguration
116 >,
117) =>
118 Effect.gen(function* () {
119 const fileSystem = yield* FileSystem.FileSystem;
120 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
121 prefix: "t3-desktop-backend-config-test-",
122 });
123
124 return yield* effect.pipe(
125 Effect.provide(
126 DesktopBackendConfiguration.layer.pipe(
127 Layer.provideMerge(layerServerExposure),
128 Layer.provideMerge(DesktopAppSettings.layerTest()),
129 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
130 Layer.provideMerge(DesktopWslServerTree.layerTest()),
131 Layer.provideMerge(layerEnvironment(baseDir)),
132 ),
133 ),
134 );
135 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer));
136
137interface PackagedWslHarnessContext {
138 readonly baseDir: string;
139 readonly archivePath: string;
140 readonly hashPath: string;
141 readonly archiveHash: string;
142 readonly mountedAppRoot: string;
143 readonly mountedEntryPath: string;
144}
145
146const withPackagedWslHarness = <A, E, R>(
147 input: {
148 readonly archiveHash: string;
149 readonly wsl: (
150 context: PackagedWslHarnessContext,
151 ) => DesktopWslEnvironment.DesktopWslEnvironmentTestStub;
152 readonly forbidFallback?: string;
153 readonly cleanupLegacy?: Effect.Effect<void>;
154 readonly forbidCleanup?: string;
155 },
156 effect: (
157 context: PackagedWslHarnessContext,
158 ) => Effect.Effect<
159 A,
160 E,
161 R | FileSystem.FileSystem | Path.Path | DesktopBackendConfiguration.DesktopBackendConfiguration
162 >,
163) =>
164 Effect.gen(function* () {
165 const fileSystem = yield* FileSystem.FileSystem;
166 const path = yield* Path.Path;
167 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
168 prefix: "t3-desktop-backend-config-test-",
169 });
170 const archivePath = path.join(baseDir, "wsl-runtime.tar.gz");
171 const hashPath = `${archivePath}.sha256`;
172 const mountedAppRoot = "/mnt/c/app.asar.unpacked";
173 const mountedEntryPath = path.join(baseDir, "app.asar.unpacked/apps/server/dist/bin.mjs");
174 yield* fileSystem.makeDirectory(path.dirname(mountedEntryPath), { recursive: true });
175 yield* fileSystem.writeFileString(mountedEntryPath, "");
176 yield* fileSystem.writeFileString(archivePath, "archive");
177 yield* fileSystem.writeFileString(hashPath, `${input.archiveHash}\n`);
178
179 const context = {
180 baseDir,
181 archivePath,
182 hashPath,
183 archiveHash: input.archiveHash,
184 mountedAppRoot,
185 mountedEntryPath,
186 } satisfies PackagedWslHarnessContext;
187 const layerServerTree = input.forbidFallback
188 ? Layer.succeed(
189 DesktopWslServerTree.DesktopWslServerTree,
190 DesktopWslServerTree.DesktopWslServerTree.of({
191 ensure: Effect.die(input.forbidFallback),
192 cleanupLegacy: input.forbidCleanup
193 ? Effect.die(input.forbidCleanup)
194 : (input.cleanupLegacy ?? Effect.void),
195 }),
196 )
197 : DesktopWslServerTree.layerTest({
198 result: { ok: true, root: path.join(baseDir, "app.asar.unpacked") },
199 cleanupLegacy: input.cleanupLegacy ?? Effect.void,
200 });
201
202 return yield* effect(context).pipe(
203 Effect.provide(
204 DesktopBackendConfiguration.layer.pipe(
205 Layer.provideMerge(layerServerExposure),
206 Layer.provideMerge(DesktopAppSettings.layerTest()),
207 Layer.provideMerge(layerServerTree),
208 Layer.provideMerge(
209 DesktopWslEnvironment.layerTest({
210 isAvailable: true,
211 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
212 windowsToWslPath: () => Option.some(mountedAppRoot),
213 getDistroIp: () => Option.some("172.27.0.99"),
214 ...input.wsl(context),
215 }),
216 ),
217 Layer.provideMerge(
218 layerEnvironment(baseDir, {
219 appPath: baseDir,
220 platform: "win32",
221 resourcesPath: baseDir,
222 }),
223 ),
224 ),
225 ),
226 );
227 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer));
228
229describe("DesktopBackendConfiguration", () => {
230 it.effect("resolvePrimary produces a stable scoped bootstrap token", () =>
231 withHarness(
232 Effect.gen(function* () {
233 const environment = yield* DesktopEnvironment.DesktopEnvironment;
234 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
235
236 const first = yield* configuration.resolvePrimary;
237 const second = yield* configuration.resolvePrimary;
238
239 assert.equal(first.executablePath, process.execPath);
240 assert.deepEqual(first.args.slice(0, 3), [
241 "--require",
242 environment.compileCachePath,
243 environment.backendEntryPath,
244 ]);
245 assert.equal(first.entryPath, environment.backendEntryPath);
246 assert.equal(first.cwd, environment.backendCwd);
247 assert.equal(first.captureOutput, true);
248 assert.equal(first.env.ELECTRON_RUN_AS_NODE, "1");
249 assert.equal(first.env.T3CODE_VERSION_OVERRIDE, environment.appVersion);
250 assert.isUndefined(first.env.T3CODE_PORT);
251 assert.isUndefined(first.env.T3CODE_MODE);
252 assert.isUndefined(first.env.T3CODE_DESKTOP_LAN_HOST);
253
254 assert.equal(first.bootstrap.mode, "desktop");
255 assert.equal(first.bootstrap.noBrowser, true);
256 assert.equal(first.bootstrap.port, 4888);
257 assert.equal(first.bootstrap.host, "0.0.0.0");
258 assert.equal(first.bootstrap.t3Home, environment.baseDir);
259 assert.equal(first.bootstrap.tailscaleServeEnabled, true);
260 assert.equal(first.bootstrap.tailscaleServePort, 8443);
261 assert.match(first.bootstrap.desktopBootstrapSecret ?? "", /^[0-9a-f]{64}$/i);
262 assert.equal(
263 second.bootstrap.desktopBootstrapSecret,
264 first.bootstrap.desktopBootstrapSecret,
265 );
266 // The launch token is the secret's token for the current window, and
267 // the renderer is handed the same one.
268 assert.equal(
269 first.bootstrap.desktopBootstrapToken,
270 currentDesktopBootstrapToken(
271 first.bootstrap.desktopBootstrapSecret ?? "",
272 yield* Clock.currentTimeMillis,
273 ),
274 );
275 assert.equal(
276 yield* configuration.currentBootstrapToken,
277 first.bootstrap.desktopBootstrapToken,
278 );
279 }),
280 ),
281 );
282
283 it.effect("resolvePrimary starts from server.asar without materializing the WSL tree", () =>
284 Effect.gen(function* () {
285 const fileSystem = yield* FileSystem.FileSystem;
286 const path = yield* Path.Path;
287 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
288 prefix: "t3-desktop-backend-config-test-",
289 });
290 const resourcesPath = path.join(baseDir, "resources");
291
292 const config = yield* Effect.gen(function* () {
293 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
294 return yield* configuration.resolvePrimary;
295 }).pipe(
296 Effect.provide(
297 DesktopBackendConfiguration.layer.pipe(
298 Layer.provideMerge(layerServerExposure),
299 Layer.provideMerge(DesktopAppSettings.layerTest()),
300 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
301 Layer.provideMerge(
302 Layer.succeed(
303 DesktopWslServerTree.DesktopWslServerTree,
304 DesktopWslServerTree.DesktopWslServerTree.of({
305 ensure: Effect.die("Windows primary must not extract the WSL server tree"),
306 cleanupLegacy: Effect.die("Windows primary must not clean the WSL server tree"),
307 }),
308 ),
309 ),
310 Layer.provideMerge(
311 layerEnvironment(baseDir, {
312 appPath: `${resourcesPath}/app.asar`,
313 platform: "win32",
314 resourcesPath,
315 }),
316 ),
317 ),
318 ),
319 );
320
321 assert.equal(
322 config.entryPath,
323 path.join(resourcesPath, "server.asar/apps/server/dist/bin.mjs"),
324 );
325 assert.equal(config.env.ELECTRON_RUN_AS_NODE, "1");
326 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
327 );
328
329 it.effect("only the local primary bootstrap claims the prepared shell environment", () =>
330 withHarness(
331 Effect.gen(function* () {
332 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
333
334 const primary = yield* configuration.resolvePrimary;
335 const wsl = yield* configuration.resolveWsl({ port: 5000, distro: null });
336
337 assert.equal(primary.bootstrap.shellEnvironmentPrepared, true);
338 // A WSL server runs on its own Linux user environment and must hydrate it.
339 assert.isUndefined(wsl.bootstrap.shellEnvironmentPrepared);
340 }),
341 ),
342 );
343
344 it.effect("resolveWsl reuses the primary's bootstrap token", () =>
345 withHarness(
346 Effect.gen(function* () {
347 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
348
349 const primary = yield* configuration.resolvePrimary;
350 const wsl = yield* configuration.resolveWsl({ port: 5000, distro: null });
351
352 assert.equal(wsl.bootstrap.desktopBootstrapToken, primary.bootstrap.desktopBootstrapToken);
353 }),
354 ),
355 );
356
357 it.effect("resolveWsl pins a default-tracking run to the concrete default distro", () =>
358 Effect.gen(function* () {
359 const fileSystem = yield* FileSystem.FileSystem;
360 const path = yield* Path.Path;
361 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
362 prefix: "t3-desktop-backend-config-test-",
363 });
364 const entryPath = path.join(baseDir, "apps/server/dist/bin.mjs");
365 yield* fileSystem.makeDirectory(path.dirname(entryPath), { recursive: true });
366 yield* fileSystem.writeFileString(entryPath, "");
367
368 const observedDistros: Array<string | null> = [];
369 const config = yield* Effect.gen(function* () {
370 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
371 return yield* configuration.resolveWsl({ port: 5000, distro: null });
372 }).pipe(
373 Effect.provide(
374 DesktopBackendConfiguration.layer.pipe(
375 Layer.provideMerge(layerServerExposure),
376 Layer.provideMerge(DesktopAppSettings.layerTest()),
377 Layer.provideMerge(DesktopWslServerTree.layerTest()),
378 Layer.provideMerge(
379 DesktopWslEnvironment.layerTest({
380 isAvailable: true,
381 distros: [
382 { name: "Debian", isDefault: false, version: 2 },
383 { name: "Ubuntu", isDefault: true, version: 2 },
384 ],
385 windowsToWslPath: (distro) => {
386 observedDistros.push(distro);
387 return Option.some("/repo");
388 },
389 ensureNodePty: (distro) => {
390 observedDistros.push(distro);
391 return { ok: true, nodePath: "/usr/bin/node", resolvedPath: "/usr/bin:/bin" };
392 },
393 getDistroIp: (distro) => {
394 observedDistros.push(distro);
395 return Option.some("172.27.0.99");
396 },
397 }),
398 ),
399 Layer.provideMerge(
400 layerEnvironment(baseDir, {
401 appPath: baseDir,
402 platform: "win32",
403 resourcesPath: baseDir,
404 }),
405 ),
406 ),
407 ),
408 );
409
410 assert.equal(config.runningDistro, "Ubuntu");
411 assert.deepEqual(config.args.slice(0, 2), ["-d", "Ubuntu"]);
412 assert.deepEqual(observedDistros, ["Ubuntu", "Ubuntu", "Ubuntu"]);
413 assert.isTrue(Option.isNone(config.preflightFailure));
414 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
415 );
416
417 it.effect("resolveWsl launches a packaged backend from the WSL-local runtime cache", () => {
418 const observedArchives: Array<{
419 windowsArchivePath: string;
420 runtimeId: string;
421 sha256: string;
422 }> = [];
423 const observedProbeRoots: string[] = [];
424 let legacyCleanupCount = 0;
425 const linuxAppRoot = "/home/test/.t3/wsl-runtime/1.2.3-x64";
426 const resolvedPath = "/home/test/.local/bin:/usr/bin:/bin";
427
428 return withPackagedWslHarness(
429 {
430 archiveHash: "a".repeat(64),
431 forbidFallback: "A valid WSL archive must not extract the Windows fallback",
432 cleanupLegacy: Effect.sync(() => {
433 legacyCleanupCount += 1;
434 }),
435 wsl: () => ({
436 prepareRuntime: (_distro, archive) => {
437 observedArchives.push({
438 windowsArchivePath: archive.windowsPath,
439 runtimeId: archive.runtimeId,
440 sha256: archive.sha256,
441 });
442 return { ok: true, linuxAppRoot };
443 },
444 probeRuntime: (_distro, root) => {
445 observedProbeRoots.push(root);
446 return { ok: true, resolvedPath };
447 },
448 // The staged runtime carries its own Node and node-pty, so it must
449 // not require the mounted server tree's native dependency check.
450 ensureNodePty: () => {
451 throw new Error("the staged runtime must not probe for node-pty");
452 },
453 }),
454 },
455 ({ archiveHash, archivePath, baseDir }) =>
456 Effect.gen(function* () {
457 const path = yield* Path.Path;
458 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
459 const config = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
460
461 assert.deepEqual(observedArchives, [
462 {
463 windowsArchivePath: archivePath,
464 runtimeId: `sha256-${archiveHash}`,
465 sha256: archiveHash,
466 },
467 ]);
468 assert.deepEqual(observedProbeRoots, [linuxAppRoot]);
469 assert.equal(
470 config.entryPath,
471 path.join(baseDir, "server.asar/apps/server/dist/bin.mjs"),
472 );
473 assert.deepEqual(config.args, [
474 "-d",
475 "Ubuntu",
476 "--exec",
477 "env",
478 `PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${resolvedPath}`,
479 `${linuxAppRoot}/t3`,
480 "--bootstrap-fd",
481 "0",
482 ]);
483 assert.notInclude(config.args, "/usr/bin/node");
484 assert.equal(config.wslRuntimeId, `sha256-${archiveHash}`);
485 assert.equal(legacyCleanupCount, 1);
486 assert.isTrue(Option.isNone(config.preflightFailure));
487 }),
488 );
489 });
490
491 it.effect("resolveWsl changes the cache id when the packaged archive changes", () => {
492 const firstHash = "a".repeat(64);
493 const secondHash = "b".repeat(64);
494 const observedRuntimeIds: string[] = [];
495 return withPackagedWslHarness(
496 {
497 archiveHash: firstHash,
498 wsl: () => ({
499 prepareRuntime: (_distro, archive) => {
500 observedRuntimeIds.push(archive.runtimeId);
501 return { ok: true, linuxAppRoot: `/runtime/${archive.runtimeId}` };
502 },
503 ensureNodePty: () => ({
504 ok: true,
505 nodePath: "/usr/bin/node",
506 resolvedPath: "/usr/bin:/bin",
507 }),
508 }),
509 },
510 ({ hashPath, mountedAppRoot }) =>
511 Effect.gen(function* () {
512 const fileSystem = yield* FileSystem.FileSystem;
513 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
514 const first = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
515 yield* fileSystem.writeFileString(hashPath, secondHash);
516 const second = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
517 yield* fileSystem.writeFileString(hashPath, "not-a-sha256");
518 const invalidIdentity = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
519
520 assert.deepEqual(observedRuntimeIds, [`sha256-${firstHash}`, `sha256-${secondHash}`]);
521 assert.equal(first.wslRuntimeId, observedRuntimeIds[0]);
522 assert.include(first.args, `/runtime/sha256-${firstHash}/t3`);
523 assert.equal(second.wslRuntimeId, observedRuntimeIds[1]);
524 assert.include(second.args, `/runtime/sha256-${secondHash}/t3`);
525 assert.isUndefined(invalidIdentity.wslRuntimeId);
526 assert.include(invalidIdentity.args, "/usr/bin/node");
527 assert.include(invalidIdentity.args, `${mountedAppRoot}/apps/server/dist/bin.mjs`);
528 }),
529 );
530 });
531
532 it.effect("resolveWsl falls back to the mounted runtime when archive staging fails", () => {
533 const observedNodePtyRoots: string[] = [];
534 return withPackagedWslHarness(
535 {
536 archiveHash: "b".repeat(64),
537 wsl: () => ({
538 prepareRuntime: () => ({ ok: false, reason: "archive is corrupt" }),
539 ensureNodePty: (_distro, root) => {
540 observedNodePtyRoots.push(root);
541 return { ok: true, nodePath: "/usr/bin/node", resolvedPath: "/usr/bin:/bin" };
542 },
543 }),
544 },
545 ({ mountedAppRoot, mountedEntryPath }) =>
546 Effect.gen(function* () {
547 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
548 const config = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
549
550 assert.deepEqual(observedNodePtyRoots, [mountedAppRoot]);
551 assert.equal(config.entryPath, mountedEntryPath);
552 assert.include(config.args, "/usr/bin/node");
553 assert.include(config.args, `${mountedAppRoot}/apps/server/dist/bin.mjs`);
554 assert.isUndefined(config.wslRuntimeId);
555 assert.isTrue(Option.isNone(config.preflightFailure));
556 }),
557 );
558 });
559
560 it.effect("resolveWsl retires a staged runtime whose executable does not start", () => {
561 const archiveHash = "c".repeat(64);
562 const stagedAppRoot = `/home/test/.t3/wsl-runtime/sha256-${archiveHash}`;
563 const observedProbeRoots: string[] = [];
564 const observedNodePtyRoots: string[] = [];
565 const invalidatedRuntimeIds: string[] = [];
566 return withPackagedWslHarness(
567 {
568 archiveHash,
569 wsl: () => ({
570 prepareRuntime: () => ({ ok: true, linuxAppRoot: stagedAppRoot }),
571 invalidateRuntime: (_distro, runtimeId) =>
572 Effect.sync(() => {
573 invalidatedRuntimeIds.push(runtimeId);
574 }),
575 probeRuntime: (_distro, root) => {
576 observedProbeRoots.push(root);
577 return { ok: false, reason: `${root}/t3 --version failed (exit 127)` };
578 },
579 ensureNodePty: (_distro, root) => {
580 observedNodePtyRoots.push(root);
581 return { ok: true, nodePath: "/usr/bin/node", resolvedPath: "/usr/bin:/bin" };
582 },
583 }),
584 },
585 ({ mountedAppRoot, mountedEntryPath }) =>
586 Effect.gen(function* () {
587 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
588 const config = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
589
590 assert.deepEqual(observedProbeRoots, [stagedAppRoot]);
591 assert.deepEqual(observedNodePtyRoots, [mountedAppRoot]);
592 assert.include(config.args, "/usr/bin/node");
593 assert.include(config.args, `${mountedAppRoot}/apps/server/dist/bin.mjs`);
594 assert.notInclude(config.args, `${stagedAppRoot}/t3`);
595 assert.equal(config.entryPath, mountedEntryPath);
596 assert.isUndefined(config.wslRuntimeId);
597 assert.isTrue(Option.isNone(config.preflightFailure));
598 assert.deepEqual(invalidatedRuntimeIds, [`sha256-${archiveHash}`]);
599 }),
600 );
601 });
602
603 it.effect("resolveWsl keeps the staged runtime when the mounted tree fails too", () => {
604 const stagedAppRoot = "/home/test/.t3/wsl-runtime/cache";
605 const invalidatedRuntimeIds: string[] = [];
606 return withPackagedWslHarness(
607 {
608 archiveHash: "d".repeat(64),
609 wsl: () => ({
610 prepareRuntime: () => ({ ok: true, linuxAppRoot: stagedAppRoot }),
611 invalidateRuntime: (_distro, runtimeId) =>
612 Effect.sync(() => {
613 invalidatedRuntimeIds.push(runtimeId);
614 }),
615 probeRuntime: () => ({
616 ok: false,
617 reason: "unsupported CPU architecture or incompatible system libraries",
618 }),
619 ensureNodePty: () => ({
620 ok: false,
621 reason: "mounted tree is broken in some other way",
622 fatal: true,
623 }),
624 }),
625 },
626 () =>
627 Effect.gen(function* () {
628 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
629 const config = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
630 const failure = Option.getOrThrow(config.preflightFailure);
631
632 assert.isTrue(failure.fatal);
633 assert.include(failure.reason, "unsupported CPU architecture");
634 assert.deepEqual(invalidatedRuntimeIds, []);
635 }),
636 );
637 });
638
639 it.effect("resolveWsl keeps WSL retryable when the mounted fallback fails transiently", () => {
640 const stagedAppRoot = "/home/test/.t3/wsl-runtime/cache";
641 const invalidatedRuntimeIds: string[] = [];
642 return withPackagedWslHarness(
643 {
644 archiveHash: "f".repeat(64),
645 wsl: () => ({
646 prepareRuntime: () => ({ ok: true, linuxAppRoot: stagedAppRoot }),
647 invalidateRuntime: (_distro, runtimeId) =>
648 Effect.sync(() => {
649 invalidatedRuntimeIds.push(runtimeId);
650 }),
651 probeRuntime: () => ({ ok: false, reason: "t3 --version failed (exit 1)" }),
652 ensureNodePty: () => ({
653 ok: false,
654 reason: "WSL backend preflight timed out while probing for Node.js.",
655 fatal: false,
656 }),
657 }),
658 },
659 () =>
660 Effect.gen(function* () {
661 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
662 const config = yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
663 const failure = Option.getOrThrow(config.preflightFailure);
664
665 assert.isFalse(failure.fatal);
666 assert.equal(failure.retryLimit, 12);
667 assert.include(failure.reason, "timed out");
668 assert.deepEqual(invalidatedRuntimeIds, []);
669 }),
670 );
671 });
672
673 it.effect(
674 "resolveWsl preserves inherited PATH with quote-sensitive values as separate args",
675 () =>
676 Effect.gen(function* () {
677 const fileSystem = yield* FileSystem.FileSystem;
678 const path = yield* Path.Path;
679 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
680 prefix: "t3-desktop-backend-config-test-",
681 });
682 const entryPath = path.join(baseDir, "apps/server/dist/bin.mjs");
683 yield* fileSystem.makeDirectory(path.dirname(entryPath), { recursive: true });
684 yield* fileSystem.writeFileString(entryPath, "");
685
686 const nodePath = "/home/test user's/.nvm/versions/node/v22.0.0/bin/node";
687 const linuxAppRoot = "/tmp/t3 code's launch";
688 const linuxEntryPath = `${linuxAppRoot}/apps/server/dist/bin.mjs`;
689 const resolvedPath = "/home/test user/bin:/opt/test's tools/bin:/usr/bin:/bin";
690 const devServerUrl = "http://127.0.0.1:5733/dev%20assets/?label=hello%20world";
691 const config = yield* Effect.gen(function* () {
692 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
693 return yield* configuration.resolveWsl({ port: 5000, distro: "Ubuntu" });
694 }).pipe(
695 Effect.provide(
696 DesktopBackendConfiguration.layer.pipe(
697 Layer.provideMerge(layerServerExposure),
698 Layer.provideMerge(DesktopAppSettings.layerTest()),
699 Layer.provideMerge(DesktopWslServerTree.layerTest()),
700 Layer.provideMerge(
701 DesktopWslEnvironment.layerTest({
702 isAvailable: true,
703 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
704 windowsToWslPath: () => Option.some(linuxAppRoot),
705 ensureNodePty: () => ({ ok: true, nodePath, resolvedPath }),
706 getDistroIp: () => Option.some("172.27.0.99"),
707 }),
708 ),
709 Layer.provideMerge(
710 layerEnvironment(baseDir, {
711 appPath: baseDir,
712 devServerUrl,
713 isPackaged: true,
714 platform: "win32",
715 resourcesPath: baseDir,
716 }),
717 ),
718 ),
719 ),
720 );
721
722 assert.equal(config.bootstrapDelivery, "stdin");
723 assert.deepEqual(config.args, [
724 "-d",
725 "Ubuntu",
726 "--exec",
727 "env",
728 "PATH=/home/test user's/.nvm/versions/node/v22.0.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/test user/bin:/opt/test's tools/bin:/usr/bin:/bin",
729 nodePath,
730 linuxEntryPath,
731 "--bootstrap-fd",
732 "0",
733 "--dev-url",
734 devServerUrl,
735 ]);
736 assert.notInclude(config.args, "bash");
737 assert.notInclude(config.args, "/bin/sh");
738 assert.notInclude(config.args, "-c");
739 assert.isTrue(Option.isNone(config.preflightFailure));
740 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
741 );
742
743 it.effect("resolvePrimary and resolveWsl share one token under concurrent resolution", () =>
744 withHarness(
745 Effect.gen(function* () {
746 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
747
748 // Resolve both before any token is cached, concurrently, so the
749 // generate step (a yield point) can interleave. The atomic
750 // get-or-create must still hand both the same token; a non-atomic
751 // Ref would let each generate its own and break the shared-token
752 // invariant.
753 const [primary, wsl] = yield* Effect.all(
754 [configuration.resolvePrimary, configuration.resolveWsl({ port: 5000, distro: null })],
755 { concurrency: "unbounded" },
756 );
757
758 assert.equal(wsl.bootstrap.desktopBootstrapToken, primary.bootstrap.desktopBootstrapToken);
759 }),
760 ),
761 );
762
763 it.effect("resolvePrimary surfaces persisted backend observability endpoints", () =>
764 withHarness(
765 Effect.gen(function* () {
766 const fileSystem = yield* FileSystem.FileSystem;
767 const environment = yield* DesktopEnvironment.DesktopEnvironment;
768 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
769
770 yield* fileSystem.makeDirectory(environment.path.dirname(environment.serverSettingsPath), {
771 recursive: true,
772 });
773 yield* fileSystem.writeFileString(
774 environment.serverSettingsPath,
775 yield* encodePersistedServerObservabilitySettingsDocument({
776 observability: {
777 otlpTracesUrl: " http://127.0.0.1:4318/v1/traces ",
778 otlpMetricsUrl: " http://127.0.0.1:4318/v1/metrics ",
779 otlpLogsUrl: " http://127.0.0.1:4318/v1/logs ",
780 },
781 }),
782 );
783
784 const config = yield* configuration.resolvePrimary;
785 assert.equal(config.bootstrap.otlpTracesUrl, "http://127.0.0.1:4318/v1/traces");
786 assert.equal(config.bootstrap.otlpMetricsUrl, "http://127.0.0.1:4318/v1/metrics");
787 assert.equal(config.bootstrap.otlpLogsUrl, "http://127.0.0.1:4318/v1/logs");
788 }),
789 ),
790 );
791
792 it.effect("resolvePrimary omits backend observability endpoints when settings are missing", () =>
793 withHarness(
794 Effect.gen(function* () {
795 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
796 const config = yield* configuration.resolvePrimary;
797
798 assert.isUndefined(config.bootstrap.otlpTracesUrl);
799 assert.isUndefined(config.bootstrap.otlpMetricsUrl);
800 assert.isUndefined(config.bootstrap.otlpLogsUrl);
801 }),
802 ),
803 );
804
805 it.effect("resolveWsl carries environment-configured observability endpoints", () =>
806 Effect.gen(function* () {
807 const fileSystem = yield* FileSystem.FileSystem;
808 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
809 prefix: "t3-desktop-backend-config-test-",
810 });
811
812 yield* Effect.gen(function* () {
813 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
814 const config = yield* configuration.resolveWsl({ port: 5050, distro: null });
815
816 // No settings.json exists here: the endpoints come from the desktop
817 // environment, and the bootstrap carries them for a WSL child that
818 // lacks the variables.
819 assert.equal(config.bootstrap.otlpTracesUrl, "http://127.0.0.1:4318/v1/traces");
820 assert.equal(config.bootstrap.otlpMetricsUrl, "http://127.0.0.1:4318/v1/metrics");
821 assert.equal(config.bootstrap.otlpLogsUrl, "http://127.0.0.1:4318/v1/logs");
822 }).pipe(
823 Effect.provide(
824 DesktopBackendConfiguration.layer.pipe(
825 Layer.provideMerge(layerServerExposure),
826 Layer.provideMerge(DesktopAppSettings.layerTest()),
827 Layer.provideMerge(DesktopWslServerTree.layerTest()),
828 Layer.provideMerge(
829 DesktopWslEnvironment.layerTest({
830 isAvailable: true,
831 windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
832 getDistroIp: () => Option.some("172.27.0.99"),
833 }),
834 ),
835 Layer.provideMerge(
836 layerEnvironment(baseDir, {
837 platform: "win32",
838 otlpTracesUrl: " http://127.0.0.1:4318/v1/traces ",
839 otlpMetricsUrl: " http://127.0.0.1:4318/v1/metrics ",
840 otlpLogsUrl: " http://127.0.0.1:4318/v1/logs ",
841 }),
842 ),
843 ),
844 ),
845 );
846 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
847 );
848
849 it.effect("environment observability endpoints win over the persisted settings file", () =>
850 Effect.gen(function* () {
851 const fileSystem = yield* FileSystem.FileSystem;
852 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
853 prefix: "t3-desktop-backend-config-test-",
854 });
855
856 yield* Effect.gen(function* () {
857 const environment = yield* DesktopEnvironment.DesktopEnvironment;
858 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
859
860 yield* fileSystem.makeDirectory(environment.path.dirname(environment.serverSettingsPath), {
861 recursive: true,
862 });
863 yield* fileSystem.writeFileString(
864 environment.serverSettingsPath,
865 yield* encodePersistedServerObservabilitySettingsDocument({
866 observability: {
867 otlpTracesUrl: "http://persisted:4318/v1/traces",
868 otlpMetricsUrl: "http://persisted:4318/v1/metrics",
869 otlpLogsUrl: "http://persisted:4318/v1/logs",
870 },
871 }),
872 );
873
874 const config = yield* configuration.resolvePrimary;
875 assert.equal(config.bootstrap.otlpLogsUrl, "http://env:4318/v1/logs");
876 // Only the logs endpoint is set in env, so the other two still come
877 // from the settings file rather than being dropped together.
878 assert.equal(config.bootstrap.otlpTracesUrl, "http://persisted:4318/v1/traces");
879 assert.equal(config.bootstrap.otlpMetricsUrl, "http://persisted:4318/v1/metrics");
880 }).pipe(
881 Effect.provide(
882 DesktopBackendConfiguration.layer.pipe(
883 Layer.provideMerge(layerServerExposure),
884 Layer.provideMerge(DesktopAppSettings.layerTest()),
885 Layer.provideMerge(DesktopWslServerTree.layerTest()),
886 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
887 Layer.provideMerge(
888 layerEnvironment(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }),
889 ),
890 ),
891 ),
892 );
893 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
894 );
895
896 it.effect("logs structured context when persisted observability settings cannot be read", () =>
897 Effect.gen(function* () {
898 const fileSystem = yield* FileSystem.FileSystem;
899 const path = yield* Path.Path;
900 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
901 prefix: "t3-desktop-backend-config-test-",
902 });
903 const settingsPath = path.join(baseDir, "userdata", "settings.json");
904 const cause = PlatformError.systemError({
905 _tag: "PermissionDenied",
906 module: "FileSystem",
907 method: "readFileString",
908 pathOrDescriptor: settingsPath,
909 });
910 const messages: Array<unknown> = [];
911 const logger = Logger.make(({ message }) => {
912 messages.push(message);
913 });
914 const layerFailingFileSystem = Layer.succeed(
915 FileSystem.FileSystem,
916 FileSystem.makeNoop({
917 readFileString: () => Effect.fail(cause),
918 }),
919 );
920
921 const config = yield* Effect.gen(function* () {
922 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
923 return yield* configuration.resolvePrimary;
924 }).pipe(
925 Effect.provide(
926 Layer.mergeAll(
927 DesktopBackendConfiguration.layer.pipe(
928 Layer.provideMerge(layerServerExposure),
929 Layer.provideMerge(DesktopAppSettings.layerTest()),
930 Layer.provideMerge(DesktopWslServerTree.layerTest()),
931 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
932 Layer.provideMerge(layerEnvironment(baseDir)),
933 Layer.provideMerge(layerFailingFileSystem),
934 ),
935 Logger.layer([logger], { mergeWithExisting: false }),
936 ),
937 ),
938 );
939
940 assert.isUndefined(config.bootstrap.otlpTracesUrl);
941 assert.isUndefined(config.bootstrap.otlpMetricsUrl);
942 assert.isUndefined(config.bootstrap.otlpLogsUrl);
943
944 const error = messages
945 .flatMap((message) => (Array.isArray(message) ? message : [message]))
946 .find(isDesktopBackendObservabilitySettingsReadError);
947 assert.isDefined(error);
948 assert.equal(error.settingsPath, settingsPath);
949 assert.equal(error.cause, cause);
950 assert.equal(
951 error.message,
952 `Failed to read persisted backend observability settings at ${settingsPath}.`,
953 );
954 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
955 );
956
957 it.effect("resolvePrimary captures backend output in dev so child logs can be persisted", () =>
958 Effect.gen(function* () {
959 const fileSystem = yield* FileSystem.FileSystem;
960 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
961 prefix: "t3-desktop-backend-config-test-",
962 });
963
964 yield* Effect.gen(function* () {
965 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
966 const config = yield* configuration.resolvePrimary;
967 assert.equal(config.captureOutput, true);
968 assert.isUndefined(config.env.T3CODE_VERSION_OVERRIDE);
969 // Dev never shares the prod compile cache.
970 assert.notInclude(config.args, "--require");
971 }).pipe(
972 Effect.provide(
973 DesktopBackendConfiguration.layer.pipe(
974 Layer.provideMerge(layerServerExposure),
975 Layer.provideMerge(DesktopAppSettings.layerTest()),
976 Layer.provideMerge(DesktopWslServerTree.layerTest()),
977 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
978 Layer.provideMerge(
979 layerEnvironment(baseDir, {
980 isPackaged: false,
981 devServerUrl: "http://127.0.0.1:5733",
982 }),
983 ),
984 ),
985 ),
986 );
987 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
988 );
989
990 it.effect("resolveWsl carries the kill switch into the distro", () =>
991 Effect.gen(function* () {
992 const fileSystem = yield* FileSystem.FileSystem;
993 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
994 prefix: "t3-desktop-backend-config-test-",
995 });
996
997 const previousWslEnv = process.env.WSLENV;
998 const previousDisabled = process.env.OTEL_SDK_DISABLED;
999 try {
1000 delete process.env.WSLENV;
1001 process.env.OTEL_SDK_DISABLED = "true";
1002
1003 yield* Effect.gen(function* () {
1004 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1005 const config = yield* configuration.resolveWsl({ port: 5050, distro: null });
1006
1007 assert.equal(config.env.OTEL_SDK_DISABLED, "true");
1008 assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_SDK_DISABLED");
1009 }).pipe(
1010 Effect.provide(
1011 DesktopBackendConfiguration.layer.pipe(
1012 Layer.provideMerge(layerServerExposure),
1013 Layer.provideMerge(DesktopAppSettings.layerTest()),
1014 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1015 Layer.provideMerge(
1016 DesktopWslEnvironment.layerTest({
1017 isAvailable: true,
1018 windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
1019 getDistroIp: () => Option.some("172.27.0.99"),
1020 }),
1021 ),
1022 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1023 ),
1024 ),
1025 );
1026 } finally {
1027 restoreEnv("WSLENV", previousWslEnv);
1028 restoreEnv("OTEL_SDK_DISABLED", previousDisabled);
1029 }
1030 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1031 );
1032
1033 it.effect(
1034 "resolveWsl carries the standard OTLP endpoint, headers, and protocol into the distro",
1035 () =>
1036 Effect.gen(function* () {
1037 const fileSystem = yield* FileSystem.FileSystem;
1038 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1039 prefix: "t3-desktop-backend-config-test-",
1040 });
1041
1042 const standard = {
1043 OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com:4318/base?api_key=secret",
1044 OTEL_EXPORTER_OTLP_LOGS_HEADERS: "authorization=Bearer%20token",
1045 T3CODE_OTLP_TRACES_URL: "http://t3.example.com:4318/v1/traces",
1046 };
1047 const previousWslEnv = process.env.WSLENV;
1048 // A developer's own OTLP variables would be forwarded too.
1049 const ambientOtel = Object.entries(process.env).filter(
1050 ([name]) => name.startsWith("OTEL_") || name.startsWith("T3CODE_OTLP_"),
1051 );
1052 try {
1053 for (const [name] of ambientOtel) delete process.env[name];
1054 delete process.env.WSLENV;
1055 Object.assign(process.env, standard);
1056
1057 yield* Effect.gen(function* () {
1058 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1059 const config = yield* configuration.resolveWsl({ port: 5050, distro: null });
1060
1061 assert.equal(
1062 config.env.OTEL_EXPORTER_OTLP_ENDPOINT,
1063 "https://collector.example.com:4318/base?api_key=secret",
1064 );
1065 assert.equal(
1066 config.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS,
1067 "authorization=Bearer%20token",
1068 );
1069 // Without a flag, WSL passes the values through untranslated.
1070 const wslEnv = (config.env.WSLENV ?? "").split(":");
1071 assert.include(wslEnv, "OTEL_EXPORTER_OTLP_ENDPOINT");
1072 assert.include(wslEnv, "OTEL_EXPORTER_OTLP_LOGS_HEADERS");
1073 assert.equal(config.env.T3CODE_OTLP_TRACES_URL, "http://t3.example.com:4318/v1/traces");
1074 assert.include(wslEnv, "T3CODE_OTLP_TRACES_URL");
1075 }).pipe(
1076 Effect.provide(
1077 DesktopBackendConfiguration.layer.pipe(
1078 Layer.provideMerge(layerServerExposure),
1079 Layer.provideMerge(DesktopAppSettings.layerTest()),
1080 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1081 Layer.provideMerge(
1082 DesktopWslEnvironment.layerTest({
1083 isAvailable: true,
1084 windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
1085 getDistroIp: () => Option.some("172.27.0.99"),
1086 }),
1087 ),
1088 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1089 ),
1090 ),
1091 );
1092 } finally {
1093 for (const name of Object.keys(standard)) delete process.env[name];
1094 restoreEnv("WSLENV", previousWslEnv);
1095 for (const [name, value] of ambientOtel) restoreEnv(name, value);
1096 }
1097 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1098 );
1099
1100 it.effect("resolveWsl preserves existing WSLENV entries when forwarding backend secrets", () =>
1101 Effect.gen(function* () {
1102 const fileSystem = yield* FileSystem.FileSystem;
1103 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1104 prefix: "t3-desktop-backend-config-test-",
1105 });
1106
1107 const previousWslEnv = process.env.WSLENV;
1108 const previousOpenAiKey = process.env.OPENAI_API_KEY;
1109 const previousAnthropicKey = process.env.ANTHROPIC_API_KEY;
1110 const previousOtlpHeaders = process.env.T3CODE_OTLP_HEADERS;
1111 const previousOtlpProtocol = process.env.T3CODE_OTLP_PROTOCOL;
1112 // A developer's own OTEL_* variables would be forwarded too.
1113 const ambientOtel = Object.entries(process.env).filter(([name]) => name.startsWith("OTEL_"));
1114 try {
1115 for (const [name] of ambientOtel) delete process.env[name];
1116 process.env.WSLENV = "GOPATH/p:OPENAI_API_KEY/u:EMPTY::AZURE_DEVOPS_EXT_PAT/u";
1117 process.env.OPENAI_API_KEY = "openai-key";
1118 process.env.ANTHROPIC_API_KEY = "anthropic-key";
1119 process.env.T3CODE_OTLP_HEADERS = 'authorization="Bearer%20my-token"';
1120 process.env.T3CODE_OTLP_PROTOCOL = "http/protobuf";
1121
1122 yield* Effect.gen(function* () {
1123 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1124 const config = yield* configuration.resolveWsl({ port: 5050, distro: null });
1125
1126 assert.equal(config.executablePath, "wsl.exe");
1127 assert.equal(config.bootstrap.port, 5050);
1128 // Binds to 0.0.0.0 inside WSL so the backend is reachable via
1129 // both wslhost-forwarded localhost and the distro's eth0 IP.
1130 assert.equal(config.bootstrap.host, "0.0.0.0");
1131 assert.equal(config.bootstrap.tailscaleServeEnabled, false);
1132 assert.notProperty(config.bootstrap, "desktopTelemetryFd");
1133 assert.notProperty(config.bootstrap, "resourceMonitorPath");
1134 // httpBaseUrl uses the resolved distro IP from the test stub,
1135 // not localhost — the renderer reaches the backend directly to
1136 // avoid relying on wslhost forwarding.
1137 assert.equal(config.httpBaseUrl.href, "http://172.27.0.99:5050/");
1138 assert.equal(config.env.OPENAI_API_KEY, "openai-key");
1139 assert.equal(config.env.ANTHROPIC_API_KEY, "anthropic-key");
1140 assert.equal(config.env.T3CODE_OTLP_PROTOCOL, "http/protobuf");
1141 // The existing WSLENV is preserved byte-for-byte (note the empty
1142 // "::" segment survives — WSL ignores it, so we don't normalize
1143 // it away) and ANTHROPIC_API_KEY is appended. OPENAI_API_KEY is
1144 // already declared, so it isn't forwarded twice.
1145 assert.equal(
1146 config.env.WSLENV,
1147 "GOPATH/p:OPENAI_API_KEY/u:EMPTY::AZURE_DEVOPS_EXT_PAT/u:ANTHROPIC_API_KEY:T3CODE_OTLP_HEADERS:T3CODE_OTLP_PROTOCOL",
1148 );
1149 }).pipe(
1150 Effect.provide(
1151 DesktopBackendConfiguration.layer.pipe(
1152 Layer.provideMerge(layerServerExposure),
1153 Layer.provideMerge(DesktopAppSettings.layerTest()),
1154 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1155 Layer.provideMerge(
1156 DesktopWslEnvironment.layerTest({
1157 isAvailable: true,
1158 windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
1159 getDistroIp: () => Option.some("172.27.0.99"),
1160 }),
1161 ),
1162 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1163 ),
1164 ),
1165 );
1166 } finally {
1167 restoreEnv("WSLENV", previousWslEnv);
1168 restoreEnv("OPENAI_API_KEY", previousOpenAiKey);
1169 restoreEnv("ANTHROPIC_API_KEY", previousAnthropicKey);
1170 restoreEnv("T3CODE_OTLP_HEADERS", previousOtlpHeaders);
1171 restoreEnv("T3CODE_OTLP_PROTOCOL", previousOtlpProtocol);
1172 for (const [name, value] of ambientOtel) restoreEnv(name, value);
1173 }
1174 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1175 );
1176
1177 it.effect(
1178 "resolvePrimary falls back to the Windows primary when wsl-only but WSL is unavailable",
1179 () =>
1180 Effect.gen(function* () {
1181 const fileSystem = yield* FileSystem.FileSystem;
1182 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1183 prefix: "t3-desktop-backend-config-test-",
1184 });
1185
1186 yield* Effect.gen(function* () {
1187 const environment = yield* DesktopEnvironment.DesktopEnvironment;
1188 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1189 const config = yield* configuration.resolvePrimary;
1190
1191 // wsl-only is persisted but WSL is unavailable, so the primary must
1192 // not spawn wsl.exe (which would loop on preflight failures while the
1193 // Connections backend control is hidden). Resolve the Windows primary.
1194 assert.equal(config.executablePath, process.execPath);
1195 assert.equal(config.bootstrap.t3Home, environment.baseDir);
1196 assert.isTrue(Option.isNone(config.preflightFailure));
1197 }).pipe(
1198 Effect.provide(
1199 DesktopBackendConfiguration.layer.pipe(
1200 Layer.provideMerge(layerServerExposure),
1201 Layer.provideMerge(
1202 DesktopAppSettings.layerTest({
1203 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
1204 wslBackendEnabled: true,
1205 wslOnly: true,
1206 }),
1207 ),
1208 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1209 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })),
1210 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1211 ),
1212 ),
1213 );
1214 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1215 );
1216
1217 it.effect(
1218 "resolvePrimary marks a removed persisted WSL distro as a fatal preflight failure",
1219 () =>
1220 Effect.gen(function* () {
1221 const fileSystem = yield* FileSystem.FileSystem;
1222 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1223 prefix: "t3-desktop-backend-config-test-",
1224 });
1225
1226 yield* Effect.gen(function* () {
1227 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1228 const config = yield* configuration.resolvePrimary;
1229 const failure = Option.getOrThrow(config.preflightFailure);
1230
1231 assert.equal(config.executablePath, "wsl.exe");
1232 assert.isTrue(failure.fatal);
1233 assert.include(failure.reason, "Removed-Distro");
1234 }).pipe(
1235 Effect.provide(
1236 DesktopBackendConfiguration.layer.pipe(
1237 Layer.provideMerge(layerServerExposure),
1238 Layer.provideMerge(
1239 DesktopAppSettings.layerTest({
1240 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
1241 wslBackendEnabled: true,
1242 wslOnly: true,
1243 wslDistro: "Removed-Distro",
1244 }),
1245 ),
1246 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1247 Layer.provideMerge(
1248 DesktopWslEnvironment.layerTest({
1249 isAvailable: true,
1250 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
1251 }),
1252 ),
1253 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1254 ),
1255 ),
1256 );
1257 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1258 );
1259
1260 it.effect("resolveWsl keeps a transient distro-list failure retryable", () =>
1261 Effect.gen(function* () {
1262 const fileSystem = yield* FileSystem.FileSystem;
1263 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1264 prefix: "t3-desktop-backend-config-test-",
1265 });
1266
1267 yield* Effect.gen(function* () {
1268 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1269 const config = yield* configuration.resolveWsl({ port: 5050, distro: "Ubuntu" });
1270 const failure = Option.getOrThrow(config.preflightFailure);
1271
1272 assert.isFalse(failure.fatal);
1273 assert.equal(failure.retryLimit, 12);
1274 assert.include(failure.reason, "timed out");
1275 }).pipe(
1276 Effect.provide(
1277 DesktopBackendConfiguration.layer.pipe(
1278 Layer.provideMerge(layerServerExposure),
1279 Layer.provideMerge(DesktopAppSettings.layerTest()),
1280 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1281 Layer.provideMerge(
1282 DesktopWslEnvironment.layerTest({
1283 isAvailable: true,
1284 distroListError: new DesktopWslEnvironment.DesktopWslDistroListError({
1285 reason: "wsl.exe --list --verbose timed out",
1286 }),
1287 }),
1288 ),
1289 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1290 ),
1291 ),
1292 );
1293 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1294 );
1295
1296 it.effect("resolveWsl marks a missing packaged server entry as fatal", () =>
1297 Effect.gen(function* () {
1298 const fileSystem = yield* FileSystem.FileSystem;
1299 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1300 prefix: "t3-desktop-backend-config-test-",
1301 });
1302
1303 yield* Effect.gen(function* () {
1304 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1305 const config = yield* configuration.resolveWsl({ port: 5050, distro: "Ubuntu" });
1306 const failure = Option.getOrThrow(config.preflightFailure);
1307
1308 assert.isTrue(failure.fatal);
1309 assert.include(failure.reason, "missing server entry");
1310 }).pipe(
1311 Effect.provide(
1312 DesktopBackendConfiguration.layer.pipe(
1313 Layer.provideMerge(layerServerExposure),
1314 Layer.provideMerge(DesktopAppSettings.layerTest()),
1315 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1316 Layer.provideMerge(
1317 DesktopWslEnvironment.layerTest({
1318 isAvailable: true,
1319 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
1320 }),
1321 ),
1322 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1323 ),
1324 ),
1325 );
1326 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1327 );
1328
1329 it.effect("resolveWsl surfaces sidecar extraction failures through typed preflight", () =>
1330 Effect.gen(function* () {
1331 const fileSystem = yield* FileSystem.FileSystem;
1332 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1333 prefix: "t3-desktop-backend-config-test-",
1334 });
1335
1336 yield* Effect.gen(function* () {
1337 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1338 const config = yield* configuration.resolveWsl({ port: 5050, distro: "Ubuntu" });
1339 const failure = Option.getOrThrow(config.preflightFailure);
1340
1341 assert.isFalse(failure.fatal);
1342 assert.equal(failure.retryLimit, 12);
1343 assert.include(failure.reason, "could not be extracted");
1344 }).pipe(
1345 Effect.provide(
1346 DesktopBackendConfiguration.layer.pipe(
1347 Layer.provideMerge(layerServerExposure),
1348 Layer.provideMerge(DesktopAppSettings.layerTest()),
1349 Layer.provideMerge(
1350 DesktopWslServerTree.layerTest({
1351 result: {
1352 ok: false,
1353 reason: "WSL server files could not be extracted",
1354 fatal: false,
1355 },
1356 }),
1357 ),
1358 Layer.provideMerge(
1359 DesktopWslEnvironment.layerTest({
1360 isAvailable: true,
1361 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
1362 }),
1363 ),
1364 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1365 ),
1366 ),
1367 );
1368 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1369 );
1370
1371 it.effect("resolveWsl marks a missing selected distro as a fatal preflight failure", () =>
1372 Effect.gen(function* () {
1373 const fileSystem = yield* FileSystem.FileSystem;
1374 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1375 prefix: "t3-desktop-backend-config-test-",
1376 });
1377
1378 yield* Effect.gen(function* () {
1379 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1380 const config = yield* configuration.resolveWsl({ port: 5050, distro: "Removed-Distro" });
1381 const failure = Option.getOrThrow(config.preflightFailure);
1382
1383 assert.isTrue(failure.fatal);
1384 assert.include(failure.reason, "Removed-Distro");
1385 }).pipe(
1386 Effect.provide(
1387 DesktopBackendConfiguration.layer.pipe(
1388 Layer.provideMerge(layerServerExposure),
1389 Layer.provideMerge(DesktopAppSettings.layerTest()),
1390 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1391 Layer.provideMerge(
1392 DesktopWslEnvironment.layerTest({
1393 isAvailable: true,
1394 distros: [{ name: "Ubuntu", isDefault: true, version: 2 }],
1395 }),
1396 ),
1397 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1398 ),
1399 ),
1400 );
1401 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1402 );
1403
1404 it.effect("resolvePrimaryLabel reports the WSL distro when wsl-only and WSL is available", () =>
1405 Effect.gen(function* () {
1406 const fileSystem = yield* FileSystem.FileSystem;
1407 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1408 prefix: "t3-desktop-backend-config-test-",
1409 });
1410
1411 yield* Effect.gen(function* () {
1412 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1413 const label = yield* configuration.resolvePrimaryLabel;
1414 assert.equal(label, "WSL (Ubuntu)");
1415 }).pipe(
1416 Effect.provide(
1417 DesktopBackendConfiguration.layer.pipe(
1418 Layer.provideMerge(layerServerExposure),
1419 Layer.provideMerge(
1420 DesktopAppSettings.layerTest({
1421 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
1422 wslBackendEnabled: true,
1423 wslOnly: true,
1424 wslDistro: "Ubuntu",
1425 }),
1426 ),
1427 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1428 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: true })),
1429 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1430 ),
1431 ),
1432 );
1433 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1434 );
1435
1436 it.effect("prefers the external packaged resource monitor over the copy inside the asar", () =>
1437 Effect.gen(function* () {
1438 const fileSystem = yield* FileSystem.FileSystem;
1439 const path = yield* Path.Path;
1440 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1441 prefix: "t3-desktop-backend-config-test-",
1442 });
1443 const resourcesPath = path.join(baseDir, "resources");
1444 const dirname = `${resourcesPath}/app.asar/apps/desktop/dist-electron`;
1445 const embeddedMonitorPath = `${resourcesPath}/app.asar/apps/desktop/prod-resources/resource-monitor/t3-resource-monitor`;
1446 const monitorPath = path.join(resourcesPath, "resource-monitor/t3-resource-monitor");
1447 yield* fileSystem.makeDirectory(
1448 `${resourcesPath}/app.asar/apps/desktop/prod-resources/resource-monitor`,
1449 { recursive: true },
1450 );
1451 yield* fileSystem.makeDirectory(`${resourcesPath}/resource-monitor`, {
1452 recursive: true,
1453 });
1454 yield* fileSystem.writeFileString(embeddedMonitorPath, "embedded");
1455 yield* fileSystem.writeFileString(monitorPath, "binary");
1456 yield* fileSystem.chmod(monitorPath, 0o755);
1457
1458 yield* Effect.gen(function* () {
1459 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1460 const config = yield* configuration.resolvePrimary;
1461 assert.equal(config.bootstrap.resourceMonitorPath, monitorPath);
1462 assert.equal(config.bootstrap.desktopTelemetryFd, 4);
1463 assert.equal(config.bootstrap.desktopTelemetryControlFd, 5);
1464 }).pipe(
1465 Effect.provide(
1466 DesktopBackendConfiguration.layer.pipe(
1467 Layer.provideMerge(layerServerExposure),
1468 Layer.provideMerge(DesktopAppSettings.layerTest()),
1469 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1470 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
1471 Layer.provideMerge(
1472 layerEnvironment(baseDir, {
1473 appPath: `${resourcesPath}/app.asar`,
1474 dirname,
1475 isPackaged: true,
1476 resourcesPath,
1477 }),
1478 ),
1479 ),
1480 ),
1481 );
1482 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1483 );
1484
1485 it.effect("prefers the release resource monitor when both development builds exist", () =>
1486 Effect.gen(function* () {
1487 const fileSystem = yield* FileSystem.FileSystem;
1488 const path = yield* Path.Path;
1489 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1490 prefix: "t3-desktop-backend-config-test-",
1491 });
1492 const dirname = path.join(baseDir, "apps/desktop/src");
1493 const releaseMonitorPath = path.join(
1494 baseDir,
1495 "native/resource-monitor/target/release/t3-resource-monitor",
1496 );
1497 const debugMonitorPath = path.join(
1498 baseDir,
1499 "native/resource-monitor/target/debug/t3-resource-monitor",
1500 );
1501 yield* fileSystem.makeDirectory(path.dirname(releaseMonitorPath), { recursive: true });
1502 yield* fileSystem.makeDirectory(path.dirname(debugMonitorPath), { recursive: true });
1503 yield* fileSystem.writeFileString(releaseMonitorPath, "release");
1504 yield* fileSystem.writeFileString(debugMonitorPath, "debug");
1505
1506 yield* Effect.gen(function* () {
1507 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1508 const config = yield* configuration.resolvePrimary;
1509 assert.equal(config.bootstrap.resourceMonitorPath, releaseMonitorPath);
1510 }).pipe(
1511 Effect.provide(
1512 DesktopBackendConfiguration.layer.pipe(
1513 Layer.provideMerge(layerServerExposure),
1514 Layer.provideMerge(DesktopAppSettings.layerTest()),
1515 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1516 Layer.provideMerge(DesktopWslEnvironment.layerTest()),
1517 Layer.provideMerge(
1518 layerEnvironment(baseDir, {
1519 dirname,
1520 devServerUrl: "http://127.0.0.1:5733",
1521 isPackaged: false,
1522 }),
1523 ),
1524 ),
1525 ),
1526 );
1527 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1528 );
1529
1530 it.effect("resolvePrimaryLabel reports the local environment on non-Windows platforms", () =>
1531 withHarness(
1532 Effect.gen(function* () {
1533 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1534 const label = yield* configuration.resolvePrimaryLabel;
1535 assert.equal(label, "Local environment");
1536 }),
1537 ),
1538 );
1539
1540 it.effect("resolvePrimaryLabel reports Windows when wsl-only but WSL is unavailable", () =>
1541 Effect.gen(function* () {
1542 const fileSystem = yield* FileSystem.FileSystem;
1543 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
1544 prefix: "t3-desktop-backend-config-test-",
1545 });
1546
1547 yield* Effect.gen(function* () {
1548 const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
1549 // Mirrors the resolvePrimary fall-back: the label must follow the
1550 // backend that actually resolves, not the persisted preference, so the
1551 // env switcher can't show "WSL" for a Windows backend.
1552 const label = yield* configuration.resolvePrimaryLabel;
1553 assert.equal(label, "Windows");
1554 }).pipe(
1555 Effect.provide(
1556 DesktopBackendConfiguration.layer.pipe(
1557 Layer.provideMerge(layerServerExposure),
1558 Layer.provideMerge(
1559 DesktopAppSettings.layerTest({
1560 ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,
1561 wslBackendEnabled: true,
1562 wslOnly: true,
1563 wslDistro: "Ubuntu",
1564 }),
1565 ),
1566 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1567 Layer.provideMerge(DesktopWslEnvironment.layerTest({ isAvailable: false })),
1568 Layer.provideMerge(layerEnvironment(baseDir, { platform: "win32" })),
1569 ),
1570 ),
1571 );
1572 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
1573 );
1574
1575 it("resolvePrimaryLabel is runSync-safe against the real WSL availability probe", async () => {
1576 // getLocalEnvironmentBootstraps is a sync IPC method: it resolves the
1577 // primary instance's lazy label through Effect.runSync. The label chains
1578 // to wslEnvironment.isAvailable, whose real layer probes the filesystem.
1579 // That probe must run once at layer build and expose a resolved value, not
1580 // a live async effect — otherwise runSync throws in the handler. Build the
1581 // real WSL layer (not the sync test stub) and resolve the label with a
1582 // top-level runSync, exactly as the handler does.
1583 // oxlint-disable-next-line t3code/no-manual-effect-runtime-in-tests -- This test intentionally replicates the sync IPC handler's runSync path to catch a regression to async-only resolution; it.effect would mask it.
1584 const runtime = ManagedRuntime.make(
1585 DesktopBackendConfiguration.layer.pipe(
1586 Layer.provideMerge(layerServerExposure),
1587 Layer.provideMerge(DesktopAppSettings.layerTest()),
1588 Layer.provideMerge(DesktopWslServerTree.layerTest()),
1589 Layer.provideMerge(DesktopWslEnvironment.layer),
1590 // isAvailable on win32 only touches the filesystem, never the spawner,
1591 // so a die-stub is enough to satisfy the layer's deps.
1592 Layer.provideMerge(
1593 Layer.succeed(
1594 ChildProcessSpawner.ChildProcessSpawner,
1595 ChildProcessSpawner.make(() =>
1596 Effect.die("spawner should not be used while probing WSL availability"),
1597 ),
1598 ),
1599 ),
1600 Layer.provideMerge(layerEnvironment("/tmp/t3-wsl-isavailable", { platform: "win32" })),
1601 Layer.provide(NodeServices.layer),
1602 ),
1603 );
1604 try {
1605 const configuration = await runtime.runPromise(
1606 DesktopBackendConfiguration.DesktopBackendConfiguration,
1607 );
1608 // oxlint-disable-next-line t3code/no-manual-effect-runtime-in-tests -- Same reason: this is the synchronous resolution the IPC handler performs.
1609 const label = Effect.runSync(configuration.resolvePrimaryLabel);
1610 assert.equal(typeof label, "string");
1611 } finally {
1612 await runtime.dispose();
1613 }
1614 });
1615});